diff --git a/qwt/COPYING b/qwt/COPYING new file mode 100644 index 000000000..9c01f7e21 --- /dev/null +++ b/qwt/COPYING @@ -0,0 +1,543 @@ + Qwt License + Version 1.0, January 1, 2003 + +The Qwt library and included programs are provided under the terms +of the GNU LESSER GENERAL PUBLIC LICENSE (LGPL) with the following +exceptions: + + 1. Widgets that are subclassed from Qwt widgets do not + constitute a derivative work. + + 2. Static linking of applications and widgets to the + Qwt library does not constitute a derivative work + and does not require the author to provide source + code for the application or widget, use the shared + Qwt libraries, or link their applications or + widgets against a user-supplied version of Qwt. + + If you link the application or widget to a modified + version of Qwt, then the changes to Qwt must be + provided under the terms of the LGPL in sections + 1, 2, and 4. + + 3. You do not have to provide a copy of the Qwt license + with programs that are linked to the Qwt library, nor + do you have to identify the Qwt license in your + program or documentation as required by section 6 + of the LGPL. + + + However, programs must still identify their use of Qwt. + The following example statement can be included in user + documentation to satisfy this requirement: + + [program/widget] is based in part on the work of + the Qwt project (http://qwt.sf.net). + +---------------------------------------------------------------------- + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/qwt/INSTALL b/qwt/INSTALL new file mode 100644 index 000000000..bafcbe039 --- /dev/null +++ b/qwt/INSTALL @@ -0,0 +1 @@ +see doc/html/qwtinstall.html diff --git a/qwt/README b/qwt/README new file mode 100644 index 000000000..46ee34e41 --- /dev/null +++ b/qwt/README @@ -0,0 +1,34 @@ + +The Qwt Widget Library +---------------------- + + Qwt is an extension to the libraries of the Qt Project. + + The Qwt library contains widgets and components which are + primarily useful for technical and scientifical purposes. + It includes a 2-D plotting widget, different kinds of sliders, + and much more. + + Qwt is hosted at http://qwt.sf.net + +Installation +------------ + + Read INSTALL how to build and install Qwt. + +Copyright +--------- + + Qwt Widget Library + Copyright (C) 1997 Josef Wilgen + Copyright (C) 2002 Uwe Rathmann + + Qwt is published under the Qwt License, Version 1.0. + You should have received a copy of this licence in the file + COPYING. + + This library 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. + + diff --git a/qwt/TODO b/qwt/TODO new file mode 100644 index 000000000..8abd45892 --- /dev/null +++ b/qwt/TODO @@ -0,0 +1,38 @@ +Qwt TODO list + +Ideas +------ +- Improve Documention +- QAbstractModel -> QwtSeriesData +- Box/Whisker plot item +- QwtSeriesData + functors +- QwtSeriesData/QwtPlotCurve + Level of details (Douglas Peucker) +- Common zoom stack for all navigation objects +- Watermark Item +- Contour algorithm for vectors: http://apptree.net/conrec.htm +- QwtPlotCanvas rendered via FBO, PBO +- Time/Date scale engine +- TeX texts +- Grid of QwtPlots +- Interval scale labels ( between 2 ticks ) +- More than 4 axes +- QwtIntervalSymbol + QPainterPath/... +- QwtPlotScene + breaking composite architecture +- Using QStaticText for markers ( and scales ? ) +- Scales/Grid item like in QwtPolarGrid +- Container for a 2D matrix +- Waterfall plots +- transform/invTransform for polygons and lines +- cursor item +- line marker with a line from the position to the axis +- quadtree +- QwtText supporting Qt::TextElideMode +- Multitouch events +- QwtKnob/QwtDial fixed contents size mode +- controls ( f.e QwtWheel ) with a very dark palette + +Bugs/Change requests +-------------------- +- Remove QwtScaleTransformation::copy() +- Reference value for QwtThermo +- Transparent canvas background + backingstore diff --git a/qwt/admin/svn2package.sh b/qwt/admin/svn2package.sh new file mode 100755 index 000000000..2e1bfd5d7 --- /dev/null +++ b/qwt/admin/svn2package.sh @@ -0,0 +1,326 @@ +#! /bin/sh +# +# Generates a Qwt package from sourceforge svn +# +# Usage: svn2package.sh [-b|--branch ] [packagename] +# + +########################## +# usage +########################## + +function usage() { + echo "Usage: $0 [-b|--branch ] [-s|--suffix ] [-html] [-pdf] [-qch] [packagename]" + exit 1 +} + +################################ +# checkout +################################ + +function checkoutQwt() { + + if [ -x $2 ] + then + rm -r $2 + if [ $? -ne 0 ] + then + exit $? + fi + fi + + svn -q co https://svn.code.sf.net/p/qwt/code/$1/$2 + if [ $? -ne 0 ] + then + echo "Can't access sourceforge SVN" + exit $? + fi + + if [ "$3" != "$2" ] + then + rm -rf $3 + mv $2 $3 + fi +} + +########################## +# cleanQwt dirname +########################## + +function cleanQwt { + + cd $1 + if [ $? -ne 0 ] + then + exit $? + fi + + find . -name .svn -print | xargs rm -r + + rm -f TODO + rm -rf admin + rm -rf doc/tex + + PROFILES="qwtbuild.pri" + for PROFILE in $PROFILES + do + sed -i -e 's/= debug/= release/' $PROFILE + sed -i -e 's/= release_and_release/= debug_and_release/' $PROFILE + done + + HEADERS=`find . -type f -name '*.h' -print` + SOURCES=`find . -type f -name '*.cpp' -print` + PROFILES=`find . -type f -name '*.pro' -print` + PRIFILES=`find . -type f -name '*.pri' -print` + + for EXPANDFILE in $HEADERS $SOURCES $PROFILES $PRIFILES + do + expand -4 $EXPANDFILE > $EXPANDFILE.expand + mv $EXPANDFILE.expand $EXPANDFILE + done + + for SRCFILE in $SOURCES $PROFILES $PRIFILES + do + sed -i -e '/#warning/d' $SRCFILE + done + + if [ "$SUFFIX" != "" ] + then + sed -i -e "s/\$\$QWT_VERSION-svn/\$\$QWT_VERSION-$SUFFIX/" qwtconfig.pri + sed -i -e "s/\$(QWTVERSION)/$VERSION-$SUFFIX/" doc/install.dox + else + sed -i -e "s/\$\$QWT_VERSION-svn/\$\$QWT_VERSION/" qwtconfig.pri + sed -i -e "s/\$(QWTVERSION)/$VERSION/" doc/install.dox + fi + + cd - > /dev/null +} + +########################## +# createDocs dirname +########################## + +function createDocs { + + ODIR=`pwd` + + cd $1 + if [ $? -ne 0 ] + then + exit $? + fi + + if [ "$SUFFIX" != "" ] + then + export QWTVERSION=$VERSION-$SUFFIX + else + export QWTVERSION=$VERSION + fi + cp Doxyfile Doxyfile.doc + + if [ $GENERATE_MAN -ne 0 ] + then + sed -i -e '/GENERATE_MAN/d' Doxyfile.doc + echo 'GENERATE_MAN = YES' >> Doxyfile.doc + fi + + if [ $GENERATE_PDF -ne 0 ] + then + # We need LateX for the qwtdoc.pdf + + sed -i -e '/GENERATE_LATEX/d' -e '/GENERATE_MAN/d' Doxyfile.doc + echo 'GENERATE_LATEX = YES' >> Doxyfile.doc + echo 'GENERATE_MAN = YES' >> Doxyfile.doc + +# sed -i -e '/INLINE_INHERITED_MEMB/d' Doxyfile.doc +# echo 'INLINE_INHERITED_MEMB = NO' >> Doxyfile.doc + fi + + if [ $GENERATE_QCH -ne 0 ] + then + sed -i -e '/GENERATE_QHP/d' Doxyfile.doc + echo "GENERATE_QHP = YES" >> Doxyfile.doc + fi + + cp ../INSTALL ../COPYING ./ + + doxygen Doxyfile.doc > /dev/null 2>&1 + if [ $? -ne 0 ] + then + exit $? + fi + + rm Doxyfile.doc Doxygen.log INSTALL COPYING + rm -r images + + if [ $GENERATE_PDF -ne 0 ] + then + cd latex + make > /dev/null 2>&1 + if [ $? -ne 0 ] + then + exit $? + fi + + cd .. + mkdir pdf + mv latex/refman.pdf pdf/qwtdoc-$VERSION.pdf + + rm -r latex + fi + + cd $ODIR +} + +########################## +# posix2dos filename +########################## + +function posix2dos { + # At least one unix2dos writes to stdout instead of overwriting the input. + # The -q option is always enabled in stdin->stdout mode. + unix2dos <$1 >$1.dos + mv $1.dos $1 +} + +########################## +# prepare4Win dirname +########################## + +function prepare4Win { + + cd $1 + if [ $? -ne 0 ] + then + exit $? + fi + + rm -rf doc/man 2> /dev/null + + # win files, but not uptodate + + BATCHES=`find . -type f -name '*.bat' -print` + HEADERS=`find . -type f -name '*.h' -print` + SOURCES=`find . -type f -name '*.cpp' -print` + PROFILES=`find . -type f -name '*.pro' -print` + PRIFILES=`find . -type f -name '*.pri' -print` + PRFFILES=`find . -type f -name '*.prf' -print` + + for FILE in $BATCHES $HEADERS $SOURCES $PROFILES $PRIFILES $PRFFILES + do + posix2dos $FILE + done + + cd - > /dev/null +} + +########################## +# prepare4Unix dirname +########################## + +function prepare4Unix { + + cd $1 + if [ $? -ne 0 ] + then + exit $? + fi + + cd - > /dev/null +} + +########################## +# main +########################## + +QWTDIR= +SVNDIR=trunk +BRANCH=qwt +SUFFIX= +VERSION= +GENERATE_DOC=0 +GENERATE_PDF=0 +GENERATE_QCH=0 +GENERATE_MAN=0 + +while [ $# -gt 0 ] ; do + case "$1" in + -h|--help) + usage; exit 1 ;; + -b|--branch) + shift; SVNDIR=branches; BRANCH=$1; shift;; + -s|--suffix) + shift; SUFFIX=$1; shift;; + -html) + GENERATE_DOC=1; shift;; + -pdf) + GENERATE_DOC=1; GENERATE_PDF=1; shift;; + -qch) + GENERATE_DOC=1; GENERATE_QCH=1; shift;; + *) + QWTDIR=qwt-$1 ; VERSION=$1; shift;; + esac +done + +if [ "$QWTDIR" == "" ] +then + usage + exit 2 +fi + +QWTNAME=$QWTDIR +if [ "$SUFFIX" != "" ] +then + QWTDIR=$QWTDIR-$SUFFIX +fi + +TMPDIR=/tmp/$QWTDIR-tmp + +echo -n "checkout to $TMPDIR ... " +checkoutQwt $SVNDIR $BRANCH $TMPDIR +cleanQwt $TMPDIR +echo done + +if [ $GENERATE_DOC -ne 0 ] +then + echo -n "generate documentation ... " + + export VERSION # used in the doxygen files + createDocs $TMPDIR/doc + + if [ $GENERATE_PDF -ne 0 ] + then + mv $TMPDIR/doc/pdf/qwtdoc-$VERSION.pdf $QWTDIR.pdf + rmdir $TMPDIR/doc/pdf + fi + + if [ $GENERATE_QCH -ne 0 ] + then + mv $TMPDIR/doc/html/qwtdoc.qch $QWTDIR.qch + fi +fi + +echo done + + +DIR=`pwd` +echo -n "create packages in $DIR ... " + +cd /tmp + +rm -rf $QWTDIR +cp -a $TMPDIR $QWTDIR +prepare4Unix $QWTDIR +tar cfj $QWTDIR.tar.bz2 $QWTDIR + +rm -rf $QWTDIR +cp -a $TMPDIR $QWTDIR +prepare4Win $QWTDIR +zip -r $QWTDIR.zip $QWTDIR > /dev/null + +rm -rf $TMPDIR $QWTDIR + +mv $QWTDIR.tar.bz2 $QWTDIR.zip $DIR/ +echo done + +exit 0 diff --git a/qwt/designer/designer.pro b/qwt/designer/designer.pro new file mode 100644 index 000000000..c269e9da2 --- /dev/null +++ b/qwt/designer/designer.pro @@ -0,0 +1,132 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +QWT_ROOT = $${PWD}/.. + +include ( $${QWT_ROOT}/qwtconfig.pri ) +include ( $${QWT_ROOT}/qwtbuild.pri ) +include ( $${QWT_ROOT}/qwtfunctions.pri ) + + +CONFIG( debug_and_release ) { + + # When building debug_and_release the designer plugin is built + # for release only. If you want to have a debug version it has to be + # done with "CONFIG += debug" only. + + message("debug_and_release: building the Qwt designer plugin in release mode only") + + CONFIG -= debug_and_release + CONFIG += release +} + +contains(QWT_CONFIG, QwtDesigner) { + + CONFIG += qt plugin + CONFIG += warn_on + + greaterThan(QT_MAJOR_VERSION, 4) { + + QT += designer + } + else { + + CONFIG += designer + } + + + TEMPLATE = lib + TARGET = qwt_designer_plugin + + DESTDIR = plugins/designer + + INCLUDEPATH += $${QWT_ROOT}/src + DEPENDPATH += $${QWT_ROOT}/src + + contains(QWT_CONFIG, QwtDll) { + + contains(QWT_CONFIG, QwtDesignerSelfContained) { + + QWT_CONFIG += include_src + } + + } else { + + # for linking against a static library the + # plugin will be self contained anyway + } + + contains(QWT_CONFIG, include_src) { + + # compile all qwt classes into the plugin + + include ( $${QWT_ROOT}/src/src.pri ) + + for( header, HEADERS) { + QWT_HEADERS += $${QWT_ROOT}/src/$${header} + } + + for( source, SOURCES ) { + QWT_SOURCES += $${QWT_ROOT}/src/$${source} + } + + HEADERS = $${QWT_HEADERS} + SOURCES = $${QWT_SOURCES} + + } else { + + # compile the path for finding the Qwt library + # into the plugin. Not supported on Windows ! + + QMAKE_RPATHDIR *= $${QWT_INSTALL_LIBS} + + contains(QWT_CONFIG, QwtFramework) { + + LIBS += -F$${QWT_ROOT}/lib + } + else { + + LIBS += -L$${QWT_ROOT}/lib + } + + qwtAddLibrary(qwt) + + contains(QWT_CONFIG, QwtDll) { + + win32 { + DEFINES += QT_DLL QWT_DLL + } + } + } + + !contains(QWT_CONFIG, QwtPlot) { + DEFINES += NO_QWT_PLOT + } + + !contains(QWT_CONFIG, QwtWidgets) { + DEFINES += NO_QWT_WIDGETS + } + + HEADERS += qwt_designer_plugin.h + SOURCES += qwt_designer_plugin.cpp + + contains(QWT_CONFIG, QwtPlot) { + + HEADERS += qwt_designer_plotdialog.h + SOURCES += qwt_designer_plotdialog.cpp + } + + RESOURCES += qwt_designer_plugin.qrc + + target.path = $${QWT_INSTALL_PLUGINS} + INSTALLS += target +} +else { + TEMPLATE = subdirs # do nothing +} diff --git a/qwt/designer/pixmaps/qwtanalogclock.png b/qwt/designer/pixmaps/qwtanalogclock.png new file mode 100644 index 000000000..89f3451ac Binary files /dev/null and b/qwt/designer/pixmaps/qwtanalogclock.png differ diff --git a/qwt/designer/pixmaps/qwtcompass.png b/qwt/designer/pixmaps/qwtcompass.png new file mode 100644 index 000000000..e1e54a11d Binary files /dev/null and b/qwt/designer/pixmaps/qwtcompass.png differ diff --git a/qwt/designer/pixmaps/qwtcounter.png b/qwt/designer/pixmaps/qwtcounter.png new file mode 100644 index 000000000..1daddb63f Binary files /dev/null and b/qwt/designer/pixmaps/qwtcounter.png differ diff --git a/qwt/designer/pixmaps/qwtdial.png b/qwt/designer/pixmaps/qwtdial.png new file mode 100644 index 000000000..46f079c71 Binary files /dev/null and b/qwt/designer/pixmaps/qwtdial.png differ diff --git a/qwt/designer/pixmaps/qwtknob.png b/qwt/designer/pixmaps/qwtknob.png new file mode 100644 index 000000000..2118dfa36 Binary files /dev/null and b/qwt/designer/pixmaps/qwtknob.png differ diff --git a/qwt/designer/pixmaps/qwtplot.png b/qwt/designer/pixmaps/qwtplot.png new file mode 100644 index 000000000..8e1ca4ed5 Binary files /dev/null and b/qwt/designer/pixmaps/qwtplot.png differ diff --git a/qwt/designer/pixmaps/qwtscale.png b/qwt/designer/pixmaps/qwtscale.png new file mode 100644 index 000000000..00a142122 Binary files /dev/null and b/qwt/designer/pixmaps/qwtscale.png differ diff --git a/qwt/designer/pixmaps/qwtslider.png b/qwt/designer/pixmaps/qwtslider.png new file mode 100644 index 000000000..8e9316c21 Binary files /dev/null and b/qwt/designer/pixmaps/qwtslider.png differ diff --git a/qwt/designer/pixmaps/qwtthermo.png b/qwt/designer/pixmaps/qwtthermo.png new file mode 100644 index 000000000..3b2bd50d8 Binary files /dev/null and b/qwt/designer/pixmaps/qwtthermo.png differ diff --git a/qwt/designer/pixmaps/qwtwheel.png b/qwt/designer/pixmaps/qwtwheel.png new file mode 100644 index 000000000..c1f562cd5 Binary files /dev/null and b/qwt/designer/pixmaps/qwtwheel.png differ diff --git a/qwt/designer/pixmaps/qwtwidget.png b/qwt/designer/pixmaps/qwtwidget.png new file mode 100644 index 000000000..cf72b754d Binary files /dev/null and b/qwt/designer/pixmaps/qwtwidget.png differ diff --git a/qwt/designer/qwt_designer_plotdialog.cpp b/qwt/designer/qwt_designer_plotdialog.cpp new file mode 100644 index 000000000..1dba04e3e --- /dev/null +++ b/qwt/designer/qwt_designer_plotdialog.cpp @@ -0,0 +1,42 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include +#include +#include +#include +#include "qwt_designer_plotdialog.h" + +using namespace QwtDesignerPlugin; + +PlotDialog::PlotDialog( const QString &properties, QWidget *parent ): + QDialog( parent ) +{ + setWindowTitle( "Plot Properties" ); + + QLineEdit *lineEdit = new QLineEdit( properties ); + connect( lineEdit, SIGNAL( textChanged( const QString & ) ), + SIGNAL( edited( const QString & ) ) ); + + QTabWidget *tabWidget = new QTabWidget( this ); + tabWidget->addTab( lineEdit, "General" ); + + QPushButton *closeButton = new QPushButton( "Close" ); + connect( closeButton, SIGNAL( clicked() ), this, SLOT( accept() ) ); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch( 1 ); + buttonLayout->addWidget( closeButton ); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget( tabWidget ); + mainLayout->addLayout( buttonLayout ); + setLayout( mainLayout ); +} + diff --git a/qwt/designer/qwt_designer_plotdialog.h b/qwt/designer/qwt_designer_plotdialog.h new file mode 100644 index 000000000..82f7f02ce --- /dev/null +++ b/qwt/designer/qwt_designer_plotdialog.h @@ -0,0 +1,31 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_DESIGNER_PLOTDIALOG_H +#define QWT_DESIGNER_PLOTDIALOG_H + +#include + +namespace QwtDesignerPlugin +{ + + class PlotDialog: public QDialog + { + Q_OBJECT + + public: + PlotDialog( const QString &properties, QWidget *parent = NULL ); + + Q_SIGNALS: + void edited( const QString& ); + }; + +} + +#endif diff --git a/qwt/designer/qwt_designer_plugin.cpp b/qwt/designer/qwt_designer_plugin.cpp new file mode 100644 index 000000000..f46a11fa4 --- /dev/null +++ b/qwt/designer/qwt_designer_plugin.cpp @@ -0,0 +1,570 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#if defined(_MSC_VER) /* MSVC Compiler */ +#pragma warning ( disable : 4786 ) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qwt_designer_plugin.h" + +#ifndef NO_QWT_PLOT +#include "qwt_designer_plotdialog.h" +#include "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_scale_widget.h" +#endif + +#ifndef NO_QWT_WIDGETS +#include "qwt_counter.h" +#include "qwt_wheel.h" +#include "qwt_thermo.h" +#include "qwt_knob.h" +#include "qwt_slider.h" +#include "qwt_dial.h" +#include "qwt_dial_needle.h" +#include "qwt_analog_clock.h" +#include "qwt_compass.h" +#endif + +#include "qwt_text_label.h" + +using namespace QwtDesignerPlugin; + +CustomWidgetInterface::CustomWidgetInterface( QObject *parent ): + QObject( parent ), + d_isInitialized( false ) +{ +} + +bool CustomWidgetInterface::isContainer() const +{ + return false; +} + +bool CustomWidgetInterface::isInitialized() const +{ + return d_isInitialized; +} + +QIcon CustomWidgetInterface::icon() const +{ + return d_icon; +} + +QString CustomWidgetInterface::codeTemplate() const +{ + return d_codeTemplate; +} + +QString CustomWidgetInterface::domXml() const +{ + return d_domXml; +} + +QString CustomWidgetInterface::group() const +{ + return "Qwt Widgets"; +} + +QString CustomWidgetInterface::includeFile() const +{ + return d_include; +} + +QString CustomWidgetInterface::name() const +{ + return d_name; +} + +QString CustomWidgetInterface::toolTip() const +{ + return d_toolTip; +} + +QString CustomWidgetInterface::whatsThis() const +{ + return d_whatsThis; +} + +void CustomWidgetInterface::initialize( + QDesignerFormEditorInterface *formEditor ) +{ + if ( d_isInitialized ) + return; + + QExtensionManager *manager = formEditor->extensionManager(); + if ( manager ) + { + manager->registerExtensions( new TaskMenuFactory( manager ), + Q_TYPEID( QDesignerTaskMenuExtension ) ); + } + + d_isInitialized = true; +} + +#ifndef NO_QWT_PLOT + +PlotInterface::PlotInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtPlot"; + d_include = "qwt_plot.h"; + d_icon = QPixmap( ":/pixmaps/qwtplot.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 400\n" + " 200\n" + " \n" + " \n" + "\n"; +} + +QWidget *PlotInterface::createWidget( QWidget *parent ) +{ + return new QwtPlot( parent ); +} + + +PlotCanvasInterface::PlotCanvasInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtPlotCanvas"; + d_include = "qwt_plot_canvas.h"; + d_icon = QPixmap( ":/pixmaps/qwtplot.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 400\n" + " 200\n" + " \n" + " \n" + "\n"; +} + +QWidget *PlotCanvasInterface::createWidget( QWidget *parent ) +{ + return new QwtPlotCanvas( qobject_cast( parent ) ); +} + +#endif + +#ifndef NO_QWT_WIDGETS + +AnalogClockInterface::AnalogClockInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtAnalogClock"; + d_include = "qwt_analog_clock.h"; + d_icon = QPixmap( ":/pixmaps/qwtanalogclock.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 200\n" + " 200\n" + " \n" + " \n" + " \n" + " 4\n" + " \n" + "\n"; +} + +QWidget *AnalogClockInterface::createWidget( QWidget *parent ) +{ + return new QwtAnalogClock( parent ); +} + +#endif + +#ifndef NO_QWT_WIDGETS + +CompassInterface::CompassInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtCompass"; + d_include = "qwt_compass.h"; + d_icon = QPixmap( ":/pixmaps/qwtcompass.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 200\n" + " 200\n" + " \n" + " \n" + " \n" + " 4\n" + " \n" + "\n"; +} + +QWidget *CompassInterface::createWidget( QWidget *parent ) +{ + QwtCompass *compass = new QwtCompass( parent ); + compass->setNeedle( new QwtCompassMagnetNeedle( + QwtCompassMagnetNeedle::TriangleStyle, + compass->palette().color( QPalette::Mid ), + compass->palette().color( QPalette::Dark ) ) ); + + return compass; +} + +#endif + +#ifndef NO_QWT_WIDGETS + +CounterInterface::CounterInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtCounter"; + d_include = "qwt_counter.h"; + d_icon = QPixmap( ":/pixmaps/qwtcounter.png" ); + d_domXml = + "\n" + "\n"; +} + +QWidget *CounterInterface::createWidget( QWidget *parent ) +{ + return new QwtCounter( parent ); +} + +#endif + +#ifndef NO_QWT_WIDGETS + +DialInterface::DialInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtDial"; + d_include = "qwt_dial.h"; + d_icon = QPixmap( ":/pixmaps/qwtdial.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 200\n" + " 200\n" + " \n" + " \n" + " \n" + " 4\n" + " \n" + "\n"; +} + +QWidget *DialInterface::createWidget( QWidget *parent ) +{ + QwtDial *dial = new QwtDial( parent ); + dial->setNeedle( new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, + dial->palette().color( QPalette::Dark ), + dial->palette().color( QPalette::Mid ) ) ); + + return dial; +} + +#endif + +#ifndef NO_QWT_WIDGETS + +KnobInterface::KnobInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtKnob"; + d_include = "qwt_knob.h"; + d_icon = QPixmap( ":/pixmaps/qwtknob.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 150\n" + " 150\n" + " \n" + " \n" + "\n"; +} + +QWidget *KnobInterface::createWidget( QWidget *parent ) +{ + return new QwtKnob( parent ); +} + +#endif + +#ifndef NO_QWT_PLOT + +ScaleWidgetInterface::ScaleWidgetInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtScaleWidget"; + d_include = "qwt_scale_widget.h"; + d_icon = QPixmap( ":/pixmaps/qwtscale.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 60\n" + " 250\n" + " \n" + " \n" + "\n"; +} + +QWidget *ScaleWidgetInterface::createWidget( QWidget *parent ) +{ + return new QwtScaleWidget( QwtScaleDraw::LeftScale, parent ); +} + +#endif + +#ifndef NO_QWT_WIDGETS + +SliderInterface::SliderInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtSlider"; + d_include = "qwt_slider.h"; + d_icon = QPixmap( ":/pixmaps/qwtslider.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 60\n" + " 250\n" + " \n" + " \n" + "\n"; +} + +QWidget *SliderInterface::createWidget( QWidget *parent ) +{ + return new QwtSlider( parent ); +} + +#endif + +TextLabelInterface::TextLabelInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtTextLabel"; + d_include = "qwt_text_label.h"; + + d_icon = QPixmap( ":/pixmaps/qwtwidget.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 100\n" + " 20\n" + " \n" + " \n" + "\n"; +} + +QWidget *TextLabelInterface::createWidget( QWidget *parent ) +{ + return new QwtTextLabel( QwtText( "Label" ), parent ); +} + +#ifndef NO_QWT_WIDGETS + +ThermoInterface::ThermoInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtThermo"; + d_include = "qwt_thermo.h"; + d_icon = QPixmap( ":/pixmaps/qwtthermo.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 60\n" + " 250\n" + " \n" + " \n" + "\n"; +} + +QWidget *ThermoInterface::createWidget( QWidget *parent ) +{ + return new QwtThermo( parent ); +} + +#endif + +#ifndef NO_QWT_WIDGETS + +WheelInterface::WheelInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtWheel"; + d_include = "qwt_wheel.h"; + d_icon = QPixmap( ":/pixmaps/qwtwheel.png" ); + d_domXml = + "\n" + "\n"; +} + +QWidget *WheelInterface::createWidget( QWidget *parent ) +{ + return new QwtWheel( parent ); +} + +#endif + +CustomWidgetCollectionInterface::CustomWidgetCollectionInterface( + QObject *parent ): + QObject( parent ) +{ +#ifndef NO_QWT_PLOT + d_plugins.append( new PlotInterface( this ) ); + +#if 0 + // better not: the designer crashes TODO .. + d_plugins.append( new PlotCanvasInterface( this ) ); +#endif + + d_plugins.append( new ScaleWidgetInterface( this ) ); +#endif + +#ifndef NO_QWT_WIDGETS + d_plugins.append( new AnalogClockInterface( this ) ); + d_plugins.append( new CompassInterface( this ) ); + d_plugins.append( new CounterInterface( this ) ); + d_plugins.append( new DialInterface( this ) ); + d_plugins.append( new KnobInterface( this ) ); + d_plugins.append( new SliderInterface( this ) ); + d_plugins.append( new ThermoInterface( this ) ); + d_plugins.append( new WheelInterface( this ) ); +#endif + + d_plugins.append( new TextLabelInterface( this ) ); +} + +QList +CustomWidgetCollectionInterface::customWidgets( void ) const +{ + return d_plugins; +} + +TaskMenuFactory::TaskMenuFactory( QExtensionManager *parent ): + QExtensionFactory( parent ) +{ +} + +QObject *TaskMenuFactory::createExtension( + QObject *object, const QString &iid, QObject *parent ) const +{ + if ( iid == Q_TYPEID( QDesignerTaskMenuExtension ) ) + { +#ifndef NO_QWT_PLOT + if ( QwtPlot *plot = qobject_cast( object ) ) + return new TaskMenuExtension( plot, parent ); +#endif +#ifndef NO_QWT_WIDGETS + if ( QwtDial *dial = qobject_cast( object ) ) + return new TaskMenuExtension( dial, parent ); +#endif + } + + return QExtensionFactory::createExtension( object, iid, parent ); +} + + +TaskMenuExtension::TaskMenuExtension( QWidget *widget, QObject *parent ): + QObject( parent ), + d_widget( widget ) +{ + d_editAction = new QAction( tr( "Edit Qwt Attributes ..." ), this ); + connect( d_editAction, SIGNAL( triggered() ), + this, SLOT( editProperties() ) ); +} + +QList TaskMenuExtension::taskActions() const +{ + QList list; + list.append( d_editAction ); + return list; +} + +QAction *TaskMenuExtension::preferredEditAction() const +{ + return d_editAction; +} + +void TaskMenuExtension::editProperties() +{ + const QVariant v = d_widget->property( "propertiesDocument" ); + if ( v.type() != QVariant::String ) + return; + +#ifndef NO_QWT_PLOT + QString properties = v.toString(); + + if ( qobject_cast( d_widget ) ) + { + PlotDialog dialog( properties ); + connect( &dialog, SIGNAL( edited( const QString& ) ), + SLOT( applyProperties( const QString & ) ) ); + ( void )dialog.exec(); + return; + } +#endif + + static QErrorMessage *errorMessage = NULL; + if ( errorMessage == NULL ) + errorMessage = new QErrorMessage(); + errorMessage->showMessage( "Not implemented yet." ); +} + +void TaskMenuExtension::applyProperties( const QString &properties ) +{ + QDesignerFormWindowInterface *formWindow = + QDesignerFormWindowInterface::findFormWindow( d_widget ); + if ( formWindow && formWindow->cursor() ) + formWindow->cursor()->setProperty( "propertiesDocument", properties ); +} + +#if QT_VERSION < 0x050000 +Q_EXPORT_PLUGIN2( QwtDesignerPlugin, CustomWidgetCollectionInterface ) +#endif diff --git a/qwt/designer/qwt_designer_plugin.h b/qwt/designer/qwt_designer_plugin.h new file mode 100644 index 000000000..c24e65600 --- /dev/null +++ b/qwt/designer/qwt_designer_plugin.h @@ -0,0 +1,248 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_DESIGNER_PLUGIN_H +#define QWT_DESIGNER_PLUGIN_H + +#include +#include +#include + +namespace QwtDesignerPlugin +{ + class CustomWidgetInterface: public QObject, + public QDesignerCustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + CustomWidgetInterface( QObject *parent ); + + virtual bool isContainer() const; + virtual bool isInitialized() const; + virtual QIcon icon() const; + virtual QString codeTemplate() const; + virtual QString domXml() const; + virtual QString group() const; + virtual QString includeFile() const; + virtual QString name() const; + virtual QString toolTip() const; + virtual QString whatsThis() const; + virtual void initialize( QDesignerFormEditorInterface * ); + + protected: + QString d_name; + QString d_include; + QString d_toolTip; + QString d_whatsThis; + QString d_domXml; + QString d_codeTemplate; + QIcon d_icon; + + private: + bool d_isInitialized; + }; + + class CustomWidgetCollectionInterface: public QObject, + public QDesignerCustomWidgetCollectionInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetCollectionInterface ) + +#if QT_VERSION >= 0x050000 + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetCollectionInterface" ) +#endif + + + public: + CustomWidgetCollectionInterface( QObject *parent = NULL ); + + virtual QList customWidgets() const; + + private: + QList d_plugins; + }; + +#ifndef NO_QWT_PLOT + class PlotInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + PlotInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; + + class PlotCanvasInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + PlotCanvasInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class AnalogClockInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + AnalogClockInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class CompassInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + CompassInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class CounterInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + CounterInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class DialInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + DialInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class KnobInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + KnobInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_PLOT + class ScaleWidgetInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + ScaleWidgetInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class SliderInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + SliderInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + + class TextLabelInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + TextLabelInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; + +#ifndef NO_QWT_WIDGETS + class ThermoInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + ThermoInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class WheelInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + WheelInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + + class TaskMenuFactory: public QExtensionFactory + { + Q_OBJECT + + public: + TaskMenuFactory( QExtensionManager *parent = 0 ); + + protected: + QObject *createExtension( QObject *object, + const QString &iid, QObject *parent ) const; + }; + + class TaskMenuExtension: public QObject, + public QDesignerTaskMenuExtension + { + Q_OBJECT + Q_INTERFACES( QDesignerTaskMenuExtension ) + + public: + TaskMenuExtension( QWidget *widget, QObject *parent ); + + QAction *preferredEditAction() const; + QList taskActions() const; + + private Q_SLOTS: + void editProperties(); + void applyProperties( const QString & ); + + private: + QAction *d_editAction; + QWidget *d_widget; + }; + +}; + +#endif diff --git a/qwt/designer/qwt_designer_plugin.qrc b/qwt/designer/qwt_designer_plugin.qrc new file mode 100644 index 000000000..01e4ffc8f --- /dev/null +++ b/qwt/designer/qwt_designer_plugin.qrc @@ -0,0 +1,15 @@ + + + pixmaps/qwtplot.png + pixmaps/qwtanalogclock.png + pixmaps/qwtcounter.png + pixmaps/qwtcompass.png + pixmaps/qwtdial.png + pixmaps/qwtknob.png + pixmaps/qwtscale.png + pixmaps/qwtslider.png + pixmaps/qwtthermo.png + pixmaps/qwtwheel.png + pixmaps/qwtwidget.png + + diff --git a/qwt/doc/Doxyfile b/qwt/doc/Doxyfile new file mode 100644 index 000000000..d1e55dd67 --- /dev/null +++ b/qwt/doc/Doxyfile @@ -0,0 +1,1799 @@ +# Doxyfile 1.8.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "Qwt User's Guide" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = $(QWTVERSION) + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = YES + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = NO + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = NO + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = NO + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = NO + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = Doxygen.log + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . \ + ../src \ + ../textengines/mathml + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = qwt.h + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = \ + QwtMathMLDocument \ + QwtPainterCommand::PixmapData \ + QwtPainterCommand::ImageData \ + QwtPainterCommand::StateData + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = . + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = images + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = Qwt \ + Q + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = qwtdoc.qch + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = net.sourceforge.qwt-svn + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = qwt-svn + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = qhelpgenerator + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = YES + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = Q_PROPERTY(x)= + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = NO + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/qwt/doc/articles/TODO b/qwt/doc/articles/TODO new file mode 100644 index 000000000..373a767a5 --- /dev/null +++ b/qwt/doc/articles/TODO @@ -0,0 +1,8 @@ +1) Installation guides +2) QwtSeriesData + QAbstractModel +3) Navigation +4) Scales +5) Alive plots + sampling threads +6) Exporting + printing plots +7) Plot Items +8) Raster items diff --git a/qwt/doc/changes.dox b/qwt/doc/changes.dox new file mode 100644 index 000000000..ee13a965f --- /dev/null +++ b/qwt/doc/changes.dox @@ -0,0 +1,306 @@ +/*! +\page qwtchangelog What's new in Qwt 6.1 + +\tableofcontents + +\section ITEMS New plot items + + - QwtPlotBarChart\n + Bar chart, see "examples/distrowatch" + + - QwtPlotMultiBarChart\n + Chart of grouped bars - stacked or aligned side by side. + See "examples/barchart" + + - QwtPlotTradingCurve\n + Candlestick or OHLC charts typically used to describe + price movements over time. See "examples/stockchart" + + - QwtPlotShapeItem\n + A plot item to display rectangles, circles, polygons and all + other type of shapes ( built from intersections or unifications ), + that can be expressed by a QPainterPath. See "examples/itemeditor" + + - QwtPlotLegendItem\n + A legend on the plot canvas. See "examples/legends" + + - QwtPlotZoneItem\n + A horizontal or vertical section + + - QwtPlotTextLabel\n + In opposite to a QwtPlotMarker the text is not aligned to a plot coordinate + but according to the geometry of the canvas ( f.e top/centered for a title ). + See "playground/curvetracker". + + +\section SCALES Scales beyond linear and logarithmic transformations + +QwtScaleTransformation has been replaced by QwtTransform and its derived classes: + + - QwtTransform + - QwtNullTransform + - QwtLogTransform + - QwtPowerTransform + +Individual transformations ( f.e. different scaling for special sections ) +can be implemented by overloading QwtTransform ( see playground/scaleengine ). + +QwtLinearScaleEngine and QwtLogScaleEngine are not limited to +base 10 anymore. + +\subsection DATETIME Datetime scales + +A set of a new classes for displaying datetime values: + + - QwtDate\n + A collection of methods to convert between QDateTime and doubles + + - QwtDateScaleEngine\n + A scale engine that aligns and finds ticks in terms of datetime units. + + - QwtDateScaleDraw\n + A scale draw mapping values to datetime strings. + +Scales for Qt::UTC and Qt::LocalTime are supported. + +\section CONTROLS Redesign of the dial and meter widgets + +Many parts of the class design of the dial and meter widgets were left over +from the 90s ( Qwt 0.2, Qt 1.1 ). + +The derivation tree is simpler and more logical: + + - QwtAbstractScale is a QWidget + - QwtAbstractSlider is a QwtAbstractScale. + ( for sliders without scales QAbstractSlider should be the base class ) + - QwtThermo is also a QwtAbstractScale + - QwtDial, QwtKnob, QwtSlider are derived from QwtAbstractSlider + - QwtCounter is derived from QWidget + +QwtDoubleRange has been removed. + +All classes use the terminology known from QAbstractSlider - as far as possible. +The extended \ref SCALES "system for scales" is completely supported. + +\section OPENGL Basic support for an OpenGL plot canvas + +QwtPlotGLCanvas offers the option to draw plot items using an +OpenGL paint engine ( QPaintEngine::OpenGL/OpenGL2 ), +This is not what could be implemented with native OpenGL, +but it offers hardware acceleration in environments, +where the raster paint engine is the only option. +( f.e Qt4/Windows, or Qt5 on all platforms ). + +QwtPlotGLCanvas is in an experimental state and is not recommended for average +use cases. + +\section LEGEND A new system for plot legends + +QwtLegend has been decoupled from QwtPlot and can be replaced by +application specific implementations. Plot items and the legend +exchange the information using QwtLegendData. + +QwtPlotLegendItem is a new plot item that displays a legend on the +plot canvas. + +The following examples demonstrate how to use the new system: + + - examples/legends\n + shows how to use the new legend system + - examples/stockchart\n + implementats a QTreeView with checkable items as legend + +\section GRAPHIC Off-screen paint device for vector graphics + +QwtGraphic can be copied like QImage or QPixmap but is scalable like QSvgGenerator. +It is implemented as a record/replay paint device like QPicture. + +\section OVERLAY QwtWidgetOverlay + +QwtWidgetOverlay is a base class for implementing widget overlays - primarily +used for use cases like graphical editors or running cursors for the plot canvas. + +The following examples show how to use overlays: + + - examples/itemeditor + - examples/curvetracker + +QwtPicker ( -> QwtPlotPicker, QwtPlotZoomer ) internally uses +QwtWidgetOverlay now, making it easier to implement individual rubber bands. + +\section SYMBOL QwtSymbol + +New symbol types have been introduced: + + - QwtSymbol::Path + - QwtSymbol::Pixmap + - QwtSymbol::Graphic + - QwtSymbol::SvgDocument + +QwtSymbol autodetect the most performant paint strategy for a paint device +what is in most situations using a QPixmap cache. + +QwtSymbol::setPinPoint() allows to align the symbol individually, f.e to the position +of the peak of an arrow. + +\section PLOTCURVE QwtPlotCurve + +Some optimizations that got lost with introducing the floating point +based render code with Qwt 6.0 have been reenabled. Other specific optimizations +have been added. + +New paint attributes: + + - QwtPlotCurve::FilterPoints + - QwtPlotCurve::MinimizeMemory + - QwtPlotCurve::ImageBuffer + +QwtPlotCurve::CacheSymbols has been removed, as caching is implemented +in QwtSymbol now. + +QwtPlotCurve::drawLines(), QwtPlotCurve::drawDots(), +QwtPlotCurve::drawSteps() and QwtPlotCurve::drawSticks() are virtual now. + +\section PLOT QwtPlot + +A footer similar to a title has been added. + +QwtPlot::ExternalLegend is obsolete with the +new \ref LEGEND "system for legends". The signals +QwtPlot::legendClicked(), QwtPlot::legendChecked() have been +removed. Applications need to connect to QwtLegend::clicked() +and QwtLegend::checked(). + +To support using an OpenGL canvas QwtPlot::setCanvas has been added. +This has 2 important implications for the application code: + + - QwtPlot::canvas() returns QWidget and needs to be casted, when + using methods of QwtPlotCanvas. + - QwtPlotCanvas can be created and assigned in application code, + what makes it possible to derive and overload methods. + +The initialization of a plot canvas with Qwt 6.1 will probably look like +this: + +\code + QwtPlotCanvas* canvas = new QwtPlotCanvas(); + canvas->setXY( ... ); + ... + + plot->setCanvas( canvas ); +\endcode + +To have a consistent API QwtPlot::setPlotLayout() has been added, + + +\section OTHER Other + +\subsection SCALEDIV QwtScaleDiv + +The following methods have been added: + + - QwtScaleDiv::inverted() + - QwtScaleDiv::bounded() + - QwtScaleDiv::isEmpty() + - QwtScaleDiv::isIncreasing() + - QDebug operator + +The following methods have been removed: + + - QwtScaleDiv::isValid(), QwtScaleDiv::invalidate()\n + The valid state was left over from early Qwt versions indicating + a state of the autoscaler. + +\subsection SCALEENGINE QwtScaleEngine + +The following methods have been added: + + - QwtScaleEngine::setBase() + - QwtScaleEngine::setTransformation() + +\subsection PLOTLAYOUT QwtPlotLayout + +The following flags have been added: + + - QwtPlotLayout::IgnoreTitle + - QwtPlotLayout::IgnoreFooter + - QwtPlotLayout::setAlignCanvasToScale() + +\subsection PLOTCANVAS QwtPlotCanvas + +Rounded borders ( like with style sheets ) can configured +using QwtPlotCanvas::setBorderRadius(); + +\subsection OTHERS Other changes + + - QwtWeedingCurveFitter\n + QwtWeedingCurveFitter::setChunkSize() has been added, with drastic + performance improvements for huge sets of points. + + - QwtPlotRenderer + The frame of the plot canvas can be rendered, what makes the result + even closer to WYSWYG. QwtPlotRenderer::exportTo() has been added. + + - QwtSystemClock + For Qt >= 4.9 QwtSystemClock uses QElapsedTimer internally. As it doesn't + support a similar feature, QwtSystemClock::precision() has been removed. + + - QwtPlotAbstractSeriesItem\n + QwtPlotAbstractSeriesItem has been split into QwtPlotSeriesItem + and QwtPlotAbstractSeriesStore. + + - QwtText\n + A metatype declaration has been added, so that QwtText can be used + with QVariant. + + - QwtEventPattern, QwtPanner, QwtMagnifier\n + Forgotten Qt3 leftovers have been fixed: int -> Qt::KeyboardModifiers + + - QPen Qt5/Qt4 incompatibility + The default pen width for Qt5 is 1, what makes it a non cosmetic. + To hide this nasty incompatibility several setPen() methods have been added + the build pens with a width 0. See QPen::isCosmetic(), + + - qwtUpperSampleIndex()\n + A binary search algorithm for sorted samples + + - QwtMatrixRasterData + QwtMatrixRasterData::setValue() has been added + + - QwtPicker + QwtPicker::rubberBandWidget(), QwtPicker::trackerWidget() have been replaced by + QwtPicker::rubberBandOverlay(), QwtPicker::trackerOverlay(). + QwtPicker::rubberBandMask() has been added. QwtPicker::pickRect() has been + replaced by QwtPicker::pickArea() + + - QwtPlotItem + QwtPlotItem::ItemInterest has been added. QwtPlotItem::setRenderThreadCount() + was shifted from QwtPlotRasterItem. + + - ... + +\section CLASSES Summary of the new classes + + - QwtAbstractLegend + - QwtDate + - QwtDateScaleDraw + - QwtDateScaleEngine + - QwtGraphic + - QwtLegendData + - QwtLegendLabel + - QwtPainterCommand + - QwtPixelMatrix + - QwtPlotAbstractBarChart + - QwtPlotBarChart + - QwtPlotMultiBarChart + - QwtPlotGLCanvas + - QwtPlotLegendItem + - QwtPlotShapeItem + - QwtPlotTextLabel + - QwtPlotTradingCurve + - QwtPlotZoneItem + - QwtPointData + - QwtPointMapper + - QwtTransform, QwtNullTransform, QwtLogTransform, QwtPowerTransform + - QwtWidgetOverlay +*/ diff --git a/qwt/doc/doc.pro b/qwt/doc/doc.pro new file mode 100644 index 000000000..538d86dc0 --- /dev/null +++ b/qwt/doc/doc.pro @@ -0,0 +1,22 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +# qmake project file for installing the documentation + +QWT_ROOT = $${PWD}/.. +include( $${QWT_ROOT}/qwtconfig.pri ) + +TEMPLATE = subdirs + +doc.files = $${QWT_ROOT}/doc/html +unix:doc.files += $${QWT_ROOT}/doc/man +doc.path = $${QWT_INSTALL_DOCS} + +INSTALLS = doc + diff --git a/qwt/doc/images/analogclock.png b/qwt/doc/images/analogclock.png new file mode 100644 index 000000000..a4c5cc21a Binary files /dev/null and b/qwt/doc/images/analogclock.png differ diff --git a/qwt/doc/images/cpuplot.png b/qwt/doc/images/cpuplot.png new file mode 100644 index 000000000..0f1bfc362 Binary files /dev/null and b/qwt/doc/images/cpuplot.png differ diff --git a/qwt/doc/images/curves.png b/qwt/doc/images/curves.png new file mode 100644 index 000000000..e24c81c96 Binary files /dev/null and b/qwt/doc/images/curves.png differ diff --git a/qwt/doc/images/dials1.png b/qwt/doc/images/dials1.png new file mode 100644 index 000000000..690c663d6 Binary files /dev/null and b/qwt/doc/images/dials1.png differ diff --git a/qwt/doc/images/dials2.png b/qwt/doc/images/dials2.png new file mode 100644 index 000000000..59d1a611d Binary files /dev/null and b/qwt/doc/images/dials2.png differ diff --git a/qwt/doc/images/graph.png b/qwt/doc/images/graph.png new file mode 100644 index 000000000..55f05b876 Binary files /dev/null and b/qwt/doc/images/graph.png differ diff --git a/qwt/doc/images/histogram.png b/qwt/doc/images/histogram.png new file mode 100644 index 000000000..234bd482a Binary files /dev/null and b/qwt/doc/images/histogram.png differ diff --git a/qwt/doc/images/knob.png b/qwt/doc/images/knob.png new file mode 100644 index 000000000..f76089f6e Binary files /dev/null and b/qwt/doc/images/knob.png differ diff --git a/qwt/doc/images/plot.png b/qwt/doc/images/plot.png new file mode 100644 index 000000000..d0881034d Binary files /dev/null and b/qwt/doc/images/plot.png differ diff --git a/qwt/doc/images/radio.png b/qwt/doc/images/radio.png new file mode 100644 index 000000000..dff1e14d9 Binary files /dev/null and b/qwt/doc/images/radio.png differ diff --git a/qwt/doc/images/scatterplot.png b/qwt/doc/images/scatterplot.png new file mode 100644 index 000000000..fd157f0b2 Binary files /dev/null and b/qwt/doc/images/scatterplot.png differ diff --git a/qwt/doc/images/sinus.png b/qwt/doc/images/sinus.png new file mode 100644 index 000000000..d8edc5dcb Binary files /dev/null and b/qwt/doc/images/sinus.png differ diff --git a/qwt/doc/images/sliders.png b/qwt/doc/images/sliders.png new file mode 100644 index 000000000..9b62a746c Binary files /dev/null and b/qwt/doc/images/sliders.png differ diff --git a/qwt/doc/images/spectrogram1.png b/qwt/doc/images/spectrogram1.png new file mode 100644 index 000000000..e2774e93b Binary files /dev/null and b/qwt/doc/images/spectrogram1.png differ diff --git a/qwt/doc/images/spectrogram2.png b/qwt/doc/images/spectrogram2.png new file mode 100644 index 000000000..dcc33857d Binary files /dev/null and b/qwt/doc/images/spectrogram2.png differ diff --git a/qwt/doc/images/spectrogram3.png b/qwt/doc/images/spectrogram3.png new file mode 100644 index 000000000..9c985a9e9 Binary files /dev/null and b/qwt/doc/images/spectrogram3.png differ diff --git a/qwt/doc/images/sysinfo.png b/qwt/doc/images/sysinfo.png new file mode 100644 index 000000000..562aa5feb Binary files /dev/null and b/qwt/doc/images/sysinfo.png differ diff --git a/qwt/doc/install.dox b/qwt/doc/install.dox new file mode 100644 index 000000000..774e4bad7 --- /dev/null +++ b/qwt/doc/install.dox @@ -0,0 +1,305 @@ +/*! +\page qwtinstall Installing Qwt + +\tableofcontents + +\section DOWNLOAD Download + + Stable Qwt releases are available from the + Qwt project page. + + Qwt-$(QWTVERSION) consists of 4 files: + + - qwt-$(QWTVERSION).zip\n + Zip file with the Qwt sources and the html documentation for Windows + + - qwt-$(QWTVERSION).tar.bz2\n + Compressed tar file with the Qwt sources and the html documentation + for UNIX systems ( Linux, Mac, ... ) + + - qwt-$(QWTVERSION).pdf\n + Qwt documentation as PDF document. + + - qwt-$(QWTVERSION).qch\n + Qwt documentation as Qt Compressed Help document, that can be loaded into + the Qt Assistant or Creator. In the Qt Creator context sensitive help will be + available like for Qt classes. + + Precompiled Qwt Designer plugins, that are compatible with some binary packages + of the Qt Creator: + + - qwtdesigner-$(QWTVERSION)-*.zip + + +\section INSTALL Installing Qwt + + Beside headers, libraries and the html version of the class documentation a proper + Qwt installation contains a Designer plugin and a Qwt features file for building + applications using Qwt. + + All files will be copied to an installation directory, that is configurable + by editing qwtconfig.pri. Its default settings is: + + - Windows\n + C:\\Qwt-$(QWTVERSION) + + - Unix like systems\n + /usr/local/qwt-$(QWTVERSION) + + For the rest of the document this install path will be written as ${QWT_ROOT} + and needs to be replaced by the real path in all commands below. + + It is not unlikely, to have more than one installation of Qwt + on the same system. F.e for using the Qwt Designer plugin in the Qt Creator + a version of Qwt is necessary with the same Qt and compiler combination, that had + been used for building the Qt Creator ( see "Help->About Qt Creator ..." ). + + Installing Qwt is done in 3 steps, that are quite common on UNIX systems. + + -# Configuration\n + In the configuration step all parameters are set to control how + to build and install Qwt + -# Build\n + In the build step binaries are built from the source files. + -# Installation\n + The installation copies and rearranges all files that are necessary to build + Qwt applications to a target directory. + + The installation doesn't modify the system beside copying files to a + directory in a proper way. After removing build and installation directories the + system is in the same state as it was before. + +\subsection CONFIGSUBSECTION Configuration + + Configuring Qwt has to be done by editing the Project files used for building: + + - qwtbuild.pri\n + qwtbuild.pri contains settings for how to build Qwt. All settings + of this file are only for building Qwt itself and doesn't have an impact + on how an application using Qwt is built. Usually its default settings + doesn't need to be modified. + + - qwtconfig.pri\n + qwtconfig.pri defines what modules of Qwt will be built and where to + install them. qwtconfig.pri gets installed together with the Qwt features + file qwt.prf and all its settings are known to project files for building + Qwt applications. + + In qwtconfig.pri the meaning of each option is explained in detail - it's worth + reading it before running into problems later. + +\subsection BUILDSUBSECTION Build and installation + + The Qt Creator is a graphical frontend for calling qmake/make and - technically - + it could be used for building and installing Qwt. But as this way requires a lot + more understanding of details the following step by step instructions are for + the easier way using the command line. + +\subsubsection qwtinstall-unix Unix-like systems + + The first step before creating the Makefile is to check that the correct version + of qmake is used. F.e. on older Linux distribution you often find a Qt3 qmake + and in the path. + + The default setting of qmake is to generate a makefile that builds Qwt for the + same environment where the version of qmake has been built for. + So creating a makefile usually means something like: + +\code + cd qwt-$(QWTVERSION) + /usr/local/Qt-5.0.1/bin/qmake qwt.pro +\endcode + + The generated Makefile includes all paths related to the chosen Qt version + and the next step is: + +\code + make +\endcode + ( On multicore systems you can speed up building the Qwt libraries with running several + jobs simultaneously: f.e. "make -j4" on a dual core. ) + + + Finally you have to install everything below the directories you have specified + in qwtconfig.pri. Usually this is one of the system directories ( /usr/local, /opt, ... ) + where you don't have write permission and then the installation + needs to be done as root: + +\code + sudo make install +\endcode + ( On systems where sudo is not supported you can do the same with: su -c "make install" ) + +\subsubsection qwtinstall-windows Windows + + Qt packages offer a command line interface, that can be found in the Qt application + menu: f.e "All Programs -> Qt -> Command Prompt". It is not mandatory to use it, but + probably the easiest way as it offers an environment, where everything is + initialized for a version of Qt ( f.e qmake is in the PATH ). + + Creating a makefile usually means something like: + +\code + cd qwt-$(QWTVERSION) + qmake qwt.pro +\endcode + + The generated makefile includes all paths related to the chosen Qt version. + +\paragraph qwtinstall-windows-mingw MinGW + + For MinGW builds the name of the make tool is "mingw32-make" + +\code + mingw32-make +\endcode + ( On multicore systems you can speed up building the Qwt libraries with running several + jobs simultaneously: "mingw32-make -j" ) + + Finally you have to install everything below the directories you have specified + in qwtconfig.pri. + +\code + mingw32-make install +\endcode + + +\paragraph qwtinstall-windows-msvc MSVC + + For MSVC builds the name of the make tool is "nmake". Alternatively + it is possible to use "jom" ( http://qt-project.org/wiki/jom ), + that is usually included in a Qt Creator package. + +\code + nmake +\endcode + + Finally you have to install everything below the directories you have specified + in qwtconfig.pri. + +\code + nmake install +\endcode + + +\section INTEGRATION Qwt and the Qt tool chain + +\subsection USEPLUGIN Designer plugin + + The Designer plugin and the corresponding Qwt library ( if the plugin has not + been built self containing ) have to be compatible with Qt version of the application + loading it ( usually the Qt Creator ) - what is often a different version of the + Qt libraries you want to build your application with. F.e on Windows the Qt Creator + is usually built with a MSVC compiler - even if included in a MinGW package ! + + To help Qt Designer/Creator with locating the Qwt Designer plugin + you have to set the environment variable QT_PLUGIN_PATH, modify qt.conf - + or install the plugin to one of the application default paths. + + The Qt documentation explains all options in detail: + + - http://qt-project.org/doc/qt-5.0/qtdoc/deployment-plugins.html + - http://qt-project.org/doc/qtcreator-2.7/adding-plugins.html. + + F.e. on a Linux system you could add the following lines to .bashrc: + +\code + QT_PLUGIN_PATH="${QWT_ROOT}/plugins:$QT_PLUGIN_PATH" + export QT_PLUGIN_PATH +\endcode + + When the plugin has not been built including the Qwt library + ( see QwtDesignerSelfContained in qwtconfig.pri ) + the Qt Designer/Creator also needs to locate the Qwt libraries. On Unix systems the + path to the installed library is compiled into the plugin ( see rpath, ldd ), but on + Windows the Qt Creator needs to be configured ( ( \ref RUNAPP ) in the same way as for + any application using Qwt. + + In case of problems the diagnostics of Qt Creator and Designer are very limited + ( usually none ), but setting the environment variable QT_DEBUG_PLUGINS might help. + In the Qt Creator it is possible to check which plugins were loaded + successfully and for certain problems it also lists those that were recognized + but failed ( Tools > Form Editor > About Qt Designer Plugins ). + +\subsection USEHELP Online Help + + The Qwt class documentation can be loaded into the Qt Creator: + + - open the settings dialog from the Tools->Options menu + - raise the tab "Help->Documentation". + - press the Add button and select qwt-$(QWTVERSION).qch. + + Now the context sensitive help ( F1 ) works for Qwt classes. + + For browsing the documentation in the Qt Assistant: + + - open the settings dialog from the Edit->Preferences menu + - raise the tab Documentation. + - press the Add button and select qwt-$(QWTVERSION).qch. + +\section COMPILEANDLINKAPP Building a Qwt application + +All flags and settings that are necessary to compile and link an application using Qwt +can be found in the file ${QWT_ROOT}/features/qwt.prf. + +When using qmake it can included from the application project file in 2 different ways: + + - Adding Qwt as qmake feature\n\n + When using the qmake feature mechanism you can bind a special version + of qmake to a special installation of Qwt without having to add + this dependency to the application project. + How to add Qwt as feature is documented in the + qmake docs. + + After adding Qwt as a feature f.e on Linux as a persistent property .... +@code + qmake -set QMAKEFEATURES ${QWT_ROOT}/features +@endcode + .. the following line can be added to the application project file: +\code + CONFIG += qwt +\endcode + + - Including qwt.prf in the application project file\n\n + Instead of using qwt.prf as qmake feature it can be included from + the application project file:\n\n +\code +include ( ${QWT_ROOT}/features/qwt.prf ) +\endcode \n + The advantage of using a direct include is, that all settings of qwt.prf + are known to the application project file ( qmake features are included after the + application project file has been parsed ) and it can be implemented depending on - + f.e. settings made in qwtconfig.pri. + +On Unix platforms it is possible to link a runtime path into the executable, so that the +location of the Qwt libraries can be found without having to configure a runtime environment: + - QMAKE_LFLAGS_RPATH + - QMAKE_RPATH + - QMAKE_RPATHDIR + +\section RUNAPP Running a Qwt application + + When using Qwt as shared library ( DLL ) the + dynamic linker has to find + it according to the rules of the operating system. + +\subsection RUNWINDOWS Windows + +The only reasonable way to configure the runtime environment - without having to copy the +Qwt libraries around - is to modify the PATH variable. F.e. this could be done by adding +the following line to some batch file: + +\code +set PATH=%PATH%;${QWT_ROOT}\lib +\endcode + +\subsection RUNLINUX GNU/Linux + + Read the documentation about: + + - ldconfig + - /etc/ld.so.conf + - LD_LIBRARY_PATH + + Using the ldd command a configuration can be tested. +*/ diff --git a/qwt/doc/qwt.dox b/qwt/doc/qwt.dox new file mode 100644 index 000000000..0d6dbc23f --- /dev/null +++ b/qwt/doc/qwt.dox @@ -0,0 +1,153 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +/* + This file contains NO source code, just some documentation for doxygen to + parse. +*/ + +/*! + \mainpage Qwt - Qt Widgets for Technical Applications + +The Qwt library contains GUI Components and utility classes which are primarily +useful for programs with a technical background. Beside a framework for 2D plots +it provides scales, sliders, dials, compasses, thermometers, wheels and knobs +to control or display values, arrays, or ranges of type double. + + \image html plot.png + + \if homepage + \section homepage Project page + The official project page is hosted at + sourceforge + \endif + + \section license License + + Qwt is distributed under the terms of the \ref qwtlicense. + + \section platforms Platforms + + Qwt 6.1 might be usable in all environments where you find + Qt. + It is compatible with Qt4 ( >= 4.4 ) and Qt5. + + \section changelogonmainpage What's new + Read the \ref qwtchangelog "summary" of the most important changes. + + \section screenshotsonmainpage Screenshots + - \ref curvescreenshots\n + - \ref scatterscreenshots\n + - \ref spectrogramscreenshots\n + - \ref histogramscreenshots\n + - \ref controlscreenshots\n + + \latexonly Screenshots are only available in the HTML docs.\endlatexonly + + \section downloads Downloads + Stable releases or prereleases are available at the Qwt project page. + + For getting a snapshot with all bugfixes for the latest 5.2 release: + \code svn checkout svn://svn.code.sf.net/p/qwt/code/branches/qwt-5.2 \endcode + + For getting a snapshot with all bugfixes for the latest 6.1 release: + \code svn checkout svn://svn.code.sf.net/p/qwt/code/branches/qwt-6.1 \endcode + + For getting a development snapshot from the SVN repository: + \code svn checkout svn://svn.code.sf.net/p/qwt/code/trunk/qwt \endcode + + \section installonmainpage Installation + + Qwt doesn't distribute binary packages, but today all major Linux distributors + offer one. Note, that these packages often don't include the examples. + + When no binary packages are available ( f.e. on Windows ) Qwt needs to be + \ref qwtinstall "compiled and installed" on the target system. + + \section support Support + - Mailing list\n + For all kind of Qwt related questions use the Qwt mailing list.\n + If you prefer newsgroups use the mail to news gateway of Gmane. + + - Forum\n + Qt Centre is a great resource for Qt + related questions. It has a sub forum, that is dedicated to + Qwt related questions. + + - Individual support\n + If you are looking for individual support, or need someone who implements + your Qwt component/application contact support@qwt-project.org. Sending + requests to this address without a good reason for not using public + support channels might be silently ignored. + + \section relatedprojects Related Projects + + QwtPolar, a polar plot widget.\n + QwtPlot3D, an OpenGL 3D plot widget.\n + + \section donations Donations + + Sourceforge offers a Donation System via PayPal. + You can use it, if you like to support the development of Qwt. + + \section credits Credits: + \par Authors: + Uwe Rathmann, Josef Wilgen ( <= Qwt 0.2 ) + \par Project admin: + Uwe Rathmann \ +*/ + +/*! + \page qwtlicense Qwt License, Version 1.0 + \include "COPYING" +*/ + +/*! + \page curvescreenshots Curve Plots + \image html plot.png + + \image html sinus.png + + \image html cpuplot.png + + \image html graph.png + + \image html curves.png +*/ + +/*! + \page scatterscreenshots Scatter Plot + \image html scatterplot.png +*/ + +/*! + \page spectrogramscreenshots Spectrogram, Contour Plot + \image html spectrogram1.png + + \image html spectrogram2.png + + \image html spectrogram3.png + +/*! + \page histogramscreenshots Histogram + \image html histogram.png +*/ + +/*! + \page controlscreenshots Dials, Compasses, Knobs, Wheels, Sliders, Thermos + \image html radio.png + + \image html sliders.png + + \image html dials1.png + + \image html dials2.png + + \image html sysinfo.png +*/ diff --git a/qwt/doc/tex/plotlayout.tex b/qwt/doc/tex/plotlayout.tex new file mode 100644 index 000000000..e875dd9f4 --- /dev/null +++ b/qwt/doc/tex/plotlayout.tex @@ -0,0 +1,155 @@ +\begin{tikzpicture}{0pt}{0pt}{500pt}{400pt} + \clip(0pt,400pt) -- (425.118pt,400pt) -- (425.118pt,63.8605pt) -- (0pt,63.8605pt) -- (0pt,400pt); + \color[rgb]{0.501961,0.501961,0.501961} + \fill(0pt,400pt) -- (424.267pt,400pt) -- (424.267pt,64.7008pt) -- (0pt,64.7008pt) -- (0pt,400pt); + \color[rgb]{0.752941,0.752941,0.752941} + \fill(130.086pt,326.89pt) -- (353.698pt,326.89pt) -- (353.698pt,114.281pt) -- (130.086pt,114.281pt) -- (130.086pt,326.89pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{242.317pt}{215.964pt}}]{\fontsize{14}{0}\selectfont{Canvas}} + \color[rgb]{1,1,1} + \fill(68.0188pt,391.597pt) -- (415.765pt,391.597pt) -- (415.765pt,373.109pt) -- (68.0188pt,373.109pt) -- (68.0188pt,391.597pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{242.317pt}{377.311pt}}]{\fontsize{14}{0}\selectfont{\textbf{Title}}} + \color[rgb]{1,1,1} + \fill(8.50235pt,326.89pt) -- (62.9174pt,326.89pt) -- (62.9174pt,114.281pt) -- (8.50235pt,114.281pt) -- (8.50235pt,326.89pt); +\begin{scope} + \clip(8.50235pt,326.89pt) -- (63.7676pt,326.89pt) -- (63.7676pt,305.881pt) -- (8.50235pt,305.881pt) -- (8.50235pt,326.89pt); + \color[rgb]{0,0,0} + \fill(10.2028pt,319.747pt) -- (17.0047pt,319.747pt) -- (17.0047pt,313.024pt) -- (10.2028pt,313.024pt) -- (10.2028pt,319.747pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{39.5359pt}{311.763pt}}]{\fontsize{14}{0}\selectfont{Item 1}} +\end{scope} +\begin{scope} + \clip(8.50235pt,300.839pt) -- (63.7676pt,300.839pt) -- (63.7676pt,279.83pt) -- (8.50235pt,279.83pt) -- (8.50235pt,300.839pt); + \color[rgb]{0,0,0} + \fill(10.2028pt,293.696pt) -- (17.0047pt,293.696pt) -- (17.0047pt,286.973pt) -- (10.2028pt,286.973pt) -- (10.2028pt,293.696pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{39.5359pt}{285.713pt}}]{\fontsize{14}{0}\selectfont{Item 2}} +\end{scope} +\begin{scope} + \color[rgb]{1,1,1} + \fill(68.0188pt,328.57pt) -- (129.236pt,328.57pt) -- (129.236pt,113.441pt) -- (68.0188pt,113.441pt) -- (68.0188pt,328.57pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{80.7724pt}{220.586pt}},rotate=90]{\fontsize{12}{0}\selectfont{\textbf{Axis: yLeft}}} + \pgftext[center, base, at={\pgfpoint{101.603pt}{113.861pt}}]{\fontsize{10}{0}\selectfont{-1,000}} + \pgftext[center, base, at={\pgfpoint{105.854pt}{165.333pt}}]{\fontsize{10}{0}\selectfont{-500}} + \pgftext[center, base, at={\pgfpoint{113.506pt}{216.804pt}}]{\fontsize{10}{0}\selectfont{0}} + \pgftext[center, base, at={\pgfpoint{107.555pt}{268.275pt}}]{\fontsize{10}{0}\selectfont{500}} + \pgftext[center, base, at={\pgfpoint{103.304pt}{319.747pt}}]{\fontsize{10}{0}\selectfont{1,000}} + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,127.727pt) -- (124.134pt,127.727pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,137.811pt) -- (124.134pt,137.811pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,148.736pt) -- (124.134pt,148.736pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,158.82pt) -- (124.134pt,158.82pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,178.988pt) -- (124.134pt,178.988pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,189.913pt) -- (124.134pt,189.913pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,199.997pt) -- (124.134pt,199.997pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,210.081pt) -- (124.134pt,210.081pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,231.09pt) -- (124.134pt,231.09pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,241.174pt) -- (124.134pt,241.174pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,251.258pt) -- (124.134pt,251.258pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,261.342pt) -- (124.134pt,261.342pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,282.351pt) -- (124.134pt,282.351pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,292.435pt) -- (124.134pt,292.435pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,302.52pt) -- (124.134pt,302.52pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,313.444pt) -- (124.134pt,313.444pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,117.643pt) -- (120.733pt,117.643pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,168.904pt) -- (120.733pt,168.904pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,220.165pt) -- (120.733pt,220.165pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,272.267pt) -- (120.733pt,272.267pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,323.528pt) -- (120.733pt,323.528pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,323.528pt) -- (127.535pt,117.643pt); + \color[rgb]{1,1,1} + \fill(354.548pt,328.57pt) -- (415.765pt,328.57pt) -- (415.765pt,113.441pt) -- (354.548pt,113.441pt) -- (354.548pt,328.57pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{403.862pt}{220.586pt}},rotate=-90]{\fontsize{12}{0}\selectfont{\textbf{Axis: yRight}}} + \pgftext[center, base, at={\pgfpoint{382.181pt}{113.861pt}}]{\fontsize{10}{0}\selectfont{-1,000}} + \pgftext[center, base, at={\pgfpoint{377.93pt}{165.333pt}}]{\fontsize{10}{0}\selectfont{-500}} + \pgftext[center, base, at={\pgfpoint{370.277pt}{216.804pt}}]{\fontsize{10}{0}\selectfont{0}} + \pgftext[center, base, at={\pgfpoint{376.229pt}{268.275pt}}]{\fontsize{10}{0}\selectfont{500}} + \pgftext[center, base, at={\pgfpoint{380.48pt}{319.747pt}}]{\fontsize{10}{0}\selectfont{1,000}} + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,127.727pt) -- (359.65pt,127.727pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,137.811pt) -- (359.65pt,137.811pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,148.736pt) -- (359.65pt,148.736pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,158.82pt) -- (359.65pt,158.82pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,178.988pt) -- (359.65pt,178.988pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,189.913pt) -- (359.65pt,189.913pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,199.997pt) -- (359.65pt,199.997pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,210.081pt) -- (359.65pt,210.081pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,231.09pt) -- (359.65pt,231.09pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,241.174pt) -- (359.65pt,241.174pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,251.258pt) -- (359.65pt,251.258pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,261.342pt) -- (359.65pt,261.342pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,282.351pt) -- (359.65pt,282.351pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,292.435pt) -- (359.65pt,292.435pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,302.52pt) -- (359.65pt,302.52pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,313.444pt) -- (359.65pt,313.444pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,117.643pt) -- (363.05pt,117.643pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,168.904pt) -- (363.05pt,168.904pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,220.165pt) -- (363.05pt,220.165pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,272.267pt) -- (363.05pt,272.267pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,323.528pt) -- (363.05pt,323.528pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,323.528pt) -- (356.249pt,117.643pt); + \color[rgb]{1,1,1} + \fill(118.183pt,113.441pt) -- (363.05pt,113.441pt) -- (363.05pt,73.1043pt) -- (118.183pt,73.1043pt) -- (118.183pt,113.441pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{241.042pt}{76.4657pt}}]{\fontsize{12}{0}\selectfont{\textbf{Axis: xBottom}}} + \pgftext[center, base, at={\pgfpoint{133.487pt}{92.4323pt}}]{\fontsize{10}{0}\selectfont{-1,000}} + \pgftext[center, base, at={\pgfpoint{187.689pt}{92.4323pt}}]{\fontsize{10}{0}\selectfont{-500}} + \pgftext[center, base, at={\pgfpoint{241.892pt}{92.4323pt}}]{\fontsize{10}{0}\selectfont{0}} + \pgftext[center, base, at={\pgfpoint{296.094pt}{92.4323pt}}]{\fontsize{10}{0}\selectfont{500}} + \pgftext[center, base, at={\pgfpoint{350.297pt}{92.4323pt}}]{\fontsize{10}{0}\selectfont{1,000}} + \draw[line width=0pt, line join=bevel, line cap=rect](144.54pt,111.76pt) -- (144.54pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](155.593pt,111.76pt) -- (155.593pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](165.796pt,111.76pt) -- (165.796pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](176.849pt,111.76pt) -- (176.849pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](198.955pt,111.76pt) -- (198.955pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](209.158pt,111.76pt) -- (209.158pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](220.211pt,111.76pt) -- (220.211pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](231.264pt,111.76pt) -- (231.264pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](252.52pt,111.76pt) -- (252.52pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](263.573pt,111.76pt) -- (263.573pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](274.626pt,111.76pt) -- (274.626pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](285.679pt,111.76pt) -- (285.679pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](306.935pt,111.76pt) -- (306.935pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](317.988pt,111.76pt) -- (317.988pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](329.041pt,111.76pt) -- (329.041pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](339.244pt,111.76pt) -- (339.244pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](133.487pt,111.76pt) -- (133.487pt,105.038pt); + \draw[line width=0pt, line join=bevel, line cap=rect](187.902pt,111.76pt) -- (187.902pt,105.038pt); + \draw[line width=0pt, line join=bevel, line cap=rect](242.317pt,111.76pt) -- (242.317pt,105.038pt); + \draw[line width=0pt, line join=bevel, line cap=rect](295.882pt,111.76pt) -- (295.882pt,105.038pt); + \draw[line width=0pt, line join=bevel, line cap=rect](350.297pt,111.76pt) -- (350.297pt,105.038pt); + \draw[line width=0pt, line join=bevel, line cap=rect](133.487pt,111.76pt) -- (350.297pt,111.76pt); + \color[rgb]{1,1,1} + \fill(118.183pt,368.067pt) -- (363.05pt,368.067pt) -- (363.05pt,327.73pt) -- (118.183pt,327.73pt) -- (118.183pt,368.067pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{241.042pt}{355.462pt}}]{\fontsize{12}{0}\selectfont{\textbf{Axis: xTop}}} + \pgftext[center, base, at={\pgfpoint{133.487pt}{341.176pt}}]{\fontsize{10}{0}\selectfont{-1,000}} + \pgftext[center, base, at={\pgfpoint{187.689pt}{341.176pt}}]{\fontsize{10}{0}\selectfont{-500}} + \pgftext[center, base, at={\pgfpoint{241.892pt}{341.176pt}}]{\fontsize{10}{0}\selectfont{0}} + \pgftext[center, base, at={\pgfpoint{296.094pt}{341.176pt}}]{\fontsize{10}{0}\selectfont{500}} + \pgftext[center, base, at={\pgfpoint{350.297pt}{341.176pt}}]{\fontsize{10}{0}\selectfont{1,000}} + \draw[line width=0pt, line join=bevel, line cap=rect](144.54pt,329.411pt) -- (144.54pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](155.593pt,329.411pt) -- (155.593pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](165.796pt,329.411pt) -- (165.796pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](176.849pt,329.411pt) -- (176.849pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](198.955pt,329.411pt) -- (198.955pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](209.158pt,329.411pt) -- (209.158pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](220.211pt,329.411pt) -- (220.211pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](231.264pt,329.411pt) -- (231.264pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](252.52pt,329.411pt) -- (252.52pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](263.573pt,329.411pt) -- (263.573pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](274.626pt,329.411pt) -- (274.626pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](285.679pt,329.411pt) -- (285.679pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](306.935pt,329.411pt) -- (306.935pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](317.988pt,329.411pt) -- (317.988pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](329.041pt,329.411pt) -- (329.041pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](339.244pt,329.411pt) -- (339.244pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](133.487pt,329.411pt) -- (133.487pt,336.133pt); + \draw[line width=0pt, line join=bevel, line cap=rect](187.902pt,329.411pt) -- (187.902pt,336.133pt); + \draw[line width=0pt, line join=bevel, line cap=rect](242.317pt,329.411pt) -- (242.317pt,336.133pt); + \draw[line width=0pt, line join=bevel, line cap=rect](295.882pt,329.411pt) -- (295.882pt,336.133pt); + \draw[line width=0pt, line join=bevel, line cap=rect](350.297pt,329.411pt) -- (350.297pt,336.133pt); + \draw[line width=0pt, line join=bevel, line cap=rect](133.487pt,329.411pt) -- (350.297pt,329.411pt); +\end{scope} +\end{tikzpicture} diff --git a/qwt/doc/tex/qwtplot.tex b/qwt/doc/tex/qwtplot.tex new file mode 100644 index 000000000..743331692 --- /dev/null +++ b/qwt/doc/tex/qwtplot.tex @@ -0,0 +1,234 @@ +\documentclass[12pt,a4paper]{book} +\usepackage[latin1]{inputenc} +\usepackage{amsmath} +\usepackage{amsfonts} +\usepackage{amssymb} +\usepackage{makeidx} +\usepackage{pdfpages} +\usepackage{tikz} +%\usepackage{fullpage} +\usepackage[left=1cm,right=2cm,top=2cm,footskip=1.5cm,bottom=1cm]{geometry} +\usepackage{listings} +\usepackage{color} + +%\setlength{\parskip}{\baselineskip} +\setlength{\parindent}{0pt} + +\definecolor{lightgray}{gray}{0.97} +\lstset{frame=single} +\lstset{framesep=5pt} +\lstset{language=C++} +\lstset{keywordstyle=\color{black}\bfseries\emph} +\lstset{backgroundcolor=\color{lightgray}} + +\author{Uwe Rathmann} +\title{Qwt Plot Framework} + + +\begin{document} + +\maketitle +\pagestyle{headings} + +\tableofcontents + +\chapter{Introduction} + +Introduction bla bla + +\chapter{Plot Widget} + +\section{Composite Widget Architecture} + +QwtPlot is a composite widget, that contains a QwtPlotCanvas, +4 QwtScaleWidgets, a QwtTextWidget for the title and an optional QwtLegend. +All widgets might be hidden or visible depending on the current configuration +of the plot widget. Many attributes of a plot are in fact +attributes of the internal child widgets, +\\ +\\ +QwtPlot has its own layout engine, that is implemented as QwtPlotLayout. +The following picture shows a layout of a plot widget, where all internal widgets are visible. + +\begin{center} +\input{plotlayout} +\end{center} + +\bigskip + +\begin{center} +\input{plotlayout} +\end{center} + +The composite widget architecture\footnote{Subject of redesign} +of QwtPlot has some consequences for the programming interface: + +\begin{itemize} + +\item Attributes\\ +Most attributes of child widgets or the layout object can't be +changed using QwtPlot methods. Instead the application code has to +use a getter method for the child and access it directly. +Beside making the programming interface less obvious it has the effect, +that the plot can't be configured in the designer ( or creator ) + +\footnote{A proof of concept has been made how to embed a special +editor for QwtPlot into the designer and how to pass the edited +properties using QwtPlot::applyProperties() - but it was never implemented.}. + +\item Events\\ +Overloading event callbacks of QwtPlot has no effect when an event is posted to a child widget. When the application wants to implement a specific event handling it needs to use a technique called event filtering. + +\end{itemize} + +\begin{lstlisting} +#include +#include +#include + +class MyPlot: public QwtPlot +{ +public: + MyPlot(QWidget *parent = NULL); + + virtual bool eventFilter(QObject *obj, QEvent *event) +}; + +MyPlot::MyPlot(QWidget *parent): + QwtPlot(parent) +{ + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + axisWidget(axis)->setFont( QFont( ... ) ); + + canvas()->setPalette( QPalette( Qt::darkBlue ) ); + canvas()->installEventFilter(this); +} + +bool MyPlot::eventFilter(QObject *object, QEvent *event) +{ + if ( object == canvas() ) + { + ... + } + return QwtPlot::eventFilter(object, event); +} +\end{lstlisting} + + +\section{Scale Widget} +\section{Legend} + + + +\chapter{Scales, Axes and Transformations} + +\section{Scale Divisions} + +\section{Scale Maps} + +\section{Scale Engine} + +\section{Scale Renderer} + +\chapter{Navigation and Selection} + +\section{Picking} +\section{Zooming} +\section{Panning} + +\chapter{Items on the Plot Canvas} + +Decorations + items representing some sort of data. + +\section{Overview} +\section{The Grid} +\section{Axes} + +\section{Markers for Coordinates or Points} +\section{Curves displaying a series of 2D Points} + +\subsection{Symbols} +\subsection{Styles} +\subsection{Curve Fitting} + + +\section{Curves displaying a series of 3D Points} +\section{Curves displaying a series of intervals} +\section{Histograms displaying ...} + +\section{Spectrograms and other items displaying raster data} + +\section{SVG Item} + +\chapter{Exporting and Printing} + +\chapter{Text Engines} + +All Qwt widgets use QwtText objects to specify text labels. +Their format attribute specifies the text engine, that is +responsible for interpreting and rendering the string. +Today there are 3 different text engines available. +\footnote{A text engine for TeX documents would be a great +solution to display labels in mathematical notation. +There are a couple Qt/KDE applications available, +that can display TeX documents. Most of them simply write +the TeX document to a file generate an image from it using the +TeX tools and load the image then - but maybe there is +somewhere an "real" implementation inside, that +translates a TeX document to QPainter calls. } + +\begin{itemize} + +\item Plain Text\\ +QwtPlainTextEngine doesn't take care of any syntax and lays out and renders +the string using QPainter and QFontMetrics. + +\item Rich Text\\ +QwtRichTextEngine is able to display a subset of HTML 4 markup using the renderer, +that is built into the Scribe classes of Qt. It is often used because of +sub- or superscripts to display very simple mathematical expressions. + +\item Mathematical Markup Language (MathML)\\ +QwtMathMLTextEngine uses the MathML renderer from the Qt solutions +\footnote{ +Unfortunately we don't know much about the quality of the MathML renderer yet +as it is not much in use because of earlier license issues. +Today the solution package is LGPL'd like the rest of Qt and in Qwt 6 it can be +installed and used easily. +} +package. In MathML it should be able to define almost any kind of formula + +\end{itemize} + +The text engines for plain and rich texts are always available. +The MathML text engine ( like any other homebrew text engine ) has to be +registered by the application code: + +\bigskip +\begin{lstlisting} +#include + +QwtText::setTextEngine(QwtText::MathMLText, new QwtMathMLTextEngine()); +\end{lstlisting} +\bigskip + +QwtText might also have a couple of additional properties, controlling how to display a text. + +\begin{itemize} +\item Font +\item Color +\item Background color and border pen +\end{itemize} + + +\chapter{Advanced Topics} + +\section{Incremental Painting} + +\section{Building Plot Grids} + +\section{Controlling the Aspect Ratio} + +\section{Levels of Detail} + +\end{document} diff --git a/qwt/examples/animation/animation.pro b/qwt/examples/animation/animation.pro new file mode 100644 index 000000000..d8cebc69e --- /dev/null +++ b/qwt/examples/animation/animation.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = animation + +HEADERS = \ + plot.h + +SOURCES = \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/animation/main.cpp b/qwt/examples/animation/main.cpp new file mode 100644 index 000000000..f5a4a9b9a --- /dev/null +++ b/qwt/examples/animation/main.cpp @@ -0,0 +1,46 @@ +#include +#include "plot.h" + +#ifndef QWT_NO_OPENGL +#define USE_OPENGL 1 +#endif + +#if USE_OPENGL +#include +#include +#else +#include +#endif + +int main ( int argc, char **argv ) +{ +#if USE_OPENGL +#if QT_VERSION >= 0x040600 && QT_VERSION < 0x050000 + // on my box QPaintEngine::OpenGL2 has serious problems, f.e: + // the lines of a simple drawRect are wrong. + + QGL::setPreferredPaintEngine( QPaintEngine::OpenGL ); +#endif +#endif + + QApplication a( argc, argv ); + + Plot plot; + +#if USE_OPENGL + QwtPlotGLCanvas *canvas = new QwtPlotGLCanvas(); + canvas->setFrameStyle( QwtPlotGLCanvas::NoFrame ); +#else + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setFrameStyle( QFrame::NoFrame ); + canvas->setPaintAttribute( QwtPlotCanvas::BackingStore, false ); +#endif + + plot.setCanvas( canvas ); + plot.setCanvasBackground( QColor( 30, 30, 50 ) ); + + plot.resize( 400, 400 ); + plot.show(); + + return a.exec(); +} diff --git a/qwt/examples/animation/plot.cpp b/qwt/examples/animation/plot.cpp new file mode 100644 index 000000000..c0e7a4e59 --- /dev/null +++ b/qwt/examples/animation/plot.cpp @@ -0,0 +1,241 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "plot.h" + +class Curve: public QwtPlotCurve +{ +public: + void setTransformation( const QTransform &transform ) + { + d_transform = transform; + } + + virtual void updateSamples( double phase ) + { + setSamples( d_transform.map( points( phase ) ) ); + } + +private: + virtual QPolygonF points( double phase ) const = 0; + +private: + QTransform d_transform; +}; + +class Curve1: public Curve +{ +public: + Curve1() + { + setPen( QColor( 150, 150, 200 ), 2 ); + setStyle( QwtPlotCurve::Lines ); + + QwtSplineCurveFitter *curveFitter = new QwtSplineCurveFitter(); + curveFitter->setSplineSize( 150 ); + setCurveFitter( curveFitter ); + + setCurveAttribute( QwtPlotCurve::Fitted, true ); + + QwtSymbol *symbol = new QwtSymbol( QwtSymbol::XCross ); + symbol->setPen( Qt::yellow ); + symbol->setSize( 7 ); + + setSymbol( symbol ); + + // somewhere to the left + QTransform transform; + transform.scale( 1.5, 1.0 ); + transform.translate( 1.5, 3.0 ); + + setTransformation( transform ); + } + + virtual QPolygonF points( double phase ) const + { + QPolygonF points; + + const int numSamples = 15; + for ( int i = 0; i < numSamples; i++ ) + { + const double v = 6.28 * double( i ) / double( numSamples - 1 ); + points += QPointF( qSin( v - phase ), v ); + } + + return points; + } +}; + +class Curve2: public Curve +{ +public: + Curve2() + { + setStyle( QwtPlotCurve::Sticks ); + setPen( QColor( 200, 150, 50 ) ); + + setSymbol( new QwtSymbol( QwtSymbol::Ellipse, + QColor( Qt::gray ), QColor( Qt::yellow ), QSize( 5, 5 ) ) ); + } + +private: + virtual QPolygonF points( double phase ) const + { + QPolygonF points; + + const int numSamples = 50; + for ( int i = 0; i < numSamples; i++ ) + { + const double v = 10.0 * i / double( numSamples - 1 ); + points += QPointF( v, qCos( 3.0 * ( v + phase ) ) ); + } + + return points; + } +}; + +class Curve3: public Curve +{ +public: + Curve3() + { + setStyle( QwtPlotCurve::Lines ); + setPen( QColor( 100, 200, 150 ), 2 ); + + QwtSplineCurveFitter* curveFitter = new QwtSplineCurveFitter(); + curveFitter->setFitMode( QwtSplineCurveFitter::ParametricSpline ); + curveFitter->setSplineSize( 200 ); + setCurveFitter( curveFitter ); + + setCurveAttribute( QwtPlotCurve::Fitted, true ); + + // somewhere in the top right corner + QTransform transform; + transform.translate( 7.0, 7.5 ); + transform.scale( 2.0, 2.0 ); + + setTransformation( transform ); + } + +private: + virtual QPolygonF points( double phase ) const + { + QPolygonF points; + + const int numSamples = 9; + for ( int i = 0; i < numSamples; i++ ) + { + const double v = i * 2.0 * M_PI / ( numSamples - 1 ); + points += QPointF( qSin( v - phase ), qCos( 3.0 * ( v + phase ) ) ); + } + + return points; + } +}; + +class Curve4: public Curve +{ +public: + Curve4() + { + setStyle( QwtPlotCurve::Lines ); + setPen( Qt::red, 2 ); + + initSamples(); + + // somewhere in the center + QTransform transform; + transform.translate( 7.0, 3.0 ); + transform.scale( 1.5, 1.5 ); + + setTransformation( transform ); + } + +private: + virtual QPolygonF points( double phase ) const + { + const double speed = 0.05; + + const double s = speed * qSin( phase ); + const double c = qSqrt( 1.0 - s * s ); + + for ( int i = 0; i < d_points.size(); i++ ) + { + const QPointF p = d_points[i]; + + const double u = p.x(); + const double v = p.y(); + + d_points[i].setX( u * c - v * s ); + d_points[i].setY( v * c + u * s ); + } + + return d_points; + } + + void initSamples() + { + const int numSamples = 15; + + for ( int i = 0; i < numSamples; i++ ) + { + const double angle = i * ( 2.0 * M_PI / ( numSamples - 1 ) ); + + QPointF p( qCos( angle ), qSin( angle ) ); + if ( i % 2 ) + p *= 0.4; + + d_points += p; + } + } + +private: + mutable QPolygonF d_points; +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent) +{ + setAutoReplot( false ); + + setTitle( "Animated Curves" ); + + // hide all axes + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + setAxisVisible( axis, false ); + + plotLayout()->setCanvasMargin( 10 ); + + d_curves[0] = new Curve1(); + d_curves[1] = new Curve2(); + d_curves[2] = new Curve3(); + d_curves[3] = new Curve4(); + + updateCurves(); + + for ( int i = 0; i < CurveCount; i++ ) + d_curves[i]->attach( this ); + + d_time.start(); + ( void )startTimer( 40 ); +} + +void Plot::timerEvent( QTimerEvent * ) +{ + updateCurves(); + replot(); +} + +void Plot::updateCurves() +{ + const double speed = 2 * M_PI / 25000.0; // a cycle every 25 seconds + + const double phase = d_time.elapsed() * speed; + for ( int i = 0; i < CurveCount; i++ ) + d_curves[i]->updateSamples( phase ); +} diff --git a/qwt/examples/animation/plot.h b/qwt/examples/animation/plot.h new file mode 100644 index 000000000..e2d51acae --- /dev/null +++ b/qwt/examples/animation/plot.h @@ -0,0 +1,21 @@ +#include +#include + +class Curve; + +class Plot: public QwtPlot +{ +public: + Plot( QWidget * = NULL); + +protected: + virtual void timerEvent( QTimerEvent * ); + +private: + void updateCurves(); + + enum { CurveCount = 4 }; + Curve *d_curves[CurveCount]; + + QTime d_time; +}; diff --git a/qwt/examples/barchart/barchart.cpp b/qwt/examples/barchart/barchart.cpp new file mode 100644 index 000000000..8b684a712 --- /dev/null +++ b/qwt/examples/barchart/barchart.cpp @@ -0,0 +1,132 @@ +#include "barchart.h" +#include +#include +#include +#include +#include +#include +#include + +BarChart::BarChart( QWidget *parent ): + QwtPlot( parent ) +{ + setAutoFillBackground( true ); + + setPalette( Qt::white ); + canvas()->setPalette( QColor( "LemonChiffon" ) ); + + setTitle( "Bar Chart" ); + + setAxisTitle( QwtAxis::yLeft, "Whatever" ); + setAxisTitle( QwtAxis::xBottom, "Whatever" ); + + d_barChartItem = new QwtPlotMultiBarChart( "Bar Chart " ); + d_barChartItem->setLayoutPolicy( QwtPlotMultiBarChart::AutoAdjustSamples ); + d_barChartItem->setSpacing( 20 ); + d_barChartItem->setMargin( 3 ); + + d_barChartItem->attach( this ); + + insertLegend( new QwtLegend() ); + + populate(); + setOrientation( 0 ); + + setAutoReplot( true ); +} + +void BarChart::populate() +{ + static const char *colors[] = { "DarkOrchid", "SteelBlue", "Gold" }; + + const int numSamples = 5; + const int numBars = sizeof( colors ) / sizeof( colors[0] ); + + QList titles; + for ( int i = 0; i < numBars; i++ ) + { + QString title("Bar %1"); + titles += title.arg( i ); + } + d_barChartItem->setBarTitles( titles ); + d_barChartItem->setLegendIconSize( QSize( 10, 14 ) ); + + for ( int i = 0; i < numBars; i++ ) + { + QwtColumnSymbol *symbol = new QwtColumnSymbol( QwtColumnSymbol::Box ); + symbol->setLineWidth( 2 ); + symbol->setFrameStyle( QwtColumnSymbol::Raised ); + symbol->setPalette( QPalette( colors[i] ) ); + + d_barChartItem->setSymbol( i, symbol ); + } + + QVector< QVector > series; + for ( int i = 0; i < numSamples; i++ ) + { + QVector values; + for ( int j = 0; j < numBars; j++ ) + values += ( 2 + qrand() % 8 ); + + series += values; + } + + d_barChartItem->setSamples( series ); +} + +void BarChart::setMode( int mode ) +{ + if ( mode == 0 ) + { + d_barChartItem->setStyle( QwtPlotMultiBarChart::Grouped ); + } + else + { + d_barChartItem->setStyle( QwtPlotMultiBarChart::Stacked ); + } +} + +void BarChart::setOrientation( int orientation ) +{ + QwtAxis::Position axis1, axis2; + + if ( orientation == 0 ) + { + axis1 = QwtAxis::xBottom; + axis2 = QwtAxis::yLeft; + + d_barChartItem->setOrientation( Qt::Vertical ); + } + else + { + axis1 = QwtAxis::yLeft; + axis2 = QwtAxis::xBottom; + + d_barChartItem->setOrientation( Qt::Horizontal ); + } + + setAxisScale( axis1, 0, d_barChartItem->dataSize() - 1, 1.0 ); + setAxisAutoScale( axis2 ); + + QwtScaleDraw *scaleDraw1 = axisScaleDraw( axis1 ); + scaleDraw1->enableComponent( QwtScaleDraw::Backbone, false ); + scaleDraw1->enableComponent( QwtScaleDraw::Ticks, false ); + + QwtScaleDraw *scaleDraw2 = axisScaleDraw( axis2 ); + scaleDraw2->enableComponent( QwtScaleDraw::Backbone, true ); + scaleDraw2->enableComponent( QwtScaleDraw::Ticks, true ); + + plotLayout()->setAlignCanvasToScale( axis1, true ); + plotLayout()->setAlignCanvasToScale( axis2, false ); + + plotLayout()->setCanvasMargin( 0 ); + updateCanvasMargins(); + + replot(); +} + +void BarChart::exportChart() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "barchart.pdf" ); +} diff --git a/qwt/examples/barchart/barchart.h b/qwt/examples/barchart/barchart.h new file mode 100644 index 000000000..ff5afd9d8 --- /dev/null +++ b/qwt/examples/barchart/barchart.h @@ -0,0 +1,25 @@ +#ifndef _BAR_CHART_H_ + +#include + +class QwtPlotMultiBarChart; + +class BarChart: public QwtPlot +{ + Q_OBJECT + +public: + BarChart( QWidget * = NULL ); + +public Q_SLOTS: + void setMode( int ); + void setOrientation( int ); + void exportChart(); + +private: + void populate(); + + QwtPlotMultiBarChart *d_barChartItem; +}; + +#endif diff --git a/qwt/examples/barchart/barchart.pro b/qwt/examples/barchart/barchart.pro new file mode 100644 index 000000000..ff069655b --- /dev/null +++ b/qwt/examples/barchart/barchart.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = barchart + +SOURCES = \ + barchart.cpp \ + main.cpp + +HEADERS = \ + barchart.h diff --git a/qwt/examples/barchart/main.cpp b/qwt/examples/barchart/main.cpp new file mode 100644 index 000000000..a47da346a --- /dev/null +++ b/qwt/examples/barchart/main.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include "barchart.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + BarChart *d_chart; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_chart = new BarChart( this ); + setCentralWidget( d_chart ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *typeBox = new QComboBox( toolBar ); + typeBox->addItem( "Grouped" ); + typeBox->addItem( "Stacked" ); + typeBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QComboBox *orientationBox = new QComboBox( toolBar ); + orientationBox->addItem( "Vertical" ); + orientationBox->addItem( "Horizontal" ); + orientationBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), d_chart, SLOT( exportChart() ) ); + + toolBar->addWidget( typeBox ); + toolBar->addWidget( orientationBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + + d_chart->setMode( typeBox->currentIndex() ); + connect( typeBox, SIGNAL( currentIndexChanged( int ) ), + d_chart, SLOT( setMode( int ) ) ); + + d_chart->setOrientation( orientationBox->currentIndex() ); + connect( orientationBox, SIGNAL( currentIndexChanged( int ) ), + d_chart, SLOT( setOrientation( int ) ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/bode/bode.pro b/qwt/examples/bode/bode.pro new file mode 100644 index 000000000..ab42e28b1 --- /dev/null +++ b/qwt/examples/bode/bode.pro @@ -0,0 +1,23 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = bode + +HEADERS = \ + mainwindow.h \ + plot.h \ + complexnumber.h \ + pixmaps.h + +SOURCES = \ + plot.cpp \ + mainwindow.cpp \ + main.cpp diff --git a/qwt/examples/bode/complexnumber.h b/qwt/examples/bode/complexnumber.h new file mode 100644 index 000000000..62dfe1acd --- /dev/null +++ b/qwt/examples/bode/complexnumber.h @@ -0,0 +1,83 @@ +#ifndef _COMPLEX_NUMBER_H_ +#define _COMPLEX_NUMBER_H_ + +#include + +class ComplexNumber +{ +public: + ComplexNumber() ; + ComplexNumber( double r, double i = 0.0 ); + + double real() const; + double imag() const; + + friend ComplexNumber operator*( + const ComplexNumber &, const ComplexNumber & ); + + friend ComplexNumber operator+( + const ComplexNumber &, const ComplexNumber & ); + + friend ComplexNumber operator-( + const ComplexNumber &, const ComplexNumber & ); + friend ComplexNumber operator/( + const ComplexNumber &, const ComplexNumber & ); + +private: + double d_real; + double d_imag; +}; + +inline ComplexNumber::ComplexNumber(): + d_real( 0.0 ), + d_imag( -0.0 ) +{ +} + +inline ComplexNumber::ComplexNumber( double re, double im ): + d_real( re ), + d_imag( im ) +{ +} + +inline double ComplexNumber::real() const +{ + return d_real; +} + +inline double ComplexNumber::imag() const +{ + return d_imag; +} + +inline ComplexNumber operator+( + const ComplexNumber &x1, const ComplexNumber &x2 ) +{ + return ComplexNumber( x1.d_real + x2.d_real, x1.d_imag + x2.d_imag ); +} + +inline ComplexNumber operator-( + const ComplexNumber &x1, const ComplexNumber &x2 ) +{ + return ComplexNumber( x1.d_real - x2.d_real, x1.d_imag - x2.d_imag ); +} + +inline ComplexNumber operator*( + const ComplexNumber &x1, const ComplexNumber &x2 ) +{ + return ComplexNumber( x1.d_real * x2.d_real - x1.d_imag * x2.d_imag, + x1.d_real * x2.d_imag + x2.d_real * x1.d_imag ); +} + +inline ComplexNumber operator/( + const ComplexNumber &x1, const ComplexNumber &x2 ) +{ + double denom = x2.d_real * x2.d_real + x2.d_imag * x2.d_imag; + + return ComplexNumber( + ( x1.d_real * x2.d_real + x1.d_imag * x2.d_imag ) / denom, + ( x1.d_imag * x2.d_real - x2.d_imag * x1.d_real ) / denom + ); +} + +#endif diff --git a/qwt/examples/bode/main.cpp b/qwt/examples/bode/main.cpp new file mode 100644 index 000000000..30fd1028c --- /dev/null +++ b/qwt/examples/bode/main.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.resize( 1000, 800 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/bode/mainwindow.cpp b/qwt/examples/bode/mainwindow.cpp new file mode 100644 index 000000000..e160478c2 --- /dev/null +++ b/qwt/examples/bode/mainwindow.cpp @@ -0,0 +1,231 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pixmaps.h" +#include "plot.h" +#include "mainwindow.h" + +class Zoomer: public QwtPlotZoomer +{ +public: + Zoomer( int xAxis, int yAxis, QWidget *canvas ): + QwtPlotZoomer( xAxis, yAxis, canvas ) + { + setTrackerMode( QwtPicker::AlwaysOff ); + setRubberBand( QwtPicker::NoRubberBand ); + + // RightButton: zoom out by 1 + // Ctrl+RightButton: zoom out to full size + + setMousePattern( QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier ); + setMousePattern( QwtEventPattern::MouseSelect3, + Qt::RightButton ); + } +}; + +//----------------------------------------------------------------- +// +// bode.cpp -- A demo program featuring QwtPlot and QwtCounter +// +// This example demonstrates the mapping of different curves +// to different axes in a QwtPlot widget. It also shows how to +// display the cursor position and how to implement zooming. +// +//----------------------------------------------------------------- + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new Plot( this ); + + const int margin = 5; + d_plot->setContentsMargins( margin, margin, margin, 0 ); + + setContextMenuPolicy( Qt::NoContextMenu ); + + d_zoomer[0] = new Zoomer( + QwtAxis::xBottom, QwtAxis::yLeft, d_plot->canvas() ); + d_zoomer[0]->setRubberBand( QwtPicker::RectRubberBand ); + d_zoomer[0]->setRubberBandPen( QColor( Qt::green ) ); + d_zoomer[0]->setTrackerMode( QwtPicker::ActiveOnly ); + d_zoomer[0]->setTrackerPen( QColor( Qt::white ) ); + + d_zoomer[1] = new Zoomer( QwtAxis::xTop, QwtAxis::yRight, + d_plot->canvas() ); + + d_panner = new QwtPlotPanner( d_plot->canvas() ); + d_panner->setMouseButton( Qt::MidButton ); + + d_picker = new QwtPlotPicker( QwtAxis::xBottom, QwtAxis::yLeft, + QwtPlotPicker::CrossRubberBand, QwtPicker::AlwaysOn, + d_plot->canvas() ); + d_picker->setStateMachine( new QwtPickerDragPointMachine() ); + d_picker->setRubberBandPen( QColor( Qt::green ) ); + d_picker->setRubberBand( QwtPicker::CrossRubberBand ); + d_picker->setTrackerPen( QColor( Qt::white ) ); + + setCentralWidget( d_plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QToolButton *btnZoom = new QToolButton( toolBar ); + btnZoom->setText( "Zoom" ); + btnZoom->setIcon( QPixmap( zoom_xpm ) ); + btnZoom->setCheckable( true ); + btnZoom->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnZoom ); + connect( btnZoom, SIGNAL( toggled( bool ) ), SLOT( enableZoomMode( bool ) ) ); + +#ifndef QT_NO_PRINTER + QToolButton *btnPrint = new QToolButton( toolBar ); + btnPrint->setText( "Print" ); + btnPrint->setIcon( QPixmap( print_xpm ) ); + btnPrint->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnPrint ); + connect( btnPrint, SIGNAL( clicked() ), SLOT( print() ) ); +#endif + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setIcon( QPixmap( print_xpm ) ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnExport ); + connect( btnExport, SIGNAL( clicked() ), SLOT( exportDocument() ) ); + + toolBar->addSeparator(); + + QWidget *hBox = new QWidget( toolBar ); + + QHBoxLayout *layout = new QHBoxLayout( hBox ); + layout->setSpacing( 0 ); + layout->addWidget( new QWidget( hBox ), 10 ); // spacer + layout->addWidget( new QLabel( "Damping Factor", hBox ), 0 ); + layout->addSpacing( 10 ); + + QwtCounter *cntDamp = new QwtCounter( hBox ); + cntDamp->setRange( 0.0, 5.0 ); + cntDamp->setSingleStep( 0.01 ); + cntDamp->setValue( 0.0 ); + + layout->addWidget( cntDamp, 0 ); + + ( void )toolBar->addWidget( hBox ); + + addToolBar( toolBar ); +#ifndef QT_NO_STATUSBAR + ( void )statusBar(); +#endif + + enableZoomMode( false ); + showInfo(); + + connect( cntDamp, SIGNAL( valueChanged( double ) ), + d_plot, SLOT( setDamp( double ) ) ); + + connect( d_picker, SIGNAL( moved( const QPoint & ) ), + SLOT( moved( const QPoint & ) ) ); + connect( d_picker, SIGNAL( selected( const QPolygon & ) ), + SLOT( selected( const QPolygon & ) ) ); +} + +#ifndef QT_NO_PRINTER + +void MainWindow::print() +{ + QPrinter printer( QPrinter::HighResolution ); + + QString docName = d_plot->title().text(); + if ( !docName.isEmpty() ) + { + docName.replace ( QRegExp ( QString::fromLatin1 ( "\n" ) ), tr ( " -- " ) ); + printer.setDocName ( docName ); + } + + printer.setCreator( "Bode example" ); + printer.setOrientation( QPrinter::Landscape ); + + QPrintDialog dialog( &printer ); + if ( dialog.exec() ) + { + QwtPlotRenderer renderer; + + if ( printer.colorMode() == QPrinter::GrayScale ) + { + renderer.setDiscardFlag( QwtPlotRenderer::DiscardBackground ); + renderer.setDiscardFlag( QwtPlotRenderer::DiscardCanvasBackground ); + renderer.setDiscardFlag( QwtPlotRenderer::DiscardCanvasFrame ); + renderer.setLayoutFlag( QwtPlotRenderer::FrameWithScales ); + } + + renderer.renderTo( d_plot, printer ); + } +} + +#endif + +void MainWindow::exportDocument() +{ + QwtPlotRenderer renderer; + renderer.exportTo( d_plot, "bode.pdf" ); +} + +void MainWindow::enableZoomMode( bool on ) +{ + d_panner->setEnabled( on ); + + d_zoomer[0]->setEnabled( on ); + d_zoomer[0]->zoom( 0 ); + + d_zoomer[1]->setEnabled( on ); + d_zoomer[1]->zoom( 0 ); + + d_picker->setEnabled( !on ); + + showInfo(); +} + +void MainWindow::showInfo( QString text ) +{ + if ( text == QString::null ) + { + if ( d_picker->rubberBand() ) + text = "Cursor Pos: Press left mouse button in plot region"; + else + text = "Zoom: Press mouse button and drag"; + } + +#ifndef QT_NO_STATUSBAR + statusBar()->showMessage( text ); +#endif +} + +void MainWindow::moved( const QPoint &pos ) +{ + QString info; + info.sprintf( "Freq=%g, Ampl=%g, Phase=%g", + d_plot->invTransform( QwtAxis::xBottom, pos.x() ), + d_plot->invTransform( QwtAxis::yLeft, pos.y() ), + d_plot->invTransform( QwtAxis::yRight, pos.y() ) + ); + showInfo( info ); +} + +void MainWindow::selected( const QPolygon & ) +{ + showInfo(); +} diff --git a/qwt/examples/bode/mainwindow.h b/qwt/examples/bode/mainwindow.h new file mode 100644 index 000000000..1373b52cb --- /dev/null +++ b/qwt/examples/bode/mainwindow.h @@ -0,0 +1,35 @@ +#include + +class QwtPlotZoomer; +class QwtPlotPicker; +class QwtPlotPanner; +class Plot; +class QPolygon; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = 0 ); + +private Q_SLOTS: + void moved( const QPoint & ); + void selected( const QPolygon & ); + +#ifndef QT_NO_PRINTER + void print(); +#endif + + void exportDocument(); + void enableZoomMode( bool ); + +private: + void showInfo( QString text = QString::null ); + + Plot *d_plot; + + QwtPlotZoomer *d_zoomer[2]; + QwtPlotPicker *d_picker; + QwtPlotPanner *d_panner; +}; diff --git a/qwt/examples/bode/pixmaps.h b/qwt/examples/bode/pixmaps.h new file mode 100644 index 000000000..c3bc0b9bc --- /dev/null +++ b/qwt/examples/bode/pixmaps.h @@ -0,0 +1,99 @@ +#ifndef PIXMAPS_H +#define PIXMAPS_H + +static const char *print_xpm[] = +{ + "32 32 12 1", + "a c #ffffff", + "h c #ffff00", + "c c #ffffff", + "f c #dcdcdc", + "b c #c0c0c0", + "j c #a0a0a4", + "e c #808080", + "g c #808000", + "d c #585858", + "i c #00ff00", + "# c #000000", + ". c None", + "................................", + "................................", + "...........###..................", + "..........#abb###...............", + ".........#aabbbbb###............", + ".........#ddaaabbbbb###.........", + "........#ddddddaaabbbbb###......", + ".......#deffddddddaaabbbbb###...", + "......#deaaabbbddddddaaabbbbb###", + ".....#deaaaaaaabbbddddddaaabbbb#", + "....#deaaabbbaaaa#ddedddfggaaad#", + "...#deaaaaaaaaaa#ddeeeeafgggfdd#", + "..#deaaabbbaaaa#ddeeeeabbbbgfdd#", + ".#deeefaaaaaaa#ddeeeeabbhhbbadd#", + "#aabbbeeefaaa#ddeeeeabbbbbbaddd#", + "#bbaaabbbeee#ddeeeeabbiibbadddd#", + "#bbbbbaaabbbeeeeeeabbbbbbaddddd#", + "#bjbbbbbbaaabbbbeabbbbbbadddddd#", + "#bjjjjbbbbbbaaaeabbbbbbaddddddd#", + "#bjaaajjjbbbbbbaaabbbbadddddddd#", + "#bbbbbaaajjjbbbbbbaaaaddddddddd#", + "#bjbbbbbbaaajjjbbbbbbddddddddd#.", + "#bjjjjbbbbbbaaajjjbbbdddddddd#..", + "#bjaaajjjbbbbbbjaajjbddddddd#...", + "#bbbbbaaajjjbbbjbbaabdddddd#....", + "###bbbbbbaaajjjjbbbbbddddd#.....", + "...###bbbbbbaaajbbbbbdddd#......", + "......###bbbbbbjbbbbbddd#.......", + ".........###bbbbbbbbbdd#........", + "............###bbbbbbd#.........", + "...............###bbb#..........", + "..................###..........." +}; + + +static const char *zoom_xpm[] = +{ + "32 32 8 1", + "# c #000000", + "b c #c0c0c0", + "a c #ffffff", + "e c #585858", + "d c #a0a0a4", + "c c #0000ff", + "f c #00ffff", + ". c None", + "..######################........", + ".#a#baaaaaaaaaaaaaaaaaa#........", + "#aa#baaaaaaaaaaaaaccaca#........", + "####baaaaaaaaaaaaaaaaca####.....", + "#bbbbaaaaaaaaaaaacccaaa#da#.....", + "#aaaaaaaaaaaaaaaacccaca#da#.....", + "#aaaaaaaaaaaaaaaaaccaca#da#.....", + "#aaaaaaaaaabe###ebaaaaa#da#.....", + "#aaaaaaaaa#########aaaa#da#.....", + "#aaaaaaaa###dbbbb###aaa#da#.....", + "#aaaaaaa###aaaaffb###aa#da#.....", + "#aaaaaab##aaccaaafb##ba#da#.....", + "#aaaaaae#daaccaccaad#ea#da#.....", + "#aaaaaa##aaaaaaccaab##a#da#.....", + "#aaaaaa##aacccaaaaab##a#da#.....", + "#aaaaaa##aaccccaccab##a#da#.....", + "#aaaaaae#daccccaccad#ea#da#.....", + "#aaaaaab##aacccaaaa##da#da#.....", + "#aaccacd###aaaaaaa###da#da#.....", + "#aaaaacad###daaad#####a#da#.....", + "#acccaaaad##########da##da#.....", + "#acccacaaadde###edd#eda#da#.....", + "#aaccacaaaabdddddbdd#eda#a#.....", + "#aaaaaaaaaaaaaaaaaadd#eda##.....", + "#aaaaaaaaaaaaaaaaaaadd#eda#.....", + "#aaaaaaaccacaaaaaaaaadd#eda#....", + "#aaaaaaaaaacaaaaaaaaaad##eda#...", + "#aaaaaacccaaaaaaaaaaaaa#d#eda#..", + "########################dd#eda#.", + "...#dddddddddddddddddddddd##eda#", + "...#aaaaaaaaaaaaaaaaaaaaaa#.####", + "...########################..##." +}; + +#endif diff --git a/qwt/examples/bode/plot.cpp b/qwt/examples/bode/plot.cpp new file mode 100644 index 000000000..baa9e9b11 --- /dev/null +++ b/qwt/examples/bode/plot.cpp @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "complexnumber.h" +#include "plot.h" + +#if QT_VERSION < 0x040601 +#define qExp(x) ::exp(x) +#define qAtan2(y, x) ::atan2(y, x) +#endif + +static void logSpace( double *array, int size, double xmin, double xmax ) +{ + if ( ( xmin <= 0.0 ) || ( xmax <= 0.0 ) || ( size <= 0 ) ) + return; + + const int imax = size - 1; + + array[0] = xmin; + array[imax] = xmax; + + const double lxmin = log( xmin ); + const double lxmax = log( xmax ); + const double lstep = ( lxmax - lxmin ) / double( imax ); + + for ( int i = 1; i < imax; i++ ) + array[i] = qExp( lxmin + double( i ) * lstep ); +} + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setAutoReplot( false ); + + setTitle( "Frequency Response of a Second-Order System" ); + setFooter( "Footer Response of a Second-Order System" ); + + setCanvasBackground( QColor( "MidnightBlue" ) ); + + // legend + QwtLegend *legend = new QwtLegend; + insertLegend( legend, QwtPlot::BottomLegend ); + + // grid + QwtPlotGrid *grid = new QwtPlotGrid; + grid->enableXMin( true ); + grid->setMajorPen( Qt::white, 0, Qt::DotLine ); + grid->setMinorPen( Qt::gray, 0 , Qt::DotLine ); + grid->setAxes( QwtAxisId( QwtAxis::xTop, 2 ), QwtAxisId( QwtAxis::yRight, 1 ) ); + grid->attach( this ); + + // axes + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + setAxesCount( axisPos, 3 ); + + for ( int i = 0; i < axesCount( axisPos ); i++ ) + { + QwtAxisId axisId( axisPos, i ); + + QString title( "Normalized Frequency or any other important stuff" ); + setAxisTitle( axisId, title + QString().setNum( i ) ); + + if ( i == 1 ) + { + setAxisScale( axisId, 1000000, 500000 ); + if ( QwtAxis::isYAxis( axisPos ) ) + setAxisLabelRotation( axisId, -60 ); + } + + if ( i == 2 ) + setAxisScale( axisId, 70.457895, 0.00000555 ); + } + } + + setAxisVisible( QwtAxis::yRight ); + setAxisVisible( QwtAxis::xTop ); + setAxisMaxMajor( QwtAxis::xBottom, 6 ); + setAxisMaxMinor( QwtAxis::xBottom, 9 ); + setAxisScaleEngine( QwtAxis::xBottom, new QwtLogScaleEngine ); + + // curves + d_curve1 = new QwtPlotCurve( "Amplitude" ); + d_curve1->setRenderHint( QwtPlotItem::RenderAntialiased ); + d_curve1->setPen( Qt::yellow ); + d_curve1->setLegendAttribute( QwtPlotCurve::LegendShowLine ); + d_curve1->setYAxis( QwtAxis::yLeft ); + d_curve1->attach( this ); + + d_curve2 = new QwtPlotCurve( "Phase" ); + d_curve2->setRenderHint( QwtPlotItem::RenderAntialiased ); + d_curve2->setPen( Qt::cyan ); + d_curve2->setLegendAttribute( QwtPlotCurve::LegendShowLine ); + d_curve2->setYAxis( QwtAxis::yRight ); + d_curve2->attach( this ); + + // marker + d_marker1 = new QwtPlotMarker(); + d_marker1->setValue( 0.0, 0.0 ); + d_marker1->setLineStyle( QwtPlotMarker::VLine ); + d_marker1->setLabelAlignment( Qt::AlignRight | Qt::AlignBottom ); + d_marker1->setLinePen( Qt::green, 0, Qt::DashDotLine ); + d_marker1->attach( this ); + + d_marker2 = new QwtPlotMarker(); + d_marker2->setLineStyle( QwtPlotMarker::HLine ); + d_marker2->setLabelAlignment( Qt::AlignRight | Qt::AlignBottom ); + d_marker2->setLinePen( QColor( 200, 150, 0 ), 0, Qt::DashDotLine ); + d_marker2->setSymbol( new QwtSymbol( QwtSymbol::Diamond, + QColor( Qt::yellow ), QColor( Qt::green ), QSize( 8, 8 ) ) ); + d_marker2->attach( this ); + + setDamp( 0.0 ); + + setAutoReplot( true ); +} + +void Plot::showData( const double *frequency, const double *amplitude, + const double *phase, int count ) +{ + d_curve1->setSamples( frequency, amplitude, count ); + d_curve2->setSamples( frequency, phase, count ); +} + +void Plot::showPeak( double freq, double amplitude ) +{ + QString label; + label.sprintf( "Peak: %.3g dB", amplitude ); + + QwtText text( label ); + text.setFont( QFont( "Helvetica", 10, QFont::Bold ) ); + text.setColor( QColor( 200, 150, 0 ) ); + + d_marker2->setValue( freq, amplitude ); + d_marker2->setLabel( text ); +} + +void Plot::show3dB( double freq ) +{ + QString label; + label.sprintf( "-3 dB at f = %.3g", freq ); + + QwtText text( label ); + text.setFont( QFont( "Helvetica", 10, QFont::Bold ) ); + text.setColor( Qt::green ); + + d_marker1->setValue( freq, 0.0 ); + d_marker1->setLabel( text ); +} + +// +// re-calculate frequency response +// +void Plot::setDamp( double damping ) +{ + const bool doReplot = autoReplot(); + setAutoReplot( false ); + + const int ArraySize = 200; + + double frequency[ArraySize]; + double amplitude[ArraySize]; + double phase[ArraySize]; + + // build frequency vector with logarithmic division + logSpace( frequency, ArraySize, 0.01, 100 ); + + int i3 = 1; + double fmax = 1; + double amax = -1000.0; + + for ( int i = 0; i < ArraySize; i++ ) + { + double f = frequency[i]; + const ComplexNumber g = + ComplexNumber( 1.0 ) / ComplexNumber( 1.0 - f * f, 2.0 * damping * f ); + + amplitude[i] = 20.0 * log10( qSqrt( g.real() * g.real() + g.imag() * g.imag() ) ); + phase[i] = qAtan2( g.imag(), g.real() ) * ( 180.0 / M_PI ); + + if ( ( i3 <= 1 ) && ( amplitude[i] < -3.0 ) ) + i3 = i; + if ( amplitude[i] > amax ) + { + amax = amplitude[i]; + fmax = frequency[i]; + } + + } + + double f3 = frequency[i3] - ( frequency[i3] - frequency[i3 - 1] ) + / ( amplitude[i3] - amplitude[i3 -1] ) * ( amplitude[i3] + 3 ); + + showPeak( fmax, amax ); + show3dB( f3 ); + showData( frequency, amplitude, phase, ArraySize ); + + setAutoReplot( doReplot ); + + replot(); +} diff --git a/qwt/examples/bode/plot.h b/qwt/examples/bode/plot.h new file mode 100644 index 000000000..4529468a6 --- /dev/null +++ b/qwt/examples/bode/plot.h @@ -0,0 +1,31 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class QwtPlotCurve; +class QwtPlotMarker; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent ); + +public Q_SLOTS: + void setDamp( double damping ); + +private: + void showData( const double *frequency, const double *amplitude, + const double *phase, int count ); + void showPeak( double freq, double amplitude ); + void show3dB( double freq ); + + QwtPlotCurve *d_curve1; + QwtPlotCurve *d_curve2; + QwtPlotMarker *d_marker1; + QwtPlotMarker *d_marker2; +}; + +#endif diff --git a/qwt/examples/controls/controls.pro b/qwt/examples/controls/controls.pro new file mode 100644 index 000000000..0c3e5f0e1 --- /dev/null +++ b/qwt/examples/controls/controls.pro @@ -0,0 +1,34 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = controls + +HEADERS = \ + sliderbox.h \ + slidertab.h \ + wheelbox.h \ + wheeltab.h \ + knobbox.h \ + knobtab.h \ + dialbox.h \ + dialtab.h + +SOURCES = \ + sliderbox.cpp \ + slidertab.cpp \ + wheelbox.cpp \ + wheeltab.cpp \ + knobbox.cpp \ + knobtab.cpp \ + dialbox.cpp \ + dialtab.cpp \ + main.cpp + diff --git a/qwt/examples/controls/dialbox.cpp b/qwt/examples/controls/dialbox.cpp new file mode 100644 index 000000000..4365c9ae9 --- /dev/null +++ b/qwt/examples/controls/dialbox.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include +#include +#include "dialbox.h" + +DialBox::DialBox( QWidget *parent, int type ): + QWidget( parent ) +{ + d_dial = createDial( type ); + + d_label = new QLabel( this ); + d_label->setAlignment( Qt::AlignCenter ); + + QVBoxLayout *layout = new QVBoxLayout( this );; + layout->setSpacing( 0 ); + layout->addWidget( d_dial, 10 ); + layout->addWidget( d_label ); + + connect( d_dial, SIGNAL( valueChanged( double ) ), + this, SLOT( setNum( double ) ) ); + + setNum( d_dial->value() ); +} + +QwtDial *DialBox::createDial( int type ) const +{ + QwtDial *dial = new QwtDial(); + dial->setTracking( true ); + dial->setFocusPolicy( Qt::StrongFocus ); + dial->setObjectName( QString( "Dial %1" ).arg( type + 1 ) ); + + QColor needleColor( Qt::red ); + + switch( type ) + { + case 0: + { + dial->setOrigin( 135.0 ); + dial->setScaleArc( 0.0, 270.0 ); + dial->setScaleMaxMinor( 4 ); + dial->setScaleMaxMajor( 10 ); + dial->setScale( -100.0, 100.0 ); + + needleColor = QColor( "Goldenrod" ); + + break; + } + case 1: + { + dial->setOrigin( 135.0 ); + dial->setScaleArc( 0.0, 270.0 ); + dial->setScaleMaxMinor( 10 ); + dial->setScaleMaxMajor( 10 ); + dial->setScale( 10.0, 0.0 ); + + QwtRoundScaleDraw *scaleDraw = new QwtRoundScaleDraw(); + scaleDraw->setSpacing( 8 ); + scaleDraw->enableComponent( + QwtAbstractScaleDraw::Backbone, false ); + scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 2 ); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 4 ); + scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 8 ); + dial->setScaleDraw( scaleDraw ); + + break; + } + case 2: + { + dial->setOrigin( 150.0 ); + dial->setScaleArc( 0.0, 240.0 ); + + QwtLinearScaleEngine *scaleEngine = new QwtLinearScaleEngine( 2 ); + scaleEngine->setTransformation( new QwtPowerTransform( 2 ) ); + dial->setScaleEngine( scaleEngine ); + + QList< double > ticks[ QwtScaleDiv::NTickTypes ]; + ticks[ QwtScaleDiv::MajorTick ] << 0 << 4 + << 16 << 32 << 64 << 96 << 128; + ticks[ QwtScaleDiv::MediumTick ] << 24 << 48 << 80 << 112; + ticks[ QwtScaleDiv::MinorTick ] + << 0.5 << 1 << 2 + << 7 << 10 << 13 + << 20 << 28 + << 40 << 56 + << 72 << 88 + << 104 << 120; + + dial->setScale( QwtScaleDiv( 0, 128, ticks ) ); + break; + } + case 3: + { + dial->setOrigin( 135.0 ); + dial->setScaleArc( 0.0, 270.0 ); + dial->setScaleMaxMinor( 9 ); + dial->setScaleEngine( new QwtLogScaleEngine ); + dial->setScale( 1.0e-2, 1.0e2 ); + + break; + } + case 4: + { + dial->setOrigin( 225.0 ); + dial->setScaleArc( 0.0, 360.0 ); + dial->setScaleMaxMinor( 5 ); + dial->setScaleStepSize( 20 ); + dial->setScale( 100.0, -100.0 ); + dial->setWrapping( true ); + dial->setTotalSteps( 40 ); + dial->setMode( QwtDial::RotateScale ); + dial->setValue( 70.0 ); + + needleColor = QColor( "DarkSlateBlue" ); + + break; + } + case 5: + { + dial->setOrigin( 45.0 ); + dial->setScaleArc( 0.0, 225.0 ); + dial->setScaleMaxMinor( 5 ); + dial->setScaleMaxMajor( 10 ); + dial->setScale( 0.0, 10.0 ); + + break; + } + } + + QwtDialSimpleNeedle *needle = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, needleColor, + QColor( Qt::gray ).light( 130 ) ); + dial->setNeedle( needle ); + + //const QColor base( QColor( "DimGray" ) ); + const QColor base( QColor( Qt::darkGray ).dark( 150 ) ); + + QPalette palette; + palette.setColor( QPalette::Base, base ); + palette.setColor( QPalette::Window, base.dark( 150 ) ); + palette.setColor( QPalette::Mid, base.dark( 110 ) ); + palette.setColor( QPalette::Light, base.light( 170 ) ); + palette.setColor( QPalette::Dark, base.dark( 170 ) ); + palette.setColor( QPalette::Text, base.dark( 200 ).light( 800 ) ); + palette.setColor( QPalette::WindowText, base.dark( 200 ) ); + + dial->setPalette( palette ); + dial->setLineWidth( 4 ); + dial->setFrameShadow( QwtDial::Sunken ); + + return dial; +} + +void DialBox::setNum( double v ) +{ + QString text; + text.setNum( v, 'f', 2 ); + + d_label->setText( text ); +} diff --git a/qwt/examples/controls/dialbox.h b/qwt/examples/controls/dialbox.h new file mode 100644 index 000000000..f0a8268be --- /dev/null +++ b/qwt/examples/controls/dialbox.h @@ -0,0 +1,25 @@ +#ifndef _DIAL_BOX_H_ +#define _DIAL_BOX_H_ + +#include + +class QLabel; +class QwtDial; + +class DialBox: public QWidget +{ + Q_OBJECT +public: + DialBox( QWidget *parent, int type ); + +private Q_SLOTS: + void setNum( double v ); + +private: + QwtDial *createDial( int type ) const; + + QwtDial *d_dial; + QLabel *d_label; +}; + +#endif diff --git a/qwt/examples/controls/dialtab.cpp b/qwt/examples/controls/dialtab.cpp new file mode 100644 index 000000000..a49725276 --- /dev/null +++ b/qwt/examples/controls/dialtab.cpp @@ -0,0 +1,17 @@ +#include "dialtab.h" +#include "dialbox.h" +#include + +DialTab::DialTab( QWidget *parent ): + QWidget( parent ) +{ + QGridLayout *layout = new QGridLayout( this ); + + const int numRows = 3; + for ( int i = 0; i < 2 * numRows; i++ ) + { + DialBox *dialBox = new DialBox( this, i ); + layout->addWidget( dialBox, i / numRows, i % numRows ); + } +} + diff --git a/qwt/examples/controls/dialtab.h b/qwt/examples/controls/dialtab.h new file mode 100644 index 000000000..61812d24e --- /dev/null +++ b/qwt/examples/controls/dialtab.h @@ -0,0 +1,12 @@ +#ifndef _DIAL_TAB_H +#define _DIAL_TAB_H 1 + +#include + +class DialTab: public QWidget +{ +public: + DialTab( QWidget *parent = NULL ); +}; + +#endif diff --git a/qwt/examples/controls/knobbox.cpp b/qwt/examples/controls/knobbox.cpp new file mode 100644 index 000000000..45d82eb79 --- /dev/null +++ b/qwt/examples/controls/knobbox.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include "knobbox.h" + +KnobBox::KnobBox( QWidget *parent, int knobType ): + QWidget( parent ) +{ + d_knob = createKnob( knobType ); + d_knob->setKnobWidth( 100 ); + + d_label = new QLabel( this ); + d_label->setAlignment( Qt::AlignCenter ); + + QVBoxLayout *layout = new QVBoxLayout( this );; + layout->setSpacing( 0 ); + layout->addWidget( d_knob, 10 ); + layout->addWidget( d_label ); + layout->addStretch( 10 ); + + connect( d_knob, SIGNAL( valueChanged( double ) ), + this, SLOT( setNum( double ) ) ); + + setNum( d_knob->value() ); +} + +QwtKnob *KnobBox::createKnob( int knobType ) const +{ + QwtKnob *knob = new QwtKnob(); + knob->setTracking( true ); + + switch( knobType ) + { + case 0: + { + knob->setKnobStyle( QwtKnob::Sunken ); + knob->setMarkerStyle( QwtKnob::Nub ); + knob->setWrapping( true ); + knob->setNumTurns( 4 ); + knob->setScaleStepSize( 10.0 ); + knob->setScale( 0, 400 ); + knob->setTotalSteps( 400 ); + break; + } + case 1: + { + knob->setKnobStyle( QwtKnob::Sunken ); + knob->setMarkerStyle( QwtKnob::Dot ); + break; + } + case 2: + { + knob->setKnobStyle( QwtKnob::Sunken ); + knob->setMarkerStyle( QwtKnob::Tick ); + + QwtLinearScaleEngine *scaleEngine = new QwtLinearScaleEngine( 2 ); + scaleEngine->setTransformation( new QwtPowerTransform( 2 ) ); + knob->setScaleEngine( scaleEngine ); + + QList< double > ticks[ QwtScaleDiv::NTickTypes ]; + ticks[ QwtScaleDiv::MajorTick ] << 0 << 4 + << 16 << 32 << 64 << 96 << 128; + ticks[ QwtScaleDiv::MediumTick ] << 24 << 48 << 80 << 112; + ticks[ QwtScaleDiv::MinorTick ] + << 0.5 << 1 << 2 + << 7 << 10 << 13 + << 20 << 28 + << 40 << 56 + << 72 << 88 + << 104 << 120; + + knob->setScale( QwtScaleDiv( 0, 128, ticks ) ); + + knob->setTotalSteps( 100 ); + knob->setStepAlignment( false ); + knob->setSingleSteps( 1 ); + knob->setPageSteps( 5 ); + + break; + } + case 3: + { + knob->setKnobStyle( QwtKnob::Flat ); + knob->setMarkerStyle( QwtKnob::Notch ); + knob->setScaleEngine( new QwtLogScaleEngine() ); + knob->setScaleStepSize( 1.0 ); + knob->setScale( 0.1, 1000.0 ); + knob->setScaleMaxMinor( 10 ); + break; + } + case 4: + { + knob->setKnobStyle( QwtKnob::Raised ); + knob->setMarkerStyle( QwtKnob::Dot ); + knob->setWrapping( true ); + break; + } + case 5: + { + knob->setKnobStyle( QwtKnob::Styled ); + knob->setMarkerStyle( QwtKnob::Triangle ); + knob->setTotalAngle( 180.0 ); + knob->setScale( 100, -100 ); + break; + } + } + + return knob; +} + +void KnobBox::setNum( double v ) +{ + QString text; + text.setNum( v, 'f', 2 ); + + d_label->setText( text ); +} diff --git a/qwt/examples/controls/knobbox.h b/qwt/examples/controls/knobbox.h new file mode 100644 index 000000000..5b5fcef37 --- /dev/null +++ b/qwt/examples/controls/knobbox.h @@ -0,0 +1,25 @@ +#ifndef _KNOB_BOX_H_ +#define _KNOB_BOX_H_ + +#include + +class QLabel; +class QwtKnob; + +class KnobBox: public QWidget +{ + Q_OBJECT +public: + KnobBox( QWidget *parent, int knobType ); + +private Q_SLOTS: + void setNum( double v ); + +private: + QwtKnob *createKnob( int knobType ) const; + + QwtKnob *d_knob; + QLabel *d_label; +}; + +#endif diff --git a/qwt/examples/controls/knobtab.cpp b/qwt/examples/controls/knobtab.cpp new file mode 100644 index 000000000..dc3ad5d38 --- /dev/null +++ b/qwt/examples/controls/knobtab.cpp @@ -0,0 +1,17 @@ +#include "knobtab.h" +#include "knobbox.h" +#include + +KnobTab::KnobTab( QWidget *parent ): + QWidget( parent ) +{ + QGridLayout *layout = new QGridLayout( this ); + + const int numRows = 3; + for ( int i = 0; i < 2 * numRows; i++ ) + { + KnobBox *knobBox = new KnobBox( this, i ); + layout->addWidget( knobBox, i / numRows, i % numRows ); + } +} + diff --git a/qwt/examples/controls/knobtab.h b/qwt/examples/controls/knobtab.h new file mode 100644 index 000000000..731668b12 --- /dev/null +++ b/qwt/examples/controls/knobtab.h @@ -0,0 +1,12 @@ +#ifndef _KNOB_TAB_H +#define _KNOB_TAB_H 1 + +#include + +class KnobTab: public QWidget +{ +public: + KnobTab( QWidget *parent = NULL ); +}; + +#endif diff --git a/qwt/examples/controls/main.cpp b/qwt/examples/controls/main.cpp new file mode 100644 index 000000000..755633aa2 --- /dev/null +++ b/qwt/examples/controls/main.cpp @@ -0,0 +1,41 @@ +#include +#include +#include "slidertab.h" +#include "wheeltab.h" +#include "knobtab.h" +#include "dialtab.h" + + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QTabWidget tabWidget; + + SliderTab *sliderTab = new SliderTab(); + sliderTab->setAutoFillBackground( true ); + sliderTab->setPalette( QColor( "DimGray" ) ); + + WheelTab *wheelTab = new WheelTab(); + wheelTab->setAutoFillBackground( true ); + wheelTab->setPalette( QColor( "Silver" ) ); + + KnobTab *knobTab = new KnobTab(); + knobTab->setAutoFillBackground( true ); + knobTab->setPalette( Qt::darkGray ); + + DialTab *dialTab = new DialTab(); + dialTab->setAutoFillBackground( true ); + dialTab->setPalette( Qt::darkGray ); + + tabWidget.addTab( new SliderTab, "Slider" ); + tabWidget.addTab( new WheelTab, "Wheel/Thermo" ); + tabWidget.addTab( knobTab, "Knob" ); + tabWidget.addTab( dialTab, "Dial" ); + + tabWidget.resize( 800, 600 ); + tabWidget.show(); + + return a.exec(); +} + diff --git a/qwt/examples/controls/sliderbox.cpp b/qwt/examples/controls/sliderbox.cpp new file mode 100644 index 000000000..ac059e348 --- /dev/null +++ b/qwt/examples/controls/sliderbox.cpp @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include "sliderbox.h" + +SliderBox::SliderBox( int sliderType, QWidget *parent ): + QWidget( parent ) +{ + d_slider = createSlider( sliderType ); + + QFlags alignment; + + if ( d_slider->orientation() == Qt::Horizontal ) + { + if ( d_slider->scalePosition() == QwtSlider::TrailingScale ) + alignment = Qt::AlignBottom; + else + alignment = Qt::AlignTop; + + alignment |= Qt::AlignHCenter; + } + else + { + if ( d_slider->scalePosition() == QwtSlider::TrailingScale ) + alignment = Qt::AlignRight; + else + alignment = Qt::AlignLeft; + + alignment |= Qt::AlignVCenter; + } + + d_label = new QLabel( this ); + d_label->setAlignment( alignment ); + d_label->setFixedWidth( d_label->fontMetrics().width( "10000.9" ) ); + + connect( d_slider, SIGNAL( valueChanged( double ) ), SLOT( setNum( double ) ) ); + + QBoxLayout *layout; + if ( d_slider->orientation() == Qt::Horizontal ) + layout = new QHBoxLayout( this ); + else + layout = new QVBoxLayout( this ); + + layout->addWidget( d_slider ); + layout->addWidget( d_label ); + + setNum( d_slider->value() ); +} + +QwtSlider *SliderBox::createSlider( int sliderType ) const +{ + QwtSlider *slider = new QwtSlider(); + + switch( sliderType ) + { + case 0: + { + slider->setOrientation( Qt::Horizontal ); + slider->setScalePosition( QwtSlider::TrailingScale ); + slider->setTrough( true ); + slider->setGroove( false ); + slider->setSpacing( 0 ); + slider->setHandleSize( QSize( 30, 16 ) ); + slider->setScale( 10.0, -10.0 ); + slider->setTotalSteps( 8 ); + slider->setSingleSteps( 1 ); + slider->setPageSteps( 1 ); + slider->setWrapping( true ); + break; + } + case 1: + { + slider->setOrientation( Qt::Horizontal ); + slider->setScalePosition( QwtSlider::NoScale ); + slider->setTrough( true ); + slider->setGroove( true ); + slider->setScale( 0.0, 1.0 ); + slider->setTotalSteps( 100 ); + slider->setSingleSteps( 1 ); + slider->setPageSteps( 5 ); + break; + } + case 2: + { + slider->setOrientation( Qt::Horizontal ); + slider->setScalePosition( QwtSlider::LeadingScale ); + slider->setTrough( false ); + slider->setGroove( true ); + slider->setHandleSize( QSize( 12, 25 ) ); + slider->setScale( 1000.0, 3000.0 ); + slider->setTotalSteps( 200.0 ); + slider->setSingleSteps( 2 ); + slider->setPageSteps( 10 ); + break; + } + case 3: + { + slider->setOrientation( Qt::Horizontal ); + slider->setScalePosition( QwtSlider::TrailingScale ); + slider->setTrough( true ); + slider->setGroove( true ); + + QwtLinearScaleEngine *scaleEngine = new QwtLinearScaleEngine( 2 ); + scaleEngine->setTransformation( new QwtPowerTransform( 2 ) ); + slider->setScaleEngine( scaleEngine ); + slider->setScale( 0.0, 128.0 ); + slider->setTotalSteps( 100 ); + slider->setStepAlignment( false ); + slider->setSingleSteps( 1 ); + slider->setPageSteps( 5 ); + break; + } + case 4: + { + slider->setOrientation( Qt::Vertical ); + slider->setScalePosition( QwtSlider::TrailingScale ); + slider->setTrough( false ); + slider->setGroove( true ); + slider->setScale( 100.0, 0.0 ); + slider->setInvertedControls( true ); + slider->setTotalSteps( 100 ); + slider->setPageSteps( 5 ); + slider->setScaleMaxMinor( 5 ); + break; + } + case 5: + { + slider->setOrientation( Qt::Vertical ); + slider->setScalePosition( QwtSlider::NoScale ); + slider->setTrough( true ); + slider->setGroove( false ); + slider->setScale( 0.0, 100.0 ); + slider->setTotalSteps( 100 ); + slider->setPageSteps( 10 ); + break; + } + case 6: + { + slider->setOrientation( Qt::Vertical ); + slider->setScalePosition( QwtSlider::LeadingScale ); + slider->setTrough( true ); + slider->setGroove( true ); + slider->setScaleEngine( new QwtLogScaleEngine ); + slider->setStepAlignment( false ); + slider->setHandleSize( QSize( 20, 32 ) ); + slider->setBorderWidth( 1 ); + slider->setScale( 1.0, 1.0e4 ); + slider->setTotalSteps( 100 ); + slider->setPageSteps( 10 ); + slider->setScaleMaxMinor( 9 ); + break; + } + } + + if ( slider ) + { + QString name( "Slider %1" ); + slider->setObjectName( name.arg( sliderType ) ); + } + + return slider; +} + +void SliderBox::setNum( double v ) +{ + QString text; + text.setNum( v, 'f', 2 ); + + d_label->setText( text ); +} diff --git a/qwt/examples/controls/sliderbox.h b/qwt/examples/controls/sliderbox.h new file mode 100644 index 000000000..c79accdee --- /dev/null +++ b/qwt/examples/controls/sliderbox.h @@ -0,0 +1,25 @@ +#ifndef _SLIDER_BOX_H_ +#define _SLIDER_BOX_H_ 1 + +#include + +class QLabel; +class QwtSlider; + +class SliderBox: public QWidget +{ + Q_OBJECT +public: + SliderBox( int sliderType, QWidget *parent = NULL ); + +private Q_SLOTS: + void setNum( double v ); + +private: + QwtSlider *createSlider( int sliderType ) const; + + QwtSlider *d_slider; + QLabel *d_label; +}; + +#endif diff --git a/qwt/examples/controls/slidertab.cpp b/qwt/examples/controls/slidertab.cpp new file mode 100644 index 000000000..311d6433c --- /dev/null +++ b/qwt/examples/controls/slidertab.cpp @@ -0,0 +1,37 @@ +#include "slidertab.h" +#include "sliderbox.h" +#include + +SliderTab::SliderTab( QWidget *parent ): + QWidget( parent ) +{ + int i; + + QBoxLayout *hLayout = createLayout( Qt::Vertical ); + for ( i = 0; i < 4; i++ ) + hLayout->addWidget( new SliderBox( i ) ); + hLayout->addStretch(); + + QBoxLayout *vLayout = createLayout( Qt::Horizontal ); + for ( ; i < 7; i++ ) + vLayout->addWidget( new SliderBox( i ) ); + + QBoxLayout *mainLayout = createLayout( Qt::Horizontal, this ); + mainLayout->addLayout( vLayout ); + mainLayout->addLayout( hLayout, 10 ); +} + +QBoxLayout *SliderTab::createLayout( + Qt::Orientation orientation, QWidget *widget ) +{ + QBoxLayout *layout = + new QBoxLayout( QBoxLayout::LeftToRight, widget ); + + if ( orientation == Qt::Vertical ) + layout->setDirection( QBoxLayout::TopToBottom ); + + layout->setSpacing( 20 ); + layout->setMargin( 0 ); + + return layout; +} diff --git a/qwt/examples/controls/slidertab.h b/qwt/examples/controls/slidertab.h new file mode 100644 index 000000000..ffa243c07 --- /dev/null +++ b/qwt/examples/controls/slidertab.h @@ -0,0 +1,18 @@ +#ifndef _SLIDER_TAB_H +#define _SLIDER_TAB_H 1 + +#include + +class QBoxLayout; + +class SliderTab: public QWidget +{ +public: + SliderTab( QWidget *parent = NULL ); + +private: + QBoxLayout *createLayout( Qt::Orientation, + QWidget *widget = NULL ); +}; + +#endif diff --git a/qwt/examples/controls/wheelbox.cpp b/qwt/examples/controls/wheelbox.cpp new file mode 100644 index 000000000..141b00341 --- /dev/null +++ b/qwt/examples/controls/wheelbox.cpp @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include +#include +#include +#include "wheelbox.h" + +WheelBox::WheelBox( Qt::Orientation orientation, + int type, QWidget *parent ): + QWidget( parent ) +{ + QWidget *box = createBox( orientation, type ); + d_label = new QLabel( this ); + d_label->setAlignment( Qt::AlignHCenter | Qt::AlignTop ); + + QBoxLayout *layout = new QVBoxLayout( this ); + layout->addWidget( box ); + layout->addWidget( d_label ); + + setNum( d_wheel->value() ); + + connect( d_wheel, SIGNAL( valueChanged( double ) ), + this, SLOT( setNum( double ) ) ); +} + +QWidget *WheelBox::createBox( + Qt::Orientation orientation, int type ) +{ + d_wheel = new QwtWheel(); + d_wheel->setValue( 80 ); + d_wheel->setWheelWidth( 20 ); + d_wheel->setMass( 1.0 ); + + d_thermo = new QwtThermo(); + d_thermo->setOrientation( orientation ); + + if ( orientation == Qt::Horizontal ) + { + d_thermo->setScalePosition( QwtThermo::LeadingScale ); + d_wheel->setOrientation( Qt::Vertical ); + } + else + { + d_thermo->setScalePosition( QwtThermo::TrailingScale ); + d_wheel->setOrientation( Qt::Horizontal ); + } + + switch( type ) + { + case 0: + { + QwtLinearColorMap *colorMap = new QwtLinearColorMap(); + colorMap->setColorInterval( Qt::blue, Qt::red ); + d_thermo->setColorMap( colorMap ); + + break; + } + case 1: + { + QwtLinearColorMap *colorMap = new QwtLinearColorMap(); + colorMap->setMode( QwtLinearColorMap::FixedColors ); + + int idx = 4; + + colorMap->setColorInterval( Qt::GlobalColor( idx ), + Qt::GlobalColor( idx + 10 ) ); + for ( int i = 1; i < 10; i++ ) + { + colorMap->addColorStop( i / 10.0, + Qt::GlobalColor( idx + i ) ); + } + + d_thermo->setColorMap( colorMap ); + break; + } + case 2: + { + d_wheel->setRange( 10, 1000 ); + d_wheel->setSingleStep( 1.0 ); + + d_thermo->setScaleEngine( new QwtLogScaleEngine ); + d_thermo->setScaleMaxMinor( 10 ); + + d_thermo->setFillBrush( Qt::darkCyan ); + d_thermo->setAlarmBrush( Qt::magenta ); + d_thermo->setAlarmLevel( 500.0 ); + + d_wheel->setValue( 800 ); + + break; + } + case 3: + { + d_wheel->setRange( -1000, 1000 ); + d_wheel->setSingleStep( 1.0 ); + d_wheel->setPalette( QColor( "Tan" ) ); + + QwtLinearScaleEngine *scaleEngine = new QwtLinearScaleEngine(); + scaleEngine->setTransformation( new QwtPowerTransform( 2 ) ); + + d_thermo->setScaleMaxMinor( 5 ); + d_thermo->setScaleEngine( scaleEngine ); + + QPalette pal = palette(); + pal.setColor( QPalette::Base, Qt::darkGray ); + pal.setColor( QPalette::ButtonText, QColor( "darkKhaki" ) ); + + d_thermo->setPalette( pal ); + break; + } + case 4: + { + d_wheel->setRange( -100, 300 ); + d_wheel->setInverted( true ); + + QwtLinearColorMap *colorMap = new QwtLinearColorMap(); + colorMap->setColorInterval( Qt::darkCyan, Qt::yellow ); + d_thermo->setColorMap( colorMap ); + + d_wheel->setValue( 243 ); + + break; + } + case 5: + { + d_thermo->setFillBrush( Qt::darkCyan ); + d_thermo->setAlarmBrush( Qt::magenta ); + d_thermo->setAlarmLevel( 60.0 ); + + break; + } + case 6: + { + d_thermo->setOriginMode( QwtThermo::OriginMinimum ); + d_thermo->setFillBrush( QBrush( "DarkSlateBlue" ) ); + d_thermo->setAlarmBrush( QBrush( "DarkOrange" ) ); + d_thermo->setAlarmLevel( 60.0 ); + + break; + } + case 7: + { + d_wheel->setRange( -100, 100 ); + + d_thermo->setOriginMode( QwtThermo::OriginCustom ); + d_thermo->setOrigin( 0.0 ); + d_thermo->setFillBrush( Qt::darkBlue ); + + break; + } + } + + double min = d_wheel->minimum(); + double max = d_wheel->maximum(); + + if ( d_wheel->isInverted() ) + qSwap( min, max ); + + d_thermo->setScale( min, max ); + d_thermo->setValue( d_wheel->value() ); + + connect( d_wheel, SIGNAL( valueChanged( double ) ), + d_thermo, SLOT( setValue( double ) ) ); + + QWidget *box = new QWidget(); + + QBoxLayout *layout; + + if ( orientation == Qt::Horizontal ) + layout = new QHBoxLayout( box ); + else + layout = new QVBoxLayout( box ); + + layout->addWidget( d_thermo, Qt::AlignCenter ); + layout->addWidget( d_wheel ); + + return box; +} + +void WheelBox::setNum( double v ) +{ + QString text; + text.setNum( v, 'f', 2 ); + + d_label->setText( text ); +} diff --git a/qwt/examples/controls/wheelbox.h b/qwt/examples/controls/wheelbox.h new file mode 100644 index 000000000..4381fa2c8 --- /dev/null +++ b/qwt/examples/controls/wheelbox.h @@ -0,0 +1,29 @@ +#ifndef _WHEEL_BOX_H_ +#define _WHEEL_BOX_H_ 1 + +#include + +class QLabel; +class QwtThermo; +class QwtWheel; + +class WheelBox: public QWidget +{ + Q_OBJECT +public: + WheelBox( Qt::Orientation, + int type, QWidget *parent = NULL ); + +private Q_SLOTS: + void setNum( double v ); + +private: + QWidget *createBox( Qt::Orientation, int type ); + +private: + QwtWheel *d_wheel; + QwtThermo *d_thermo; + QLabel *d_label; +}; + +#endif diff --git a/qwt/examples/controls/wheeltab.cpp b/qwt/examples/controls/wheeltab.cpp new file mode 100644 index 000000000..a309a6f5a --- /dev/null +++ b/qwt/examples/controls/wheeltab.cpp @@ -0,0 +1,28 @@ +#include "wheeltab.h" +#include "wheelbox.h" +#include + +WheelTab::WheelTab( QWidget *parent ): + QWidget( parent ) +{ + const int numBoxes = 4; + + QGridLayout *layout1 = new QGridLayout(); + for ( int i = 0; i < numBoxes; i++ ) + { + WheelBox *box = new WheelBox( Qt::Vertical, i ); + layout1->addWidget( box, i / 2, i % 2 ); + } + + QGridLayout *layout2 = new QGridLayout(); + for ( int i = 0; i < numBoxes; i++ ) + { + WheelBox *box = new WheelBox( Qt::Horizontal, i + numBoxes ); + layout2->addWidget( box, i / 2, i % 2 ); + } + + QHBoxLayout *layout = new QHBoxLayout( this ); + layout->addLayout( layout1, 2 ); + layout->addLayout( layout2, 5 ); +} + diff --git a/qwt/examples/controls/wheeltab.h b/qwt/examples/controls/wheeltab.h new file mode 100644 index 000000000..4ea971789 --- /dev/null +++ b/qwt/examples/controls/wheeltab.h @@ -0,0 +1,12 @@ +#ifndef _WHEEL_TAB_H +#define _WHEEL_TAB_H 1 + +#include + +class WheelTab: public QWidget +{ +public: + WheelTab( QWidget *parent = NULL ); +}; + +#endif diff --git a/qwt/examples/cpuplot/cpupiemarker.cpp b/qwt/examples/cpuplot/cpupiemarker.cpp new file mode 100644 index 000000000..4f4c700be --- /dev/null +++ b/qwt/examples/cpuplot/cpupiemarker.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include "cpuplot.h" +#include "cpupiemarker.h" + +CpuPieMarker::CpuPieMarker() +{ + setZ( 1000 ); + setRenderHint( QwtPlotItem::RenderAntialiased, true ); +} + +int CpuPieMarker::rtti() const +{ + return QwtPlotItem::Rtti_PlotUserItem; +} + +void CpuPieMarker::draw( QPainter *painter, + const QwtScaleMap &, const QwtScaleMap &, + const QRectF &rect ) const +{ + const CpuPlot *cpuPlot = static_cast ( plot() ); + + const QwtScaleMap yMap = cpuPlot->canvasMap( QwtAxis::yLeft ); + + const int margin = 5; + + QRectF pieRect; + pieRect.setX( rect.x() + margin ); + pieRect.setY( rect.y() + margin ); + pieRect.setHeight( yMap.transform( 80.0 ) ); + pieRect.setWidth( pieRect.height() ); + + const int dataType[] = { CpuPlot::User, CpuPlot::System, CpuPlot::Idle }; + + int angle = static_cast( 5760 * 0.75 ); + for ( unsigned int i = 0; + i < sizeof( dataType ) / sizeof( dataType[0] ); i++ ) + { + const QwtPlotCurve *curve = cpuPlot->cpuCurve( dataType[i] ); + if ( curve->dataSize() > 0 ) + { + const int value = static_cast( 5760 * curve->sample( 0 ).y() / 100.0 ); + + painter->save(); + painter->setBrush( QBrush( curve->pen().color(), Qt::SolidPattern ) ); + if ( value != 0 ) + painter->drawPie( pieRect, -angle, -value ); + painter->restore(); + + angle += value; + } + } +} + diff --git a/qwt/examples/cpuplot/cpupiemarker.h b/qwt/examples/cpuplot/cpupiemarker.h new file mode 100644 index 000000000..366138a56 --- /dev/null +++ b/qwt/examples/cpuplot/cpupiemarker.h @@ -0,0 +1,17 @@ +//----------------------------------------------------------------- +// This class shows how to extend QwtPlotItems. It displays a +// pie chart of user/total/idle cpu usage in percent. +//----------------------------------------------------------------- + +#include + +class CpuPieMarker: public QwtPlotItem +{ +public: + CpuPieMarker(); + + virtual int rtti() const; + + virtual void draw( QPainter *, + const QwtScaleMap &, const QwtScaleMap &, const QRectF & ) const; +}; diff --git a/qwt/examples/cpuplot/cpuplot.cpp b/qwt/examples/cpuplot/cpuplot.cpp new file mode 100644 index 000000000..e8035492a --- /dev/null +++ b/qwt/examples/cpuplot/cpuplot.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cpupiemarker.h" +#include "cpuplot.h" + +class TimeScaleDraw: public QwtScaleDraw +{ +public: + TimeScaleDraw( const QTime &base ): + baseTime( base ) + { + } + virtual QwtText label( double v ) const + { + QTime upTime = baseTime.addSecs( static_cast( v ) ); + return upTime.toString(); + } +private: + QTime baseTime; +}; + +class Background: public QwtPlotItem +{ +public: + Background() + { + setZ( 0.0 ); + } + + virtual int rtti() const + { + return QwtPlotItem::Rtti_PlotUserItem; + } + + virtual void draw( QPainter *painter, + const QwtScaleMap &, const QwtScaleMap &yMap, + const QRectF &canvasRect ) const + { + QColor c( Qt::white ); + QRectF r = canvasRect; + + for ( int i = 100; i > 0; i -= 10 ) + { + r.setBottom( yMap.transform( i - 10 ) ); + r.setTop( yMap.transform( i ) ); + painter->fillRect( r, c ); + + c = c.dark( 110 ); + } + } +}; + +class CpuCurve: public QwtPlotCurve +{ +public: + CpuCurve( const QString &title ): + QwtPlotCurve( title ) + { + setRenderHint( QwtPlotItem::RenderAntialiased ); + } + + void setColor( const QColor &color ) + { + QColor c = color; + c.setAlpha( 150 ); + + setPen( c ); + setBrush( c ); + } +}; + +CpuPlot::CpuPlot( QWidget *parent ): + QwtPlot( parent ), + dataCount( 0 ) +{ + setAutoReplot( false ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setBorderRadius( 10 ); + + setCanvas( canvas ); + + plotLayout()->setAlignCanvasToScales( true ); + + QwtLegend *legend = new QwtLegend; + legend->setDefaultItemMode( QwtLegendData::Checkable ); + insertLegend( legend, QwtPlot::RightLegend ); + + setAxisTitle( QwtAxis::xBottom, " System Uptime [h:m:s]" ); + setAxisScaleDraw( QwtAxis::xBottom, + new TimeScaleDraw( cpuStat.upTime() ) ); + setAxisScale( QwtAxis::xBottom, 0, HISTORY ); + setAxisLabelRotation( QwtAxis::xBottom, -50.0 ); + setAxisLabelAlignment( QwtAxis::xBottom, Qt::AlignLeft | Qt::AlignBottom ); + + /* + In situations, when there is a label at the most right position of the + scale, additional space is needed to display the overlapping part + of the label would be taken by reducing the width of scale and canvas. + To avoid this "jumping canvas" effect, we add a permanent margin. + We don't need to do the same for the left border, because there + is enough space for the overlapping label below the left scale. + */ + + QwtScaleWidget *scaleWidget = axisWidget( QwtAxis::xBottom ); + const int fmh = QFontMetrics( scaleWidget->font() ).height(); + scaleWidget->setMinBorderDist( 0, fmh / 2 ); + + setAxisTitle( QwtAxis::yLeft, "Cpu Usage [%]" ); + setAxisScale( QwtAxis::yLeft, 0, 100 ); + + Background *bg = new Background(); + bg->attach( this ); + + CpuPieMarker *pie = new CpuPieMarker(); + pie->attach( this ); + + CpuCurve *curve; + + curve = new CpuCurve( "System" ); + curve->setColor( Qt::red ); + curve->attach( this ); + data[System].curve = curve; + + curve = new CpuCurve( "User" ); + curve->setColor( Qt::blue ); + curve->setZ( curve->z() - 1 ); + curve->attach( this ); + data[User].curve = curve; + + curve = new CpuCurve( "Total" ); + curve->setColor( Qt::black ); + curve->setZ( curve->z() - 2 ); + curve->attach( this ); + data[Total].curve = curve; + + curve = new CpuCurve( "Idle" ); + curve->setColor( Qt::darkCyan ); + curve->setZ( curve->z() - 3 ); + curve->attach( this ); + data[Idle].curve = curve; + + showCurve( data[System].curve, true ); + showCurve( data[User].curve, true ); + showCurve( data[Total].curve, false ); + showCurve( data[Idle].curve, false ); + + for ( int i = 0; i < HISTORY; i++ ) + timeData[HISTORY - 1 - i] = i; + + ( void )startTimer( 1000 ); // 1 second + + connect( legend, SIGNAL( checked( const QVariant &, bool, int ) ), + SLOT( legendChecked( const QVariant &, bool ) ) ); +} + +void CpuPlot::timerEvent( QTimerEvent * ) +{ + for ( int i = dataCount; i > 0; i-- ) + { + for ( int c = 0; c < NCpuData; c++ ) + { + if ( i < HISTORY ) + data[c].data[i] = data[c].data[i-1]; + } + } + + cpuStat.statistic( data[User].data[0], data[System].data[0] ); + + data[Total].data[0] = data[User].data[0] + data[System].data[0]; + data[Idle].data[0] = 100.0 - data[Total].data[0]; + + if ( dataCount < HISTORY ) + dataCount++; + + for ( int j = 0; j < HISTORY; j++ ) + timeData[j]++; + + setAxisScale( QwtAxis::xBottom, + timeData[HISTORY - 1], timeData[0] ); + + for ( int c = 0; c < NCpuData; c++ ) + { + data[c].curve->setRawSamples( + timeData, data[c].data, dataCount ); + } + + replot(); +} + +void CpuPlot::legendChecked( const QVariant &itemInfo, bool on ) +{ + QwtPlotItem *plotItem = infoToItem( itemInfo ); + if ( plotItem ) + showCurve( plotItem, on ); +} + +void CpuPlot::showCurve( QwtPlotItem *item, bool on ) +{ + item->setVisible( on ); + + QwtLegend *lgd = qobject_cast( legend() ); + + QList legendWidgets = + lgd->legendWidgets( itemToInfo( item ) ); + + if ( legendWidgets.size() == 1 ) + { + QwtLegendLabel *legendLabel = + qobject_cast( legendWidgets[0] ); + + if ( legendLabel ) + legendLabel->setChecked( on ); + } + + replot(); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QWidget vBox; + vBox.setWindowTitle( "Cpu Plot" ); + + CpuPlot *plot = new CpuPlot( &vBox ); + plot->setTitle( "History" ); + + const int margin = 5; + plot->setContentsMargins( margin, margin, margin, margin ); + + QString info( "Press the legend to en/disable a curve" ); + + QLabel *label = new QLabel( info, &vBox ); + + QVBoxLayout *layout = new QVBoxLayout( &vBox ); + layout->addWidget( plot ); + layout->addWidget( label ); + + vBox.resize( 600, 400 ); + vBox.show(); + + return a.exec(); +} + diff --git a/qwt/examples/cpuplot/cpuplot.h b/qwt/examples/cpuplot/cpuplot.h new file mode 100644 index 000000000..a0c39a3f8 --- /dev/null +++ b/qwt/examples/cpuplot/cpuplot.h @@ -0,0 +1,47 @@ +#include +#include "cpustat.h" + +#define HISTORY 60 // seconds + +class QwtPlotCurve; + +class CpuPlot : public QwtPlot +{ + Q_OBJECT +public: + enum CpuData + { + User, + System, + Total, + Idle, + + NCpuData + }; + + CpuPlot( QWidget * = 0 ); + const QwtPlotCurve *cpuCurve( int id ) const + { + return data[id].curve; + } + +protected: + void timerEvent( QTimerEvent *e ); + +private Q_SLOTS: + void legendChecked( const QVariant &, bool on ); + +private: + void showCurve( QwtPlotItem *, bool on ); + + struct + { + QwtPlotCurve *curve; + double data[HISTORY]; + } data[NCpuData]; + + double timeData[HISTORY]; + + int dataCount; + CpuStat cpuStat; +}; diff --git a/qwt/examples/cpuplot/cpuplot.pro b/qwt/examples/cpuplot/cpuplot.pro new file mode 100644 index 000000000..1cc631299 --- /dev/null +++ b/qwt/examples/cpuplot/cpuplot.pro @@ -0,0 +1,22 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = cpuplot + +HEADERS = \ + cpuplot.h \ + cpustat.h \ + cpupiemarker.h + +SOURCES = \ + cpuplot.cpp \ + cpustat.cpp \ + cpupiemarker.cpp diff --git a/qwt/examples/cpuplot/cpustat.cpp b/qwt/examples/cpuplot/cpustat.cpp new file mode 100644 index 000000000..f9c7552ca --- /dev/null +++ b/qwt/examples/cpuplot/cpustat.cpp @@ -0,0 +1,220 @@ +#include +#include +#include +#include "cpustat.h" + +CpuStat::CpuStat() +{ + lookUp( procValues ); +} + +QTime CpuStat::upTime() const +{ + QTime t( 0, 0, 0 ); + for ( int i = 0; i < NValues; i++ ) + t = t.addSecs( int( procValues[i] / 100 ) ); + + return t; +} + +void CpuStat::statistic( double &user, double &system ) +{ + double values[NValues]; + + lookUp( values ); + + double userDelta = values[User] + values[Nice] + - procValues[User] - procValues[Nice]; + double systemDelta = values[System] - procValues[System]; + + double totalDelta = 0; + for ( int i = 0; i < NValues; i++ ) + totalDelta += values[i] - procValues[i]; + + user = userDelta / totalDelta * 100.0; + system = systemDelta / totalDelta * 100.0; + + for ( int j = 0; j < NValues; j++ ) + procValues[j] = values[j]; +} + +void CpuStat::lookUp( double values[NValues] ) const +{ + QFile file( "/proc/stat" ); + if ( !file.open( QIODevice::ReadOnly ) ) + { + static double dummyValues[][NValues] = + { + { 103726, 0, 23484, 819556 }, + { 103783, 0, 23489, 819604 }, + { 103798, 0, 23490, 819688 }, + { 103820, 0, 23490, 819766 }, + { 103840, 0, 23493, 819843 }, + { 103875, 0, 23499, 819902 }, + { 103917, 0, 23504, 819955 }, + { 103950, 0, 23508, 820018 }, + { 103987, 0, 23510, 820079 }, + { 104020, 0, 23513, 820143 }, + { 104058, 0, 23514, 820204 }, + { 104099, 0, 23520, 820257 }, + { 104121, 0, 23525, 820330 }, + { 104159, 0, 23530, 820387 }, + { 104176, 0, 23534, 820466 }, + { 104215, 0, 23538, 820523 }, + { 104245, 0, 23541, 820590 }, + { 104267, 0, 23545, 820664 }, + { 104311, 0, 23555, 820710 }, + { 104355, 0, 23565, 820756 }, + { 104367, 0, 23567, 820842 }, + { 104383, 0, 23572, 820921 }, + { 104396, 0, 23577, 821003 }, + { 104413, 0, 23579, 821084 }, + { 104446, 0, 23588, 821142 }, + { 104521, 0, 23594, 821161 }, + { 104611, 0, 23604, 821161 }, + { 104708, 0, 23607, 821161 }, + { 104804, 0, 23611, 821161 }, + { 104895, 0, 23620, 821161 }, + { 104993, 0, 23622, 821161 }, + { 105089, 0, 23626, 821161 }, + { 105185, 0, 23630, 821161 }, + { 105281, 0, 23634, 821161 }, + { 105379, 0, 23636, 821161 }, + { 105472, 0, 23643, 821161 }, + { 105569, 0, 23646, 821161 }, + { 105666, 0, 23649, 821161 }, + { 105763, 0, 23652, 821161 }, + { 105828, 0, 23661, 821187 }, + { 105904, 0, 23666, 821206 }, + { 105999, 0, 23671, 821206 }, + { 106094, 0, 23676, 821206 }, + { 106184, 0, 23686, 821206 }, + { 106273, 0, 23692, 821211 }, + { 106306, 0, 23700, 821270 }, + { 106341, 0, 23703, 821332 }, + { 106392, 0, 23709, 821375 }, + { 106423, 0, 23715, 821438 }, + { 106472, 0, 23721, 821483 }, + { 106531, 0, 23727, 821517 }, + { 106562, 0, 23732, 821582 }, + { 106597, 0, 23736, 821643 }, + { 106633, 0, 23737, 821706 }, + { 106666, 0, 23742, 821768 }, + { 106697, 0, 23744, 821835 }, + { 106730, 0, 23748, 821898 }, + { 106765, 0, 23751, 821960 }, + { 106799, 0, 23754, 822023 }, + { 106831, 0, 23758, 822087 }, + { 106862, 0, 23761, 822153 }, + { 106899, 0, 23763, 822214 }, + { 106932, 0, 23766, 822278 }, + { 106965, 0, 23768, 822343 }, + { 107009, 0, 23771, 822396 }, + { 107040, 0, 23775, 822461 }, + { 107092, 0, 23780, 822504 }, + { 107143, 0, 23787, 822546 }, + { 107200, 0, 23795, 822581 }, + { 107250, 0, 23803, 822623 }, + { 107277, 0, 23810, 822689 }, + { 107286, 0, 23810, 822780 }, + { 107313, 0, 23817, 822846 }, + { 107325, 0, 23818, 822933 }, + { 107332, 0, 23818, 823026 }, + { 107344, 0, 23821, 823111 }, + { 107357, 0, 23821, 823198 }, + { 107368, 0, 23823, 823284 }, + { 107375, 0, 23824, 823377 }, + { 107386, 0, 23825, 823465 }, + { 107396, 0, 23826, 823554 }, + { 107422, 0, 23830, 823624 }, + { 107434, 0, 23831, 823711 }, + { 107456, 0, 23835, 823785 }, + { 107468, 0, 23838, 823870 }, + { 107487, 0, 23840, 823949 }, + { 107515, 0, 23843, 824018 }, + { 107528, 0, 23846, 824102 }, + { 107535, 0, 23851, 824190 }, + { 107548, 0, 23853, 824275 }, + { 107562, 0, 23857, 824357 }, + { 107656, 0, 23863, 824357 }, + { 107751, 0, 23868, 824357 }, + { 107849, 0, 23870, 824357 }, + { 107944, 0, 23875, 824357 }, + { 108043, 0, 23876, 824357 }, + { 108137, 0, 23882, 824357 }, + { 108230, 0, 23889, 824357 }, + { 108317, 0, 23902, 824357 }, + { 108412, 0, 23907, 824357 }, + { 108511, 0, 23908, 824357 }, + { 108608, 0, 23911, 824357 }, + { 108704, 0, 23915, 824357 }, + { 108801, 0, 23918, 824357 }, + { 108891, 0, 23928, 824357 }, + { 108987, 0, 23932, 824357 }, + { 109072, 0, 23943, 824361 }, + { 109079, 0, 23943, 824454 }, + { 109086, 0, 23944, 824546 }, + { 109098, 0, 23950, 824628 }, + { 109108, 0, 23955, 824713 }, + { 109115, 0, 23957, 824804 }, + { 109122, 0, 23958, 824896 }, + { 109132, 0, 23959, 824985 }, + { 109142, 0, 23961, 825073 }, + { 109146, 0, 23962, 825168 }, + { 109153, 0, 23964, 825259 }, + { 109162, 0, 23966, 825348 }, + { 109168, 0, 23969, 825439 }, + { 109176, 0, 23971, 825529 }, + { 109185, 0, 23974, 825617 }, + { 109193, 0, 23977, 825706 }, + { 109198, 0, 23978, 825800 }, + { 109206, 0, 23978, 825892 }, + { 109212, 0, 23981, 825983 }, + { 109219, 0, 23981, 826076 }, + { 109225, 0, 23981, 826170 }, + { 109232, 0, 23984, 826260 }, + { 109242, 0, 23984, 826350 }, + { 109255, 0, 23986, 826435 }, + { 109268, 0, 23987, 826521 }, + { 109283, 0, 23990, 826603 }, + { 109288, 0, 23991, 826697 }, + { 109295, 0, 23993, 826788 }, + { 109308, 0, 23994, 826874 }, + { 109322, 0, 24009, 826945 }, + { 109328, 0, 24011, 827037 }, + { 109338, 0, 24012, 827126 }, + { 109347, 0, 24012, 827217 }, + { 109354, 0, 24017, 827305 }, + { 109367, 0, 24017, 827392 }, + { 109371, 0, 24019, 827486 }, + }; + static int counter = 0; + + for ( int i = 0; i < NValues; i++ ) + values[i] = dummyValues[counter][i]; + + counter = ( counter + 1 ) + % ( sizeof( dummyValues ) / sizeof( dummyValues[0] ) ); + } + else + { + QTextStream textStream( &file ); + do + { + QString line = textStream.readLine(); + line = line.trimmed(); + if ( line.startsWith( "cpu " ) ) + { + const QStringList valueList = + line.split( " ", QString::SkipEmptyParts ); + if ( valueList.count() >= 5 ) + { + for ( int i = 0; i < NValues; i++ ) + values[i] = valueList[i+1].toDouble(); + } + break; + } + } + while( !textStream.atEnd() ); + } +} diff --git a/qwt/examples/cpuplot/cpustat.h b/qwt/examples/cpuplot/cpustat.h new file mode 100644 index 000000000..019bea855 --- /dev/null +++ b/qwt/examples/cpuplot/cpustat.h @@ -0,0 +1,23 @@ +#include + +class CpuStat +{ +public: + CpuStat(); + void statistic( double &user, double &system ); + QTime upTime() const; + + enum Value + { + User, + Nice, + System, + Idle, + + NValues + }; + +private: + void lookUp( double[NValues] ) const; + double procValues[NValues]; +}; diff --git a/qwt/examples/curvdemo1/curvdemo1.cpp b/qwt/examples/curvdemo1/curvdemo1.cpp new file mode 100644 index 000000000..e813dc198 --- /dev/null +++ b/qwt/examples/curvdemo1/curvdemo1.cpp @@ -0,0 +1,202 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + +//------------------------------------------------------------ +// curvdemo1 +// +// This example program features some of the different +// display styles of the QwtPlotCurve class +//------------------------------------------------------------ + + +// +// Array Sizes +// +const int Size = 27; +const int CurvCnt = 6; + +// +// Arrays holding the values +// +double xval[Size]; +double yval[Size]; +QwtScaleMap xMap; +QwtScaleMap yMap; + +class MainWin : public QFrame +{ +public: + MainWin(); + +protected: + virtual void paintEvent( QPaintEvent * ); + void drawContents( QPainter *p ); + +private: + void shiftDown( QRect &rect, int offset ) const; + + QwtPlotCurve d_curves[CurvCnt]; +}; + +MainWin::MainWin() +{ + int i; + + xMap.setScaleInterval( -0.5, 10.5 ); + yMap.setScaleInterval( -1.1, 1.1 ); + + // + // Frame style + // + setFrameStyle( QFrame::Box | QFrame::Raised ); + setLineWidth( 2 ); + setMidLineWidth( 3 ); + + // + // Calculate values + // + for( i = 0; i < Size; i++ ) + { + xval[i] = double( i ) * 10.0 / double( Size - 1 ); + yval[i] = qSin( xval[i] ) * qCos( 2.0 * xval[i] ); + } + + // + // define curve styles + // + i = 0; + + d_curves[i].setSymbol( new QwtSymbol( QwtSymbol::Cross, Qt::NoBrush, + QPen( Qt::black ), QSize( 5, 5 ) ) ); + d_curves[i].setPen( Qt::darkGreen ); + d_curves[i].setStyle( QwtPlotCurve::Lines ); + d_curves[i].setCurveAttribute( QwtPlotCurve::Fitted ); + i++; + + d_curves[i].setSymbol( new QwtSymbol( QwtSymbol::Ellipse, Qt::yellow, + QPen( Qt::blue ), QSize( 5, 5 ) ) ); + d_curves[i].setPen( Qt::red ); + d_curves[i].setStyle( QwtPlotCurve::Sticks ); + i++; + + d_curves[i].setPen( Qt::darkBlue ); + d_curves[i].setStyle( QwtPlotCurve::Lines ); + i++; + + d_curves[i].setPen( Qt::darkBlue ); + d_curves[i].setStyle( QwtPlotCurve::Lines ); + d_curves[i].setRenderHint( QwtPlotItem::RenderAntialiased ); + i++; + + d_curves[i].setPen( Qt::darkCyan ); + d_curves[i].setStyle( QwtPlotCurve::Steps ); + i++; + + d_curves[i].setSymbol( new QwtSymbol( QwtSymbol::XCross, Qt::NoBrush, + QPen( Qt::darkMagenta ), QSize( 5, 5 ) ) ); + d_curves[i].setStyle( QwtPlotCurve::NoCurve ); + i++; + + + // + // attach data + // + for( i = 0; i < CurvCnt; i++ ) + d_curves[i].setRawSamples( xval, yval, Size ); +} + +void MainWin::shiftDown( QRect &rect, int offset ) const +{ + rect.translate( 0, offset ); +} + +void MainWin::paintEvent( QPaintEvent *event ) +{ + QFrame::paintEvent( event ); + + QPainter painter( this ); + painter.setClipRect( contentsRect() ); + drawContents( &painter ); +} + + +// +// REDRAW CONTENTS +// +void MainWin::drawContents( QPainter *painter ) +{ + int deltay, i; + + QRect r = contentsRect(); + + deltay = r.height() / CurvCnt - 1; + + r.setHeight( deltay ); + + // + // draw curves + // + for ( i = 0; i < CurvCnt; i++ ) + { + xMap.setPaintInterval( r.left(), r.right() ); + yMap.setPaintInterval( r.top(), r.bottom() ); + + painter->setRenderHint( QPainter::Antialiasing, + d_curves[i].testRenderHint( QwtPlotItem::RenderAntialiased ) ); + d_curves[i].draw( painter, xMap, yMap, r ); + + shiftDown( r, deltay ); + } + + // + // draw titles + // + r = contentsRect(); // reset r + painter->setFont( QFont( "Helvetica", 8 ) ); + + const int alignment = Qt::AlignTop | Qt::AlignHCenter; + + painter->setPen( Qt::black ); + + painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: Line/Fitted, Symbol: Cross" ); + shiftDown( r, deltay ); + + painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: Sticks, Symbol: Ellipse" ); + shiftDown( r, deltay ); + + painter->drawText( 0 , r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: Lines, Symbol: None" ); + shiftDown( r, deltay ); + + painter->drawText( 0 , r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: Lines, Symbol: None, Antialiased" ); + shiftDown( r, deltay ); + + painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: Steps, Symbol: None" ); + shiftDown( r, deltay ); + + painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: NoCurve, Symbol: XCross" ); +} + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWin w; + + w.resize( 300, 600 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/curvdemo1/curvdemo1.pro b/qwt/examples/curvdemo1/curvdemo1.pro new file mode 100644 index 000000000..298c97744 --- /dev/null +++ b/qwt/examples/curvdemo1/curvdemo1.pro @@ -0,0 +1,15 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = curvdemo1 + +SOURCES = \ + curvdemo1.cpp diff --git a/qwt/examples/dials/attitude_indicator.cpp b/qwt/examples/dials/attitude_indicator.cpp new file mode 100644 index 000000000..a5810be67 --- /dev/null +++ b/qwt/examples/dials/attitude_indicator.cpp @@ -0,0 +1,127 @@ +#include "attitude_indicator.h" +#include +#include +#include +#include +#include + +AttitudeIndicatorNeedle::AttitudeIndicatorNeedle( const QColor &color ) +{ + QPalette palette; + palette.setColor( QPalette::Text, color ); + setPalette( palette ); +} + +void AttitudeIndicatorNeedle::drawNeedle( QPainter *painter, + double length, QPalette::ColorGroup colorGroup ) const +{ + double triangleSize = length * 0.1; + double pos = length - 2.0; + + QPainterPath path; + path.moveTo( pos, 0 ); + path.lineTo( pos - 2 * triangleSize, triangleSize ); + path.lineTo( pos - 2 * triangleSize, -triangleSize ); + path.closeSubpath(); + + painter->setBrush( palette().brush( colorGroup, QPalette::Text ) ); + painter->drawPath( path ); + + double l = length - 2; + painter->setPen( QPen( palette().color( colorGroup, QPalette::Text ), 3 ) ); + painter->drawLine( QPointF( 0.0, -l ), QPointF( 0.0, l ) ); +} + +AttitudeIndicator::AttitudeIndicator( + QWidget *parent ): + QwtDial( parent ), + d_gradient( 0.0 ) +{ + QwtRoundScaleDraw *scaleDraw = new QwtRoundScaleDraw(); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Labels, false ); + setScaleDraw( scaleDraw ); + + setMode( RotateScale ); + setWrapping( true ); + + setOrigin( 270.0 ); + + setScaleMaxMinor( 0 ); + setScaleStepSize( 30.0 ); + setScale( 0.0, 360.0 ); + + const QColor color = palette().color( QPalette::Text ); + setNeedle( new AttitudeIndicatorNeedle( color ) ); +} + +void AttitudeIndicator::setGradient( double gradient ) +{ + if ( gradient < -1.0 ) + gradient = -1.0; + else if ( gradient > 1.0 ) + gradient = 1.0; + + if ( d_gradient != gradient ) + { + d_gradient = gradient; + update(); + } +} + +void AttitudeIndicator::drawScale( QPainter *painter, + const QPointF ¢er, double radius ) const +{ + const double offset = 4.0; + + const QPointF p0 = qwtPolar2Pos( center, offset, 1.5 * M_PI ); + + const double w = innerRect().width(); + + QPainterPath path; + path.moveTo( qwtPolar2Pos( p0, w, 0.0 ) ); + path.lineTo( qwtPolar2Pos( path.currentPosition(), 2 * w, M_PI ) ); + path.lineTo( qwtPolar2Pos( path.currentPosition(), w, 0.5 * M_PI ) ); + path.lineTo( qwtPolar2Pos( path.currentPosition(), w, 0.0 ) ); + + painter->save(); + painter->setClipPath( path ); // swallow 180 - 360 degrees + + QwtDial::drawScale( painter, center, radius ); + + painter->restore(); +} + +void AttitudeIndicator::drawScaleContents( QPainter *painter, + const QPointF &, double ) const +{ + int dir = 360 - qRound( origin() - value() ); // counter clockwise + int arc = 90 + qRound( gradient() * 90 ); + + const QColor skyColor( 38, 151, 221 ); + + painter->save(); + painter->setBrush( skyColor ); + painter->drawChord( scaleInnerRect(), + ( dir - arc ) * 16, 2 * arc * 16 ); + painter->restore(); +} + +void AttitudeIndicator::keyPressEvent( QKeyEvent *event ) +{ + switch( event->key() ) + { + case Qt::Key_Plus: + { + setGradient( gradient() + 0.05 ); + break; + } + case Qt::Key_Minus: + { + setGradient( gradient() - 0.05 ); + break; + } + default: + QwtDial::keyPressEvent( event ); + } +} diff --git a/qwt/examples/dials/attitude_indicator.h b/qwt/examples/dials/attitude_indicator.h new file mode 100644 index 000000000..d4eb0c833 --- /dev/null +++ b/qwt/examples/dials/attitude_indicator.h @@ -0,0 +1,39 @@ +#include +#include + +class AttitudeIndicatorNeedle: public QwtDialNeedle +{ +public: + AttitudeIndicatorNeedle( const QColor & ); + +protected: + virtual void drawNeedle( QPainter *, + double length, QPalette::ColorGroup ) const; +}; + +class AttitudeIndicator: public QwtDial +{ + Q_OBJECT + +public: + AttitudeIndicator( QWidget *parent = NULL ); + + double angle() const { return value(); } + double gradient() const { return d_gradient; } + +public Q_SLOTS: + void setGradient( double ); + void setAngle( double angle ) { setValue( angle ); } + +protected: + virtual void keyPressEvent( QKeyEvent * ); + + virtual void drawScale( QPainter *, + const QPointF ¢er, double radius ) const; + + virtual void drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const; + +private: + double d_gradient; +}; diff --git a/qwt/examples/dials/cockpit_grid.cpp b/qwt/examples/dials/cockpit_grid.cpp new file mode 100644 index 000000000..f298fd452 --- /dev/null +++ b/qwt/examples/dials/cockpit_grid.cpp @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include "attitude_indicator.h" +#include "speedo_meter.h" +#include "cockpit_grid.h" + +CockpitGrid::CockpitGrid( QWidget *parent ): + QFrame( parent ) +{ + setAutoFillBackground( true ); + + setPalette( colorTheme( QColor( Qt::darkGray ).dark( 150 ) ) ); + + QGridLayout *layout = new QGridLayout( this ); + layout->setSpacing( 5 ); + layout->setMargin( 0 ); + + int i; + for ( i = 0; i < 3; i++ ) + { + QwtDial *dial = createDial( i ); + layout->addWidget( dial, 0, i ); + } + + for ( i = 0; i < layout->columnCount(); i++ ) + layout->setColumnStretch( i, 1 ); +} + +QwtDial *CockpitGrid::createDial( int pos ) +{ + QwtDial *dial = NULL; + switch( pos ) + { + case 0: + { + d_clock = new QwtAnalogClock( this ); +#if 0 + // disable minor ticks + d_clock->scaleDraw()->setTickLength( QwtScaleDiv::MinorTick, 0 ); +#endif + + const QColor knobColor = QColor( Qt::gray ).light( 130 ); + + for ( int i = 0; i < QwtAnalogClock::NHands; i++ ) + { + QColor handColor = QColor( Qt::gray ).light( 150 ); + int width = 8; + + if ( i == QwtAnalogClock::SecondHand ) + { + handColor = Qt::gray; + width = 5; + } + + QwtDialSimpleNeedle *hand = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, handColor, knobColor ); + hand->setWidth( width ); + + d_clock->setHand( static_cast( i ), hand ); + } + + QTimer *timer = new QTimer( d_clock ); + timer->connect( timer, SIGNAL( timeout() ), + d_clock, SLOT( setCurrentTime() ) ); + timer->start( 1000 ); + + dial = d_clock; + break; + } + case 1: + { + d_speedo = new SpeedoMeter( this ); + d_speedo->setScaleStepSize( 20.0 ); + d_speedo->setScale( 0.0, 240.0 ); + d_speedo->scaleDraw()->setPenWidth( 2 ); + + QTimer *timer = new QTimer( d_speedo ); + timer->connect( timer, SIGNAL( timeout() ), + this, SLOT( changeSpeed() ) ); + timer->start( 50 ); + + dial = d_speedo; + break; + } + case 2: + { + d_ai = new AttitudeIndicator( this ); + d_ai->scaleDraw()->setPenWidth( 3 ); + + QTimer *gradientTimer = new QTimer( d_ai ); + gradientTimer->connect( gradientTimer, SIGNAL( timeout() ), + this, SLOT( changeGradient() ) ); + gradientTimer->start( 100 ); + + QTimer *angleTimer = new QTimer( d_ai ); + angleTimer->connect( angleTimer, SIGNAL( timeout() ), + this, SLOT( changeAngle() ) ); + angleTimer->start( 100 ); + + dial = d_ai; + break; + } + + } + + if ( dial ) + { + dial->setReadOnly( true ); + dial->setLineWidth( 4 ); + dial->setFrameShadow( QwtDial::Sunken ); + } + return dial; +} + +QPalette CockpitGrid::colorTheme( const QColor &base ) const +{ + QPalette palette; + palette.setColor( QPalette::Base, base ); + palette.setColor( QPalette::Window, base.dark( 150 ) ); + palette.setColor( QPalette::Mid, base.dark( 110 ) ); + palette.setColor( QPalette::Light, base.light( 170 ) ); + palette.setColor( QPalette::Dark, base.dark( 170 ) ); + palette.setColor( QPalette::Text, base.dark( 200 ).light( 800 ) ); + palette.setColor( QPalette::WindowText, base.dark( 200 ) ); + + return palette; +} + +void CockpitGrid::changeSpeed() +{ + static double offset = 0.8; + + double speed = d_speedo->value(); + + if ( ( speed < 7.0 && offset < 0.0 ) || + ( speed > 203.0 && offset > 0.0 ) ) + { + offset = -offset; + } + + static int counter = 0; + switch( counter++ % 12 ) + { + case 0: + case 2: + case 7: + case 8: + break; + default: + d_speedo->setValue( speed + offset ); + } +} + +void CockpitGrid::changeAngle() +{ + static double offset = 0.05; + + double angle = d_ai->angle(); + if ( angle > 180.0 ) + angle -= 360.0; + + if ( ( angle < -5.0 && offset < 0.0 ) || + ( angle > 5.0 && offset > 0.0 ) ) + { + offset = -offset; + } + + d_ai->setAngle( angle + offset ); +} + +void CockpitGrid::changeGradient() +{ + static double offset = 0.005; + + double gradient = d_ai->gradient(); + + if ( ( gradient < -0.05 && offset < 0.0 ) || + ( gradient > 0.05 && offset > 0.0 ) ) + { + offset = -offset; + } + + d_ai->setGradient( gradient + offset ); +} diff --git a/qwt/examples/dials/cockpit_grid.h b/qwt/examples/dials/cockpit_grid.h new file mode 100644 index 000000000..38139ee51 --- /dev/null +++ b/qwt/examples/dials/cockpit_grid.h @@ -0,0 +1,28 @@ +#include +#include + +class QwtDial; +class QwtAnalogClock; +class SpeedoMeter; +class AttitudeIndicator; + +class CockpitGrid: public QFrame +{ + Q_OBJECT + +public: + CockpitGrid( QWidget *parent = NULL ); + +private Q_SLOTS: + void changeSpeed(); + void changeGradient(); + void changeAngle(); + +private: + QPalette colorTheme( const QColor & ) const; + QwtDial *createDial( int pos ); + + QwtAnalogClock *d_clock; + SpeedoMeter *d_speedo; + AttitudeIndicator *d_ai; +}; diff --git a/qwt/examples/dials/compass_grid.cpp b/qwt/examples/dials/compass_grid.cpp new file mode 100644 index 000000000..02f66ed84 --- /dev/null +++ b/qwt/examples/dials/compass_grid.cpp @@ -0,0 +1,226 @@ +#include +#include +#include +#include +#include "compass_grid.h" + +CompassGrid::CompassGrid( QWidget *parent ): + QFrame( parent ) +{ + QPalette p = palette(); + p.setColor( backgroundRole(), Qt::gray ); + setPalette( p ); + + setAutoFillBackground( true ); + + QGridLayout *layout = new QGridLayout( this ); + layout->setSpacing( 5 ); + layout->setMargin( 0 ); + + int i; + for ( i = 0; i < 6; i++ ) + { + QwtCompass *compass = createCompass( i ); + layout->addWidget( compass, i / 3, i % 3 ); + } + + for ( i = 0; i < layout->columnCount(); i++ ) + layout->setColumnStretch( i, 1 ); +} + +QwtCompass *CompassGrid::createCompass( int pos ) +{ + int c; + + QPalette palette0; + for ( c = 0; c < QPalette::NColorRoles; c++ ) + { + const QPalette::ColorRole colorRole = + static_cast( c ); + + palette0.setColor( colorRole, QColor() ); + } + + palette0.setColor( QPalette::Base, + palette().color( backgroundRole() ).light( 120 ) ); + palette0.setColor( QPalette::WindowText, + palette0.color( QPalette::Base ) ); + + QwtCompass *compass = new QwtCompass( this ); + compass->setLineWidth( 4 ); + compass->setFrameShadow( + pos <= 2 ? QwtCompass::Sunken : QwtCompass::Raised ); + + switch( pos ) + { + case 0: + { + /* + A compass with a rose and no needle. Scale and rose are + rotating. + */ + compass->setMode( QwtCompass::RotateScale ); + + QwtSimpleCompassRose *rose = new QwtSimpleCompassRose( 16, 2 ); + rose->setWidth( 0.15 ); + + compass->setRose( rose ); + break; + } + case 1: + { + /* + A windrose, with a scale indicating the main directions only + */ + QMap map; + map.insert( 0.0, "N" ); + map.insert( 90.0, "E" ); + map.insert( 180.0, "S" ); + map.insert( 270.0, "W" ); + + compass->setScaleDraw( new QwtCompassScaleDraw( map ) ); + + QwtSimpleCompassRose *rose = new QwtSimpleCompassRose( 4, 1 ); + compass->setRose( rose ); + + compass->setNeedle( + new QwtCompassWindArrow( QwtCompassWindArrow::Style2 ) ); + compass->setValue( 60.0 ); + break; + } + case 2: + { + /* + A compass with a rotating needle in darkBlue. Shows + a ticks for each degree. + */ + + palette0.setColor( QPalette::Base, Qt::darkBlue ); + palette0.setColor( QPalette::WindowText, + QColor( Qt::darkBlue ).dark( 120 ) ); + palette0.setColor( QPalette::Text, Qt::white ); + + QwtCompassScaleDraw *scaleDraw = new QwtCompassScaleDraw(); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Ticks, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Labels, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 1 ); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 1 ); + scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 3 ); + + compass->setScaleDraw( scaleDraw ); + + compass->setScaleMaxMajor( 36 ); + compass->setScaleMaxMinor( 5 ); + + compass->setNeedle( + new QwtCompassMagnetNeedle( QwtCompassMagnetNeedle::ThinStyle ) ); + compass->setValue( 220.0 ); + + break; + } + case 3: + { + /* + A compass without a frame, showing numbers as tick labels. + The origin is at 220.0 + */ + palette0.setColor( QPalette::Base, + palette().color( backgroundRole() ) ); + palette0.setColor( QPalette::WindowText, Qt::blue ); + + compass->setLineWidth( 0 ); + + QMap map; + for ( double d = 0.0; d < 360.0; d += 60.0 ) + { + QString label; + label.sprintf( "%.0f", d ); + map.insert( d, label ); + } + + QwtCompassScaleDraw *scaleDraw = + new QwtCompassScaleDraw( map ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Ticks, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Labels, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, true ); + scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 0 ); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 0 ); + scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 3 ); + + compass->setScaleDraw( scaleDraw ); + + compass->setScaleMaxMajor( 36 ); + compass->setScaleMaxMinor( 5 ); + + compass->setNeedle( new QwtDialSimpleNeedle( QwtDialSimpleNeedle::Ray, + true, Qt::white ) ); + compass->setOrigin( 220.0 ); + compass->setValue( 20.0 ); + break; + } + case 4: + { + /* + A compass showing another needle + */ + QwtCompassScaleDraw *scaleDraw = new QwtCompassScaleDraw(); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Ticks, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Labels, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 0 ); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 0 ); + scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 3 ); + + compass->setScaleDraw( scaleDraw ); + + compass->setNeedle( new QwtCompassMagnetNeedle( + QwtCompassMagnetNeedle::TriangleStyle, Qt::white, Qt::red ) ); + compass->setValue( 220.0 ); + break; + } + case 5: + { + /* + A compass with a yellow on black ray + */ + palette0.setColor( QPalette::WindowText, Qt::black ); + + compass->setNeedle( new QwtDialSimpleNeedle( QwtDialSimpleNeedle::Ray, + false, Qt::yellow ) ); + compass->setValue( 315.0 ); + break; + } + } + + QPalette newPalette = compass->palette(); + for ( c = 0; c < QPalette::NColorRoles; c++ ) + { + const QPalette::ColorRole colorRole = + static_cast( c ); + + if ( palette0.color( colorRole ).isValid() ) + newPalette.setColor( colorRole, palette0.color( colorRole ) ); + } + + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + const QPalette::ColorGroup colorGroup = + static_cast( i ); + + const QColor light = + newPalette.color( colorGroup, QPalette::Base ).light( 170 ); + const QColor dark = newPalette.color( colorGroup, QPalette::Base ).dark( 170 ); + const QColor mid = compass->frameShadow() == QwtDial::Raised + ? newPalette.color( colorGroup, QPalette::Base ).dark( 110 ) + : newPalette.color( colorGroup, QPalette::Base ).light( 110 ); + + newPalette.setColor( colorGroup, QPalette::Dark, dark ); + newPalette.setColor( colorGroup, QPalette::Mid, mid ); + newPalette.setColor( colorGroup, QPalette::Light, light ); + } + + compass->setPalette( newPalette ); + + return compass; +} diff --git a/qwt/examples/dials/compass_grid.h b/qwt/examples/dials/compass_grid.h new file mode 100644 index 000000000..386927009 --- /dev/null +++ b/qwt/examples/dials/compass_grid.h @@ -0,0 +1,11 @@ +#include +class QwtCompass; + +class CompassGrid: public QFrame +{ +public: + CompassGrid( QWidget *parent = NULL ); + +private: + QwtCompass *createCompass( int pos ); +}; diff --git a/qwt/examples/dials/dials.cpp b/qwt/examples/dials/dials.cpp new file mode 100644 index 000000000..e34a5828d --- /dev/null +++ b/qwt/examples/dials/dials.cpp @@ -0,0 +1,24 @@ +#include +#include +#include "compass_grid.h" +#include "cockpit_grid.h" + +//----------------------------------------------------------------- +// +// dials.cpp -- A demo program featuring QwtDial and friends +// +//----------------------------------------------------------------- + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QTabWidget tabWidget; + tabWidget.addTab( new CompassGrid, "Compass" ); + tabWidget.addTab( new CockpitGrid, "Cockpit" ); + + tabWidget.show(); + + return a.exec(); +} + diff --git a/qwt/examples/dials/dials.pro b/qwt/examples/dials/dials.pro new file mode 100644 index 000000000..11db8664e --- /dev/null +++ b/qwt/examples/dials/dials.pro @@ -0,0 +1,26 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = dials + +HEADERS = \ + attitude_indicator.h \ + speedo_meter.h \ + cockpit_grid.h \ + compass_grid.h + +SOURCES = \ + attitude_indicator.cpp \ + speedo_meter.cpp \ + cockpit_grid.cpp \ + compass_grid.cpp \ + dials.cpp + diff --git a/qwt/examples/dials/speedo_meter.cpp b/qwt/examples/dials/speedo_meter.cpp new file mode 100644 index 000000000..3205d4cc8 --- /dev/null +++ b/qwt/examples/dials/speedo_meter.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include "speedo_meter.h" + +SpeedoMeter::SpeedoMeter( QWidget *parent ): + QwtDial( parent ), + d_label( "km/h" ) +{ + QwtRoundScaleDraw *scaleDraw = new QwtRoundScaleDraw(); + scaleDraw->setSpacing( 8 ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 0 ); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 4 ); + scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 8 ); + setScaleDraw( scaleDraw ); + + setWrapping( false ); + setReadOnly( true ); + + setOrigin( 135.0 ); + setScaleArc( 0.0, 270.0 ); + + QwtDialSimpleNeedle *needle = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, Qt::red, + QColor( Qt::gray ).light( 130 ) ); + setNeedle( needle ); +} + +void SpeedoMeter::setLabel( const QString &label ) +{ + d_label = label; + update(); +} + +QString SpeedoMeter::label() const +{ + return d_label; +} + +void SpeedoMeter::drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const +{ + QRectF rect( 0.0, 0.0, 2.0 * radius, 2.0 * radius - 10.0 ); + rect.moveCenter( center ); + + const QColor color = palette().color( QPalette::Text ); + painter->setPen( color ); + + const int flags = Qt::AlignBottom | Qt::AlignHCenter; + painter->drawText( rect, flags, d_label ); +} diff --git a/qwt/examples/dials/speedo_meter.h b/qwt/examples/dials/speedo_meter.h new file mode 100644 index 000000000..3f49e28cb --- /dev/null +++ b/qwt/examples/dials/speedo_meter.h @@ -0,0 +1,18 @@ +#include +#include + +class SpeedoMeter: public QwtDial +{ +public: + SpeedoMeter( QWidget *parent = NULL ); + + void setLabel( const QString & ); + QString label() const; + +protected: + virtual void drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const; + +private: + QString d_label; +}; diff --git a/qwt/examples/distrowatch/barchart.cpp b/qwt/examples/distrowatch/barchart.cpp new file mode 100644 index 000000000..c8a3c4fcb --- /dev/null +++ b/qwt/examples/distrowatch/barchart.cpp @@ -0,0 +1,192 @@ +#include "barchart.h" +#include +#include +#include +#include +#include +#include +#include + +class DistroScaleDraw: public QwtScaleDraw +{ +public: + DistroScaleDraw( Qt::Orientation orientation, const QStringList &labels ): + d_labels( labels ) + { + setTickLength( QwtScaleDiv::MinorTick, 0 ); + setTickLength( QwtScaleDiv::MediumTick, 0 ); + setTickLength( QwtScaleDiv::MajorTick, 2 ); + + enableComponent( QwtScaleDraw::Backbone, false ); + + if ( orientation == Qt::Vertical ) + { + setLabelRotation( -60.0 ); + } + else + { + setLabelRotation( -20.0 ); + } + + setLabelAlignment( Qt::AlignLeft | Qt::AlignVCenter ); + } + + virtual QwtText label( double value ) const + { + QwtText lbl; + + const int index = qRound( value ); + if ( index >= 0 && index <= d_labels.size() ) + { + lbl = d_labels[ index ]; + } + + return lbl; + } + +private: + const QStringList d_labels; +}; + +class DistroChartItem: public QwtPlotBarChart +{ +public: + DistroChartItem(): + QwtPlotBarChart( "Page Hits" ) + { + setLegendMode( QwtPlotBarChart::LegendBarTitles ); + setLegendIconSize( QSize( 10, 14 ) ); + } + + void addDistro( const QString &distro, const QColor &color ) + { + d_colors += color; + d_distros += distro; + itemChanged(); + } + + virtual QwtColumnSymbol *specialSymbol( + int index, const QPointF& ) const + { + // we want to have individual colors for each bar + + QwtColumnSymbol *symbol = new QwtColumnSymbol( QwtColumnSymbol::Box ); + symbol->setLineWidth( 2 ); + symbol->setFrameStyle( QwtColumnSymbol::Raised ); + + QColor c( Qt::white ); + if ( index >= 0 && index < d_colors.size() ) + c = d_colors[ index ]; + + symbol->setPalette( c ); + + return symbol; + } + + virtual QwtText barTitle( int sampleIndex ) const + { + QwtText title; + if ( sampleIndex >= 0 && sampleIndex < d_distros.size() ) + title = d_distros[ sampleIndex ]; + + return title; + } + +private: + QList d_colors; + QList d_distros; +}; + +BarChart::BarChart( QWidget *parent ): + QwtPlot( parent ) +{ + const struct + { + const char *distro; + const int hits; + QColor color; + + } pageHits[] = + { + { "Arch", 1114, QColor( "DodgerBlue" ) }, + { "Debian", 1373, QColor( "#d70751" ) }, + { "Fedora", 1638, QColor( "SteelBlue" ) }, + { "Mageia", 1395, QColor( "Indigo" ) }, + { "Mint", 3874, QColor( 183, 255, 183 ) }, + { "openSuSE", 1532, QColor( 115, 186, 37 ) }, + { "Puppy", 1059, QColor( "LightSkyBlue" ) }, + { "Ubuntu", 2391, QColor( "FireBrick" ) } + }; + + setAutoFillBackground( true ); + setPalette( QColor( "Linen" ) ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setLineWidth( 2 ); + canvas->setFrameStyle( QFrame::Box | QFrame::Sunken ); + canvas->setBorderRadius( 10 ); + + QPalette canvasPalette( QColor( "Plum" ) ); + canvasPalette.setColor( QPalette::Foreground, QColor( "Indigo" ) ); + canvas->setPalette( canvasPalette ); + + setCanvas( canvas ); + + setTitle( "DistroWatch Page Hit Ranking, April 2012" ); + + d_barChartItem = new DistroChartItem(); + + QVector< double > samples; + + for ( uint i = 0; i < sizeof( pageHits ) / sizeof( pageHits[ 0 ] ); i++ ) + { + d_distros += pageHits[ i ].distro; + samples += pageHits[ i ].hits; + + d_barChartItem->addDistro( + pageHits[ i ].distro, pageHits[ i ].color ); + } + + d_barChartItem->setSamples( samples ); + + d_barChartItem->attach( this ); + + insertLegend( new QwtLegend() ); + + setOrientation( 0 ); + setAutoReplot( false ); +} + +void BarChart::setOrientation( int o ) +{ + const Qt::Orientation orientation = + ( o == 0 ) ? Qt::Vertical : Qt::Horizontal; + + int axis1 = QwtAxis::xBottom; + int axis2 = QwtAxis::yLeft; + + if ( orientation == Qt::Horizontal ) + qSwap( axis1, axis2 ); + + d_barChartItem->setOrientation( orientation ); + + setAxisTitle( axis1, "Distros" ); + setAxisMaxMinor( axis1, 3 ); + setAxisScaleDraw( axis1, new DistroScaleDraw( orientation, d_distros ) ); + + setAxisTitle( axis2, "Hits per day ( HPD )" ); + setAxisMaxMinor( axis2, 3 ); + + QwtScaleDraw *scaleDraw = new QwtScaleDraw(); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 4 ); + setAxisScaleDraw( axis2, scaleDraw ); + + plotLayout()->setCanvasMargin( 0 ); + replot(); +} + +void BarChart::exportChart() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "distrowatch.pdf" ); +} diff --git a/qwt/examples/distrowatch/barchart.h b/qwt/examples/distrowatch/barchart.h new file mode 100644 index 000000000..33afa4028 --- /dev/null +++ b/qwt/examples/distrowatch/barchart.h @@ -0,0 +1,26 @@ +#ifndef _BAR_CHART_H_ + +#include +#include + +class DistroChartItem; + +class BarChart: public QwtPlot +{ + Q_OBJECT + +public: + BarChart( QWidget * = NULL ); + +public Q_SLOTS: + void setOrientation( int ); + void exportChart(); + +private: + void populate(); + + DistroChartItem *d_barChartItem; + QStringList d_distros; +}; + +#endif diff --git a/qwt/examples/distrowatch/distrowatch.pro b/qwt/examples/distrowatch/distrowatch.pro new file mode 100644 index 000000000..f24102387 --- /dev/null +++ b/qwt/examples/distrowatch/distrowatch.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = distrowatch + +HEADERS = \ + barchart.h + +SOURCES = \ + barchart.cpp \ + main.cpp diff --git a/qwt/examples/distrowatch/main.cpp b/qwt/examples/distrowatch/main.cpp new file mode 100644 index 000000000..88d4e4809 --- /dev/null +++ b/qwt/examples/distrowatch/main.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include "barchart.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + BarChart *d_chart; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_chart = new BarChart( this ); + setCentralWidget( d_chart ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *orientationBox = new QComboBox( toolBar ); + orientationBox->addItem( "Vertical" ); + orientationBox->addItem( "Horizontal" ); + orientationBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), d_chart, SLOT( exportChart() ) ); + + toolBar->addWidget( orientationBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + + d_chart->setOrientation( orientationBox->currentIndex() ); + connect( orientationBox, SIGNAL( currentIndexChanged( int ) ), + d_chart, SLOT( setOrientation( int ) ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/event_filter/README b/qwt/examples/event_filter/README new file mode 100644 index 000000000..05fc24205 --- /dev/null +++ b/qwt/examples/event_filter/README @@ -0,0 +1,27 @@ +QwtPlot is a composite widget consisting of a title label, +the canvas, the scales and a legend. Although all components +should be exchangable some day, the current design isn´t ready for it. + +In this situation event filtering is the mechanism to extend the behaviour +of the plot components. event_filter shows 3 examples how to use it: + +1) CanvasPicker + +The CanvasPicker implements a solution, how to move points on the canvas +with mouse and keyboard. + +2) ScalePicker + +The ScalePicker translates the position of mouse clicks on the scales +and emits them as signals. + +3) Plot: ColorBar, QSlider + +The Plot class shows how to add widgets to the scales. In this example +there is no filter class. The derived plot widget filters its components. + + +Please note that CanvasPicker and ScalePicker are standalone classes +that could be connected with your QwtPlot as well. + +Uwe diff --git a/qwt/examples/event_filter/canvaspicker.cpp b/qwt/examples/event_filter/canvaspicker.cpp new file mode 100644 index 000000000..f8bb9fcca --- /dev/null +++ b/qwt/examples/event_filter/canvaspicker.cpp @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "canvaspicker.h" + +CanvasPicker::CanvasPicker( QwtPlot *plot ): + QObject( plot ), + d_selectedCurve( NULL ), + d_selectedPoint( -1 ) +{ + QwtPlotCanvas *canvas = qobject_cast( plot->canvas() ); + canvas->installEventFilter( this ); + + // We want the focus, but no focus rect. The + // selected point will be highlighted instead. + + canvas->setFocusPolicy( Qt::StrongFocus ); +#ifndef QT_NO_CURSOR + canvas->setCursor( Qt::PointingHandCursor ); +#endif + canvas->setFocusIndicator( QwtPlotCanvas::ItemFocusIndicator ); + canvas->setFocus(); + + const char *text = + "All points can be moved using the left mouse button " + "or with these keys:\n\n" + "- Up:\t\tSelect next curve\n" + "- Down:\t\tSelect previous curve\n" + "- Left, ´-´:\tSelect next point\n" + "- Right, ´+´:\tSelect previous point\n" + "- 7, 8, 9, 4, 6, 1, 2, 3:\tMove selected point"; + canvas->setWhatsThis( text ); + + shiftCurveCursor( true ); +} + +QwtPlot *CanvasPicker::plot() +{ + return qobject_cast( parent() ); +} + +const QwtPlot *CanvasPicker::plot() const +{ + return qobject_cast( parent() ); +} + +bool CanvasPicker::event( QEvent *ev ) +{ + if ( ev->type() == QEvent::User ) + { + showCursor( true ); + return true; + } + return QObject::event( ev ); +} + +bool CanvasPicker::eventFilter( QObject *object, QEvent *event ) +{ + if ( plot() == NULL || object != plot()->canvas() ) + return false; + + switch( event->type() ) + { + case QEvent::FocusIn: + { + showCursor( true ); + break; + } + case QEvent::FocusOut: + { + showCursor( false ); + break; + } + case QEvent::Paint: + { + QApplication::postEvent( this, new QEvent( QEvent::User ) ); + break; + } + case QEvent::MouseButtonPress: + { + const QMouseEvent *mouseEvent = static_cast( event ); + select( mouseEvent->pos() ); + return true; + } + case QEvent::MouseMove: + { + const QMouseEvent *mouseEvent = static_cast( event ); + move( mouseEvent->pos() ); + return true; + } + case QEvent::KeyPress: + { + const QKeyEvent *keyEvent = static_cast( event ); + + const int delta = 5; + switch( keyEvent->key() ) + { + case Qt::Key_Up: + { + shiftCurveCursor( true ); + return true; + } + case Qt::Key_Down: + { + shiftCurveCursor( false ); + return true; + } + case Qt::Key_Right: + case Qt::Key_Plus: + { + if ( d_selectedCurve ) + shiftPointCursor( true ); + else + shiftCurveCursor( true ); + return true; + } + case Qt::Key_Left: + case Qt::Key_Minus: + { + if ( d_selectedCurve ) + shiftPointCursor( false ); + else + shiftCurveCursor( true ); + return true; + } + + // The following keys represent a direction, they are + // organized on the keyboard. + + case Qt::Key_1: + { + moveBy( -delta, delta ); + break; + } + case Qt::Key_2: + { + moveBy( 0, delta ); + break; + } + case Qt::Key_3: + { + moveBy( delta, delta ); + break; + } + case Qt::Key_4: + { + moveBy( -delta, 0 ); + break; + } + case Qt::Key_6: + { + moveBy( delta, 0 ); + break; + } + case Qt::Key_7: + { + moveBy( -delta, -delta ); + break; + } + case Qt::Key_8: + { + moveBy( 0, -delta ); + break; + } + case Qt::Key_9: + { + moveBy( delta, -delta ); + break; + } + default: + break; + } + } + default: + break; + } + + return QObject::eventFilter( object, event ); +} + +// Select the point at a position. If there is no point +// deselect the selected point + +void CanvasPicker::select( const QPoint &pos ) +{ + QwtPlotCurve *curve = NULL; + double dist = 10e10; + int index = -1; + + const QwtPlotItemList& itmList = plot()->itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + if ( ( *it )->rtti() == QwtPlotItem::Rtti_PlotCurve ) + { + QwtPlotCurve *c = static_cast( *it ); + + double d; + int idx = c->closestPoint( pos, &d ); + if ( d < dist ) + { + curve = c; + index = idx; + dist = d; + } + } + } + + showCursor( false ); + d_selectedCurve = NULL; + d_selectedPoint = -1; + + if ( curve && dist < 10 ) // 10 pixels tolerance + { + d_selectedCurve = curve; + d_selectedPoint = index; + showCursor( true ); + } +} + +// Move the selected point +void CanvasPicker::moveBy( int dx, int dy ) +{ + if ( dx == 0 && dy == 0 ) + return; + + if ( !d_selectedCurve ) + return; + + const QPointF sample = + d_selectedCurve->sample( d_selectedPoint ); + + const double x = plot()->transform( + d_selectedCurve->xAxis(), sample.x() ); + const double y = plot()->transform( + d_selectedCurve->yAxis(), sample.y() ); + + move( QPoint( qRound( x + dx ), qRound( y + dy ) ) ); +} + +// Move the selected point +void CanvasPicker::move( const QPoint &pos ) +{ + if ( !d_selectedCurve ) + return; + + QVector xData( d_selectedCurve->dataSize() ); + QVector yData( d_selectedCurve->dataSize() ); + + for ( int i = 0; + i < static_cast( d_selectedCurve->dataSize() ); i++ ) + { + if ( i == d_selectedPoint ) + { + xData[i] = plot()->invTransform( + d_selectedCurve->xAxis(), pos.x() ); + yData[i] = plot()->invTransform( + d_selectedCurve->yAxis(), pos.y() ); + } + else + { + const QPointF sample = d_selectedCurve->sample( i ); + xData[i] = sample.x(); + yData[i] = sample.y(); + } + } + d_selectedCurve->setSamples( xData, yData ); + + /* + Enable QwtPlotCanvas::ImmediatePaint, so that the canvas has been + updated before we paint the cursor on it. + */ + QwtPlotCanvas *plotCanvas = + qobject_cast( plot()->canvas() ); + + plotCanvas->setPaintAttribute( QwtPlotCanvas::ImmediatePaint, true ); + plot()->replot(); + plotCanvas->setPaintAttribute( QwtPlotCanvas::ImmediatePaint, false ); + + showCursor( true ); +} + +// Hightlight the selected point +void CanvasPicker::showCursor( bool showIt ) +{ + if ( !d_selectedCurve ) + return; + + QwtSymbol *symbol = const_cast( d_selectedCurve->symbol() ); + + const QBrush brush = symbol->brush(); + if ( showIt ) + symbol->setBrush( symbol->brush().color().dark( 180 ) ); + + QwtPlotDirectPainter directPainter; + directPainter.drawSeries( d_selectedCurve, d_selectedPoint, d_selectedPoint ); + + if ( showIt ) + symbol->setBrush( brush ); // reset brush +} + +// Select the next/previous curve +void CanvasPicker::shiftCurveCursor( bool up ) +{ + QwtPlotItemIterator it; + + const QwtPlotItemList &itemList = plot()->itemList(); + + QwtPlotItemList curveList; + for ( it = itemList.begin(); it != itemList.end(); ++it ) + { + if ( ( *it )->rtti() == QwtPlotItem::Rtti_PlotCurve ) + curveList += *it; + } + if ( curveList.isEmpty() ) + return; + + it = curveList.begin(); + + if ( d_selectedCurve ) + { + for ( it = curveList.begin(); it != curveList.end(); ++it ) + { + if ( d_selectedCurve == *it ) + break; + } + if ( it == curveList.end() ) // not found + it = curveList.begin(); + + if ( up ) + { + ++it; + if ( it == curveList.end() ) + it = curveList.begin(); + } + else + { + if ( it == curveList.begin() ) + it = curveList.end(); + --it; + } + } + + showCursor( false ); + d_selectedPoint = 0; + d_selectedCurve = static_cast( *it ); + showCursor( true ); +} + +// Select the next/previous neighbour of the selected point +void CanvasPicker::shiftPointCursor( bool up ) +{ + if ( !d_selectedCurve ) + return; + + int index = d_selectedPoint + ( up ? 1 : -1 ); + index = ( index + d_selectedCurve->dataSize() ) % d_selectedCurve->dataSize(); + + if ( index != d_selectedPoint ) + { + showCursor( false ); + d_selectedPoint = index; + showCursor( true ); + } +} diff --git a/qwt/examples/event_filter/canvaspicker.h b/qwt/examples/event_filter/canvaspicker.h new file mode 100644 index 000000000..c5b0f2f05 --- /dev/null +++ b/qwt/examples/event_filter/canvaspicker.h @@ -0,0 +1,33 @@ +#include + +class QPoint; +class QCustomEvent; +class QwtPlot; +class QwtPlotCurve; + +class CanvasPicker: public QObject +{ + Q_OBJECT +public: + CanvasPicker( QwtPlot *plot ); + virtual bool eventFilter( QObject *, QEvent * ); + + virtual bool event( QEvent * ); + +private: + void select( const QPoint & ); + void move( const QPoint & ); + void moveBy( int dx, int dy ); + + void release(); + + void showCursor( bool enable ); + void shiftPointCursor( bool up ); + void shiftCurveCursor( bool up ); + + QwtPlot *plot(); + const QwtPlot *plot() const; + + QwtPlotCurve *d_selectedCurve; + int d_selectedPoint; +}; diff --git a/qwt/examples/event_filter/colorbar.cpp b/qwt/examples/event_filter/colorbar.cpp new file mode 100644 index 000000000..113377928 --- /dev/null +++ b/qwt/examples/event_filter/colorbar.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include "colorbar.h" + +ColorBar::ColorBar( Qt::Orientation o, QWidget *parent ): + QWidget( parent ), + d_orientation( o ), + d_light( Qt::white ), + d_dark( Qt::black ) +{ +#ifndef QT_NO_CURSOR + setCursor( Qt::PointingHandCursor ); +#endif +} + +void ColorBar::setOrientation( Qt::Orientation o ) +{ + d_orientation = o; + update(); +} + +void ColorBar::setLight( const QColor &light ) +{ + d_light = light; + update(); +} + +void ColorBar::setDark( const QColor &dark ) +{ + d_dark = dark; + update(); +} + +void ColorBar::setRange( const QColor &light, const QColor &dark ) +{ + d_light = light; + d_dark = dark; + update(); +} + +void ColorBar::mousePressEvent( QMouseEvent *e ) +{ + if( e->button() == Qt::LeftButton ) + { + // emit the color of the position where the mouse click + // happened + + const QPixmap pm = QPixmap::grabWidget( this ); + const QRgb rgb = pm.toImage().pixel( e->x(), e->y() ); + + Q_EMIT selected( QColor( rgb ) ); + e->accept(); + } +} + +void ColorBar::paintEvent( QPaintEvent * ) +{ + QPainter painter( this ); + drawColorBar( &painter, rect() ); +} + +void ColorBar::drawColorBar( QPainter *painter, const QRect &rect ) const +{ + int h1, s1, v1; + int h2, s2, v2; + + d_light.getHsv( &h1, &s1, &v1 ); + d_dark.getHsv( &h2, &s2, &v2 ); + + painter->save(); + painter->setClipRect( rect ); + painter->setClipping( true ); + + painter->fillRect( rect, d_dark ); + + const int sectionSize = 2; + + int numIntervals; + if ( d_orientation == Qt::Horizontal ) + numIntervals = rect.width() / sectionSize; + else + numIntervals = rect.height() / sectionSize; + + for ( int i = 0; i < numIntervals; i++ ) + { + QRect section; + if ( d_orientation == Qt::Horizontal ) + { + section.setRect( rect.x() + i * sectionSize, rect.y(), + sectionSize, rect.height() ); + } + else + { + section.setRect( rect.x(), rect.y() + i * sectionSize, + rect.width(), sectionSize ); + } + + const double ratio = i / static_cast( numIntervals ); + + QColor c; + c.setHsv( h1 + qRound( ratio * ( h2 - h1 ) ), + s1 + qRound( ratio * ( s2 - s1 ) ), + v1 + qRound( ratio * ( v2 - v1 ) ) ); + + painter->fillRect( section, c ); + } + + painter->restore(); +} + diff --git a/qwt/examples/event_filter/colorbar.h b/qwt/examples/event_filter/colorbar.h new file mode 100644 index 000000000..757581323 --- /dev/null +++ b/qwt/examples/event_filter/colorbar.h @@ -0,0 +1,33 @@ +#include + +class ColorBar: public QWidget +{ + Q_OBJECT + +public: + ColorBar( Qt::Orientation = Qt::Horizontal, QWidget * = NULL ); + + virtual void setOrientation( Qt::Orientation ); + Qt::Orientation orientation() const { return d_orientation; } + + void setRange( const QColor &light, const QColor &dark ); + void setLight( const QColor &light ); + void setDark( const QColor &dark ); + + QColor light() const { return d_light; } + QColor dark() const { return d_dark; } + +Q_SIGNALS: + void selected( const QColor & ); + +protected: + virtual void mousePressEvent( QMouseEvent * ); + virtual void paintEvent( QPaintEvent * ); + + void drawColorBar( QPainter *, const QRect & ) const; + +private: + Qt::Orientation d_orientation; + QColor d_light; + QColor d_dark; +}; diff --git a/qwt/examples/event_filter/event_filter.cpp b/qwt/examples/event_filter/event_filter.cpp new file mode 100644 index 000000000..efa8ba3cd --- /dev/null +++ b/qwt/examples/event_filter/event_filter.cpp @@ -0,0 +1,51 @@ +//----------------------------------------------------------------- +// A demo program showing how to use event filtering +//----------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include "plot.h" +#include "canvaspicker.h" +#include "scalepicker.h" + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QMainWindow mainWindow; + QToolBar *toolBar = new QToolBar( &mainWindow ); + QAction *action = QWhatsThis::createAction( toolBar ); + toolBar->addAction( action ); + mainWindow.addToolBar( toolBar ); + + Plot *plot = new Plot( &mainWindow ); + + // The canvas picker handles all mouse and key + // events on the plot canvas + + ( void ) new CanvasPicker( plot ); + + // The scale picker translates mouse clicks + // int o clicked() signals + + ScalePicker *scalePicker = new ScalePicker( plot ); + a.connect( scalePicker, SIGNAL( clicked( int, double ) ), + plot, SLOT( insertCurve( int, double ) ) ); + + mainWindow.setCentralWidget( plot ); + + mainWindow.resize( 540, 400 ); + mainWindow.show(); + + const char *text = + "An useless plot to demonstrate how to use event filtering.\n\n" + "You can click on the color bar, the scales or move the wheel.\n" + "All points can be moved using the mouse or the keyboard."; + plot->setWhatsThis( text ); + + int rv = a.exec(); + return rv; +} diff --git a/qwt/examples/event_filter/event_filter.pro b/qwt/examples/event_filter/event_filter.pro new file mode 100644 index 000000000..c50daa895 --- /dev/null +++ b/qwt/examples/event_filter/event_filter.pro @@ -0,0 +1,25 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = event_filter + +HEADERS = \ + colorbar.h \ + scalepicker.h \ + canvaspicker.h \ + plot.h + +SOURCES = \ + colorbar.cpp \ + scalepicker.cpp \ + canvaspicker.cpp \ + plot.cpp \ + event_filter.cpp diff --git a/qwt/examples/event_filter/plot.cpp b/qwt/examples/event_filter/plot.cpp new file mode 100644 index 000000000..bfc9ece96 --- /dev/null +++ b/qwt/examples/event_filter/plot.cpp @@ -0,0 +1,173 @@ +#include "plot.h" +#include "colorbar.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setTitle( "Interactive Plot" ); + + setCanvasColor( Qt::darkCyan ); + + QwtPlotGrid *grid = new QwtPlotGrid; + grid->setMajorPen( Qt::white, 0, Qt::DotLine ); + grid->attach( this ); + + // axes + + setAxisScale( QwtAxis::xBottom, 0.0, 100.0 ); + setAxisScale( QwtAxis::yLeft, 0.0, 100.0 ); + + // Avoid jumping when label with 3 digits + // appear/disappear when scrolling vertically + + QwtScaleDraw *sd = axisScaleDraw( QwtAxis::yLeft ); + sd->setMinimumExtent( sd->extent( axisWidget( QwtAxis::yLeft )->font() ) ); + + plotLayout()->setAlignCanvasToScales( true ); + + insertCurve( Qt::Vertical, Qt::blue, 30.0 ); + insertCurve( Qt::Vertical, Qt::magenta, 70.0 ); + insertCurve( Qt::Horizontal, Qt::yellow, 30.0 ); + insertCurve( Qt::Horizontal, Qt::white, 70.0 ); + + replot(); + + // ------------------------------------ + // We add a color bar to the left axis + // ------------------------------------ + + QwtScaleWidget *scaleWidget = axisWidget( QwtAxis::yLeft ); + scaleWidget->setMargin( 10 ); // area for the color bar + d_colorBar = new ColorBar( Qt::Vertical, scaleWidget ); + d_colorBar->setRange( Qt::red, Qt::darkBlue ); + d_colorBar->setFocusPolicy( Qt::TabFocus ); + + connect( d_colorBar, SIGNAL( selected( const QColor & ) ), + SLOT( setCanvasColor( const QColor & ) ) ); + + // we need the resize events, to lay out the color bar + scaleWidget->installEventFilter( this ); + + // ------------------------------------ + // We add a wheel to the canvas + // ------------------------------------ + + d_wheel = new QwtWheel( canvas() ); + d_wheel->setOrientation( Qt::Vertical ); + d_wheel->setRange( -100, 100 ); + d_wheel->setValue( 0.0 ); + d_wheel->setMass( 0.2 ); + d_wheel->setTotalAngle( 4 * 360.0 ); + + connect( d_wheel, SIGNAL( valueChanged( double ) ), + SLOT( scrollLeftAxis( double ) ) ); + + // we need the resize events, to lay out the wheel + canvas()->installEventFilter( this ); + + d_colorBar->setWhatsThis( + "Selecting a color will change the background of the plot." ); + scaleWidget->setWhatsThis( + "Selecting a value at the scale will insert a new curve." ); + d_wheel->setWhatsThis( + "With the wheel you can move the visible area." ); + axisWidget( QwtAxis::xBottom )->setWhatsThis( + "Selecting a value at the scale will insert a new curve." ); +} + +void Plot::setCanvasColor( const QColor &c ) +{ + setCanvasBackground( c ); + replot(); +} + +void Plot::scrollLeftAxis( double value ) +{ + setAxisScale( QwtAxis::yLeft, value, value + 100.0 ); + replot(); +} + +bool Plot::eventFilter( QObject *object, QEvent *e ) +{ + if ( e->type() == QEvent::Resize ) + { + const QSize size = static_cast( e )->size(); + if ( object == axisWidget( QwtAxis::yLeft ) ) + { + const QwtScaleWidget *scaleWidget = axisWidget( QwtAxis::yLeft ); + + const int margin = 2; + + // adjust the color bar to the scale backbone + const int x = size.width() - scaleWidget->margin() + margin; + const int w = scaleWidget->margin() - 2 * margin; + const int y = scaleWidget->startBorderDist(); + const int h = size.height() - + scaleWidget->startBorderDist() - scaleWidget->endBorderDist(); + + d_colorBar->setGeometry( x, y, w, h ); + } + if ( object == canvas() ) + { + const int w = 16; + const int h = 50; + const int margin = 2; + + const QRect cr = canvas()->contentsRect(); + d_wheel->setGeometry( + cr.right() - margin - w, cr.center().y() - h / 2, w, h ); + } + } + + return QwtPlot::eventFilter( object, e ); +} + +void Plot::insertCurve( int axis, double base ) +{ + const Qt::Orientation o = + QwtAxis::isYAxis( axis ) ? Qt::Horizontal : Qt::Vertical; + + QRgb rgb = static_cast( rand() ); + insertCurve( o, QColor( rgb ), base ); + replot(); +} + +void Plot::insertCurve( Qt::Orientation o, + const QColor &c, double base ) +{ + QwtPlotCurve *curve = new QwtPlotCurve(); + + curve->setPen( c ); + curve->setSymbol( new QwtSymbol( QwtSymbol::Ellipse, + Qt::gray, c, QSize( 8, 8 ) ) ); + + double x[10]; + double y[sizeof( x ) / sizeof( x[0] )]; + + for ( uint i = 0; i < sizeof( x ) / sizeof( x[0] ); i++ ) + { + double v = 5.0 + i * 10.0; + if ( o == Qt::Horizontal ) + { + x[i] = v; + y[i] = base; + } + else + { + x[i] = base; + y[i] = v; + } + } + + curve->setSamples( x, y, sizeof( x ) / sizeof( x[0] ) ); + curve->attach( this ); +} diff --git a/qwt/examples/event_filter/plot.h b/qwt/examples/event_filter/plot.h new file mode 100644 index 000000000..8f0798c65 --- /dev/null +++ b/qwt/examples/event_filter/plot.h @@ -0,0 +1,25 @@ +#include + +class ColorBar; +class QwtWheel; + +class Plot: public QwtPlot +{ + Q_OBJECT +public: + Plot( QWidget *parent = NULL ); + virtual bool eventFilter( QObject *, QEvent * ); + +public Q_SLOTS: + void setCanvasColor( const QColor & ); + void insertCurve( int axis, double base ); + +private Q_SLOTS: + void scrollLeftAxis( double ); + +private: + void insertCurve( Qt::Orientation, const QColor &, double base ); + + ColorBar *d_colorBar; + QwtWheel *d_wheel; +}; diff --git a/qwt/examples/event_filter/scalepicker.cpp b/qwt/examples/event_filter/scalepicker.cpp new file mode 100644 index 000000000..ee273a3f6 --- /dev/null +++ b/qwt/examples/event_filter/scalepicker.cpp @@ -0,0 +1,119 @@ +#include "scalepicker.h" +#include +#include +#include +#include + +ScalePicker::ScalePicker( QwtPlot *plot ): + QObject( plot ) +{ + for ( uint i = 0; i < QwtAxis::PosCount; i++ ) + { + QwtScaleWidget *scaleWidget = plot->axisWidget( i ); + if ( scaleWidget ) + scaleWidget->installEventFilter( this ); + } +} + +bool ScalePicker::eventFilter( QObject *object, QEvent *event ) +{ + if ( event->type() == QEvent::MouseButtonPress ) + { + QwtScaleWidget *scaleWidget = qobject_cast( object ); + if ( scaleWidget ) + { + QMouseEvent *mouseEvent = static_cast( event ); + mouseClicked( scaleWidget, mouseEvent->pos() ); + + return true; + } + } + + return QObject::eventFilter( object, event ); +} + +void ScalePicker::mouseClicked( const QwtScaleWidget *scale, const QPoint &pos ) +{ + QRect rect = scaleRect( scale ); + + int margin = 10; // 10 pixels tolerance + rect.setRect( rect.x() - margin, rect.y() - margin, + rect.width() + 2 * margin, rect.height() + 2 * margin ); + + if ( rect.contains( pos ) ) // No click on the title + { + // translate the position in a value on the scale + + double value = 0.0; + int axis = -1; + + const QwtScaleDraw *sd = scale->scaleDraw(); + switch( scale->alignment() ) + { + case QwtScaleDraw::LeftScale: + { + value = sd->scaleMap().invTransform( pos.y() ); + axis = QwtAxis::yLeft; + break; + } + case QwtScaleDraw::RightScale: + { + value = sd->scaleMap().invTransform( pos.y() ); + axis = QwtAxis::yRight; + break; + } + case QwtScaleDraw::BottomScale: + { + value = sd->scaleMap().invTransform( pos.x() ); + axis = QwtAxis::xBottom; + break; + } + case QwtScaleDraw::TopScale: + { + value = sd->scaleMap().invTransform( pos.x() ); + axis = QwtAxis::xTop; + break; + } + } + Q_EMIT clicked( axis, value ); + } +} + +// The rect of a scale without the title +QRect ScalePicker::scaleRect( const QwtScaleWidget *scale ) const +{ + const int bld = scale->margin(); + const int mjt = qCeil( scale->scaleDraw()->maxTickLength() ); + const int sbd = scale->startBorderDist(); + const int ebd = scale->endBorderDist(); + + QRect rect; + switch( scale->alignment() ) + { + case QwtScaleDraw::LeftScale: + { + rect.setRect( scale->width() - bld - mjt, sbd, + mjt, scale->height() - sbd - ebd ); + break; + } + case QwtScaleDraw::RightScale: + { + rect.setRect( bld, sbd, + mjt, scale->height() - sbd - ebd ); + break; + } + case QwtScaleDraw::BottomScale: + { + rect.setRect( sbd, bld, + scale->width() - sbd - ebd, mjt ); + break; + } + case QwtScaleDraw::TopScale: + { + rect.setRect( sbd, scale->height() - bld - mjt, + scale->width() - sbd - ebd, mjt ); + break; + } + } + return rect; +} diff --git a/qwt/examples/event_filter/scalepicker.h b/qwt/examples/event_filter/scalepicker.h new file mode 100644 index 000000000..bcdc12d9e --- /dev/null +++ b/qwt/examples/event_filter/scalepicker.h @@ -0,0 +1,20 @@ +#include +#include + +class QwtPlot; +class QwtScaleWidget; + +class ScalePicker: public QObject +{ + Q_OBJECT +public: + ScalePicker( QwtPlot *plot ); + virtual bool eventFilter( QObject *, QEvent * ); + +Q_SIGNALS: + void clicked( int axis, double value ); + +private: + void mouseClicked( const QwtScaleWidget *, const QPoint & ); + QRect scaleRect( const QwtScaleWidget * ) const; +}; diff --git a/qwt/examples/examples.pri b/qwt/examples/examples.pri new file mode 100644 index 000000000..33c7be821 --- /dev/null +++ b/qwt/examples/examples.pri @@ -0,0 +1,77 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################### + +QWT_ROOT = $${PWD}/.. +include( $${QWT_ROOT}/qwtconfig.pri ) +include( $${QWT_ROOT}/qwtbuild.pri ) +include( $${QWT_ROOT}/qwtfunctions.pri ) + +TEMPLATE = app + +INCLUDEPATH += $${QWT_ROOT}/src +DEPENDPATH += $${QWT_ROOT}/src + +!debug_and_release { + + DESTDIR = $${QWT_ROOT}/examples/bin +} +else { + CONFIG(debug, debug|release) { + + DESTDIR = $${QWT_ROOT}/examples/bin_debug + } + else { + + DESTDIR = $${QWT_ROOT}/examples/bin + } +} + +QMAKE_RPATHDIR *= $${QWT_ROOT}/lib + +contains(QWT_CONFIG, QwtFramework) { + + LIBS += -F$${QWT_ROOT}/lib +} +else { + + LIBS += -L$${QWT_ROOT}/lib +} + +qwtAddLibrary(qwt) + +greaterThan(QT_MAJOR_VERSION, 4) { + + QT += printsupport + QT += concurrent +} + +contains(QWT_CONFIG, QwtOpenGL ) { + + QT += opengl +} +else { + + DEFINES += QWT_NO_OPENGL +} + +contains(QWT_CONFIG, QwtSvg) { + + QT += svg +} +else { + + DEFINES += QWT_NO_SVG +} + + +win32 { + contains(QWT_CONFIG, QwtDll) { + DEFINES += QT_DLL QWT_DLL + } +} diff --git a/qwt/examples/examples.pro b/qwt/examples/examples.pro new file mode 100644 index 000000000..1f0ea4d9d --- /dev/null +++ b/qwt/examples/examples.pro @@ -0,0 +1,51 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../qwtconfig.pri ) + +TEMPLATE = subdirs + +contains(QWT_CONFIG, QwtPlot) { + + SUBDIRS += \ + animation \ + barchart \ + cpuplot \ + curvdemo1 \ + distrowatch \ + friedberg \ + itemeditor \ + legends \ + stockchart \ + simpleplot \ + sinusplot \ + realtime \ + refreshtest \ + scatterplot \ + spectrogram \ + rasterview \ + tvplot + + contains(QWT_CONFIG, QwtWidgets) { + + SUBDIRS += \ + bode \ + event_filter \ + oscilloscope + } +} + +contains(QWT_CONFIG, QwtWidgets) { + + SUBDIRS += \ + sysinfo \ + radio \ + dials \ + controls +} diff --git a/qwt/examples/friedberg/friedberg.pro b/qwt/examples/friedberg/friedberg.pro new file mode 100644 index 000000000..3a15c5b6b --- /dev/null +++ b/qwt/examples/friedberg/friedberg.pro @@ -0,0 +1,21 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = friedberg + +HEADERS = \ + plot.h \ + friedberg2007.h + +SOURCES = \ + friedberg2007.cpp \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/friedberg/friedberg2007.cpp b/qwt/examples/friedberg/friedberg2007.cpp new file mode 100644 index 000000000..19f688e9e --- /dev/null +++ b/qwt/examples/friedberg/friedberg2007.cpp @@ -0,0 +1,384 @@ +#include "friedberg2007.h" + +// Temperature 2007 from Friedberg somewhere in Germany +// See: http://wetter61169.de + +Temperature friedberg2007[] = +{ + /* 01.01 */ Temperature( 2.6, 9.8, 7.07862 ), + /* 02.01 */ Temperature( 0.8, 5.8, 3.6993 ), + /* 03.01 */ Temperature( 2, 7, 5.02388 ), + /* 04.01 */ Temperature( 5.3, 7.8, 6.37778 ), + /* 05.01 */ Temperature( 5.6, 7.7, 6.83149 ), + /* 06.01 */ Temperature( 7.2, 8.9, 8.0816 ), + /* 07.01 */ Temperature( 4.2, 9.9, 7.54704 ), + /* 08.01 */ Temperature( 3.5, 8.9, 6.71951 ), + /* 09.01 */ Temperature( 8.2, 12.9, 10.8594 ), + /* 10.01 */ Temperature( 6.3, 11.9, 9.76424 ), + /* 11.01 */ Temperature( 3.9, 9.2, 6.18223 ), + /* 12.01 */ Temperature( 6.9, 9.7, 8.44236 ), + /* 13.01 */ Temperature( 9, 12.3, 10.6649 ), + /* 14.01 */ Temperature( 1.8, 10.8, 7.23438 ), + /* 15.01 */ Temperature( -2.8, 1.8, -0.518403 ), + /* 16.01 */ Temperature( -0.6, 4.5, 2.39479 ), + /* 17.01 */ Temperature( 4.3, 10.2, 7.23472 ), + /* 18.01 */ Temperature( 9.1, 13.6, 10.9316 ), + /* 19.01 */ Temperature( 6.9, 12.4, 9.4128 ), + /* 20.01 */ Temperature( 7.1, 13.3, 10.5083 ), + /* 21.01 */ Temperature( 3.5, 9.6, 6.10871 ), + /* 22.01 */ Temperature( -1.8, 6, 2.89028 ), + /* 23.01 */ Temperature( -5.4, 1.7, -2.46678 ), + /* 24.01 */ Temperature( -5.3, -1.3, -3.71483 ), + /* 25.01 */ Temperature( -7.5, 3.3, -3.36736 ), + /* 26.01 */ Temperature( -11.1, 0.3, -5.50662 ), + /* 27.01 */ Temperature( 0.2, 3.2, 1.95345 ), + /* 28.01 */ Temperature( 1.9, 5.2, 3.43633 ), + /* 29.01 */ Temperature( 4.4, 9.1, 6.24236 ), + /* 30.01 */ Temperature( 2.3, 11.5, 6.03114 ), + /* 31.01 */ Temperature( 4.6, 10.2, 6.04192 ), + + /* 01.02 */ Temperature( 4.8, 13.8, 7.87674 ), + /* 02.02 */ Temperature( 5.7, 10, 7.28646 ), + /* 03.02 */ Temperature( 2.9, 8.2, 5.71771 ), + /* 04.02 */ Temperature( -1.5, 7.2, 4.71319 ), + /* 05.02 */ Temperature( -2.6, 4.4, 1.23542 ), + /* 06.02 */ Temperature( 0.3, 9.2, 2.59965 ), + /* 07.02 */ Temperature( -0.4, 2.4, 0.641667 ), + /* 08.02 */ Temperature( -1.7, 3.8, 0.811458 ), + /* 09.02 */ Temperature( 0.7, 7, 3.58328 ), + /* 10.02 */ Temperature( 1, 6, 3.51181 ), + /* 11.02 */ Temperature( 4.7, 9.6, 6.14913 ), + /* 12.02 */ Temperature( 5.3, 8.7, 6.80552 ), + /* 13.02 */ Temperature( 4.4, 10.3, 6.84552 ), + /* 14.02 */ Temperature( 2.6, 6.5, 4.58681 ), + /* 15.02 */ Temperature( -0.8, 13.4, 6.38542 ), + /* 16.02 */ Temperature( -3, 14.4, 4.11336 ), + /* 17.02 */ Temperature( 0.5, 13, 5.87457 ), + /* 18.02 */ Temperature( -2.2, 14.1, 4.36528 ), + /* 19.02 */ Temperature( 3.9, 5.6, 4.63737 ), + /* 20.02 */ Temperature( -0.4, 9.2, 4.37014 ), + /* 21.02 */ Temperature( -1.9, 5.5, 1.85675 ), + /* 22.02 */ Temperature( 1, 13.1, 5.41176 ), + /* 23.02 */ Temperature( 1.9, 13.9, 7.74251 ), + /* 24.02 */ Temperature( 3.8, 9.6, 7.19306 ), + /* 25.02 */ Temperature( 5.8, 10.8, 7.80312 ), + /* 26.02 */ Temperature( 5.2, 10.4, 6.79481 ), + /* 27.02 */ Temperature( 3.2, 7.4, 5.22986 ), + /* 28.02 */ Temperature( 6.4, 13.4, 9.13356 ), + + /* 01.03 */ Temperature( 4.6, 11.4, 7.70554 ), + /* 02.03 */ Temperature( 3.4, 10.9, 5.98408 ), + /* 03.03 */ Temperature( 2.9, 10.5, 5.45675 ), + /* 04.03 */ Temperature( -0.7, 16.8, 7.29585 ), + /* 05.03 */ Temperature( 4.2, 13.4, 8.35862 ), + /* 06.03 */ Temperature( 3, 13, 7.76644 ), + /* 07.03 */ Temperature( 2, 13.3, 8.24618 ), + /* 08.03 */ Temperature( -0.8, 15, 6.11765 ), + /* 09.03 */ Temperature( -0.7, 11, 5.7568 ), + /* 10.03 */ Temperature( 1.2, 14.4, 6.61389 ), + /* 11.03 */ Temperature( -1.7, 18, 6.66146 ), + /* 12.03 */ Temperature( -0.6, 21.9, 8.9816 ), + /* 13.03 */ Temperature( -0.9, 19.6, 9.08299 ), + /* 14.03 */ Temperature( 5.3, 18.9, 10.5562 ), + /* 15.03 */ Temperature( 2, 20.5, 9.65156 ), + /* 16.03 */ Temperature( 0.2, 16.7, 7.8699 ), + /* 17.03 */ Temperature( 4.5, 10.6, 7.87535 ), + /* 18.03 */ Temperature( 2.7, 9.7, 6.71806 ), + /* 19.03 */ Temperature( 0.4, 10.9, 3.92404 ), + /* 20.03 */ Temperature( -2, 12.7, 4.01359 ), + /* 21.03 */ Temperature( 0.3, 6.8, 3.00382 ), + /* 22.03 */ Temperature( 0.9, 4.2, 2.2816 ), + /* 23.03 */ Temperature( 2, 5.7, 3.39233 ), + /* 24.03 */ Temperature( 3.9, 9.3, 6.41076 ), + /* 25.03 */ Temperature( 4.2, 19.1, 9.92182 ), + /* 26.03 */ Temperature( 2.3, 22, 12.5716 ), + /* 27.03 */ Temperature( 4.9, 20.6, 13.4568 ), + /* 28.03 */ Temperature( 0.3, 22.8, 10.755 ), + /* 29.03 */ Temperature( 1.8, 17.2, 9.43924 ), + /* 30.03 */ Temperature( 1.9, 19.8, 10.25 ), + /* 31.03 */ Temperature( 6.7, 17, 11.1324 ), + + /* 01.04 */ Temperature( 5.7, 22, 12.8457 ), + /* 02.04 */ Temperature( 6.4, 22.1, 13.3847 ), + /* 03.04 */ Temperature( 5.8, 17.5, 10.5614 ), + /* 04.04 */ Temperature( 2.8, 16.2, 8.06574 ), + /* 05.04 */ Temperature( -0.6, 20.8, 9.18062 ), + /* 06.04 */ Temperature( 2.1, 24, 13.0069 ), + /* 07.04 */ Temperature( 5.3, 16.2, 10.2771 ), + /* 08.04 */ Temperature( 0.1, 20.7, 9.79861 ), + /* 09.04 */ Temperature( 0.3, 18.9, 10.0087 ), + /* 10.04 */ Temperature( 4, 16.4, 11.4208 ), + /* 11.04 */ Temperature( 2.3, 23.4, 13.083 ), + /* 12.04 */ Temperature( 7, 29.4, 16.5826 ), + /* 13.04 */ Temperature( 10.6, 31.5, 19.2249 ), + /* 14.04 */ Temperature( 11.8, 34, 21.441 ), + /* 15.04 */ Temperature( 11.6, 33.8, 21.0201 ), + /* 16.04 */ Temperature( 8.7, 31.1, 18.7885 ), + /* 17.04 */ Temperature( 5.5, 27.2, 16.1432 ), + /* 18.04 */ Temperature( 6.1, 17.2, 10.6688 ), + /* 19.04 */ Temperature( -0.6, 21.3, 10.4806 ), + /* 20.04 */ Temperature( 5.9, 21.6, 12.6257 ), + /* 21.04 */ Temperature( 2.1, 21.6, 11.0858 ), + /* 22.04 */ Temperature( 3.9, 25.9, 14.2108 ), + /* 23.04 */ Temperature( 3.1, 27.8, 15.7111 ), + /* 24.04 */ Temperature( 13.7, 29, 19.6397 ), + /* 25.04 */ Temperature( 9.8, 31.6, 19.601 ), + /* 26.04 */ Temperature( 8.2, 32.4, 20.0389 ), + /* 27.04 */ Temperature( 11.8, 32.1, 21.0726 ), + /* 28.04 */ Temperature( 12.6, 33.3, 21.6993 ), + /* 29.04 */ Temperature( 10.5, 27.4, 19.1206 ), + /* 30.04 */ Temperature( 5.3, 26.4, 15.0972 ), + + /* 01.05 */ Temperature( 6.9, 25.3, 15.2802 ), + /* 02.05 */ Temperature( 4.3, 26.2, 14.8401 ), + /* 03.05 */ Temperature( 7.1, 28.5, 17.2145 ), + /* 04.05 */ Temperature( 11, 28.5, 18.537 ), + /* 05.05 */ Temperature( 12, 28, 18.1672 ), + /* 06.05 */ Temperature( 10.4, 29, 18.3844 ), + /* 07.05 */ Temperature( 13, 18.1, 15.0028 ), + /* 08.05 */ Temperature( 10.7, 18.3, 13.2014 ), + /* 09.05 */ Temperature( 10.8, 14.4, 12.5208 ), + /* 10.05 */ Temperature( 11.9, 23.5, 16.9632 ), + /* 11.05 */ Temperature( 9.8, 16.9, 15.0795 ), + /* 12.05 */ Temperature( 9.2, 19.6, 13.8521 ), + /* 13.05 */ Temperature( 8.9, 26.3, 16.2028 ), + /* 14.05 */ Temperature( 11.1, 17.5, 13.2934 ), + /* 15.05 */ Temperature( 6.5, 17, 11.7743 ), + /* 16.05 */ Temperature( 4.9, 13.6, 9.75625 ), + /* 17.05 */ Temperature( 6.8, 16.6, 9.96701 ), + /* 18.05 */ Temperature( 2.4, 21.2, 11.4311 ), + /* 19.05 */ Temperature( 8.2, 24.4, 15.4188 ), + /* 20.05 */ Temperature( 14.1, 31.7, 21.3303 ), + /* 21.05 */ Temperature( 11, 30.9, 21.5359 ), + /* 22.05 */ Temperature( 13.8, 31, 21.5177 ), + /* 23.05 */ Temperature( 16, 27.8, 21.0271 ), + /* 24.05 */ Temperature( 15, 34, 23.4142 ), + /* 25.05 */ Temperature( 14.3, 31.8, 22.8903 ), + /* 26.05 */ Temperature( 13.6, 33.1, 22.6156 ), + /* 27.05 */ Temperature( 11.2, 23.4, 16.6192 ), + /* 28.05 */ Temperature( 9.6, 13.1, 11.3222 ), + /* 29.05 */ Temperature( 8.3, 11.2, 10.3529 ), + /* 30.05 */ Temperature( 4.2, 20.8, 12.6218 ), + /* 31.05 */ Temperature( 9.2, 23.6, 15.1073 ), + + /* 01.06 */ Temperature( 10.8, 24.4, 16.3205 ), + /* 02.06 */ Temperature( 13, 26.5, 18.9649 ), + /* 03.06 */ Temperature( 14, 25.1, 18.5398 ), + /* 04.06 */ Temperature( 13, 28, 20.2139 ), + /* 05.06 */ Temperature( 14, 28.8, 20.438 ), + /* 06.06 */ Temperature( 14, 30.4, 21.7821 ), + /* 07.06 */ Temperature( 17, 34.8, 25.3087 ), + /* 08.06 */ Temperature( 17.9, 35.7, 25.7872 ), + /* 09.06 */ Temperature( 17.8, 31.6, 22.0788 ), + /* 10.06 */ Temperature( 15.5, 33.4, 22.4458 ), + /* 11.06 */ Temperature( 16.6, 28.3, 19.8797 ), + /* 12.06 */ Temperature( 14, 27.3, 20.2566 ), + /* 13.06 */ Temperature( 13.2, 28.2, 19.4233 ), + /* 14.06 */ Temperature( 12.7, 30, 20.1427 ), + /* 15.06 */ Temperature( 15.2, 22.6, 18.5917 ), + /* 16.06 */ Temperature( 13.2, 24, 17.7014 ), + /* 17.06 */ Temperature( 11.7, 27.9, 19.8229 ), + /* 18.06 */ Temperature( 15.9, 27.2, 20.3358 ), + /* 19.06 */ Temperature( 12.6, 33.7, 22.2427 ), + /* 20.06 */ Temperature( 15.7, 30.8, 23.7507 ), + /* 21.06 */ Temperature( 14.8, 22.6, 18.2538 ), + /* 22.06 */ Temperature( 12.4, 21.3, 15.9969 ), + /* 23.06 */ Temperature( 12.6, 21.6, 15.8149 ), + /* 24.06 */ Temperature( 13, 26, 18.4176 ), + /* 25.06 */ Temperature( 12.9, 24.4, 17.1299 ), + /* 26.06 */ Temperature( 10.8, 18.8, 13.2913 ), + /* 27.06 */ Temperature( 9.9, 18.8, 13.5465 ), + /* 28.06 */ Temperature( 12, 19.8, 14.8434 ), + /* 29.06 */ Temperature( 12, 19, 15.155 ), + /* 30.06 */ Temperature( 12.4, 22.4, 17.1354 ), + + /* 01.07 */ Temperature( 12.1, 24.9, 19.1639 ), + /* 02.07 */ Temperature( 15.7, 24.3, 18.4554 ), + /* 03.07 */ Temperature( 12.7, 17.2, 14.6564 ), + /* 04.07 */ Temperature( 11.2, 19, 13.9529 ), + /* 05.07 */ Temperature( 11.5, 19, 14.6422 ), + /* 06.07 */ Temperature( 12.4, 22, 16.6146 ), + /* 07.07 */ Temperature( 11.6, 24, 17.666 ), + /* 08.07 */ Temperature( 9, 28, 19.1351 ), + /* 09.07 */ Temperature( 11.3, 21.5, 16.5271 ), + /* 10.07 */ Temperature( 11.3, 20.2, 14.2326 ), + /* 11.07 */ Temperature( 10.2, 19.2, 14.0649 ), + /* 12.07 */ Temperature( 13.2, 23.1, 16.6346 ), + /* 13.07 */ Temperature( 15, 27, 19.6844 ), + /* 14.07 */ Temperature( 13.4, 32.4, 23.845 ), + /* 15.07 */ Temperature( 15, 38.2, 26.8559 ), + /* 16.07 */ Temperature( 16.1, 36.5, 26.4483 ), + /* 17.07 */ Temperature( 19.7, 30.5, 24.189 ), + /* 18.07 */ Temperature( 14.2, 29.3, 22.1363 ), + /* 19.07 */ Temperature( 16.4, 25.9, 19.0819 ), + /* 20.07 */ Temperature( 16.2, 30.8, 22.151 ), + /* 21.07 */ Temperature( 14, 24.3, 18.6573 ), + /* 22.07 */ Temperature( 13.2, 24.5, 18.3301 ), + /* 23.07 */ Temperature( 10.6, 23.4, 16.6903 ), + /* 24.07 */ Temperature( 13.2, 20.8, 16.2743 ), + /* 25.07 */ Temperature( 12.2, 25.8, 18.8267 ), + /* 26.07 */ Temperature( 11.9, 28.9, 20.5522 ), + /* 27.07 */ Temperature( 17.6, 25.8, 21.5691 ), + /* 28.07 */ Temperature( 16.6, 24.6, 19.2295 ), + /* 29.07 */ Temperature( 13, 19, 15.9021 ), + /* 30.07 */ Temperature( 9.6, 19.7, 13.875 ), + /* 31.07 */ Temperature( 8, 22, 14.5284 ), + + /* 01.08 */ Temperature( 7.6, 27.5, 17.5684 ), + /* 02.08 */ Temperature( 9.2, 22.2, 16.1035 ), + /* 03.08 */ Temperature( 12.7, 25.3, 18.2958 ), + /* 04.08 */ Temperature( 8.6, 31.3, 19.7941 ), + /* 05.08 */ Temperature( 10.3, 32.7, 21.492 ), + /* 06.08 */ Temperature( 10, 33.4, 22.4431 ), + /* 07.08 */ Temperature( 16.8, 22.6, 19.5583 ), + /* 08.08 */ Temperature( 13.5, 16.7, 15.0264 ), + /* 09.08 */ Temperature( 13.2, 18.8, 15.6003 ), + /* 10.08 */ Temperature( 14.6, 27.9, 18.8292 ), + /* 11.08 */ Temperature( 16.3, 26.4, 20.3837 ), + /* 12.08 */ Temperature( 12.1, 28.7, 19.9892 ), + /* 13.08 */ Temperature( 15, 27.4, 19.7542 ), + /* 14.08 */ Temperature( 11.3, 28.3, 20.5656 ), + /* 15.08 */ Temperature( 18.6, 28.4, 23.1215 ), + /* 16.08 */ Temperature( 16, 23.6, 19.491 ), + /* 17.08 */ Temperature( 12.6, 22, 17.0437 ), + /* 18.08 */ Temperature( 8.5, 25.7, 16.5589 ), + /* 19.08 */ Temperature( 13.4, 25.8, 18.0543 ), + /* 20.08 */ Temperature( 10.9, 21.5, 16.1306 ), + /* 21.08 */ Temperature( 10.6, 19.2, 14.6177 ), + /* 22.08 */ Temperature( 14, 24.6, 17.3841 ), + /* 23.08 */ Temperature( 13.8, 30.4, 20.6125 ), + /* 24.08 */ Temperature( 12.3, 30.3, 20.7622 ), + /* 25.08 */ Temperature( 12.8, 30.2, 21.6736 ), + /* 26.08 */ Temperature( 15, 29.3, 21.266 ), + /* 27.08 */ Temperature( 12.9, 25.9, 18.791 ), + /* 28.08 */ Temperature( 9.3, 24.6, 16.2833 ), + /* 29.08 */ Temperature( 10.8, 25, 16.8459 ), + /* 30.08 */ Temperature( 8.2, 24.4, 15.9267 ), + /* 31.08 */ Temperature( 14.1, 20.5, 16.6128 ), + + /* 01.09 */ Temperature( 13.4, 21.9, 16.2205 ), + /* 02.09 */ Temperature( 12, 20.7, 16.0882 ), + /* 03.09 */ Temperature( 10.8, 21.3, 14.7913 ), + /* 04.09 */ Temperature( 7.8, 18.2, 12.2747 ), + /* 05.09 */ Temperature( 8.1, 22.2, 12.9406 ), + /* 06.09 */ Temperature( 10, 23.8, 13.8785 ), + /* 07.09 */ Temperature( 10.7, 21.2, 15.4823 ), + /* 08.09 */ Temperature( 12.4, 21, 15.8194 ), + /* 09.09 */ Temperature( 12.7, 16.9, 14.7212 ), + /* 10.09 */ Temperature( 10.3, 17.7, 12.9271 ), + /* 11.09 */ Temperature( 10.6, 20.8, 14.4788 ), + /* 12.09 */ Temperature( 10.8, 21.9, 15.0184 ), + /* 13.09 */ Temperature( 6.9, 24.6, 14.5222 ), + /* 14.09 */ Temperature( 8.1, 24, 15.6583 ), + /* 15.09 */ Temperature( 8.8, 22.8, 15.941 ), + /* 16.09 */ Temperature( 3.1, 24.5, 14.1486 ), + /* 17.09 */ Temperature( 12.4, 21.2, 16.0497 ), + /* 18.09 */ Temperature( 7.8, 16.1, 12.024 ), + /* 19.09 */ Temperature( 5.3, 18.1, 10.3003 ), + /* 20.09 */ Temperature( 6.4, 20.3, 12.3177 ), + /* 21.09 */ Temperature( 6, 23.8, 13.6247 ), + /* 22.09 */ Temperature( 5.7, 27, 14.6847 ), + /* 23.09 */ Temperature( 7.8, 28, 16.6238 ), + /* 24.09 */ Temperature( 9.6, 24.9, 16.7191 ), + /* 25.09 */ Temperature( 8.4, 17.6, 12.636 ), + /* 26.09 */ Temperature( 4.3, 18.9, 10.0809 ), + /* 27.09 */ Temperature( 9.4, 11.2, 10.3344 ), + /* 28.09 */ Temperature( 7.7, 12.6, 10.5337 ), + /* 29.09 */ Temperature( 9.8, 15.3, 11.9306 ), + /* 30.09 */ Temperature( 9.6, 21.1, 13.6635 ), + + /* 01.10 */ Temperature( 8.9, 24.5, 14.8163 ), + /* 02.10 */ Temperature( 13.5, 20.2, 16.1628 ), + /* 03.10 */ Temperature( 12.5, 18, 15.4691 ), + /* 04.10 */ Temperature( 13.8, 25, 17.2073 ), + /* 05.10 */ Temperature( 9.1, 23.2, 14.6181 ), + /* 06.10 */ Temperature( 6.4, 23.4, 12.8625 ), + /* 07.10 */ Temperature( 4.6, 22.1, 11.0052 ), + /* 08.10 */ Temperature( 2, 22.2, 10.1677 ), + /* 09.10 */ Temperature( 7.8, 21.6, 12.2139 ), + /* 10.10 */ Temperature( 7.1, 22.7, 13.0115 ), + /* 11.10 */ Temperature( 6.1, 21.2, 11.4333 ), + /* 12.10 */ Temperature( 4.3, 15.2, 10.6104 ), + /* 13.10 */ Temperature( 5.8, 23, 12.8875 ), + /* 14.10 */ Temperature( 1, 23, 9.72986 ), + /* 15.10 */ Temperature( 1, 19.3, 9.33021 ), + /* 16.10 */ Temperature( 8.5, 20.4, 13.2639 ), + /* 17.10 */ Temperature( 6.8, 17.3, 11.8174 ), + /* 18.10 */ Temperature( 5.2, 15.6, 9.06076 ), + /* 19.10 */ Temperature( 2.7, 13.5, 7.1309 ), + /* 20.10 */ Temperature( -0.2, 15.8, 6.01667 ), + /* 21.10 */ Temperature( 2.6, 6.1, 4.9441 ), + /* 22.10 */ Temperature( -0.8, 13.2, 4.50694 ), + /* 23.10 */ Temperature( -0.4, 13.3, 4.71007 ), + /* 24.10 */ Temperature( 2.9, 8.1, 5.96979 ), + /* 25.10 */ Temperature( 6.3, 10.5, 8.01206 ), + /* 26.10 */ Temperature( 7, 10.8, 8.14965 ), + /* 27.10 */ Temperature( 6.6, 9.7, 7.7809 ), + /* 28.10 */ Temperature( 1.7, 10.8, 6.95728 ), + /* 29.10 */ Temperature( 2.2, 9.9, 6.62917 ), + /* 30.10 */ Temperature( 5.8, 15, 8.76181 ), + /* 31.10 */ Temperature( 0.7, 15, 6.01528 ), + + /* 01.11 */ Temperature( -0.2, 9.7, 3.75842 ), + /* 02.11 */ Temperature( 6.4, 9.6, 8.00138 ), + /* 03.11 */ Temperature( 8.7, 13.1, 10.5676 ), + /* 04.11 */ Temperature( 8, 11.8, 9.54306 ), + /* 05.11 */ Temperature( 5.8, 15.9, 8.52345 ), + /* 06.11 */ Temperature( 5.5, 10.8, 7.16493 ), + /* 07.11 */ Temperature( 5.5, 8.9, 7.30172 ), + /* 08.11 */ Temperature( 7, 11.7, 8.96701 ), + /* 09.11 */ Temperature( 2.5, 8.4, 4.86528 ), + /* 10.11 */ Temperature( 3.7, 9, 5.20828 ), + /* 11.11 */ Temperature( 2.8, 10.6, 6.80756 ), + /* 12.11 */ Temperature( 2.7, 9.5, 5.07647 ), + /* 13.11 */ Temperature( 0.1, 5.4, 3.3945 ), + /* 14.11 */ Temperature( -0.7, 7.9, 2.02234 ), + /* 15.11 */ Temperature( -1.8, 6.5, 1.07778 ), + /* 16.11 */ Temperature( -4.4, 5.1, -0.693772 ), + /* 17.11 */ Temperature( -0.3, 3.4, 1.33229 ), + /* 18.11 */ Temperature( -0.4, 4.3, 2.4622 ), + /* 19.11 */ Temperature( 1.8, 3.6, 2.78282 ), + /* 20.11 */ Temperature( 1.3, 5.6, 2.95979 ), + /* 21.11 */ Temperature( 1.6, 5.7, 3.62284 ), + /* 22.11 */ Temperature( 3.1, 7.3, 5.60277 ), + /* 23.11 */ Temperature( 4.2, 7.7, 6.28166 ), + /* 24.11 */ Temperature( -0.5, 11.5, 3.25931 ), + /* 25.11 */ Temperature( -1, 8.8, 2.86505 ), + /* 26.11 */ Temperature( 1.2, 6.8, 3.09414 ), + /* 27.11 */ Temperature( -0.8, 7.5, 3.17805 ), + /* 28.11 */ Temperature( -2.8, 3.1, -0.920139 ), + /* 29.11 */ Temperature( -2.6, 1.7, -0.491696 ), + /* 30.11 */ Temperature( 1.3, 6.5, 3.85 ), + + /* 01.12 */ Temperature( 4.1, 8.7, 5.88924 ), + /* 02.12 */ Temperature( 4.8, 9, 6.81667 ), + /* 03.12 */ Temperature( 3.5, 8.5, 6.23633 ), + /* 04.12 */ Temperature( 2.7, 6.6, 4.63045 ), + /* 05.12 */ Temperature( 4.3, 8.6, 6.85993 ), + /* 06.12 */ Temperature( 5.5, 9.3, 7.79201 ), + /* 07.12 */ Temperature( 3.1, 13.4, 8.79444 ), + /* 08.12 */ Temperature( 2.6, 6.3, 4.67093 ), + /* 09.12 */ Temperature( 3, 10.4, 5.75724 ), + /* 10.12 */ Temperature( 4.1, 6.8, 5.31834 ), + /* 11.12 */ Temperature( 4.1, 7.4, 5.28993 ), + /* 12.12 */ Temperature( 3.9, 6.4, 4.64479 ), + /* 13.12 */ Temperature( 1.7, 9.1, 4.15363 ), + /* 14.12 */ Temperature( 0.4, 1.8, 0.934602 ), + /* 15.12 */ Temperature( -4.5, 2.1, -1.17292 ), + /* 16.12 */ Temperature( -5, 4.8, -2.17431 ), + /* 17.12 */ Temperature( -5.6, 6.1, -1.35448 ), + /* 18.12 */ Temperature( -4.9, 6.4, -1.25502 ), + /* 19.12 */ Temperature( -4.4, 6.6, -1.02396 ), + /* 20.12 */ Temperature( -7.3, 5.2, -2.63854 ), + /* 21.12 */ Temperature( -8.5, 5.7, -3.58333 ), + /* 22.12 */ Temperature( -7.9, -5.3, -6.13438 ), + /* 23.12 */ Temperature( -6.1, -4.4, -5.23472 ), + /* 24.12 */ Temperature( -4.6, -3.3, -3.84291 ), + /* 25.12 */ Temperature( -4.9, -2.8, -3.9066 ), + /* 26.12 */ Temperature( -4.7, -1.9, -3.10379 ), + /* 27.12 */ Temperature( -1.9, -0.2, -0.679791 ), + /* 28.12 */ Temperature( -1.8, 0.5, -0.521875 ), + /* 29.12 */ Temperature( -2.2, 2.3, -0.430796 ), + /* 30.12 */ Temperature( 0.9, 5.2, 2.83437 ), + /* 31.12 */ Temperature( -1, 8.3, 2.27093 ) +}; diff --git a/qwt/examples/friedberg/friedberg2007.h b/qwt/examples/friedberg/friedberg2007.h new file mode 100644 index 000000000..691f10141 --- /dev/null +++ b/qwt/examples/friedberg/friedberg2007.h @@ -0,0 +1,28 @@ +#ifndef _FRIEDBERG_2007_H_ +#define _FRIEDBERG_2007_H_ + +class Temperature +{ +public: + Temperature(): + minValue( 0.0 ), + maxValue( 0.0 ), + averageValue( 0.0 ) + { + } + + Temperature( double min, double max, double average ): + minValue( min ), + maxValue( max ), + averageValue( average ) + { + } + + double minValue; + double maxValue; + double averageValue; +}; + +extern Temperature friedberg2007[]; + +#endif diff --git a/qwt/examples/friedberg/main.cpp b/qwt/examples/friedberg/main.cpp new file mode 100644 index 000000000..498c7144a --- /dev/null +++ b/qwt/examples/friedberg/main.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + Plot *d_plot; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new Plot( this ); + setCentralWidget( d_plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *typeBox = new QComboBox( toolBar ); + typeBox->addItem( "Bars" ); + typeBox->addItem( "Tube" ); + typeBox->setCurrentIndex( 1 ); + typeBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), d_plot, SLOT( exportPlot() ) ); + + toolBar->addWidget( typeBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + + d_plot->setMode( typeBox->currentIndex() ); + connect( typeBox, SIGNAL( currentIndexChanged( int ) ), + d_plot, SLOT( setMode( int ) ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.setObjectName( "MainWindow" ); + w.resize( 600, 400 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/friedberg/plot.cpp b/qwt/examples/friedberg/plot.cpp new file mode 100644 index 000000000..331ba09bd --- /dev/null +++ b/qwt/examples/friedberg/plot.cpp @@ -0,0 +1,209 @@ +#include "plot.h" +#include "friedberg2007.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Grid: public QwtPlotGrid +{ +public: + Grid() + { + enableXMin( true ); + setMajorPen( Qt::white, 0, Qt::DotLine ); + setMinorPen( Qt::gray, 0, Qt::DotLine ); + } + + virtual void updateScaleDiv( const QwtScaleDiv &xScaleDiv, + const QwtScaleDiv &yScaleDiv ) + { + QwtScaleDiv scaleDiv( xScaleDiv.lowerBound(), + xScaleDiv.upperBound() ); + + scaleDiv.setTicks( QwtScaleDiv::MinorTick, + xScaleDiv.ticks( QwtScaleDiv::MinorTick ) ); + scaleDiv.setTicks( QwtScaleDiv::MajorTick, + xScaleDiv.ticks( QwtScaleDiv::MediumTick ) ); + + QwtPlotGrid::updateScaleDiv( scaleDiv, yScaleDiv ); + } +}; + +class YearScaleDraw: public QwtScaleDraw +{ +public: + YearScaleDraw() + { + setTickLength( QwtScaleDiv::MajorTick, 0 ); + setTickLength( QwtScaleDiv::MinorTick, 0 ); + setTickLength( QwtScaleDiv::MediumTick, 6 ); + + setLabelRotation( -60.0 ); + setLabelAlignment( Qt::AlignLeft | Qt::AlignVCenter ); + + setSpacing( 15 ); + } + + virtual QwtText label( double value ) const + { + return QDate::longMonthName( int( value / 30 ) + 1 ); + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setObjectName( "FriedbergPlot" ); + setTitle( "Temperature of Friedberg/Germany" ); + + setAxisTitle( QwtAxis::xBottom, "2007" ); + setAxisScaleDiv( QwtAxis::xBottom, yearScaleDiv() ); + setAxisScaleDraw( QwtAxis::xBottom, new YearScaleDraw() ); + + setAxisTitle( QwtAxis::yLeft, + QString( "Temperature [%1C]" ).arg( QChar( 0x00B0 ) ) ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setPalette( Qt::darkGray ); + canvas->setBorderRadius( 10 ); + + setCanvas( canvas ); + + // grid + QwtPlotGrid *grid = new Grid; + grid->attach( this ); + + insertLegend( new QwtLegend(), QwtPlot::RightLegend ); + + const int numDays = 365; + QVector averageData( numDays ); + QVector rangeData( numDays ); + + for ( int i = 0; i < numDays; i++ ) + { + const Temperature &t = friedberg2007[i]; + averageData[i] = QPointF( double( i ), t.averageValue ); + rangeData[i] = QwtIntervalSample( double( i ), + QwtInterval( t.minValue, t.maxValue ) ); + } + + insertCurve( "Average", averageData, Qt::black ); + insertErrorBars( "Range", rangeData, Qt::blue ); + + // LeftButton for the zooming + // MidButton for the panning + // RightButton: zoom out by 1 + // Ctrl+RighButton: zoom out to full size + + QwtPlotZoomer* zoomer = new QwtPlotZoomer( canvas ); + zoomer->setRubberBandPen( QColor( Qt::black ) ); + zoomer->setTrackerPen( QColor( Qt::black ) ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect3, + Qt::RightButton ); + + QwtPlotPanner *panner = new QwtPlotPanner( canvas ); + panner->setMouseButton( Qt::MidButton ); +} + +QwtScaleDiv Plot::yearScaleDiv() const +{ + const int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + QList mediumTicks; + mediumTicks += 0.0; + for ( uint i = 0; i < sizeof( days ) / sizeof( days[0] ); i++ ) + mediumTicks += mediumTicks.last() + days[i]; + + QList minorTicks; + for ( int i = 1; i <= 365; i += 7 ) + minorTicks += i; + + QList majorTicks; + for ( int i = 0; i < 12; i++ ) + majorTicks += i * 30 + 15; + + QwtScaleDiv scaleDiv( mediumTicks.first(), mediumTicks.last() + 1, + minorTicks, mediumTicks, majorTicks ); + return scaleDiv; +} + +void Plot::insertCurve( const QString& title, + const QVector& samples, const QColor &color ) +{ + d_curve = new QwtPlotCurve( title ); + d_curve->setRenderHint( QwtPlotItem::RenderAntialiased ); + d_curve->setStyle( QwtPlotCurve::NoCurve ); + d_curve->setLegendAttribute( QwtPlotCurve::LegendShowSymbol ); + + QwtSymbol *symbol = new QwtSymbol( QwtSymbol::XCross ); + symbol->setSize( 4 ); + symbol->setPen( color ); + d_curve->setSymbol( symbol ); + + d_curve->setSamples( samples ); + d_curve->attach( this ); +} + +void Plot::insertErrorBars( + const QString &title, + const QVector& samples, + const QColor &color ) +{ + d_intervalCurve = new QwtPlotIntervalCurve( title ); + d_intervalCurve->setRenderHint( QwtPlotItem::RenderAntialiased ); + d_intervalCurve->setPen( Qt::white ); + + QColor bg( color ); + bg.setAlpha( 150 ); + d_intervalCurve->setBrush( QBrush( bg ) ); + d_intervalCurve->setStyle( QwtPlotIntervalCurve::Tube ); + + d_intervalCurve->setSamples( samples ); + d_intervalCurve->attach( this ); +} + +void Plot::setMode( int style ) +{ + if ( style == Tube ) + { + d_intervalCurve->setStyle( QwtPlotIntervalCurve::Tube ); + d_intervalCurve->setSymbol( NULL ); + d_intervalCurve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + } + else + { + d_intervalCurve->setStyle( QwtPlotIntervalCurve::NoCurve ); + + QColor c( d_intervalCurve->brush().color().rgb() ); // skip alpha + + QwtIntervalSymbol *errorBar = + new QwtIntervalSymbol( QwtIntervalSymbol::Bar ); + errorBar->setWidth( 8 ); // should be something even + errorBar->setPen( c ); + + d_intervalCurve->setSymbol( errorBar ); + d_intervalCurve->setRenderHint( QwtPlotItem::RenderAntialiased, false ); + } + + replot(); +} + +void Plot::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "friedberg.pdf" ); +} diff --git a/qwt/examples/friedberg/plot.h b/qwt/examples/friedberg/plot.h new file mode 100644 index 000000000..387655433 --- /dev/null +++ b/qwt/examples/friedberg/plot.h @@ -0,0 +1,43 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include +#include +#include + +class QwtPlotCurve; +class QwtPlotIntervalCurve; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + enum Mode + { + Bars, + Tube + }; + + Plot( QWidget * = NULL ); + +public Q_SLOTS: + void setMode( int ); + void exportPlot(); + +private: + void insertCurve( const QString &title, + const QVector &, const QColor & ); + + void insertErrorBars( const QString &title, + const QVector &, + const QColor &color ); + + + QwtScaleDiv yearScaleDiv() const; + + QwtPlotIntervalCurve *d_intervalCurve; + QwtPlotCurve *d_curve; +}; + +#endif diff --git a/qwt/examples/itemeditor/editor.cpp b/qwt/examples/itemeditor/editor.cpp new file mode 100644 index 000000000..236fb2a3f --- /dev/null +++ b/qwt/examples/itemeditor/editor.cpp @@ -0,0 +1,374 @@ +#include "editor.h" +#include +#include +#include +#include +#include + +class Overlay: public QwtWidgetOverlay +{ +public: + Overlay( QWidget *parent, Editor *editor ): + QwtWidgetOverlay( parent ), + d_editor( editor ) + { + switch( editor->mode() ) + { + case Editor::NoMask: + { + setMaskMode( QwtWidgetOverlay::NoMask ); + setRenderMode( QwtWidgetOverlay::AutoRenderMode ); + break; + } + case Editor::Mask: + { + setMaskMode( QwtWidgetOverlay::MaskHint ); + setRenderMode( QwtWidgetOverlay::AutoRenderMode ); + break; + } + case Editor::AlphaMask: + { + setMaskMode( QwtWidgetOverlay::AlphaMask ); + setRenderMode( QwtWidgetOverlay::AutoRenderMode ); + break; + } + case Editor::AlphaMaskRedraw: + { + setMaskMode( QwtWidgetOverlay::AlphaMask ); + setRenderMode( QwtWidgetOverlay::DrawOverlay ); + break; + } + case Editor::AlphaMaskCopyMask: + { + setMaskMode( QwtWidgetOverlay::AlphaMask ); + setRenderMode( QwtWidgetOverlay::CopyAlphaMask ); + break; + } + } + } + +protected: + virtual void drawOverlay( QPainter *painter ) const + { + d_editor->drawOverlay( painter ); + } + + virtual QRegion maskHint() const + { + return d_editor->maskHint(); + } + +private: + Editor *d_editor; +}; + +Editor::Editor( QwtPlot* plot ): + QObject( plot ), + d_isEnabled( false ), + d_overlay( NULL ), + d_mode( Mask ) +{ + setEnabled( true ); +} + + +Editor::~Editor() +{ + delete d_overlay; +} + +QwtPlot *Editor::plot() +{ + return qobject_cast( parent() ); +} + +const QwtPlot *Editor::plot() const +{ + return qobject_cast( parent() ); +} + +void Editor::setMode( Mode mode ) +{ + d_mode = mode; +} + +Editor::Mode Editor::mode() const +{ + return d_mode; +} + +void Editor::setEnabled( bool on ) +{ + if ( on == d_isEnabled ) + return; + + QwtPlot *plot = qobject_cast( parent() ); + if ( plot ) + { + d_isEnabled = on; + + if ( on ) + { + plot->canvas()->installEventFilter( this ); + } + else + { + plot->canvas()->removeEventFilter( this ); + + delete d_overlay; + d_overlay = NULL; + } + } +} + +bool Editor::isEnabled() const +{ + return d_isEnabled; +} + +bool Editor::eventFilter( QObject* object, QEvent* event ) +{ + QwtPlot *plot = qobject_cast( parent() ); + if ( plot && object == plot->canvas() ) + { + switch( event->type() ) + { + case QEvent::MouseButtonPress: + { + const QMouseEvent* mouseEvent = + dynamic_cast( event ); + + if ( d_overlay == NULL && + mouseEvent->button() == Qt::LeftButton ) + { + const bool accepted = pressed( mouseEvent->pos() ); + if ( accepted ) + { + d_overlay = new Overlay( plot->canvas(), this ); + + d_overlay->updateOverlay(); + d_overlay->show(); + } + } + + break; + } + case QEvent::MouseMove: + { + if ( d_overlay ) + { + const QMouseEvent* mouseEvent = + dynamic_cast< QMouseEvent* >( event ); + + const bool accepted = moved( mouseEvent->pos() ); + if ( accepted ) + d_overlay->updateOverlay(); + } + + break; + } + case QEvent::MouseButtonRelease: + { + const QMouseEvent* mouseEvent = + static_cast( event ); + + if ( d_overlay && mouseEvent->button() == Qt::LeftButton ) + { + released( mouseEvent->pos() ); + + delete d_overlay; + d_overlay = NULL; + } + + break; + } + default: + break; + } + + return false; + } + + return QObject::eventFilter( object, event ); +} + +bool Editor::pressed( const QPoint& pos ) +{ + d_editedItem = itemAt( pos ); + if ( d_editedItem ) + { + d_currentPos = pos; + setItemVisible( d_editedItem, false ); + + return true; + } + + return false; // don't accept the position +} + +bool Editor::moved( const QPoint& pos ) +{ + if ( plot() == NULL ) + return false; + + const QwtScaleMap xMap = plot()->canvasMap( d_editedItem->xAxis() ); + const QwtScaleMap yMap = plot()->canvasMap( d_editedItem->yAxis() ); + + const QPointF p1 = QwtScaleMap::invTransform( xMap, yMap, d_currentPos ); + const QPointF p2 = QwtScaleMap::invTransform( xMap, yMap, pos ); + +#if QT_VERSION >= 0x040600 + const QPainterPath shape = d_editedItem->shape().translated( p2 - p1 ); +#else + const double dx = p2.x() - p1.x(); + const double dy = p2.y() - p1.y(); + + QPainterPath shape = d_editedItem->shape(); + for ( int i = 0; i < shape.elementCount(); i++ ) + { + const QPainterPath::Element &el = shape.elementAt( i ); + shape.setElementPositionAt( i, el.x + dx, el.y + dy ); + } +#endif + + d_editedItem->setShape( shape ); + d_currentPos = pos; + + return true; +} + +void Editor::released( const QPoint& pos ) +{ + Q_UNUSED( pos ); + + if ( d_editedItem ) + { + raiseItem( d_editedItem ); + setItemVisible( d_editedItem, true ); + } +} + +QwtPlotShapeItem* Editor::itemAt( const QPoint& pos ) const +{ + const QwtPlot *plot = this->plot(); + if ( plot == NULL ) + return NULL; + + // translate pos into the plot coordinates + double coords[ QwtAxis::PosCount ]; + coords[ QwtAxis::xBottom ] = + plot->canvasMap( QwtAxis::xBottom ).invTransform( pos.x() ); + coords[ QwtAxis::xTop ] = + plot->canvasMap( QwtAxis::xTop ).invTransform( pos.x() ); + coords[ QwtAxis::yLeft ] = + plot->canvasMap( QwtAxis::yLeft ).invTransform( pos.y() ); + coords[ QwtAxis::yRight ] = + plot->canvasMap( QwtAxis::yRight ).invTransform( pos.y() ); + + QwtPlotItemList items = plot->itemList(); + for ( int i = items.size() - 1; i >= 0; i-- ) + { + QwtPlotItem *item = items[ i ]; + if ( item->isVisible() && + item->rtti() == QwtPlotItem::Rtti_PlotShape ) + { + QwtPlotShapeItem *shapeItem = static_cast( item ); + const QPointF p( coords[ item->xAxis().pos ], coords[ item->yAxis().pos ] ); + + if ( shapeItem->boundingRect().contains( p ) + && shapeItem->shape().contains( p ) ) + { + return shapeItem; + } + } + } + + return NULL; +} + +QRegion Editor::maskHint() const +{ + return maskHint( d_editedItem ); +} + +QRegion Editor::maskHint( QwtPlotShapeItem *shapeItem ) const +{ + const QwtPlot *plot = this->plot(); + if ( plot == NULL || shapeItem == NULL ) + return QRegion(); + + const QwtScaleMap xMap = plot->canvasMap( shapeItem->xAxis() ); + const QwtScaleMap yMap = plot->canvasMap( shapeItem->yAxis() ); + + QRect rect = QwtScaleMap::transform( xMap, yMap, + shapeItem->shape().boundingRect() ).toRect(); + + const int m = 5; // some margin for the pen + return rect.adjusted( -m, -m, m, m ); +} + +void Editor::drawOverlay( QPainter* painter ) const +{ + const QwtPlot *plot = this->plot(); + if ( plot == NULL || d_editedItem == NULL ) + return; + + const QwtScaleMap xMap = plot->canvasMap( d_editedItem->xAxis() ); + const QwtScaleMap yMap = plot->canvasMap( d_editedItem->yAxis() ); + + painter->setRenderHint( QPainter::Antialiasing, + d_editedItem->testRenderHint( QwtPlotItem::RenderAntialiased ) ); + d_editedItem->draw( painter, xMap, yMap, + plot->canvas()->contentsRect() ); +} + +void Editor::raiseItem( QwtPlotShapeItem *shapeItem ) +{ + const QwtPlot *plot = this->plot(); + if ( plot == NULL || shapeItem == NULL ) + return; + + const QwtPlotItemList items = plot->itemList(); + for ( int i = items.size() - 1; i >= 0; i-- ) + { + QwtPlotItem *item = items[ i ]; + if ( shapeItem == item ) + return; + + if ( item->isVisible() && + item->rtti() == QwtPlotItem::Rtti_PlotShape ) + { + shapeItem->setZ( item->z() + 1 ); + return; + } + } +} + +void Editor::setItemVisible( QwtPlotShapeItem *item, bool on ) +{ + if ( plot() == NULL || item == NULL || item->isVisible() == on ) + return; + + const bool doAutoReplot = plot()->autoReplot(); + plot()->setAutoReplot( false ); + + item->setVisible( on ); + + plot()->setAutoReplot( doAutoReplot ); + + /* + Avoid replot with a full repaint of the canvas. + For special combinations - f.e. using the + raster paint engine on a remote display - + this makes a difference. + */ + + QwtPlotCanvas *canvas = + qobject_cast( plot()->canvas() ); + if ( canvas ) + canvas->invalidateBackingStore(); + + plot()->canvas()->update( maskHint( item ) ); + +} + diff --git a/qwt/examples/itemeditor/editor.h b/qwt/examples/itemeditor/editor.h new file mode 100644 index 000000000..7f8c8965a --- /dev/null +++ b/qwt/examples/itemeditor/editor.h @@ -0,0 +1,66 @@ +#ifndef _EDITOR_H_ +#define _EDITOR_H_ + +#include +#include +#include +#include + +class QwtPlot; +class QwtPlotShapeItem; +class QPainter; +class QPoint; + +class Editor: public QObject +{ + Q_OBJECT + +public: + enum Mode + { + NoMask, + Mask, + AlphaMask, + AlphaMaskRedraw, + AlphaMaskCopyMask + }; + + Editor( QwtPlot * ); + virtual ~Editor(); + + const QwtPlot *plot() const; + QwtPlot *plot(); + + virtual void setEnabled( bool on ); + bool isEnabled() const; + + void drawOverlay( QPainter * ) const; + QRegion maskHint() const; + + virtual bool eventFilter( QObject *, QEvent *); + + void setMode( Mode mode ); + Mode mode() const; + +private: + bool pressed( const QPoint & ); + bool moved( const QPoint & ); + void released( const QPoint & ); + + QwtPlotShapeItem* itemAt( const QPoint& ) const; + void raiseItem( QwtPlotShapeItem * ); + + QRegion maskHint( QwtPlotShapeItem * ) const; + void setItemVisible( QwtPlotShapeItem *item, bool on ); + + bool d_isEnabled; + QPointer d_overlay; + + // Mouse positions + QPointF d_currentPos; + QwtPlotShapeItem* d_editedItem; + + Mode d_mode; +}; + +#endif diff --git a/qwt/examples/itemeditor/itemeditor.pro b/qwt/examples/itemeditor/itemeditor.pro new file mode 100644 index 000000000..734209115 --- /dev/null +++ b/qwt/examples/itemeditor/itemeditor.pro @@ -0,0 +1,24 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = itemeditor + +HEADERS = \ + editor.h \ + shapefactory.h \ + plot.h + +SOURCES = \ + editor.cpp \ + shapefactory.cpp \ + plot.cpp \ + main.cpp + diff --git a/qwt/examples/itemeditor/main.cpp b/qwt/examples/itemeditor/main.cpp new file mode 100644 index 000000000..d66fc6358 --- /dev/null +++ b/qwt/examples/itemeditor/main.cpp @@ -0,0 +1,54 @@ +#include "plot.h" +#include +#include +#include +#include +#include + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + Plot *plot = new Plot( this ); + setCentralWidget( plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *modeBox = new QComboBox( toolBar ); + modeBox->addItem( "No Mask" ); + modeBox->addItem( "Mask" ); + modeBox->addItem( "Alpha Mask" ); + modeBox->addItem( "Alpha Mask/Redraw" ); + modeBox->addItem( "Alpha Mask/Copy Mask" ); + modeBox->setCurrentIndex( 1 ); + modeBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + connect( modeBox, SIGNAL( currentIndexChanged( int ) ), + plot, SLOT( setMode( int ) ) ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), plot, SLOT( exportPlot() ) ); + + toolBar->addWidget( modeBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + +} + +int main( int argc, char **argv ) +{ + QApplication app( argc, argv ); + + MainWindow window; + window.resize( 600, 400 ); + window.show(); + + return app.exec(); +} diff --git a/qwt/examples/itemeditor/plot.cpp b/qwt/examples/itemeditor/plot.cpp new file mode 100644 index 000000000..0aeb3f2ad --- /dev/null +++ b/qwt/examples/itemeditor/plot.cpp @@ -0,0 +1,120 @@ +#include "plot.h" +#include "editor.h" +#include +#include +#include +#include +#include + +class Legend: public QwtLegend +{ +protected: + virtual QWidget *createWidget( const QwtLegendData &data ) const + { + QWidget *w = QwtLegend::createWidget( data ); + if ( w ) + { + w->setStyleSheet( + "border-radius: 5px;" + "padding: 2px;" + "background: LemonChiffon;" + ); + } + + return w; + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setAutoReplot( false ); + + setTitle( "Movable Items" ); + + const int margin = 5; + setContentsMargins( margin, margin, margin, margin ); + + setAutoFillBackground( true ); + setPalette( QColor( "DimGray" ).lighter( 110 ) ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); +#if 0 + // a gradient making a replot slow on X11 + canvas->setStyleSheet( + "border: 2px solid Black;" + "border-radius: 15px;" + "background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1," + "stop: 0 LemonChiffon, stop: 0.5 PaleGoldenrod, stop: 1 LemonChiffon );" + ); +#else + canvas->setStyleSheet( + "border: 2px inset DimGray;" + "border-radius: 15px;" + "background: LemonChiffon;" + ); +#endif + + setCanvas( canvas ); + insertLegend( new Legend(), QwtPlot::RightLegend ); + + populate(); + + updateAxes(); + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + setAxisAutoScale( axis, false ); + + d_editor = new Editor( this ); + ( void ) new QwtPlotMagnifier( canvas ); +} + +void Plot::populate() +{ + addShape( "Rectangle", ShapeFactory::Rect, "RoyalBlue", + QPointF( 30.0, 50.0 ), QSizeF( 40.0, 50.0 ) ); + addShape( "Ellipse", ShapeFactory::Ellipse, "IndianRed", + QPointF( 80.0, 130.0 ), QSizeF( 50.0, 40.0 ) ); + addShape( "Ring", ShapeFactory::Ring, "DarkOliveGreen", + QPointF( 30.0, 165.0 ), QSizeF( 40.0, 40.0 ) ); + addShape( "Triangle", ShapeFactory::Triangle, "SandyBrown", + QPointF( 165.0, 165.0 ), QSizeF( 60.0, 40.0 ) ); + addShape( "Star", ShapeFactory::Star, "DarkViolet", + QPointF( 165.0, 50.0 ), QSizeF( 40.0, 50.0 ) ); + addShape( "Hexagon", ShapeFactory::Hexagon, "DarkSlateGray", + QPointF( 120.0, 70.0 ), QSizeF( 50.0, 50.0 ) ); + +} + +void Plot::addShape( const QString &title, + ShapeFactory::Shape shape, const QColor &color, + const QPointF &pos, const QSizeF &size ) +{ + QwtPlotShapeItem *item = new QwtPlotShapeItem( title ); + item->setItemAttribute( QwtPlotItem::Legend, true ); + item->setLegendMode( QwtPlotShapeItem::LegendShape ); + item->setLegendIconSize( QSize( 20, 20 ) ); + item->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + item->setShape( ShapeFactory::path( shape, pos, size ) ); + + QColor fillColor = color; + fillColor.setAlpha( 200 ); + + QPen pen( color, 3 ); + pen.setJoinStyle( Qt::MiterJoin ); + item->setPen( pen ); + item->setBrush( fillColor ); + + item->attach( this ); +} + +void Plot::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "shapes.pdf" ); +} + +void Plot::setMode( int mode ) +{ + d_editor->setMode( static_cast( mode ) ); +} + diff --git a/qwt/examples/itemeditor/plot.h b/qwt/examples/itemeditor/plot.h new file mode 100644 index 000000000..1652a6466 --- /dev/null +++ b/qwt/examples/itemeditor/plot.h @@ -0,0 +1,33 @@ +#ifndef _PLOT_H +#define _PLOT_H + +#include +#include "shapefactory.h" + +class QColor; +class QSizeF; +class QPointF; +class Editor; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent = NULL ); + +public Q_SLOTS: + void exportPlot(); + void setMode( int ); + +private: + void populate(); + + void addShape( const QString &title, + ShapeFactory::Shape, const QColor &, + const QPointF &, const QSizeF & ); + + Editor *d_editor; +}; + +#endif diff --git a/qwt/examples/itemeditor/shapefactory.cpp b/qwt/examples/itemeditor/shapefactory.cpp new file mode 100644 index 000000000..2a9b01d46 --- /dev/null +++ b/qwt/examples/itemeditor/shapefactory.cpp @@ -0,0 +1,113 @@ +#include "shapefactory.h" + +QPainterPath ShapeFactory::path( Shape shape, + const QPointF &pos, const QSizeF &size ) +{ + QRectF rect; + rect.setSize( size ); + rect.moveCenter( pos ); + + QPainterPath path; + + switch( shape ) + { + case Rect: + { + path.addRect( rect ); + break; + } + case Triangle: + { + QPolygonF triangle; + triangle += rect.bottomLeft(); + triangle += QPointF( rect.center().x(), rect.top() ); + triangle += rect.bottomRight(); + + path.addPolygon( triangle ); + break; + } + case Ellipse: + { + path.addEllipse( rect ); + break; + } + case Ring: + { + path.addEllipse( rect ); + + const double w = 0.25 * rect.width(); + path.addEllipse( rect.adjusted( w, w, -w, -w ) ); + break; + } + case Star: + { + const double cos30 = 0.866025; + + const double dy = 0.25 * size.height(); + const double dx = 0.5 * size.width() * cos30 / 3.0; + + double x1 = pos.x() - 3 * dx; + double y1 = pos.y() - 2 * dy; + + const double x2 = x1 + 1 * dx; + const double x3 = x1 + 2 * dx; + const double x4 = x1 + 3 * dx; + const double x5 = x1 + 4 * dx; + const double x6 = x1 + 5 * dx; + const double x7 = x1 + 6 * dx; + + const double y2 = y1 + 1 * dy; + const double y3 = y1 + 2 * dy; + const double y4 = y1 + 3 * dy; + const double y5 = y1 + 4 * dy; + + QPolygonF star; + star += QPointF( x4, y1 ); + star += QPointF( x5, y2 ); + star += QPointF( x7, y2 ); + star += QPointF( x6, y3 ); + star += QPointF( x7, y4 ); + star += QPointF( x5, y4 ); + star += QPointF( x4, y5 ); + star += QPointF( x3, y4 ); + star += QPointF( x1, y4 ); + star += QPointF( x2, y3 ); + star += QPointF( x1, y2 ); + star += QPointF( x3, y2 ); + + path.addPolygon( star ); + break; + } + case Hexagon: + { + const double cos30 = 0.866025; + + const double dx = 0.5 * size.width() - cos30; + const double dy = 0.25 * size.height(); + + double x1 = pos.x() - dx; + double y1 = pos.y() - 2 * dy; + + const double x2 = x1 + 1 * dx; + const double x3 = x1 + 2 * dx; + + const double y2 = y1 + 1 * dy; + const double y3 = y1 + 3 * dy; + const double y4 = y1 + 4 * dy; + + QPolygonF hexagon; + hexagon += QPointF( x2, y1 ); + hexagon += QPointF( x3, y2 ); + hexagon += QPointF( x3, y3 ); + hexagon += QPointF( x2, y4 ); + hexagon += QPointF( x1, y3 ); + hexagon += QPointF( x1, y2 ); + + path.addPolygon( hexagon ); + break; + } + }; + + path.closeSubpath(); + return path; +} diff --git a/qwt/examples/itemeditor/shapefactory.h b/qwt/examples/itemeditor/shapefactory.h new file mode 100644 index 000000000..3dcd72535 --- /dev/null +++ b/qwt/examples/itemeditor/shapefactory.h @@ -0,0 +1,21 @@ +#ifndef _SHAPE_FACTORY_H_ +#define _SHAPE_FACTORY_H_ + +#include + +namespace ShapeFactory +{ + enum Shape + { + Rect, + Triangle, + Ellipse, + Ring, + Star, + Hexagon + }; + + QPainterPath path( Shape, const QPointF &, const QSizeF & ); +}; + +#endif diff --git a/qwt/examples/legends/legends.pro b/qwt/examples/legends/legends.pro new file mode 100644 index 000000000..a8e9e7821 --- /dev/null +++ b/qwt/examples/legends/legends.pro @@ -0,0 +1,24 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = legends + +HEADERS = \ + mainwindow.h \ + panel.h \ + settings.h \ + plot.h + +SOURCES = \ + mainwindow.cpp \ + panel.cpp \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/legends/main.cpp b/qwt/examples/legends/main.cpp new file mode 100644 index 000000000..91b30b5a1 --- /dev/null +++ b/qwt/examples/legends/main.cpp @@ -0,0 +1,14 @@ +#include +#include "mainwindow.h" + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + a.setStyle( "Windows" ); + + MainWindow w; + w.resize( 700, 500 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/legends/mainwindow.cpp b/qwt/examples/legends/mainwindow.cpp new file mode 100644 index 000000000..200d8e4c7 --- /dev/null +++ b/qwt/examples/legends/mainwindow.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include "plot.h" +#include "panel.h" +#include "mainwindow.h" + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new Plot(); + + Settings settings; + settings.legend.isEnabled = true; + settings.legend.position = QwtPlot::BottomLegend; + + settings.legendItem.isEnabled = false; + settings.legendItem.numColumns = 1; + settings.legendItem.alignment = Qt::AlignRight | Qt::AlignVCenter; + settings.legendItem.backgroundMode = 0; + settings.legendItem.size = d_plot->canvas()->font().pointSize(); + + settings.curve.numCurves = 4; + settings.curve.title = "Curve"; + + d_panel = new Panel(); + d_panel->setSettings( settings ); + + QWidget *box = new QWidget( this ); + QHBoxLayout *layout = new QHBoxLayout( box ); + layout->addWidget( d_plot, 10 ); + layout->addWidget( d_panel ); + + setCentralWidget( box ); + + QToolBar *toolBar = new QToolBar( this ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + toolBar->addWidget( btnExport ); + + addToolBar( toolBar ); + + updatePlot(); + + connect( d_panel, SIGNAL( edited() ), SLOT( updatePlot() ) ); + connect( btnExport, SIGNAL( clicked() ), SLOT( exportPlot() ) ); +} + +void MainWindow::updatePlot() +{ + d_plot->applySettings( d_panel->settings() ); +} + +void MainWindow::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( d_plot, "legends.pdf" ); +} diff --git a/qwt/examples/legends/mainwindow.h b/qwt/examples/legends/mainwindow.h new file mode 100644 index 000000000..26e36a00b --- /dev/null +++ b/qwt/examples/legends/mainwindow.h @@ -0,0 +1,20 @@ +#include + +class Plot; +class Panel; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = 0 ); + +private Q_SLOTS: + void updatePlot(); + void exportPlot(); + +private: + Plot *d_plot; + Panel *d_panel; +}; diff --git a/qwt/examples/legends/panel.cpp b/qwt/examples/legends/panel.cpp new file mode 100644 index 000000000..256fc5b8a --- /dev/null +++ b/qwt/examples/legends/panel.cpp @@ -0,0 +1,216 @@ +#include "panel.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Panel::Panel( QWidget *parent ): + QWidget( parent ) +{ + // create widgets + + d_legend.checkBox = new QCheckBox( "Enabled" ); + + d_legend.positionBox = new QComboBox(); + d_legend.positionBox->addItem( "Left", QwtPlot::LeftLegend ); + d_legend.positionBox->addItem( "Right", QwtPlot::RightLegend ); + d_legend.positionBox->addItem( "Bottom", QwtPlot::BottomLegend ); + d_legend.positionBox->addItem( "Top", QwtPlot::TopLegend ); + d_legend.positionBox->addItem( "External", QwtPlot::TopLegend + 1 ); + + d_legendItem.checkBox = new QCheckBox( "Enabled" ); + + d_legendItem.numColumnsBox = new QSpinBox(); + d_legendItem.numColumnsBox->setRange( 0, 10 ); + d_legendItem.numColumnsBox->setSpecialValueText( "Unlimited" ); + + d_legendItem.hAlignmentBox = new QComboBox(); + d_legendItem.hAlignmentBox->addItem( "Left", Qt::AlignLeft ); + d_legendItem.hAlignmentBox->addItem( "Centered", Qt::AlignHCenter ); + d_legendItem.hAlignmentBox->addItem( "Right", Qt::AlignRight ); + + d_legendItem.vAlignmentBox = new QComboBox(); + d_legendItem.vAlignmentBox->addItem( "Top", Qt::AlignTop ); + d_legendItem.vAlignmentBox->addItem( "Centered", Qt::AlignVCenter ); + d_legendItem.vAlignmentBox->addItem( "Bottom", Qt::AlignBottom ); + + d_legendItem.backgroundBox = new QComboBox(); + d_legendItem.backgroundBox->addItem( "Legend", + QwtPlotLegendItem::LegendBackground ); + d_legendItem.backgroundBox->addItem( "Items", + QwtPlotLegendItem::ItemBackground ); + + d_legendItem.sizeBox = new QSpinBox(); + d_legendItem.sizeBox->setRange( 8, 22 ); + + d_curve.numCurves = new QSpinBox(); + d_curve.numCurves->setRange( 0, 99 ); + + d_curve.title = new QLineEdit(); + + // layout + + QGroupBox *legendBox = new QGroupBox( "Legend" ); + QGridLayout *legendBoxLayout = new QGridLayout( legendBox ); + + int row = 0; + legendBoxLayout->addWidget( d_legend.checkBox, row, 0, 1, -1 ); + + row++; + legendBoxLayout->addWidget( new QLabel( "Position" ), row, 0 ); + legendBoxLayout->addWidget( d_legend.positionBox, row, 1 ); + + + QGroupBox *legendItemBox = new QGroupBox( "Legend Item" ); + QGridLayout *legendItemBoxLayout = new QGridLayout( legendItemBox ); + + row = 0; + legendItemBoxLayout->addWidget( d_legendItem.checkBox, row, 0, 1, -1 ); + + row++; + legendItemBoxLayout->addWidget( new QLabel( "Columns" ), row, 0 ); + legendItemBoxLayout->addWidget( d_legendItem.numColumnsBox, row, 1 ); + + row++; + legendItemBoxLayout->addWidget( new QLabel( "Horizontal" ), row, 0 ); + legendItemBoxLayout->addWidget( d_legendItem.hAlignmentBox, row, 1 ); + + row++; + legendItemBoxLayout->addWidget( new QLabel( "Vertical" ), row, 0 ); + legendItemBoxLayout->addWidget( d_legendItem.vAlignmentBox, row, 1 ); + + row++; + legendItemBoxLayout->addWidget( new QLabel( "Background" ), row, 0 ); + legendItemBoxLayout->addWidget( d_legendItem.backgroundBox, row, 1 ); + + row++; + legendItemBoxLayout->addWidget( new QLabel( "Size" ), row, 0 ); + legendItemBoxLayout->addWidget( d_legendItem.sizeBox, row, 1 ); + + QGroupBox *curveBox = new QGroupBox( "Curves" ); + QGridLayout *curveBoxLayout = new QGridLayout( curveBox ); + + row = 0; + curveBoxLayout->addWidget( new QLabel( "Number" ), row, 0 ); + curveBoxLayout->addWidget( d_curve.numCurves, row, 1 ); + + row++; + curveBoxLayout->addWidget( new QLabel( "Title" ), row, 0 ); + curveBoxLayout->addWidget( d_curve.title, row, 1 ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->addWidget( legendBox ); + layout->addWidget( legendItemBox ); + layout->addWidget( curveBox ); + layout->addStretch( 10 ); + + connect( d_legend.checkBox, + SIGNAL( stateChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legend.positionBox, + SIGNAL( currentIndexChanged( int ) ), SIGNAL( edited() ) ); + + connect( d_legendItem.checkBox, + SIGNAL( stateChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legendItem.numColumnsBox, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legendItem.hAlignmentBox, + SIGNAL( currentIndexChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legendItem.vAlignmentBox, + SIGNAL( currentIndexChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legendItem.backgroundBox, + SIGNAL( currentIndexChanged( int ) ), SIGNAL( edited() ) ); + connect( d_curve.numCurves, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legendItem.sizeBox, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); + connect( d_curve.title, + SIGNAL( textEdited( const QString & ) ), SIGNAL( edited() ) ); +} + +void Panel::setSettings( const Settings &settings) +{ + blockSignals( true ); + + d_legend.checkBox->setCheckState( + settings.legend.isEnabled ? Qt::Checked : Qt::Unchecked ); + d_legend.positionBox->setCurrentIndex( settings.legend.position ); + + d_legendItem.checkBox->setCheckState( + settings.legendItem.isEnabled ? Qt::Checked : Qt::Unchecked ); + + d_legendItem.numColumnsBox->setValue( settings.legendItem.numColumns ); + + int align = settings.legendItem.alignment; + + if ( align & Qt::AlignLeft ) + d_legendItem.hAlignmentBox->setCurrentIndex( 0 ); + else if ( align & Qt::AlignRight ) + d_legendItem.hAlignmentBox->setCurrentIndex( 2 ); + else + d_legendItem.hAlignmentBox->setCurrentIndex( 1 ); + + if ( align & Qt::AlignTop ) + d_legendItem.vAlignmentBox->setCurrentIndex( 0 ); + else if ( align & Qt::AlignBottom ) + d_legendItem.vAlignmentBox->setCurrentIndex( 2 ); + else + d_legendItem.vAlignmentBox->setCurrentIndex( 1 ); + + d_legendItem.backgroundBox->setCurrentIndex( + settings.legendItem.backgroundMode ); + + d_legendItem.sizeBox->setValue( settings.legendItem.size ); + + d_curve.numCurves->setValue( settings.curve.numCurves ); + d_curve.title->setText( settings.curve.title ); + + blockSignals( false ); +} + +Settings Panel::settings() const +{ + Settings s; + + s.legend.isEnabled = + d_legend.checkBox->checkState() == Qt::Checked; + s.legend.position = d_legend.positionBox->currentIndex(); + + s.legendItem.isEnabled = + d_legendItem.checkBox->checkState() == Qt::Checked; + s.legendItem.numColumns = d_legendItem.numColumnsBox->value(); + + int align = 0; + + int hIndex = d_legendItem.hAlignmentBox->currentIndex(); + if ( hIndex == 0 ) + align |= Qt::AlignLeft; + else if ( hIndex == 2 ) + align |= Qt::AlignRight; + else + align |= Qt::AlignHCenter; + + int vIndex = d_legendItem.vAlignmentBox->currentIndex(); + if ( vIndex == 0 ) + align |= Qt::AlignTop; + else if ( vIndex == 2 ) + align |= Qt::AlignBottom; + else + align |= Qt::AlignVCenter; + + s.legendItem.alignment = align; + + s.legendItem.backgroundMode = + d_legendItem.backgroundBox->currentIndex(); + s.legendItem.size = d_legendItem.sizeBox->value(); + + s.curve.numCurves = d_curve.numCurves->value(); + s.curve.title = d_curve.title->text(); + + return s; +} diff --git a/qwt/examples/legends/panel.h b/qwt/examples/legends/panel.h new file mode 100644 index 000000000..570f4943f --- /dev/null +++ b/qwt/examples/legends/panel.h @@ -0,0 +1,52 @@ +#ifndef _PANEL_ +#define _PANEL_ + +#include "settings.h" +#include + +class QCheckBox; +class QComboBox; +class QSpinBox; +class QLineEdit; + +class Panel: public QWidget +{ + Q_OBJECT + +public: + Panel( QWidget *parent = NULL ); + + void setSettings( const Settings &); + Settings settings() const; + +Q_SIGNALS: + void edited(); + +private: + struct + { + QCheckBox *checkBox; + QComboBox *positionBox; + + } d_legend; + + struct + { + QCheckBox *checkBox; + QSpinBox *numColumnsBox; + QComboBox *hAlignmentBox; + QComboBox *vAlignmentBox; + QComboBox *backgroundBox; + QSpinBox *sizeBox; + + } d_legendItem; + + struct + { + QSpinBox *numCurves; + QLineEdit *title; + + } d_curve; +}; + +#endif diff --git a/qwt/examples/legends/plot.cpp b/qwt/examples/legends/plot.cpp new file mode 100644 index 000000000..6ff4ab0f3 --- /dev/null +++ b/qwt/examples/legends/plot.cpp @@ -0,0 +1,263 @@ +#include "plot.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include + +class LegendItem: public QwtPlotLegendItem +{ +public: + LegendItem() + { + setRenderHint( QwtPlotItem::RenderAntialiased ); + + QColor color( Qt::white ); + + setTextPen( color ); +#if 1 + setBorderPen( color ); + + QColor c( Qt::gray ); + c.setAlpha( 200 ); + + setBackgroundBrush( c ); +#endif + } +}; + +class Curve: public QwtPlotCurve +{ +public: + Curve( int index ): + d_index( index ) + { + setRenderHint( QwtPlotItem::RenderAntialiased ); + initData(); + } + + void setCurveTitle( const QString &title ) + { + QString txt("%1 %2"); + setTitle( QString( "%1 %2" ).arg( title ).arg( d_index ) ); + } + + void initData() + { + QVector points; + + double y = qrand() % 1000; + + for ( double x = 0.0; x <= 1000.0; x += 100.0 ) + { + double off = qrand() % 200 - 100; + if ( y + off > 980.0 || y + off < 20.0 ) + off = -off; + + y += off; + + points += QPointF( x, y ); + } + + setSamples( points ); + } + +private: + const int d_index; +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ), + d_externalLegend( NULL ), + d_legendItem( NULL ), + d_isDirty( false ) +{ + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setFocusIndicator( QwtPlotCanvas::CanvasFocusIndicator ); + canvas->setFocusPolicy( Qt::StrongFocus ); + canvas->setPalette( Qt::black ); + setCanvas( canvas ); + + setAutoReplot( false ); + + setTitle( "Legend Test" ); + setFooter( "Footer" ); + + // grid + QwtPlotGrid *grid = new QwtPlotGrid; + grid->enableXMin( true ); + grid->setMajorPen( Qt::gray, 0, Qt::DotLine ); + grid->setMinorPen( Qt::darkGray, 0, Qt::DotLine ); + grid->attach( this ); + + // axis + setAxisScale( QwtAxis::yLeft, 0.0, 1000.0 ); + setAxisScale( QwtAxis::xBottom, 0.0, 1000.0 ); +} + +Plot::~Plot() +{ + delete d_externalLegend; +} + +void Plot::insertCurve() +{ + static int counter = 1; + + const char *colors[] = + { + "LightSalmon", + "SteelBlue", + "Yellow", + "Fuchsia", + "PaleGreen", + "PaleTurquoise", + "Cornsilk", + "HotPink", + "Peru", + "Maroon" + }; + const int numColors = sizeof( colors ) / sizeof( colors[0] ); + + QwtPlotCurve *curve = new Curve( counter++ ); + curve->setPen( QColor( colors[ counter % numColors ] ), 2 ); + curve->attach( this ); +} + +void Plot::applySettings( const Settings &settings ) +{ + d_isDirty = false; + setAutoReplot( true ); + + if ( settings.legend.isEnabled ) + { + if ( settings.legend.position > QwtPlot::TopLegend ) + { + if ( legend() ) + { + // remove legend controlled by the plot + insertLegend( NULL ); + } + + if ( d_externalLegend == NULL ) + { + d_externalLegend = new QwtLegend(); + d_externalLegend->setWindowTitle("Plot Legend"); + + connect( + this, + SIGNAL( legendDataChanged( const QVariant &, + const QList & ) ), + d_externalLegend, + SLOT( updateLegend( const QVariant &, + const QList & ) ) ); + + d_externalLegend->show(); + + // populate the new legend + updateLegend(); + } + } + else + { + delete d_externalLegend; + d_externalLegend = NULL; + + if ( legend() == NULL || + plotLayout()->legendPosition() != settings.legend.position ) + { + insertLegend( new QwtLegend(), + QwtPlot::LegendPosition( settings.legend.position ) ); + } + } + } + else + { + insertLegend( NULL ); + + delete d_externalLegend; + d_externalLegend = NULL; + } + + if ( settings.legendItem.isEnabled ) + { + if ( d_legendItem == NULL ) + { + d_legendItem = new LegendItem(); + d_legendItem->attach( this ); + } + + d_legendItem->setMaxColumns( settings.legendItem.numColumns ); + d_legendItem->setAlignment( Qt::Alignment( settings.legendItem.alignment ) ); + d_legendItem->setBackgroundMode( + QwtPlotLegendItem::BackgroundMode( settings.legendItem.backgroundMode ) ); + if ( settings.legendItem.backgroundMode == + QwtPlotLegendItem::ItemBackground ) + { + d_legendItem->setBorderRadius( 4 ); + d_legendItem->setMargin( 0 ); + d_legendItem->setSpacing( 4 ); + d_legendItem->setItemMargin( 2 ); + } + else + { + d_legendItem->setBorderRadius( 8 ); + d_legendItem->setMargin( 4 ); + d_legendItem->setSpacing( 2 ); + d_legendItem->setItemMargin( 0 ); + } + + QFont font = d_legendItem->font(); + font.setPointSize( settings.legendItem.size ); + d_legendItem->setFont( font ); + } + else + { + delete d_legendItem; + d_legendItem = NULL; + } + + QwtPlotItemList curveList = itemList( QwtPlotItem::Rtti_PlotCurve ); + if ( curveList.size() != settings.curve.numCurves ) + { + while ( curveList.size() > settings.curve.numCurves ) + { + QwtPlotItem* curve = curveList.takeFirst(); + delete curve; + } + + for ( int i = curveList.size(); i < settings.curve.numCurves; i++ ) + insertCurve(); + } + + curveList = itemList( QwtPlotItem::Rtti_PlotCurve ); + for ( int i = 0; i < curveList.count(); i++ ) + { + Curve* curve = static_cast( curveList[i] ); + curve->setCurveTitle( settings.curve.title ); + + int sz = 0.5 * settings.legendItem.size; + curve->setLegendIconSize( QSize( sz, sz ) ); + } + + setAutoReplot( false ); + if ( d_isDirty ) + { + d_isDirty = false; + replot(); + } +} + +void Plot::replot() +{ + if ( autoReplot() ) + { + d_isDirty = true; + return; + } + + QwtPlot::replot(); +} + diff --git a/qwt/examples/legends/plot.h b/qwt/examples/legends/plot.h new file mode 100644 index 000000000..b29c9d3f1 --- /dev/null +++ b/qwt/examples/legends/plot.h @@ -0,0 +1,32 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class Settings; +class LegendItem; +class QwtLegend; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent = NULL ); + virtual ~Plot(); + +public Q_SLOTS: + void applySettings( const Settings & ); + +public: + virtual void replot(); + +private: + void insertCurve(); + + QwtLegend *d_externalLegend; + LegendItem *d_legendItem; + bool d_isDirty; +}; + +#endif diff --git a/qwt/examples/legends/settings.h b/qwt/examples/legends/settings.h new file mode 100644 index 000000000..2f9061b68 --- /dev/null +++ b/qwt/examples/legends/settings.h @@ -0,0 +1,47 @@ +#ifndef _SETTINGS_ +#define _SETTINGS_ + +#include + +class Settings +{ +public: + Settings() + { + legend.isEnabled = false; + legend.position = 0; + + legendItem.isEnabled = false; + legendItem.numColumns = 0; + legendItem.alignment = 0; + legendItem.backgroundMode = 0; + legendItem.size = 12; + + curve.numCurves = 0; + curve.title = "Curve"; + } + + struct + { + bool isEnabled; + int position; + } legend; + + struct + { + bool isEnabled; + int numColumns; + int alignment; + int backgroundMode; + int size; + + } legendItem; + + struct + { + int numCurves; + QString title; + } curve; +}; + +#endif diff --git a/qwt/examples/oscilloscope/curvedata.cpp b/qwt/examples/oscilloscope/curvedata.cpp new file mode 100644 index 000000000..28e02eb72 --- /dev/null +++ b/qwt/examples/oscilloscope/curvedata.cpp @@ -0,0 +1,27 @@ +#include "curvedata.h" +#include "signaldata.h" + +const SignalData &CurveData::values() const +{ + return SignalData::instance(); +} + +SignalData &CurveData::values() +{ + return SignalData::instance(); +} + +QPointF CurveData::sample( size_t i ) const +{ + return SignalData::instance().value( i ); +} + +size_t CurveData::size() const +{ + return SignalData::instance().size(); +} + +QRectF CurveData::boundingRect() const +{ + return SignalData::instance().boundingRect(); +} diff --git a/qwt/examples/oscilloscope/curvedata.h b/qwt/examples/oscilloscope/curvedata.h new file mode 100644 index 000000000..246eca583 --- /dev/null +++ b/qwt/examples/oscilloscope/curvedata.h @@ -0,0 +1,16 @@ +#include +#include + +class SignalData; + +class CurveData: public QwtSeriesData +{ +public: + const SignalData &values() const; + SignalData &values(); + + virtual QPointF sample( size_t i ) const; + virtual size_t size() const; + + virtual QRectF boundingRect() const; +}; diff --git a/qwt/examples/oscilloscope/knob.cpp b/qwt/examples/oscilloscope/knob.cpp new file mode 100644 index 000000000..8bb61edc8 --- /dev/null +++ b/qwt/examples/oscilloscope/knob.cpp @@ -0,0 +1,95 @@ +#include "knob.h" +#include +#include +#include +#include +#include +#include +#include + +Knob::Knob( const QString &title, double min, double max, QWidget *parent ): + QWidget( parent ) +{ + QFont font( "Helvetica", 10 ); + + d_knob = new QwtKnob( this ); + d_knob->setFont( font ); + + QwtScaleDiv scaleDiv = + d_knob->scaleEngine()->divideScale( min, max, 5, 3 ); + + QList ticks = scaleDiv.ticks( QwtScaleDiv::MajorTick ); + if ( ticks.size() > 0 && ticks[0] > min ) + { + if ( ticks.first() > min ) + ticks.prepend( min ); + if ( ticks.last() < max ) + ticks.append( max ); + } + scaleDiv.setTicks( QwtScaleDiv::MajorTick, ticks ); + d_knob->setScale( scaleDiv ); + + d_knob->setKnobWidth( 50 ); + + font.setBold( true ); + d_label = new QLabel( title, this ); + d_label->setFont( font ); + d_label->setAlignment( Qt::AlignTop | Qt::AlignHCenter ); + + setSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding ); + + connect( d_knob, SIGNAL( valueChanged( double ) ), + this, SIGNAL( valueChanged( double ) ) ); +} + +QSize Knob::sizeHint() const +{ + QSize sz1 = d_knob->sizeHint(); + QSize sz2 = d_label->sizeHint(); + + const int w = qMax( sz1.width(), sz2.width() ); + const int h = sz1.height() + sz2.height(); + + int off = qCeil( d_knob->scaleDraw()->extent( d_knob->font() ) ); + off -= 15; // spacing + + return QSize( w, h - off ); +} + +void Knob::setValue( double value ) +{ + d_knob->setValue( value ); +} + +double Knob::value() const +{ + return d_knob->value(); +} + +void Knob::setTheme( const QColor &color ) +{ + d_knob->setPalette( color ); +} + +QColor Knob::theme() const +{ + return d_knob->palette().color( QPalette::Window ); +} + +void Knob::resizeEvent( QResizeEvent *event ) +{ + const QSize sz = event->size(); + const QSize hint = d_label->sizeHint(); + + d_label->setGeometry( 0, sz.height() - hint.height(), + sz.width(), hint.height() ); + + const int knobHeight = d_knob->sizeHint().height(); + + int off = qCeil( d_knob->scaleDraw()->extent( d_knob->font() ) ); + off -= 15; // spacing + + d_knob->setGeometry( 0, d_label->pos().y() - knobHeight + off, + sz.width(), knobHeight ); +} diff --git a/qwt/examples/oscilloscope/knob.h b/qwt/examples/oscilloscope/knob.h new file mode 100644 index 000000000..bfc70d715 --- /dev/null +++ b/qwt/examples/oscilloscope/knob.h @@ -0,0 +1,38 @@ +#ifndef _KNOB_H_ +#define _KNOB_H_ + +#include + +class QwtKnob; +class QLabel; + +class Knob: public QWidget +{ + Q_OBJECT + + Q_PROPERTY( QColor theme READ theme WRITE setTheme ) + +public: + Knob( const QString &title, + double min, double max, QWidget *parent = NULL ); + + virtual QSize sizeHint() const; + + void setValue( double value ); + double value() const; + + void setTheme( const QColor & ); + QColor theme() const; + +Q_SIGNALS: + double valueChanged( double ); + +protected: + virtual void resizeEvent( QResizeEvent * ); + +private: + QwtKnob *d_knob; + QLabel *d_label; +}; + +#endif diff --git a/qwt/examples/oscilloscope/main.cpp b/qwt/examples/oscilloscope/main.cpp new file mode 100644 index 000000000..75ffbe4a7 --- /dev/null +++ b/qwt/examples/oscilloscope/main.cpp @@ -0,0 +1,36 @@ +#include +#include "mainwindow.h" +#include "samplingthread.h" + +int main( int argc, char **argv ) +{ + QApplication app( argc, argv ); + app.setPalette( Qt::darkGray ); + + MainWindow window; + window.resize( 800, 400 ); + + SamplingThread samplingThread; + samplingThread.setFrequency( window.frequency() ); + samplingThread.setAmplitude( window.amplitude() ); + samplingThread.setInterval( window.signalInterval() ); + + window.connect( &window, SIGNAL( frequencyChanged( double ) ), + &samplingThread, SLOT( setFrequency( double ) ) ); + window.connect( &window, SIGNAL( amplitudeChanged( double ) ), + &samplingThread, SLOT( setAmplitude( double ) ) ); + window.connect( &window, SIGNAL( signalIntervalChanged( double ) ), + &samplingThread, SLOT( setInterval( double ) ) ); + + window.show(); + + samplingThread.start(); + window.start(); + + bool ok = app.exec(); + + samplingThread.stop(); + samplingThread.wait( 1000 ); + + return ok; +} diff --git a/qwt/examples/oscilloscope/mainwindow.cpp b/qwt/examples/oscilloscope/mainwindow.cpp new file mode 100644 index 000000000..8c46f447b --- /dev/null +++ b/qwt/examples/oscilloscope/mainwindow.cpp @@ -0,0 +1,69 @@ +#include "mainwindow.h" +#include "plot.h" +#include "knob.h" +#include "wheelbox.h" +#include +#include +#include + +MainWindow::MainWindow( QWidget *parent ): + QWidget( parent ) +{ + const double intervalLength = 10.0; // seconds + + d_plot = new Plot( this ); + d_plot->setIntervalLength( intervalLength ); + + d_amplitudeKnob = new Knob( "Amplitude", 0.0, 200.0, this ); + d_amplitudeKnob->setValue( 160.0 ); + + d_frequencyKnob = new Knob( "Frequency [Hz]", 0.1, 20.0, this ); + d_frequencyKnob->setValue( 17.8 ); + + d_intervalWheel = new WheelBox( "Displayed [s]", 1.0, 100.0, 1.0, this ); + d_intervalWheel->setValue( intervalLength ); + + d_timerWheel = new WheelBox( "Sample Interval [ms]", 0.0, 20.0, 0.1, this ); + d_timerWheel->setValue( 10.0 ); + + QVBoxLayout* vLayout1 = new QVBoxLayout(); + vLayout1->addWidget( d_intervalWheel ); + vLayout1->addWidget( d_timerWheel ); + vLayout1->addStretch( 10 ); + vLayout1->addWidget( d_amplitudeKnob ); + vLayout1->addWidget( d_frequencyKnob ); + + QHBoxLayout *layout = new QHBoxLayout( this ); + layout->addWidget( d_plot, 10 ); + layout->addLayout( vLayout1 ); + + connect( d_amplitudeKnob, SIGNAL( valueChanged( double ) ), + SIGNAL( amplitudeChanged( double ) ) ); + connect( d_frequencyKnob, SIGNAL( valueChanged( double ) ), + SIGNAL( frequencyChanged( double ) ) ); + connect( d_timerWheel, SIGNAL( valueChanged( double ) ), + SIGNAL( signalIntervalChanged( double ) ) ); + + connect( d_intervalWheel, SIGNAL( valueChanged( double ) ), + d_plot, SLOT( setIntervalLength( double ) ) ); +} + +void MainWindow::start() +{ + d_plot->start(); +} + +double MainWindow::frequency() const +{ + return d_frequencyKnob->value(); +} + +double MainWindow::amplitude() const +{ + return d_amplitudeKnob->value(); +} + +double MainWindow::signalInterval() const +{ + return d_timerWheel->value(); +} diff --git a/qwt/examples/oscilloscope/mainwindow.h b/qwt/examples/oscilloscope/mainwindow.h new file mode 100644 index 000000000..8994f0488 --- /dev/null +++ b/qwt/examples/oscilloscope/mainwindow.h @@ -0,0 +1,32 @@ +#include + +class Plot; +class Knob; +class WheelBox; + +class MainWindow : public QWidget +{ + Q_OBJECT + +public: + MainWindow( QWidget * = NULL ); + + void start(); + + double amplitude() const; + double frequency() const; + double signalInterval() const; + +Q_SIGNALS: + void amplitudeChanged( double ); + void frequencyChanged( double ); + void signalIntervalChanged( double ); + +private: + Knob *d_frequencyKnob; + Knob *d_amplitudeKnob; + WheelBox *d_timerWheel; + WheelBox *d_intervalWheel; + + Plot *d_plot; +}; diff --git a/qwt/examples/oscilloscope/osci.css b/qwt/examples/oscilloscope/osci.css new file mode 100644 index 000000000..344a8c426 --- /dev/null +++ b/qwt/examples/oscilloscope/osci.css @@ -0,0 +1,55 @@ +MainWindow +{ + border: 1px solid white; + border-radius: 20px; + padding: 10px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #31312C, stop: 0.5 #808080 stop: 1 #31312C ); +} + +QwtPlotCanvas +{ + border: 1px solid White; + border-radius: 10px; + background-color: #101010; + color: yellow; /* used as curve color */ +} + +QwtScaleWidget +{ + color: white; +} + +WheelBox +{ + qproperty-theme: #878787; +} + +QwtWheel +{ + /* background-color: yellow; */ + qproperty-mass: 0.0; + qproperty-tickCount: 5; + qproperty-wheelWidth: 15; + qproperty-borderWidth: 2; + qproperty-wheelBorderWidth: 2; + qproperty-wrapping: true; +} + +Knob +{ + qproperty-theme: #606060; +} + +QwtKnob +{ + qproperty-knobStyle: Sunken; + qproperty-markerStyle: Nub; + qproperty-markerSize: 8; + qproperty-borderWidth: 2; +} + +QLCDNumber +{ + color: yellow; +} diff --git a/qwt/examples/oscilloscope/oscilloscope.pro b/qwt/examples/oscilloscope/oscilloscope.pro new file mode 100644 index 000000000..2fa8e1ea3 --- /dev/null +++ b/qwt/examples/oscilloscope/oscilloscope.pro @@ -0,0 +1,31 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = oscilloscope + +HEADERS = \ + signaldata.h \ + plot.h \ + knob.h \ + wheelbox.h \ + samplingthread.h \ + curvedata.h \ + mainwindow.h + +SOURCES = \ + signaldata.cpp \ + plot.cpp \ + knob.cpp \ + wheelbox.cpp \ + samplingthread.cpp \ + curvedata.cpp \ + mainwindow.cpp \ + main.cpp diff --git a/qwt/examples/oscilloscope/plot.cpp b/qwt/examples/oscilloscope/plot.cpp new file mode 100644 index 000000000..98a30824f --- /dev/null +++ b/qwt/examples/oscilloscope/plot.cpp @@ -0,0 +1,254 @@ +#include "plot.h" +#include "curvedata.h" +#include "signaldata.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Canvas: public QwtPlotCanvas +{ +public: + Canvas( QwtPlot *plot = NULL ): + QwtPlotCanvas( plot ) + { + // The backing store is important, when working with widget + // overlays ( f.e rubberbands for zooming ). + // Here we don't have them and the internal + // backing store of QWidget is good enough. + + setPaintAttribute( QwtPlotCanvas::BackingStore, false ); + setBorderRadius( 10 ); + + if ( QwtPainter::isX11GraphicsSystem() ) + { +#if QT_VERSION < 0x050000 + // Even if not liked by the Qt development, Qt::WA_PaintOutsidePaintEvent + // works on X11. This has a nice effect on the performance. + + setAttribute( Qt::WA_PaintOutsidePaintEvent, true ); +#endif + + // Disabling the backing store of Qt improves the performance + // for the direct painter even more, but the canvas becomes + // a native window of the window system, receiving paint events + // for resize and expose operations. Those might be expensive + // when there are many points and the backing store of + // the canvas is disabled. So in this application + // we better don't both backing stores. + + if ( testPaintAttribute( QwtPlotCanvas::BackingStore ) ) + { + setAttribute( Qt::WA_PaintOnScreen, true ); + setAttribute( Qt::WA_NoSystemBackground, true ); + } + } + + setupPalette(); + } + +private: + void setupPalette() + { + QPalette pal = palette(); + +#if QT_VERSION >= 0x040400 + QLinearGradient gradient; + gradient.setCoordinateMode( QGradient::StretchToDeviceMode ); + gradient.setColorAt( 0.0, QColor( 0, 49, 110 ) ); + gradient.setColorAt( 1.0, QColor( 0, 87, 174 ) ); + + pal.setBrush( QPalette::Window, QBrush( gradient ) ); +#else + pal.setBrush( QPalette::Window, QBrush( color ) ); +#endif + + // QPalette::WindowText is used for the curve color + pal.setColor( QPalette::WindowText, Qt::green ); + + setPalette( pal ); + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ), + d_paintedPoints( 0 ), + d_interval( 0.0, 10.0 ), + d_timerId( -1 ) +{ + d_directPainter = new QwtPlotDirectPainter(); + + setAutoReplot( false ); + setCanvas( new Canvas() ); + + plotLayout()->setAlignCanvasToScales( true ); + + setAxisTitle( QwtAxis::xBottom, "Time [s]" ); + setAxisScale( QwtAxis::xBottom, d_interval.minValue(), d_interval.maxValue() ); + setAxisScale( QwtAxis::yLeft, -200.0, 200.0 ); + + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->setPen( Qt::gray, 0.0, Qt::DotLine ); + grid->enableX( true ); + grid->enableXMin( true ); + grid->enableY( true ); + grid->enableYMin( false ); + grid->attach( this ); + + d_origin = new QwtPlotMarker(); + d_origin->setLineStyle( QwtPlotMarker::Cross ); + d_origin->setValue( d_interval.minValue() + d_interval.width() / 2.0, 0.0 ); + d_origin->setLinePen( Qt::gray, 0.0, Qt::DashLine ); + d_origin->attach( this ); + + d_curve = new QwtPlotCurve(); + d_curve->setStyle( QwtPlotCurve::Lines ); + d_curve->setPen( canvas()->palette().color( QPalette::WindowText ) ); + d_curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + d_curve->setPaintAttribute( QwtPlotCurve::ClipPolygons, false ); + d_curve->setData( new CurveData() ); + d_curve->attach( this ); +} + +Plot::~Plot() +{ + delete d_directPainter; +} + +void Plot::start() +{ + d_clock.start(); + d_timerId = startTimer( 10 ); +} + +void Plot::replot() +{ + CurveData *data = static_cast( d_curve->data() ); + data->values().lock(); + + QwtPlot::replot(); + d_paintedPoints = data->size(); + + data->values().unlock(); +} + +void Plot::setIntervalLength( double interval ) +{ + if ( interval > 0.0 && interval != d_interval.width() ) + { + d_interval.setMaxValue( d_interval.minValue() + interval ); + setAxisScale( QwtAxis::xBottom, + d_interval.minValue(), d_interval.maxValue() ); + + replot(); + } +} + +void Plot::updateCurve() +{ + CurveData *data = static_cast( d_curve->data() ); + data->values().lock(); + + const int numPoints = data->size(); + if ( numPoints > d_paintedPoints ) + { + const bool doClip = !canvas()->testAttribute( Qt::WA_PaintOnScreen ); + if ( doClip ) + { + /* + Depending on the platform setting a clip might be an important + performance issue. F.e. for Qt Embedded this reduces the + part of the backing store that has to be copied out - maybe + to an unaccelerated frame buffer device. + */ + + const QwtScaleMap xMap = canvasMap( d_curve->xAxis() ); + const QwtScaleMap yMap = canvasMap( d_curve->yAxis() ); + + QRectF br = qwtBoundingRect( *data, + d_paintedPoints - 1, numPoints - 1 ); + + const QRect clipRect = QwtScaleMap::transform( xMap, yMap, br ).toRect(); + d_directPainter->setClipRegion( clipRect ); + } + + d_directPainter->drawSeries( d_curve, + d_paintedPoints - 1, numPoints - 1 ); + d_paintedPoints = numPoints; + } + + data->values().unlock(); +} + +void Plot::incrementInterval() +{ + d_interval = QwtInterval( d_interval.maxValue(), + d_interval.maxValue() + d_interval.width() ); + + CurveData *data = static_cast( d_curve->data() ); + data->values().clearStaleValues( d_interval.minValue() ); + + // To avoid, that the grid is jumping, we disable + // the autocalculation of the ticks and shift them + // manually instead. + + QwtScaleDiv scaleDiv = axisScaleDiv( QwtAxis::xBottom ); + scaleDiv.setInterval( d_interval ); + + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + { + QList ticks = scaleDiv.ticks( i ); + for ( int j = 0; j < ticks.size(); j++ ) + ticks[j] += d_interval.width(); + scaleDiv.setTicks( i, ticks ); + } + setAxisScaleDiv( QwtAxis::xBottom, scaleDiv ); + + d_origin->setValue( d_interval.minValue() + d_interval.width() / 2.0, 0.0 ); + + d_paintedPoints = 0; + replot(); +} + +void Plot::timerEvent( QTimerEvent *event ) +{ + if ( event->timerId() == d_timerId ) + { + updateCurve(); + + const double elapsed = d_clock.elapsed() / 1000.0; + if ( elapsed > d_interval.maxValue() ) + incrementInterval(); + + return; + } + + QwtPlot::timerEvent( event ); +} + +void Plot::resizeEvent( QResizeEvent *event ) +{ + d_directPainter->reset(); + QwtPlot::resizeEvent( event ); +} + +void Plot::showEvent( QShowEvent * ) +{ + replot(); +} + +bool Plot::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == canvas() && + event->type() == QEvent::PaletteChange ) + { + d_curve->setPen( canvas()->palette().color( QPalette::WindowText ) ); + } + + return QwtPlot::eventFilter( object, event ); +} diff --git a/qwt/examples/oscilloscope/plot.h b/qwt/examples/oscilloscope/plot.h new file mode 100644 index 000000000..16a53b8d7 --- /dev/null +++ b/qwt/examples/oscilloscope/plot.h @@ -0,0 +1,44 @@ +#include +#include +#include + +class QwtPlotCurve; +class QwtPlotMarker; +class QwtPlotDirectPainter; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + virtual ~Plot(); + + void start(); + virtual void replot(); + + virtual bool eventFilter( QObject *, QEvent * ); + +public Q_SLOTS: + void setIntervalLength( double ); + +protected: + virtual void showEvent( QShowEvent * ); + virtual void resizeEvent( QResizeEvent * ); + virtual void timerEvent( QTimerEvent * ); + +private: + void updateCurve(); + void incrementInterval(); + + QwtPlotMarker *d_origin; + QwtPlotCurve *d_curve; + int d_paintedPoints; + + QwtPlotDirectPainter *d_directPainter; + + QwtInterval d_interval; + int d_timerId; + + QwtSystemClock d_clock; +}; diff --git a/qwt/examples/oscilloscope/samplingthread.cpp b/qwt/examples/oscilloscope/samplingthread.cpp new file mode 100644 index 000000000..7c0733ccb --- /dev/null +++ b/qwt/examples/oscilloscope/samplingthread.cpp @@ -0,0 +1,54 @@ +#include "samplingthread.h" +#include "signaldata.h" +#include +#include + +#if QT_VERSION < 0x040600 +#define qFastSin(x) ::sin(x) +#endif + +SamplingThread::SamplingThread( QObject *parent ): + QwtSamplingThread( parent ), + d_frequency( 5.0 ), + d_amplitude( 20.0 ) +{ +} + +void SamplingThread::setFrequency( double frequency ) +{ + d_frequency = frequency; +} + +double SamplingThread::frequency() const +{ + return d_frequency; +} + +void SamplingThread::setAmplitude( double amplitude ) +{ + d_amplitude = amplitude; +} + +double SamplingThread::amplitude() const +{ + return d_amplitude; +} + +void SamplingThread::sample( double elapsed ) +{ + if ( d_frequency > 0.0 ) + { + const QPointF s( elapsed, value( elapsed ) ); + SignalData::instance().append( s ); + } +} + +double SamplingThread::value( double timeStamp ) const +{ + const double period = 1.0 / d_frequency; + + const double x = ::fmod( timeStamp, period ); + const double v = d_amplitude * qFastSin( x / period * 2 * M_PI ); + + return v; +} diff --git a/qwt/examples/oscilloscope/samplingthread.h b/qwt/examples/oscilloscope/samplingthread.h new file mode 100644 index 000000000..141611322 --- /dev/null +++ b/qwt/examples/oscilloscope/samplingthread.h @@ -0,0 +1,25 @@ +#include + +class SamplingThread: public QwtSamplingThread +{ + Q_OBJECT + +public: + SamplingThread( QObject *parent = NULL ); + + double frequency() const; + double amplitude() const; + +public Q_SLOTS: + void setAmplitude( double ); + void setFrequency( double ); + +protected: + virtual void sample( double elapsed ); + +private: + virtual double value( double timeStamp ) const; + + double d_frequency; + double d_amplitude; +}; diff --git a/qwt/examples/oscilloscope/signaldata.cpp b/qwt/examples/oscilloscope/signaldata.cpp new file mode 100644 index 000000000..b5ef07f98 --- /dev/null +++ b/qwt/examples/oscilloscope/signaldata.cpp @@ -0,0 +1,133 @@ +#include "signaldata.h" +#include +#include +#include + +class SignalData::PrivateData +{ +public: + PrivateData(): + boundingRect( 1.0, 1.0, -2.0, -2.0 ) // invalid + { + values.reserve( 1000 ); + } + + inline void append( const QPointF &sample ) + { + values.append( sample ); + + // adjust the bounding rectangle + + if ( boundingRect.width() < 0 || boundingRect.height() < 0 ) + { + boundingRect.setRect( sample.x(), sample.y(), 0.0, 0.0 ); + } + else + { + boundingRect.setRight( sample.x() ); + + if ( sample.y() > boundingRect.bottom() ) + boundingRect.setBottom( sample.y() ); + + if ( sample.y() < boundingRect.top() ) + boundingRect.setTop( sample.y() ); + } + } + + QReadWriteLock lock; + + QVector values; + QRectF boundingRect; + + QMutex mutex; // protecting pendingValues + QVector pendingValues; +}; + +SignalData::SignalData() +{ + d_data = new PrivateData(); +} + +SignalData::~SignalData() +{ + delete d_data; +} + +int SignalData::size() const +{ + return d_data->values.size(); +} + +QPointF SignalData::value( int index ) const +{ + return d_data->values[index]; +} + +QRectF SignalData::boundingRect() const +{ + return d_data->boundingRect; +} + +void SignalData::lock() +{ + d_data->lock.lockForRead(); +} + +void SignalData::unlock() +{ + d_data->lock.unlock(); +} + +void SignalData::append( const QPointF &sample ) +{ + d_data->mutex.lock(); + d_data->pendingValues += sample; + + const bool isLocked = d_data->lock.tryLockForWrite(); + if ( isLocked ) + { + const int numValues = d_data->pendingValues.size(); + const QPointF *pendingValues = d_data->pendingValues.data(); + + for ( int i = 0; i < numValues; i++ ) + d_data->append( pendingValues[i] ); + + d_data->pendingValues.clear(); + + d_data->lock.unlock(); + } + + d_data->mutex.unlock(); +} + +void SignalData::clearStaleValues( double limit ) +{ + d_data->lock.lockForWrite(); + + d_data->boundingRect = QRectF( 1.0, 1.0, -2.0, -2.0 ); // invalid + + const QVector values = d_data->values; + d_data->values.clear(); + d_data->values.reserve( values.size() ); + + int index; + for ( index = values.size() - 1; index >= 0; index-- ) + { + if ( values[index].x() < limit ) + break; + } + + if ( index > 0 ) + d_data->append( values[index++] ); + + while ( index < values.size() - 1 ) + d_data->append( values[index++] ); + + d_data->lock.unlock(); +} + +SignalData &SignalData::instance() +{ + static SignalData valueVector; + return valueVector; +} diff --git a/qwt/examples/oscilloscope/signaldata.h b/qwt/examples/oscilloscope/signaldata.h new file mode 100644 index 000000000..61b05ade4 --- /dev/null +++ b/qwt/examples/oscilloscope/signaldata.h @@ -0,0 +1,33 @@ +#ifndef _SIGNAL_DATA_H_ +#define _SIGNAL_DATA_H_ 1 + +#include + +class SignalData +{ +public: + static SignalData &instance(); + + void append( const QPointF &pos ); + void clearStaleValues( double min ); + + int size() const; + QPointF value( int index ) const; + + QRectF boundingRect() const; + + void lock(); + void unlock(); + +private: + SignalData(); + SignalData( const SignalData & ); + SignalData &operator=( const SignalData & ); + + virtual ~SignalData(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/examples/oscilloscope/wheelbox.cpp b/qwt/examples/oscilloscope/wheelbox.cpp new file mode 100644 index 000000000..44ea3a0e7 --- /dev/null +++ b/qwt/examples/oscilloscope/wheelbox.cpp @@ -0,0 +1,102 @@ +#include "wheelbox.h" +#include +#include +#include +#include +#include +#include + +class Wheel: public QwtWheel +{ +public: + Wheel( WheelBox *parent ): + QwtWheel( parent ) + { + setFocusPolicy( Qt::WheelFocus ); + parent->installEventFilter( this ); + } + + virtual bool eventFilter( QObject *object, QEvent *event ) + { + if ( event->type() == QEvent::Wheel ) + { + const QWheelEvent *we = static_cast( event ); + + QWheelEvent wheelEvent( QPoint( 5, 5 ), we->delta(), + we->buttons(), we->modifiers(), + we->orientation() ); + + QApplication::sendEvent( this, &wheelEvent ); + return true; + } + return QwtWheel::eventFilter( object, event ); + } +}; + +WheelBox::WheelBox( const QString &title, + double min, double max, double stepSize, QWidget *parent ): + QWidget( parent ) +{ + + d_number = new QLCDNumber( this ); + d_number->setSegmentStyle( QLCDNumber::Filled ); + d_number->setAutoFillBackground( true ); + d_number->setFixedHeight( d_number->sizeHint().height() * 2 ); + d_number->setFocusPolicy( Qt::WheelFocus ); + + QPalette pal( Qt::black ); + pal.setColor( QPalette::WindowText, Qt::green ); + d_number->setPalette( pal ); + + d_wheel = new Wheel( this ); + d_wheel->setOrientation( Qt::Vertical ); + d_wheel->setInverted( true ); + d_wheel->setRange( min, max ); + d_wheel->setSingleStep( stepSize ); + d_wheel->setPageStepCount( 5 ); + d_wheel->setFixedHeight( d_number->height() ); + + d_number->setFocusProxy( d_wheel ); + + QFont font( "Helvetica", 10 ); + font.setBold( true ); + + d_label = new QLabel( title, this ); + d_label->setFont( font ); + + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->setContentsMargins( 0, 0, 0, 0 ); + hLayout->setSpacing( 2 ); + hLayout->addWidget( d_number, 10 ); + hLayout->addWidget( d_wheel ); + + QVBoxLayout *vLayout = new QVBoxLayout( this ); + vLayout->addLayout( hLayout, 10 ); + vLayout->addWidget( d_label, 0, Qt::AlignTop | Qt::AlignHCenter ); + + connect( d_wheel, SIGNAL( valueChanged( double ) ), + d_number, SLOT( display( double ) ) ); + connect( d_wheel, SIGNAL( valueChanged( double ) ), + this, SIGNAL( valueChanged( double ) ) ); +} + +void WheelBox::setTheme( const QColor &color ) +{ + d_wheel->setPalette( color ); +} + +QColor WheelBox::theme() const +{ + return d_wheel->palette().color( QPalette::Window ); +} + +void WheelBox::setValue( double value ) +{ + d_wheel->setValue( value ); + d_number->display( value ); +} + +double WheelBox::value() const +{ + return d_wheel->value(); +} diff --git a/qwt/examples/oscilloscope/wheelbox.h b/qwt/examples/oscilloscope/wheelbox.h new file mode 100644 index 000000000..5331692a3 --- /dev/null +++ b/qwt/examples/oscilloscope/wheelbox.h @@ -0,0 +1,40 @@ +#ifndef _WHEELBOX_H_ +#define _WHEELBOX_H_ + +#include + +class QwtWheel; +class QLabel; +class QLCDNumber; + +class WheelBox: public QWidget +{ + Q_OBJECT + Q_PROPERTY( QColor theme READ theme WRITE setTheme ) + +public: + WheelBox( const QString &title, + double min, double max, double stepSize, + QWidget *parent = NULL ); + + void setTheme( const QColor & ); + QColor theme() const; + + void setUnit( const QString & ); + QString unit() const; + + void setValue( double value ); + double value() const; + +Q_SIGNALS: + double valueChanged( double ); + +private: + QLCDNumber *d_number; + QwtWheel *d_wheel; + QLabel *d_label; + + QString d_unit; +}; + +#endif diff --git a/qwt/examples/radio/ampfrm.cpp b/qwt/examples/radio/ampfrm.cpp new file mode 100644 index 000000000..7dffc749a --- /dev/null +++ b/qwt/examples/radio/ampfrm.cpp @@ -0,0 +1,201 @@ +#include "ampfrm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION < 0x040600 +#define qFastSin(x) ::sin(x) +#define qFastCos(x) ::cos(x) +#endif + +class Knob: public QWidget +{ +public: + Knob( const QString &title, double min, double max, QWidget *parent ): + QWidget( parent ) + { + d_knob = new QwtKnob( this ); + d_knob->setScale( min, max ); + d_knob->setTotalSteps( 0 ); // disable + d_knob->setScaleMaxMajor( 10 ); + + d_knob->setKnobStyle( QwtKnob::Raised ); + d_knob->setKnobWidth( 50 ); + d_knob->setBorderWidth( 2 ); + d_knob->setMarkerStyle( QwtKnob::Notch ); + d_knob->setMarkerSize( 8 ); + + d_knob->scaleDraw()->setTickLength( QwtScaleDiv::MinorTick, 4 ); + d_knob->scaleDraw()->setTickLength( QwtScaleDiv::MediumTick, 4 ); + d_knob->scaleDraw()->setTickLength( QwtScaleDiv::MajorTick, 6 ); + + d_label = new QLabel( title, this ); + d_label->setAlignment( Qt::AlignTop | Qt::AlignHCenter ); + + setSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding ); + } + + virtual QSize sizeHint() const + { + QSize sz1 = d_knob->sizeHint(); + QSize sz2 = d_label->sizeHint(); + + const int w = qMax( sz1.width(), sz2.width() ); + const int h = sz1.height() + sz2.height(); + + int off = qCeil( d_knob->scaleDraw()->extent( d_knob->font() ) ); + off -= 10; // spacing + + return QSize( w, h - off ); + } + + void setValue( double value ) + { + d_knob->setValue( value ); + } + + double value() const + { + return d_knob->value(); + } + +protected: + virtual void resizeEvent( QResizeEvent *e ) + { + const QSize sz = e->size(); + + int h = d_label->sizeHint().height(); + + d_label->setGeometry( 0, sz.height() - h, sz.width(), h ); + + h = d_knob->sizeHint().height(); + int off = qCeil( d_knob->scaleDraw()->extent( d_knob->font() ) ); + off -= 10; // spacing + + d_knob->setGeometry( 0, d_label->pos().y() - h + off, + sz.width(), h ); + } + +private: + QwtKnob *d_knob; + QLabel *d_label; +}; + +class Thermo: public QWidget +{ +public: + Thermo( const QString &title, QWidget *parent ): + QWidget( parent ) + { + d_thermo = new QwtThermo( this ); + d_thermo->setPipeWidth( 6 ); + d_thermo->setScale( -40, 10 ); + d_thermo->setFillBrush( Qt::green ); + d_thermo->setAlarmBrush( Qt::red ); + d_thermo->setAlarmLevel( 0.0 ); + d_thermo->setAlarmEnabled( true ); + + QLabel *label = new QLabel( title, this ); + label->setAlignment( Qt::AlignTop | Qt::AlignLeft ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setMargin( 0 ); + layout->setSpacing( 0 ); + layout->addWidget( d_thermo, 10 ); + layout->addWidget( label ); + } + + void setValue( double value ) + { + d_thermo->setValue( value ); + } + +private: + QwtThermo *d_thermo; +}; + +AmpFrame::AmpFrame( QWidget *p ): + QFrame( p ) +{ + d_knbVolume = new Knob( "Volume", 0.0, 10.0, this ); + d_knbBalance = new Knob( "Balance", -10.0, 10.0, this ); + d_knbTreble = new Knob( "Treble", -10.0, 10.0, this ); + d_knbBass = new Knob( "Bass", -10.0, 10.0, this ); + + d_thmLeft = new Thermo( "Left [dB]", this ); + d_thmRight = new Thermo( "Right [dB]", this ); + + QHBoxLayout *layout = new QHBoxLayout( this ); + layout->setSpacing( 0 ); + layout->setMargin( 10 ); + layout->addWidget( d_knbVolume ); + layout->addWidget( d_knbBalance); + layout->addWidget( d_knbTreble); + layout->addWidget( d_knbBass ); + layout->addSpacing( 20 ); + layout->addStretch( 10 ); + layout->addWidget( d_thmLeft ); + layout->addSpacing( 10 ); + layout->addWidget( d_thmRight ); + + d_knbVolume->setValue( 7.0 ); + ( void )startTimer( 50 ); +} + +void AmpFrame::timerEvent( QTimerEvent * ) +{ + static double phs = 0; + + // + // This amplifier generates its own input signal... + // + + const double sig_bass = ( 1.0 + 0.1 * d_knbBass->value() ) + * qFastSin( 13.0 * phs ); + const double sig_mid_l = qFastSin( 17.0 * phs ); + const double sig_mid_r = qFastCos( 17.5 * phs ); + const double sig_trbl_l = 0.5 * ( 1.0 + 0.1 * d_knbTreble->value() ) + * qFastSin( 35.0 * phs ); + const double sig_trbl_r = 0.5 * ( 1.0 + 0.1 * d_knbTreble->value() ) + * qFastSin( 34.0 * phs ); + + double sig_l = 0.05 * d_master * d_knbVolume->value() + * qwtSqr( sig_bass + sig_mid_l + sig_trbl_l ); + double sig_r = 0.05 * d_master * d_knbVolume->value() + * qwtSqr( sig_bass + sig_mid_r + sig_trbl_r ); + + double balance = 0.1 * d_knbBalance->value(); + if ( balance > 0 ) + sig_l *= ( 1.0 - balance ); + else + sig_r *= ( 1.0 + balance ); + + if ( sig_l > 0.01 ) + sig_l = 20.0 * log10( sig_l ); + else + sig_l = -40.0; + + if ( sig_r > 0.01 ) + sig_r = 20.0 * log10( sig_r ); + else + sig_r = - 40.0; + + d_thmLeft->setValue( sig_l ); + d_thmRight->setValue( sig_r ); + + phs += M_PI / 100; + if ( phs > M_PI ) + phs = 0; +} + +void AmpFrame::setMaster( double v ) +{ + d_master = v; +} diff --git a/qwt/examples/radio/ampfrm.h b/qwt/examples/radio/ampfrm.h new file mode 100644 index 000000000..abec2a5d3 --- /dev/null +++ b/qwt/examples/radio/ampfrm.h @@ -0,0 +1,29 @@ +#include + +class Knob; +class Thermo; + +class AmpFrame : public QFrame +{ + Q_OBJECT +public: + AmpFrame( QWidget * ); + +public Q_SLOTS: + void setMaster( double v ); + +protected: + void timerEvent( QTimerEvent * ); + +private: + Knob *d_knbVolume; + Knob *d_knbBalance; + Knob *d_knbTreble; + Knob *d_knbBass; + Thermo *d_thmLeft; + Thermo *d_thmRight; + double d_master; +}; + + + diff --git a/qwt/examples/radio/mainwindow.cpp b/qwt/examples/radio/mainwindow.cpp new file mode 100644 index 000000000..ecae45734 --- /dev/null +++ b/qwt/examples/radio/mainwindow.cpp @@ -0,0 +1,50 @@ +#include +#include "tunerfrm.h" +#include "ampfrm.h" +#include "mainwindow.h" + +MainWindow::MainWindow(): + QWidget() +{ + TunerFrame *frmTuner = new TunerFrame( this ); + frmTuner->setFrameStyle( QFrame::Panel | QFrame::Raised ); + + AmpFrame *frmAmp = new AmpFrame( this ); + frmAmp->setFrameStyle( QFrame::Panel | QFrame::Raised ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setMargin( 0 ); + layout->setSpacing( 0 ); + layout->addWidget( frmTuner ); + layout->addWidget( frmAmp ); + + connect( frmTuner, SIGNAL( fieldChanged( double ) ), + frmAmp, SLOT( setMaster( double ) ) ); + + frmTuner->setFreq( 90.0 ); + + setPalette( QPalette( QColor( 192, 192, 192 ) ) ); + updateGradient(); +} + +void MainWindow::resizeEvent( QResizeEvent * ) +{ + // Qt 4.7.1: QGradient::StretchToDeviceMode is buggy on X11 + updateGradient(); +} + +void MainWindow::updateGradient() +{ + QPalette pal = palette(); + + const QColor buttonColor = pal.color( QPalette::Button ); + const QColor midLightColor = pal.color( QPalette::Midlight ); + + QLinearGradient gradient( rect().topLeft(), rect().topRight() ); + gradient.setColorAt( 0.0, midLightColor ); + gradient.setColorAt( 0.7, buttonColor ); + gradient.setColorAt( 1.0, buttonColor ); + + pal.setBrush( QPalette::Window, gradient ); + setPalette( pal ); +} diff --git a/qwt/examples/radio/mainwindow.h b/qwt/examples/radio/mainwindow.h new file mode 100644 index 000000000..b3da23559 --- /dev/null +++ b/qwt/examples/radio/mainwindow.h @@ -0,0 +1,15 @@ +#include + +class MainWindow : public QWidget +{ +public: + MainWindow(); + +protected: + virtual void resizeEvent( QResizeEvent * ); + +private: + void updateGradient(); +}; + + diff --git a/qwt/examples/radio/radio.cpp b/qwt/examples/radio/radio.cpp new file mode 100644 index 000000000..cfa60841f --- /dev/null +++ b/qwt/examples/radio/radio.cpp @@ -0,0 +1,12 @@ +#include +#include "mainwindow.h" + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/radio/radio.pro b/qwt/examples/radio/radio.pro new file mode 100644 index 000000000..5af596e6c --- /dev/null +++ b/qwt/examples/radio/radio.pro @@ -0,0 +1,23 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = radio + +HEADERS = \ + mainwindow.h \ + ampfrm.h \ + tunerfrm.h + +SOURCES = \ + mainwindow.cpp \ + ampfrm.cpp \ + tunerfrm.cpp \ + radio.cpp diff --git a/qwt/examples/radio/tunerfrm.cpp b/qwt/examples/radio/tunerfrm.cpp new file mode 100644 index 000000000..fced7df25 --- /dev/null +++ b/qwt/examples/radio/tunerfrm.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include "tunerfrm.h" + +#if QT_VERSION < 0x040600 +#define qFastSin(x) ::sin(x) +#define qFastCos(x) ::cos(x) +#endif + +class TuningThermo: public QWidget +{ +public: + TuningThermo( QWidget *parent ): + QWidget( parent ) + { + d_thermo = new QwtThermo( this ); + d_thermo->setOrientation( Qt::Horizontal ); + d_thermo->setScalePosition( QwtThermo::NoScale ); + d_thermo->setScale( 0.0, 1.0 ); + d_thermo->setFillBrush( Qt::green ); + + QLabel *label = new QLabel( "Tuning", this ); + label->setAlignment( Qt::AlignCenter ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setMargin( 0 ); + layout->addWidget( d_thermo ); + layout->addWidget( label ); + + setFixedWidth( 3 * label->sizeHint().width() ); + } + + void setValue( double value ) + { + d_thermo->setValue( value ); + } + +private: + QwtThermo *d_thermo; +}; + +TunerFrame::TunerFrame( QWidget *parent ): + QFrame( parent ) +{ + const double freqMin = 87.5; + const double freqMax = 108; + + d_sliderFrequency = new QwtSlider( this ); + d_sliderFrequency->setOrientation( Qt::Horizontal ); + d_sliderFrequency->setScalePosition( QwtSlider::TrailingScale ); + d_sliderFrequency->setScale( freqMin, freqMax ); + d_sliderFrequency->setTotalSteps( + qRound( ( freqMax - freqMin ) / 0.01 ) ); + d_sliderFrequency->setSingleSteps( 1 ); + d_sliderFrequency->setPageSteps( 10 ); + d_sliderFrequency->setScaleMaxMinor( 5 ); + d_sliderFrequency->setScaleMaxMajor( 12 ); + d_sliderFrequency->setHandleSize( QSize( 80, 20 ) ); + d_sliderFrequency->setBorderWidth( 1 ); + + d_thermoTune = new TuningThermo( this ); + + d_wheelFrequency = new QwtWheel( this ); + d_wheelFrequency->setMass( 0.5 ); + d_wheelFrequency->setRange( 87.5, 108 ); + d_wheelFrequency->setSingleStep( 0.01 ); + d_wheelFrequency->setPageStepCount( 10 ); + d_wheelFrequency->setTotalAngle( 3600.0 ); + d_wheelFrequency->setFixedHeight( 30 ); + + + connect( d_wheelFrequency, SIGNAL( valueChanged( double ) ), SLOT( adjustFreq( double ) ) ); + connect( d_sliderFrequency, SIGNAL( valueChanged( double ) ), SLOT( adjustFreq( double ) ) ); + + QVBoxLayout *mainLayout = new QVBoxLayout( this ); + mainLayout->setMargin( 10 ); + mainLayout->setSpacing( 5 ); + mainLayout->addWidget( d_sliderFrequency ); + + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->setMargin( 0 ); + hLayout->addWidget( d_thermoTune, 0 ); + hLayout->addStretch( 5 ); + hLayout->addWidget( d_wheelFrequency, 2 ); + + mainLayout->addLayout( hLayout ); +} + +void TunerFrame::adjustFreq( double frq ) +{ + const double factor = 13.0 / ( 108 - 87.5 ); + + const double x = ( frq - 87.5 ) * factor; + const double field = qwtSqr( qFastSin( x ) * qFastCos( 4.0 * x ) ); + + d_thermoTune->setValue( field ); + + if ( d_sliderFrequency->value() != frq ) + d_sliderFrequency->setValue( frq ); + if ( d_wheelFrequency->value() != frq ) + d_wheelFrequency->setValue( frq ); + + Q_EMIT fieldChanged( field ); +} + +void TunerFrame::setFreq( double frq ) +{ + d_wheelFrequency->setValue( frq ); +} diff --git a/qwt/examples/radio/tunerfrm.h b/qwt/examples/radio/tunerfrm.h new file mode 100644 index 000000000..48dd1bcd8 --- /dev/null +++ b/qwt/examples/radio/tunerfrm.h @@ -0,0 +1,30 @@ +#include + +class QwtWheel; +class QwtSlider; +class TuningThermo; + +class TunerFrame : public QFrame +{ + Q_OBJECT +public: + TunerFrame( QWidget *p ); + +Q_SIGNALS: + void fieldChanged( double f ); + +public Q_SLOTS: + void setFreq( double frq ); + +private Q_SLOTS: + void adjustFreq( double frq ); + +private: + QwtWheel *d_wheelFrequency; + TuningThermo *d_thermoTune; + QwtSlider *d_sliderFrequency; +}; + + + + diff --git a/qwt/examples/rasterview/main.cpp b/qwt/examples/rasterview/main.cpp new file mode 100644 index 000000000..da9d09f1b --- /dev/null +++ b/qwt/examples/rasterview/main.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + Plot *plot = new Plot( this ); + setCentralWidget( plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *rasterBox = new QComboBox( toolBar ); + rasterBox->addItem( "Wikipedia" ); + + toolBar->addWidget( new QLabel( "Data ", toolBar ) ); + toolBar->addWidget( rasterBox ); + toolBar->addSeparator(); + + QComboBox *modeBox = new QComboBox( toolBar ); + modeBox->addItem( "Nearest Neighbour" ); + modeBox->addItem( "Bilinear Interpolation" ); + + toolBar->addWidget( new QLabel( "Resampling ", toolBar ) ); + toolBar->addWidget( modeBox ); + + toolBar->addSeparator(); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnExport ); + + addToolBar( toolBar ); + + connect( modeBox, SIGNAL( activated( int ) ), plot, SLOT( setResampleMode( int ) ) ); + connect( btnExport, SIGNAL( clicked() ), plot, SLOT( exportPlot() ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/rasterview/plot.cpp b/qwt/examples/rasterview/plot.cpp new file mode 100644 index 000000000..35568ad17 --- /dev/null +++ b/qwt/examples/rasterview/plot.cpp @@ -0,0 +1,112 @@ +#include "plot.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class RasterData: public QwtMatrixRasterData +{ +public: + RasterData() + { + const double matrix[] = + { + 1, 2, 4, 1, + 6, 3, 5, 2, + 4, 2, 1, 5, + 5, 4, 2, 3 + }; + + QVector values; + for ( uint i = 0; i < sizeof( matrix ) / sizeof( double ); i++ ) + values += matrix[i]; + + const int numColumns = 4; + setValueMatrix( values, numColumns ); + + setInterval( Qt::XAxis, + QwtInterval( -0.5, 3.5, QwtInterval::ExcludeMaximum ) ); + setInterval( Qt::YAxis, + QwtInterval( -0.5, 3.5, QwtInterval::ExcludeMaximum ) ); + setInterval( Qt::ZAxis, QwtInterval( 1.0, 6.0 ) ); + } +}; + +class ColorMap: public QwtLinearColorMap +{ +public: + ColorMap(): + QwtLinearColorMap( Qt::darkBlue, Qt::darkRed ) + { + addColorStop( 0.2, Qt::blue ); + addColorStop( 0.4, Qt::cyan ); + addColorStop( 0.6, Qt::yellow ); + addColorStop( 0.8, Qt::red ); + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setBorderRadius( 10 ); + setCanvas( canvas ); + +#if 0 + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->setPen( Qt::DotLine ); + grid->attach( this ); +#endif + + d_spectrogram = new QwtPlotSpectrogram(); + d_spectrogram->setRenderThreadCount( 0 ); // use system specific thread count + + d_spectrogram->setColorMap( new ColorMap() ); + + d_spectrogram->setData( new RasterData() ); + d_spectrogram->attach( this ); + + const QwtInterval zInterval = d_spectrogram->data()->interval( Qt::ZAxis ); + // A color bar on the right axis + QwtScaleWidget *rightAxis = axisWidget( QwtAxis::yRight ); + rightAxis->setColorBarEnabled( true ); + rightAxis->setColorBarWidth( 40 ); + rightAxis->setColorMap( zInterval, new ColorMap() ); + + setAxisScale( QwtAxis::yRight, zInterval.minValue(), zInterval.maxValue() ); + setAxisVisible( QwtAxis::yRight ); + + plotLayout()->setAlignCanvasToScales( true ); + + setAxisScale( QwtAxis::xBottom, 0.0, 3.0 ); + setAxisMaxMinor( QwtAxis::xBottom, 0 ); + setAxisScale( QwtAxis::yLeft, 0.0, 3.0 ); + setAxisMaxMinor( QwtAxis::yLeft, 0 ); + + QwtPlotMagnifier *magnifier = new QwtPlotMagnifier( canvas ); + magnifier->setAxisEnabled( QwtAxis::yRight, false ); + + QwtPlotPanner *panner = new QwtPlotPanner( canvas ); + panner->setAxisEnabled( QwtAxis::yRight, false ); +} + +void Plot::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "rasterview.pdf" ); +} + +void Plot::setResampleMode( int mode ) +{ + RasterData *data = static_cast( d_spectrogram->data() ); + data->setResampleMode( + static_cast( mode ) ); + + replot(); +} diff --git a/qwt/examples/rasterview/plot.h b/qwt/examples/rasterview/plot.h new file mode 100644 index 000000000..140145f9f --- /dev/null +++ b/qwt/examples/rasterview/plot.h @@ -0,0 +1,17 @@ +#include +#include + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + +public Q_SLOTS: + void exportPlot(); + void setResampleMode( int ); + +private: + QwtPlotSpectrogram *d_spectrogram; +}; diff --git a/qwt/examples/rasterview/rasterview.pro b/qwt/examples/rasterview/rasterview.pro new file mode 100644 index 000000000..15fc8cc42 --- /dev/null +++ b/qwt/examples/rasterview/rasterview.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = rasterview + +HEADERS = \ + plot.h + +SOURCES = \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/realtime/README b/qwt/examples/realtime/README new file mode 100644 index 000000000..32c7f1393 --- /dev/null +++ b/qwt/examples/realtime/README @@ -0,0 +1,25 @@ +1) Incremental plots + +IncrementalPlot shows an example how to implement a plot that +displays growing data. + +The example produces random data when you push the start button. +With 'Timer' you can adjust the intervall between the +the generation of the points, with 'Points' you can set the number +of points to be generated. + +Unfortunately in Qt4 incremental painting is not possible with QPaintEngines +that doesn't support the QPaintEngine::PaintOutsidePaintEvent feature. +( These are all common paint engines beside the OpenGL engine, but this one +is not supported by Qwt yet. ) +That is the reason why you can see much faster repaints with Qt3. + +2) Stacked Zooming with scrollbars + +ScrollZoomer adds scrollbars for zooming. There are a couple of +reasons why the implementation is a hack and therefore the class +is not part of the Qwt lib, but it should be working with all +types of QwtPlots. Copy the code of scrollbar.[h|cpp] and +scrollzoomer.[h|cpp] to the application code. + +Uwe diff --git a/qwt/examples/realtime/clear.xpm b/qwt/examples/realtime/clear.xpm new file mode 100644 index 000000000..86c72b849 --- /dev/null +++ b/qwt/examples/realtime/clear.xpm @@ -0,0 +1,51 @@ +/* XPM */ +static const char *clear_xpm[] = { +/* width height num_colors chars_per_pixel */ +" 32 32 12 1", +/* colors */ +". c #000000", +"# c #004040", +"a c #303030", +"b c #400000", +"c c #404000", +"d c #585858", +"e c #808080", +"f c #a0a0a4", +"g c #bdbdbd", +"h c #c0c0c0", +"i c #dcdcdc", +"j c #ffffff", +/* pixels */ +"gggggggggggggggggggggggggggggggg", +"gggggggggggggg..gggggggggggggggg", +"gggggggggg....ficggggggggggggggg", +"ggggggg...fdad#ai......ggggggggg", +"gggg...fhjjidfbc#f.fffe...gggggg", +"ggg.fhjjjjihc#dhef.fhhhffe.ggggg", +"ggg.#jjjjjihhhhhe..ehhhfff.ggggg", +"ggg.#dffjjjjiihhcadehhfddd.ggggg", +"ggg.iiiffhfjjjjjhhhfdddddd.ggggg", +"ggg.#fjjiiffeeeeddddeeeddd.ggggg", +"gggg.#eeiiiiifffffffeee...gggggg", +"gggg.ffffffiifffffffddddd.gggggg", +"gggg.fffjfffeeeeddddeed.d.gggggg", +"gggg.fefiiiifhffeeeeded.d.gggggg", +"gggg.fefijhfhifefff.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fffijeffifeefe.deddd.gggggg", +"gggg.ffiijeffifeefeddedd#.gggggg", +"gggg.eiijjjeiifdffedded#..gggggg", +"ggggg..fjjiiiiffffedddd..ggggggg", +"ggggggg...fhhfffffdd...ggggggggg", +"gggggggggg..........gggggggggggg" +}; diff --git a/qwt/examples/realtime/incrementalplot.cpp b/qwt/examples/realtime/incrementalplot.cpp new file mode 100644 index 000000000..85a349afc --- /dev/null +++ b/qwt/examples/realtime/incrementalplot.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include +#include "incrementalplot.h" +#include + +class CurveData: public QwtArraySeriesData +{ +public: + CurveData() + { + } + + virtual QRectF boundingRect() const + { + if ( d_boundingRect.width() < 0.0 ) + d_boundingRect = qwtBoundingRect( *this ); + + return d_boundingRect; + } + + inline void append( const QPointF &point ) + { + d_samples += point; + } + + void clear() + { + d_samples.clear(); + d_samples.squeeze(); + d_boundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); + } +}; + +IncrementalPlot::IncrementalPlot( QWidget *parent ): + QwtPlot( parent ), + d_curve( NULL ) +{ + d_directPainter = new QwtPlotDirectPainter( this ); + + if ( QwtPainter::isX11GraphicsSystem() ) + { +#if QT_VERSION < 0x050000 + canvas()->setAttribute( Qt::WA_PaintOutsidePaintEvent, true ); +#endif + canvas()->setAttribute( Qt::WA_PaintOnScreen, true ); + } + + d_curve = new QwtPlotCurve( "Test Curve" ); + d_curve->setData( new CurveData() ); + showSymbols( true ); + + d_curve->attach( this ); + + setAutoReplot( false ); +} + +IncrementalPlot::~IncrementalPlot() +{ + delete d_curve; +} + +void IncrementalPlot::appendPoint( const QPointF &point ) +{ + CurveData *data = static_cast( d_curve->data() ); + data->append( point ); + + const bool doClip = !canvas()->testAttribute( Qt::WA_PaintOnScreen ); + if ( doClip ) + { + /* + Depending on the platform setting a clip might be an important + performance issue. F.e. for Qt Embedded this reduces the + part of the backing store that has to be copied out - maybe + to an unaccelerated frame buffer device. + */ + const QwtScaleMap xMap = canvasMap( d_curve->xAxis() ); + const QwtScaleMap yMap = canvasMap( d_curve->yAxis() ); + + QRegion clipRegion; + + const QSize symbolSize = d_curve->symbol()->size(); + QRect r( 0, 0, symbolSize.width() + 2, symbolSize.height() + 2 ); + + const QPointF center = + QwtScaleMap::transform( xMap, yMap, point ); + r.moveCenter( center.toPoint() ); + clipRegion += r; + + d_directPainter->setClipRegion( clipRegion ); + } + + d_directPainter->drawSeries( d_curve, + data->size() - 1, data->size() - 1 ); +} + +void IncrementalPlot::clearPoints() +{ + CurveData *data = static_cast( d_curve->data() ); + data->clear(); + + replot(); +} + +void IncrementalPlot::showSymbols( bool on ) +{ + if ( on ) + { + d_curve->setStyle( QwtPlotCurve::NoCurve ); + d_curve->setSymbol( new QwtSymbol( QwtSymbol::XCross, + Qt::NoBrush, QPen( Qt::white ), QSize( 4, 4 ) ) ); + } + else + { + d_curve->setPen( Qt::white ); + d_curve->setStyle( QwtPlotCurve::Dots ); + d_curve->setSymbol( NULL ); + } + + replot(); +} diff --git a/qwt/examples/realtime/incrementalplot.h b/qwt/examples/realtime/incrementalplot.h new file mode 100644 index 000000000..33f19a111 --- /dev/null +++ b/qwt/examples/realtime/incrementalplot.h @@ -0,0 +1,28 @@ +#ifndef _INCREMENTALPLOT_H_ +#define _INCREMENTALPLOT_H_ 1 + +#include + +class QwtPlotCurve; +class QwtPlotDirectPainter; + +class IncrementalPlot : public QwtPlot +{ + Q_OBJECT + +public: + IncrementalPlot( QWidget *parent = NULL ); + virtual ~IncrementalPlot(); + + void appendPoint( const QPointF & ); + void clearPoints(); + +public Q_SLOTS: + void showSymbols( bool ); + +private: + QwtPlotCurve *d_curve; + QwtPlotDirectPainter *d_directPainter; +}; + +#endif // _INCREMENTALPLOT_H_ diff --git a/qwt/examples/realtime/main.cpp b/qwt/examples/realtime/main.cpp new file mode 100644 index 000000000..3e33f781e --- /dev/null +++ b/qwt/examples/realtime/main.cpp @@ -0,0 +1,12 @@ +#include +#include "mainwindow.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/realtime/mainwindow.cpp b/qwt/examples/realtime/mainwindow.cpp new file mode 100644 index 000000000..b06be4c0b --- /dev/null +++ b/qwt/examples/realtime/mainwindow.cpp @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "randomplot.h" +#include "mainwindow.h" +#include "start.xpm" +#include "clear.xpm" + +class MyToolBar: public QToolBar +{ +public: + MyToolBar( MainWindow *parent ): + QToolBar( parent ) + { + } + void addSpacing( int spacing ) + { + QLabel *label = new QLabel( this ); + addWidget( label ); + label->setFixedWidth( spacing ); + } +}; + +class Counter: public QWidget +{ +public: + Counter( QWidget *parent, + const QString &prefix, const QString &suffix, + int min, int max, int step ): + QWidget( parent ) + { + QHBoxLayout *layout = new QHBoxLayout( this ); + + if ( !prefix.isEmpty() ) + layout->addWidget( new QLabel( prefix + " ", this ) ); + + d_counter = new QSpinBox( this ); + d_counter->setRange( min, max ); + d_counter->setSingleStep( step ); + layout->addWidget( d_counter ); + + if ( !suffix.isEmpty() ) + layout->addWidget( new QLabel( QString( " " ) + suffix, this ) ); + } + + void setValue( int value ) { d_counter->setValue( value ); } + int value() const { return d_counter->value(); } + +private: + QSpinBox *d_counter; +}; + +MainWindow::MainWindow() +{ + addToolBar( toolBar() ); +#ifndef QT_NO_STATUSBAR + ( void )statusBar(); +#endif + + d_plot = new RandomPlot( this ); + const int margin = 4; + d_plot->setContentsMargins( margin, margin, margin, margin ); + + setCentralWidget( d_plot ); + + connect( d_startAction, SIGNAL( toggled( bool ) ), this, SLOT( appendPoints( bool ) ) ); + connect( d_clearAction, SIGNAL( triggered() ), d_plot, SLOT( clear() ) ); + connect( d_symbolType, SIGNAL( toggled( bool ) ), d_plot, SLOT( showSymbols( bool ) ) ); + connect( d_plot, SIGNAL( running( bool ) ), this, SLOT( showRunning( bool ) ) ); + connect( d_plot, SIGNAL( elapsed( int ) ), this, SLOT( showElapsed( int ) ) ); + + initWhatsThis(); + + setContextMenuPolicy( Qt::NoContextMenu ); +} + +QToolBar *MainWindow::toolBar() +{ + MyToolBar *toolBar = new MyToolBar( this ); + + toolBar->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); + setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + + d_startAction = new QAction( QPixmap( start_xpm ), "Start", toolBar ); + d_startAction->setCheckable( true ); + d_clearAction = new QAction( QPixmap( clear_xpm ), "Clear", toolBar ); + QAction *whatsThisAction = QWhatsThis::createAction( toolBar ); + whatsThisAction->setText( "Help" ); + + toolBar->addAction( d_startAction ); + toolBar->addAction( d_clearAction ); + toolBar->addAction( whatsThisAction ); + + setIconSize( QSize( 22, 22 ) ); + + QWidget *hBox = new QWidget( toolBar ); + + d_symbolType = new QCheckBox( "Symbols", hBox ); + d_symbolType->setChecked( true ); + + d_randomCount = + new Counter( hBox, "Points", QString::null, 1, 100000, 100 ); + d_randomCount->setValue( 1000 ); + + d_timerCount = new Counter( hBox, "Delay", "ms", 0, 100000, 100 ); + d_timerCount->setValue( 0 ); + + QHBoxLayout *layout = new QHBoxLayout( hBox ); + layout->setMargin( 0 ); + layout->setSpacing( 0 ); + layout->addSpacing( 10 ); + layout->addWidget( new QWidget( hBox ), 10 ); // spacer + layout->addWidget( d_symbolType ); + layout->addSpacing( 5 ); + layout->addWidget( d_randomCount ); + layout->addSpacing( 5 ); + layout->addWidget( d_timerCount ); + + showRunning( false ); + + toolBar->addWidget( hBox ); + + return toolBar; +} + +void MainWindow::appendPoints( bool on ) +{ + if ( on ) + d_plot->append( d_timerCount->value(), + d_randomCount->value() ); + else + d_plot->stop(); +} + +void MainWindow::showRunning( bool running ) +{ + d_randomCount->setEnabled( !running ); + d_timerCount->setEnabled( !running ); + d_startAction->setChecked( running ); + d_startAction->setText( running ? "Stop" : "Start" ); +} + +void MainWindow::showElapsed( int ms ) +{ + QString text; + text.setNum( ms ); + text += " ms"; + + statusBar()->showMessage( text ); +} + +void MainWindow::initWhatsThis() +{ + const char *text1 = + "Zooming is enabled until the selected area gets " + "too small for the significance on the axes.\n\n" + "You can zoom in using the left mouse button.\n" + "The middle mouse button is used to go back to the " + "previous zoomed area.\n" + "The right mouse button is used to unzoom completely."; + + const char *text2 = + "Number of random points that will be generated."; + + const char *text3 = + "Delay between the generation of two random points."; + + const char *text4 = + "Start generation of random points.\n\n" + "The intention of this example is to show how to implement " + "growing curves. The points will be generated and displayed " + "one after the other.\n" + "To check the performance, a small delay and a large number " + "of points are useful. To watch the curve growing, a delay " + " > 300 ms and less points are better.\n" + "To inspect the curve, stacked zooming is implemented using the " + "mouse buttons on the plot."; + + const char *text5 = "Remove all points."; + + d_plot->setWhatsThis( text1 ); + d_randomCount->setWhatsThis( text2 ); + d_timerCount->setWhatsThis( text3 ); + d_startAction->setWhatsThis( text4 ); + d_clearAction->setWhatsThis( text5 ); +} + diff --git a/qwt/examples/realtime/mainwindow.h b/qwt/examples/realtime/mainwindow.h new file mode 100644 index 000000000..bc6975515 --- /dev/null +++ b/qwt/examples/realtime/mainwindow.h @@ -0,0 +1,37 @@ +#ifndef _MAINWINDOW_H_ +#define _MAINWINDOW_H_ 1 + +#include +#include + +class QSpinBox; +class QPushButton; +class RandomPlot; +class Counter; +class QCheckBox; + +class MainWindow: public QMainWindow +{ + Q_OBJECT +public: + MainWindow(); + +private Q_SLOTS: + void showRunning( bool ); + void appendPoints( bool ); + void showElapsed( int ); + +private: + QToolBar *toolBar(); + void initWhatsThis(); + +private: + Counter *d_randomCount; + Counter *d_timerCount; + QCheckBox *d_symbolType; + QAction *d_startAction; + QAction *d_clearAction; + RandomPlot *d_plot; +}; + +#endif diff --git a/qwt/examples/realtime/randomplot.cpp b/qwt/examples/realtime/randomplot.cpp new file mode 100644 index 000000000..f08d6141a --- /dev/null +++ b/qwt/examples/realtime/randomplot.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include +#include "scrollzoomer.h" +#include "randomplot.h" + +const unsigned int c_rangeMax = 1000; + +class Zoomer: public ScrollZoomer +{ +public: + Zoomer( QWidget *canvas ): + ScrollZoomer( canvas ) + { +#if 0 + setRubberBandPen( QPen( Qt::red, 2, Qt::DotLine ) ); +#else + setRubberBandPen( QPen( Qt::red ) ); +#endif + } + + virtual QwtText trackerTextF( const QPointF &pos ) const + { + QColor bg( Qt::white ); + + QwtText text = QwtPlotZoomer::trackerTextF( pos ); + text.setBackgroundBrush( QBrush( bg ) ); + return text; + } + + virtual void rescale() + { + QwtScaleWidget *scaleWidget = plot()->axisWidget( yAxis() ); + QwtScaleDraw *sd = scaleWidget->scaleDraw(); + + double minExtent = 0.0; + if ( zoomRectIndex() > 0 ) + { + // When scrolling in vertical direction + // the plot is jumping in horizontal direction + // because of the different widths of the labels + // So we better use a fixed extent. + + minExtent = sd->spacing() + sd->maxTickLength() + 1; + minExtent += sd->labelSize( + scaleWidget->font(), c_rangeMax ).width(); + } + + sd->setMinimumExtent( minExtent ); + + ScrollZoomer::rescale(); + } +}; + +RandomPlot::RandomPlot( QWidget *parent ): + IncrementalPlot( parent ), + d_timer( 0 ), + d_timerCount( 0 ) +{ + setFrameStyle( QFrame::NoFrame ); + setLineWidth( 0 ); + + plotLayout()->setAlignCanvasToScales( true ); + + QwtPlotGrid *grid = new QwtPlotGrid; + grid->setMajorPen( Qt::gray, 0, Qt::DotLine ); + grid->attach( this ); + + setCanvasBackground( QColor( 29, 100, 141 ) ); // nice blue + + setAxisScale( QwtAxis::xBottom, 0, c_rangeMax ); + setAxisScale( QwtAxis::yLeft, 0, c_rangeMax ); + + replot(); + + // enable zooming + + ( void ) new Zoomer( canvas() ); +} + +QSize RandomPlot::sizeHint() const +{ + return QSize( 540, 400 ); +} + +void RandomPlot::appendPoint() +{ + double x = qrand() % c_rangeMax; + x += ( qrand() % 100 ) / 100; + + double y = qrand() % c_rangeMax; + y += ( qrand() % 100 ) / 100; + + IncrementalPlot::appendPoint( QPointF( x, y ) ); + + if ( --d_timerCount <= 0 ) + stop(); +} + +void RandomPlot::append( int timeout, int count ) +{ + if ( !d_timer ) + { + d_timer = new QTimer( this ); + connect( d_timer, SIGNAL( timeout() ), SLOT( appendPoint() ) ); + } + + d_timerCount = count; + + Q_EMIT running( true ); + d_timeStamp.start(); + + QwtPlotCanvas *plotCanvas = qobject_cast( canvas() ); + plotCanvas->setPaintAttribute( QwtPlotCanvas::BackingStore, false ); + + d_timer->start( timeout ); +} + +void RandomPlot::stop() +{ + Q_EMIT elapsed( d_timeStamp.elapsed() ); + + if ( d_timer ) + { + d_timer->stop(); + Q_EMIT running( false ); + } + + QwtPlotCanvas *plotCanvas = qobject_cast( canvas() ); + plotCanvas->setPaintAttribute( QwtPlotCanvas::BackingStore, true ); +} + +void RandomPlot::clear() +{ + clearPoints(); + replot(); +} diff --git a/qwt/examples/realtime/randomplot.h b/qwt/examples/realtime/randomplot.h new file mode 100644 index 000000000..44d1ba4bc --- /dev/null +++ b/qwt/examples/realtime/randomplot.h @@ -0,0 +1,39 @@ +#ifndef _RANDOMPLOT_H_ +#define _RANDOMPLOT_H_ 1 + +#include "incrementalplot.h" +#include + +class QTimer; + +class RandomPlot: public IncrementalPlot +{ + Q_OBJECT + +public: + RandomPlot( QWidget *parent ); + + virtual QSize sizeHint() const; + +Q_SIGNALS: + void running( bool ); + void elapsed( int ms ); + +public Q_SLOTS: + void clear(); + void stop(); + void append( int timeout, int count ); + +private Q_SLOTS: + void appendPoint(); + +private: + void initCurve(); + + QTimer *d_timer; + int d_timerCount; + + QTime d_timeStamp; +}; + +#endif // _RANDOMPLOT_H_ diff --git a/qwt/examples/realtime/realtime.pro b/qwt/examples/realtime/realtime.pro new file mode 100644 index 000000000..5f726d7b0 --- /dev/null +++ b/qwt/examples/realtime/realtime.pro @@ -0,0 +1,28 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = realtime + +HEADERS = \ + mainwindow.h \ + scrollzoomer.h \ + scrollbar.h \ + incrementalplot.h \ + randomplot.h + +SOURCES = \ + main.cpp \ + mainwindow.cpp \ + scrollzoomer.cpp \ + scrollbar.cpp \ + incrementalplot.cpp \ + randomplot.cpp + diff --git a/qwt/examples/realtime/scrollbar.cpp b/qwt/examples/realtime/scrollbar.cpp new file mode 100644 index 000000000..bd44c6c53 --- /dev/null +++ b/qwt/examples/realtime/scrollbar.cpp @@ -0,0 +1,170 @@ +#include +#include +#include "scrollbar.h" + +ScrollBar::ScrollBar( QWidget * parent ): + QScrollBar( parent ) +{ + init(); +} + +ScrollBar::ScrollBar( Qt::Orientation o, + QWidget *parent ): + QScrollBar( o, parent ) +{ + init(); +} + +ScrollBar::ScrollBar( double minBase, double maxBase, + Qt::Orientation o, QWidget *parent ): + QScrollBar( o, parent ) +{ + init(); + setBase( minBase, maxBase ); + moveSlider( minBase, maxBase ); +} + +void ScrollBar::init() +{ + d_inverted = orientation() == Qt::Vertical; + d_baseTicks = 1000000; + d_minBase = 0.0; + d_maxBase = 1.0; + moveSlider( d_minBase, d_maxBase ); + + connect( this, SIGNAL( sliderMoved( int ) ), SLOT( catchSliderMoved( int ) ) ); + connect( this, SIGNAL( valueChanged( int ) ), SLOT( catchValueChanged( int ) ) ); +} + +void ScrollBar::setInverted( bool inverted ) +{ + if ( d_inverted != inverted ) + { + d_inverted = inverted; + moveSlider( minSliderValue(), maxSliderValue() ); + } +} + +bool ScrollBar::isInverted() const +{ + return d_inverted; +} + +void ScrollBar::setBase( double min, double max ) +{ + if ( min != d_minBase || max != d_maxBase ) + { + d_minBase = min; + d_maxBase = max; + + moveSlider( minSliderValue(), maxSliderValue() ); + } +} + +void ScrollBar::moveSlider( double min, double max ) +{ + const int sliderTicks = qRound( ( max - min ) / + ( d_maxBase - d_minBase ) * d_baseTicks ); + + // setRange initiates a valueChanged of the scrollbars + // in some situations. So we block + // and unblock the signals. + + blockSignals( true ); + + setRange( sliderTicks / 2, d_baseTicks - sliderTicks / 2 ); + int steps = sliderTicks / 200; + if ( steps <= 0 ) + steps = 1; + + setSingleStep( steps ); + setPageStep( sliderTicks ); + + int tick = mapToTick( min + ( max - min ) / 2 ); + if ( isInverted() ) + tick = d_baseTicks - tick; + + setSliderPosition( tick ); + blockSignals( false ); +} + +double ScrollBar::minBaseValue() const +{ + return d_minBase; +} + +double ScrollBar::maxBaseValue() const +{ + return d_maxBase; +} + +void ScrollBar::sliderRange( int value, double &min, double &max ) const +{ + if ( isInverted() ) + value = d_baseTicks - value; + + const int visibleTicks = pageStep(); + + min = mapFromTick( value - visibleTicks / 2 ); + max = mapFromTick( value + visibleTicks / 2 ); +} + +double ScrollBar::minSliderValue() const +{ + double min, dummy; + sliderRange( value(), min, dummy ); + + return min; +} + +double ScrollBar::maxSliderValue() const +{ + double max, dummy; + sliderRange( value(), dummy, max ); + + return max; +} + +int ScrollBar::mapToTick( double v ) const +{ + const double pos = ( v - d_minBase ) / ( d_maxBase - d_minBase ) * d_baseTicks; + return static_cast( pos ); +} + +double ScrollBar::mapFromTick( int tick ) const +{ + return d_minBase + ( d_maxBase - d_minBase ) * tick / d_baseTicks; +} + +void ScrollBar::catchValueChanged( int value ) +{ + double min, max; + sliderRange( value, min, max ); + Q_EMIT valueChanged( orientation(), min, max ); +} + +void ScrollBar::catchSliderMoved( int value ) +{ + double min, max; + sliderRange( value, min, max ); + Q_EMIT sliderMoved( orientation(), min, max ); +} + +int ScrollBar::extent() const +{ + QStyleOptionSlider opt; + opt.init( this ); + opt.subControls = QStyle::SC_None; + opt.activeSubControls = QStyle::SC_None; + opt.orientation = orientation(); + opt.minimum = minimum(); + opt.maximum = maximum(); + opt.sliderPosition = sliderPosition(); + opt.sliderValue = value(); + opt.singleStep = singleStep(); + opt.pageStep = pageStep(); + opt.upsideDown = invertedAppearance(); + if ( orientation() == Qt::Horizontal ) + opt.state |= QStyle::State_Horizontal; + return style()->pixelMetric( QStyle::PM_ScrollBarExtent, &opt, this ); +} diff --git a/qwt/examples/realtime/scrollbar.h b/qwt/examples/realtime/scrollbar.h new file mode 100644 index 000000000..821cc79ef --- /dev/null +++ b/qwt/examples/realtime/scrollbar.h @@ -0,0 +1,53 @@ +#ifndef _SCROLLBAR_H +#define _SCROLLBAR_H 1 + +#include + +class ScrollBar: public QScrollBar +{ + Q_OBJECT + +public: + ScrollBar( QWidget *parent = NULL ); + ScrollBar( Qt::Orientation, QWidget *parent = NULL ); + ScrollBar( double minBase, double maxBase, + Qt::Orientation o, QWidget *parent = NULL ); + + void setInverted( bool ); + bool isInverted() const; + + double minBaseValue() const; + double maxBaseValue() const; + + double minSliderValue() const; + double maxSliderValue() const; + + int extent() const; + +Q_SIGNALS: + void sliderMoved( Qt::Orientation, double, double ); + void valueChanged( Qt::Orientation, double, double ); + +public Q_SLOTS: + virtual void setBase( double min, double max ); + virtual void moveSlider( double min, double max ); + +protected: + void sliderRange( int value, double &min, double &max ) const; + int mapToTick( double ) const; + double mapFromTick( int ) const; + +private Q_SLOTS: + void catchValueChanged( int value ); + void catchSliderMoved( int value ); + +private: + void init(); + + bool d_inverted; + double d_minBase; + double d_maxBase; + int d_baseTicks; +}; + +#endif diff --git a/qwt/examples/realtime/scrollzoomer.cpp b/qwt/examples/realtime/scrollzoomer.cpp new file mode 100644 index 000000000..dd219deaa --- /dev/null +++ b/qwt/examples/realtime/scrollzoomer.cpp @@ -0,0 +1,478 @@ +#include +#include +#include +#include +#include +#include "scrollbar.h" +#include "scrollzoomer.h" + +class ScrollData +{ +public: + ScrollData(): + scrollBar( NULL ), + position( ScrollZoomer::OppositeToScale ), + mode( Qt::ScrollBarAsNeeded ) + { + } + + ~ScrollData() + { + delete scrollBar; + } + + ScrollBar *scrollBar; + ScrollZoomer::ScrollBarPosition position; + Qt::ScrollBarPolicy mode; +}; + +ScrollZoomer::ScrollZoomer( QWidget *canvas ): + QwtPlotZoomer( canvas ), + d_cornerWidget( NULL ), + d_hScrollData( NULL ), + d_vScrollData( NULL ), + d_inZoom( false ) +{ + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + d_alignCanvasToScales[ axis ] = false; + + if ( !canvas ) + return; + + d_hScrollData = new ScrollData; + d_vScrollData = new ScrollData; +} + +ScrollZoomer::~ScrollZoomer() +{ + delete d_cornerWidget; + delete d_vScrollData; + delete d_hScrollData; +} + +void ScrollZoomer::rescale() +{ + QwtScaleWidget *xScale = plot()->axisWidget( xAxis() ); + QwtScaleWidget *yScale = plot()->axisWidget( yAxis() ); + + if ( zoomRectIndex() <= 0 ) + { + if ( d_inZoom ) + { + xScale->setMinBorderDist( 0, 0 ); + yScale->setMinBorderDist( 0, 0 ); + + QwtPlotLayout *layout = plot()->plotLayout(); + + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + layout->setAlignCanvasToScale( axis, d_alignCanvasToScales ); + + d_inZoom = false; + } + } + else + { + if ( !d_inZoom ) + { + /* + We set a minimum border distance. + Otherwise the canvas size changes when scrolling, + between situations where the major ticks are at + the canvas borders (requiring extra space for the label) + and situations where all labels can be painted below/top + or left/right of the canvas. + */ + int start, end; + + xScale->getBorderDistHint( start, end ); + xScale->setMinBorderDist( start, end ); + + yScale->getBorderDistHint( start, end ); + yScale->setMinBorderDist( start, end ); + + QwtPlotLayout *layout = plot()->plotLayout(); + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + { + d_alignCanvasToScales[axis] = + layout->alignCanvasToScale( axis ); + } + + layout->setAlignCanvasToScales( false ); + + d_inZoom = true; + } + } + + QwtPlotZoomer::rescale(); + updateScrollBars(); +} + +ScrollBar *ScrollZoomer::scrollBar( Qt::Orientation orientation ) +{ + ScrollBar *&sb = ( orientation == Qt::Vertical ) + ? d_vScrollData->scrollBar : d_hScrollData->scrollBar; + + if ( sb == NULL ) + { + sb = new ScrollBar( orientation, canvas() ); + sb->hide(); + connect( sb, + SIGNAL( valueChanged( Qt::Orientation, double, double ) ), + SLOT( scrollBarMoved( Qt::Orientation, double, double ) ) ); + } + return sb; +} + +ScrollBar *ScrollZoomer::horizontalScrollBar() const +{ + return d_hScrollData->scrollBar; +} + +ScrollBar *ScrollZoomer::verticalScrollBar() const +{ + return d_vScrollData->scrollBar; +} + +void ScrollZoomer::setHScrollBarMode( Qt::ScrollBarPolicy mode ) +{ + if ( hScrollBarMode() != mode ) + { + d_hScrollData->mode = mode; + updateScrollBars(); + } +} + +void ScrollZoomer::setVScrollBarMode( Qt::ScrollBarPolicy mode ) +{ + if ( vScrollBarMode() != mode ) + { + d_vScrollData->mode = mode; + updateScrollBars(); + } +} + +Qt::ScrollBarPolicy ScrollZoomer::hScrollBarMode() const +{ + return d_hScrollData->mode; +} + +Qt::ScrollBarPolicy ScrollZoomer::vScrollBarMode() const +{ + return d_vScrollData->mode; +} + +void ScrollZoomer::setHScrollBarPosition( ScrollBarPosition pos ) +{ + if ( d_hScrollData->position != pos ) + { + d_hScrollData->position = pos; + updateScrollBars(); + } +} + +void ScrollZoomer::setVScrollBarPosition( ScrollBarPosition pos ) +{ + if ( d_vScrollData->position != pos ) + { + d_vScrollData->position = pos; + updateScrollBars(); + } +} + +ScrollZoomer::ScrollBarPosition ScrollZoomer::hScrollBarPosition() const +{ + return d_hScrollData->position; +} + +ScrollZoomer::ScrollBarPosition ScrollZoomer::vScrollBarPosition() const +{ + return d_vScrollData->position; +} + +void ScrollZoomer::setCornerWidget( QWidget *w ) +{ + if ( w != d_cornerWidget ) + { + if ( canvas() ) + { + delete d_cornerWidget; + d_cornerWidget = w; + if ( d_cornerWidget->parent() != canvas() ) + d_cornerWidget->setParent( canvas() ); + + updateScrollBars(); + } + } +} + +QWidget *ScrollZoomer::cornerWidget() const +{ + return d_cornerWidget; +} + +bool ScrollZoomer::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == canvas() ) + { + switch( event->type() ) + { + case QEvent::Resize: + { + int left, top, right, bottom; + canvas()->getContentsMargins( &left, &top, &right, &bottom ); + + QRect rect; + rect.setSize( static_cast( event )->size() ); + rect.adjust( left, top, -right, -bottom ); + + layoutScrollBars( rect ); + break; + } + case QEvent::ChildRemoved: + { + const QObject *child = + static_cast( event )->child(); + + if ( child == d_cornerWidget ) + d_cornerWidget = NULL; + else if ( child == d_hScrollData->scrollBar ) + d_hScrollData->scrollBar = NULL; + else if ( child == d_vScrollData->scrollBar ) + d_vScrollData->scrollBar = NULL; + break; + } + default: + break; + } + } + return QwtPlotZoomer::eventFilter( object, event ); +} + +bool ScrollZoomer::needScrollBar( Qt::Orientation orientation ) const +{ + Qt::ScrollBarPolicy mode; + double zoomMin, zoomMax, baseMin, baseMax; + + if ( orientation == Qt::Horizontal ) + { + mode = d_hScrollData->mode; + baseMin = zoomBase().left(); + baseMax = zoomBase().right(); + zoomMin = zoomRect().left(); + zoomMax = zoomRect().right(); + } + else + { + mode = d_vScrollData->mode; + baseMin = zoomBase().top(); + baseMax = zoomBase().bottom(); + zoomMin = zoomRect().top(); + zoomMax = zoomRect().bottom(); + } + + bool needed = false; + switch( mode ) + { + case Qt::ScrollBarAlwaysOn: + needed = true; + break; + case Qt::ScrollBarAlwaysOff: + needed = false; + break; + default: + { + if ( baseMin < zoomMin || baseMax > zoomMax ) + needed = true; + break; + } + } + return needed; +} + +void ScrollZoomer::updateScrollBars() +{ + if ( !canvas() ) + return; + + const int xAxis = QwtPlotZoomer::xAxis().pos; + const int yAxis = QwtPlotZoomer::yAxis().pos; + + int xScrollBarAxis = xAxis; + if ( hScrollBarPosition() == OppositeToScale ) + xScrollBarAxis = oppositeAxis( xScrollBarAxis ); + + int yScrollBarAxis = yAxis; + if ( vScrollBarPosition() == OppositeToScale ) + yScrollBarAxis = oppositeAxis( yScrollBarAxis ); + + + QwtPlotLayout *layout = plot()->plotLayout(); + + bool showHScrollBar = needScrollBar( Qt::Horizontal ); + if ( showHScrollBar ) + { + ScrollBar *sb = scrollBar( Qt::Horizontal ); + sb->setPalette( plot()->palette() ); + sb->setInverted( !plot()->axisScaleDiv( xAxis ).isIncreasing() ); + sb->setBase( zoomBase().left(), zoomBase().right() ); + sb->moveSlider( zoomRect().left(), zoomRect().right() ); + + if ( !sb->isVisibleTo( canvas() ) ) + { + sb->show(); + layout->setCanvasMargin( layout->canvasMargin( xScrollBarAxis ) + + sb->extent(), xScrollBarAxis ); + } + } + else + { + if ( horizontalScrollBar() ) + { + horizontalScrollBar()->hide(); + layout->setCanvasMargin( layout->canvasMargin( xScrollBarAxis ) + - horizontalScrollBar()->extent(), xScrollBarAxis ); + } + } + + bool showVScrollBar = needScrollBar( Qt::Vertical ); + if ( showVScrollBar ) + { + ScrollBar *sb = scrollBar( Qt::Vertical ); + sb->setPalette( plot()->palette() ); + sb->setInverted( !plot()->axisScaleDiv( yAxis ).isIncreasing() ); + sb->setBase( zoomBase().top(), zoomBase().bottom() ); + sb->moveSlider( zoomRect().top(), zoomRect().bottom() ); + + if ( !sb->isVisibleTo( canvas() ) ) + { + sb->show(); + layout->setCanvasMargin( layout->canvasMargin( yScrollBarAxis ) + + sb->extent(), yScrollBarAxis ); + } + } + else + { + if ( verticalScrollBar() ) + { + verticalScrollBar()->hide(); + layout->setCanvasMargin( layout->canvasMargin( yScrollBarAxis ) + - verticalScrollBar()->extent(), yScrollBarAxis ); + } + } + + if ( showHScrollBar && showVScrollBar ) + { + if ( d_cornerWidget == NULL ) + { + d_cornerWidget = new QWidget( canvas() ); + d_cornerWidget->setAutoFillBackground( true ); + d_cornerWidget->setPalette( plot()->palette() ); + } + d_cornerWidget->show(); + } + else + { + if ( d_cornerWidget ) + d_cornerWidget->hide(); + } + + layoutScrollBars( canvas()->contentsRect() ); + plot()->updateLayout(); +} + +void ScrollZoomer::layoutScrollBars( const QRect &rect ) +{ + int hPos = xAxis().pos; + if ( hScrollBarPosition() == OppositeToScale ) + hPos = oppositeAxis( hPos ); + + int vPos = yAxis().pos; + if ( vScrollBarPosition() == OppositeToScale ) + vPos = oppositeAxis( vPos ); + + ScrollBar *hScrollBar = horizontalScrollBar(); + ScrollBar *vScrollBar = verticalScrollBar(); + + const int hdim = hScrollBar ? hScrollBar->extent() : 0; + const int vdim = vScrollBar ? vScrollBar->extent() : 0; + + if ( hScrollBar && hScrollBar->isVisible() ) + { + int x = rect.x(); + int y = ( hPos == QwtAxis::xTop ) + ? rect.top() : rect.bottom() - hdim + 1; + int w = rect.width(); + + if ( vScrollBar && vScrollBar->isVisible() ) + { + if ( vPos == QwtAxis::yLeft ) + x += vdim; + w -= vdim; + } + + hScrollBar->setGeometry( x, y, w, hdim ); + } + if ( vScrollBar && vScrollBar->isVisible() ) + { + int pos = yAxis().pos; + if ( vScrollBarPosition() == OppositeToScale ) + pos = oppositeAxis( pos ); + + int x = ( vPos == QwtAxis::yLeft ) + ? rect.left() : rect.right() - vdim + 1; + int y = rect.y(); + + int h = rect.height(); + + if ( hScrollBar && hScrollBar->isVisible() ) + { + if ( hPos == QwtAxis::xTop ) + y += hdim; + + h -= hdim; + } + + vScrollBar->setGeometry( x, y, vdim, h ); + } + if ( hScrollBar && hScrollBar->isVisible() && + vScrollBar && vScrollBar->isVisible() ) + { + if ( d_cornerWidget ) + { + QRect cornerRect( + vScrollBar->pos().x(), hScrollBar->pos().y(), + vdim, hdim ); + d_cornerWidget->setGeometry( cornerRect ); + } + } +} + +void ScrollZoomer::scrollBarMoved( + Qt::Orientation o, double min, double max ) +{ + Q_UNUSED( max ); + + if ( o == Qt::Horizontal ) + moveTo( QPointF( min, zoomRect().top() ) ); + else + moveTo( QPointF( zoomRect().left(), min ) ); + + Q_EMIT zoomed( zoomRect() ); +} + +int ScrollZoomer::oppositeAxis( int axis ) const +{ + switch( axis ) + { + case QwtAxis::xBottom: + return QwtAxis::xTop; + case QwtAxis::xTop: + return QwtAxis::xBottom; + case QwtAxis::yLeft: + return QwtAxis::yRight; + case QwtAxis::yRight: + return QwtAxis::yLeft; + } + + return axis; +} diff --git a/qwt/examples/realtime/scrollzoomer.h b/qwt/examples/realtime/scrollzoomer.h new file mode 100644 index 000000000..1249906f5 --- /dev/null +++ b/qwt/examples/realtime/scrollzoomer.h @@ -0,0 +1,67 @@ +#ifndef _SCROLLZOOMER_H +#define _SCROLLZOOMER_H + +#include +#include +#include + +class ScrollData; +class ScrollBar; + +class ScrollZoomer: public QwtPlotZoomer +{ + Q_OBJECT +public: + enum ScrollBarPosition + { + AttachedToScale, + OppositeToScale + }; + + ScrollZoomer( QWidget * ); + virtual ~ScrollZoomer(); + + ScrollBar *horizontalScrollBar() const; + ScrollBar *verticalScrollBar() const; + + void setHScrollBarMode( Qt::ScrollBarPolicy ); + void setVScrollBarMode( Qt::ScrollBarPolicy ); + + Qt::ScrollBarPolicy vScrollBarMode () const; + Qt::ScrollBarPolicy hScrollBarMode () const; + + void setHScrollBarPosition( ScrollBarPosition ); + void setVScrollBarPosition( ScrollBarPosition ); + + ScrollBarPosition hScrollBarPosition() const; + ScrollBarPosition vScrollBarPosition() const; + + QWidget* cornerWidget() const; + virtual void setCornerWidget( QWidget * ); + + virtual bool eventFilter( QObject *, QEvent * ); + + virtual void rescale(); + +protected: + virtual ScrollBar *scrollBar( Qt::Orientation ); + virtual void updateScrollBars(); + virtual void layoutScrollBars( const QRect & ); + +private Q_SLOTS: + void scrollBarMoved( Qt::Orientation o, double min, double max ); + +private: + bool needScrollBar( Qt::Orientation ) const; + int oppositeAxis( int ) const; + + QWidget *d_cornerWidget; + + ScrollData *d_hScrollData; + ScrollData *d_vScrollData; + + bool d_inZoom; + bool d_alignCanvasToScales[ QwtAxis::PosCount ]; +}; + +#endif diff --git a/qwt/examples/realtime/start.xpm b/qwt/examples/realtime/start.xpm new file mode 100644 index 000000000..62146842a --- /dev/null +++ b/qwt/examples/realtime/start.xpm @@ -0,0 +1,266 @@ +/* XPM */ +static const char *start_xpm[] = { +/* width height num_colors chars_per_pixel */ +" 32 32 227 2", +/* colors */ +".. c #040204", +".# c #848684", +".a c #c4c2b4", +".b c #843a04", +".c c #444244", +".d c #ece2cc", +".e c #fca234", +".f c #c45e04", +".g c #bca27c", +".h c #646264", +".i c #e4c69c", +".j c #847254", +".k c #c4a684", +".l c #443e34", +".m c #a48e6c", +".n c #f4f2e4", +".o c #24261c", +".p c #a44a04", +".q c #c4825c", +".r c #644634", +".s c #b4b2ac", +".t c #747274", +".u c #844e2c", +".v c #ece6dc", +".w c #c4b6a4", +".x c #a49274", +".y c #343634", +".z c #fcd69c", +".A c #b4aa9c", +".B c #8c8e8c", +".C c #545254", +".D c #f4f2ec", +".E c #fcb67c", +".F c #e4965c", +".G c #e46634", +".H c #141614", +".I c #d4c2a4", +".J c #746a5c", +".K c #fcc2a4", +".L c #342a1c", +".M c #fc9204", +".N c #a45e2c", +".O c #94521c", +".P c #a4560c", +".Q c #645e54", +".R c #ec7a04", +".S c #f4deac", +".T c #5c462c", +".U c #bcaa8c", +".V c #d4be9c", +".W c #fcfaf4", +".X c #d4cab4", +".Y c #1c0a04", +".Z c #6c6a6c", +".0 c #e4caa4", +".1 c #2c2a1c", +".2 c #74462c", +".3 c #84562c", +".4 c #f4eee4", +".5 c #c4beb4", +".6 c #a49a84", +".7 c #f4ba7c", +".8 c #dc966c", +".9 c #948674", +"#. c #fc8a04", +"## c #f4eab4", +"#a c #fcb26c", +"#b c #c4ae94", +"#c c #f4e6d4", +"#d c #9c8e74", +"#e c #fc7e04", +"#f c #140604", +"#g c #b4a28c", +"#h c #6c625c", +"#i c #8c7e64", +"#j c #f4ae84", +"#k c #e4decc", +"#l c #ac5204", +"#m c #e48a4c", +"#n c #7c7a7c", +"#o c #ccba9c", +"#p c #fcd2b4", +"#q c #bcae9c", +"#r c #dcc6a4", +"#s c #ac723c", +"#t c #e4ceb4", +"#u c #ec9e74", +"#v c #8c8a8c", +"#w c #8c4204", +"#x c #4c4a34", +"#y c #7c3a04", +"#z c #fcfecc", +"#A c #2c221c", +"#B c #ac4e04", +"#C c #d48264", +"#D c #bcb2a4", +"#E c #a49684", +"#F c #b4aeac", +"#G c #5c5a5c", +"#H c #fcf2ec", +"#I c #fcb28c", +"#J c #7c6e5c", +"#K c #fcce9c", +"#L c #3c2e24", +"#M c #bc9e71", +"#N c #fc922c", +"#O c #bc622c", +"#P c #b45604", +"#Q c #f47a08", +"#R c #fcdeb8", +"#S c #544e44", +"#T c #fcfefc", +"#U c #e4ceaa", +"#V c #8c5a2c", +"#W c #e49e7c", +"#X c #f4eadb", +"#Y c #9c9284", +"#Z c #f4ae90", +"#0 c #c47e5c", +"#1 c #bc824c", +"#2 c #e47634", +"#3 c #e46e24", +"#4 c #b48e6c", +"#5 c #7c5a4c", +"#6 c #744e2c", +"#7 c #fcba9c", +"#8 c #cccacc", +"#9 c #f4722c", +"a. c #c46224", +"a# c #e47a54", +"aa c #ac663c", +"ab c #fce2cc", +"ac c #945634", +"ad c #fceacc", +"ae c #3c3e3c", +"af c #ec9e54", +"ag c #843e1c", +"ah c #fccab0", +"ai c #8c8274", +"aj c #4c4634", +"ak c #ecc2ac", +"al c #8c765c", +"am c #7c7264", +"an c #e49a7c", +"ao c #6c4e34", +"ap c #fc9a2c", +"aq c #4c4a4c", +"ar c #ccbea4", +"as c #fcf6dc", +"at c #3c3a3c", +"au c #949294", +"av c #fceebc", +"aw c #fcaa7c", +"ax c #ecdac8", +"ay c #0c0604", +"az c #fc8204", +"aA c #847664", +"aB c #e4d6c4", +"aC c #fcd2ac", +"aD c #1c1a14", +"aE c #342e2c", +"aF c #240e04", +"aG c #2c2e2c", +"aH c #fcbe7c", +"aI c #fc8e14", +"aJ c #fc7a14", +"aK c #944604", +"aL c #7c3e14", +"aM c #fcfadc", +"aN c #645244", +"aO c #bcb6b4", +"aP c #bc5604", +"aQ c #7c522c", +"aR c #cc8264", +"aS c #dccab0", +"aT c #ac9a84", +"aU c #f4e2cc", +"aV c #a45e3c", +"aW c #9c5634", +"aX c #fca634", +"aY c #c4aa89", +"aZ c #a44e07", +"a0 c #b4b6b4", +"a1 c #c4baa9", +"a2 c #a4967c", +"a3 c #b4aea4", +"a4 c #d4c6a8", +"a5 c #5c4a34", +"a6 c #bcae94", +"a7 c #845a2c", +"a8 c #948a7c", +"a9 c #c4b299", +"b. c #b4a690", +"b# c #6c6658", +"ba c #fcd6b4", +"bb c #2c261d", +"bc c #fcf6f0", +"bd c #fcb694", +"be c #fc9624", +"bf c #646664", +"bg c #747674", +"bh c #eceadc", +"bi c #545654", +"bj c #b49e7c", +"bk c #6c6e6c", +"bl c #fc8e04", +"bm c #fcb66c", +"bn c #7c7e7c", +"bo c #5c5e5c", +"bp c #8c8674", +"bq c #fc8604", +"br c #bc5a04", +"bs c #fca23c", +"bt c #443e3c", +"bu c #a4927c", +"bv c #b4aaa4", +"bw c #746a64", +"bx c #342a24", +"by c #fcfafc", +"bz c #2c2a24", +"bA c #a49a8c", +"bB c #bcbabc", +"bC c #9c8e7c", +"bD c #8c7e6c", +"bE c #ccbaa4", +"bF c #fcd2bc", +"bG c #fcb294", +/* pixels */ +"#Gbi#G.#bnbg.t.Zbfbf.hbo#G.Caqaq.c.C.C.C.C.C.C.C.C.C.C.Cbi#Gbi#G", +"#Gbi#Gbg#8#8.a#8#8#8#8#8#8#8#8.B#8#8#8#8#8#8#8#8#8#8#8.C#Gbi#Gbi", +"bi#Gbi#n#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#T#T#T#T#8aq#6afbm#z", +"#Gbi#Gbk#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#T#T#T#T#8#6af#aavaX", +"bi#Gbibk#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#T#T#T#T#6af#a##aX#.", +"#Gbi#Gbk#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#T#T#T.3af#a.S.e#.bq", +"#Gbi#G.Z#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#T#TaQaf#a#R.e#eazbq", +"bi#GbibkaubBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBa7af#aba.e#eazbq.M", +"#Gbi#G.Z#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#Vaf#ababs#ebqbq#.az", +"#Gbi#Gbf#8#T#T#T#T#T#T#T#T#T#TbB#T#T#Tby#T#saf#a#Kap#ebqbqbl#Q.f", +"bi#Gbi.Z#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T.Naf#a.z#N#ebqbqbl.R.f#l", +"#Gbi#Gbf#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#1af.EaHbe#ebqbq#..Rbr#B#y", +"bi#Gbibf#8#T#T#T#T#T#T#T#T#T#TbB#T#T.F.7#jawaI#ebqbqbl.R#PaZ.b..", +"#Gbi#GbfaubBbBbBbBbBbBbBbBbBbBbBbBbG#RaMak#m#ebqbqbl#Q#P#B#w.Y.y", +"bi#Gbibf#8#T#T#T#T#T#T#T#T#T#TaObyaC.Wab#Z#2bqbq.M.RaP.p#way.y.y", +"#Gbi#G.h#8#T#T#T#T#T#T#T#T#Tbya0#I#Tad.K#j#2#QaJ.Rbr.p#yaF.y.yat", +"bi#Gbi.h#8#T#T#T#T#T#T#T#Tby.W.saCasba#Za#.G#9#3aPaZaK.Y.y.yat.c", +"#Gbi#Gbo#8#T#T#T#T#T#T#Tby.Wbc#I#T#p#7.8#0a.#O.P.paLay...yatbtaq", +"bi#Gbi.h#8#T#T#T#T#T#Tby.Wbc.DaCadah#W#0aa.O.2.ragaF#h..ataeaq.C", +"#Gbi#GboaubBbBbBbBbBaOa0.sa3bdasahanaRaV.u.Ta5ae#f.Q#S..aeaq.Cbi", +"bi#Gbibo#8#T#T#T#T.Wbcbc.D#HaCbF#uaRaWaQa5ajbt.HbDai#J..aq.Cbibi", +"#Gbi#Gbo#8#T#Tby.W.W.D#H.nbdab#u#Cac.uaN.o..bDaiaia8#i...Cbibi#G", +"bi#Gbibo#8#T#Tbybc.Dbc.n.4#4.8.q#5.r.l..#vbDaia8a2#g#d..bibi#Gbi", +"#Gbi#G#G#8#T.Wbc.D#H.D#X.j.Lao#5#L.H#vaibpbpbCaT.U#oa2..bi#Gbi#G", +"bi#Gbi#G#8.Wbc.D#H.n.4bjajaD#A...#bpai.9bC#E#ga9.V#r.gbb#Gbi#Gbi", +"#Gbi#Gbiaua0.s.s#Fa3bvaG....#vbwb#b#.JbwaA#i.9bC.m.xal.1bi#Gbi#G", +"bi#Gbi#G#8.D#H.4.4#X.v#x#v#qbAb##Y.6b.a6ar.I#r#r.0.i.g.Lbi#Gbi#G", +"bi#Gbibi#8.D.4.4#X#c.vax.X.AbAamb.#D#oa4aS#r.0.0.i.i#M#A#Gbi#Gbi", +"#Gbi#G.C#8.n.4#X#X.daUaBaS.wa6aiar#raS.0#U#U.0.i.0#r#Mbb#Gbi#Gbi", +"bi#Gbiaq#8.4#Xbh.v#c.d#kaB.Xa4buaS#U#t#U#U.0.0#r.i.i#Mbbbi#Gbi#G", +"#Gbi#Gae.a.a.5a1bE.w.w.w#ba6.U#iaYaYaY.k.g.g.g#M#M#M#M.Lbi#Gbi#G", +"bi#Gbi.HbxaEbxaEbz.LaEbzbzbbbzbbbbbxbb.Lbbbb.1.Lbb.1#Aay#Gbi#Gbi" +}; diff --git a/qwt/examples/refreshtest/circularbuffer.cpp b/qwt/examples/refreshtest/circularbuffer.cpp new file mode 100644 index 000000000..1fec47243 --- /dev/null +++ b/qwt/examples/refreshtest/circularbuffer.cpp @@ -0,0 +1,73 @@ +#include "circularbuffer.h" +#include + +CircularBuffer::CircularBuffer( double interval, size_t numPoints ): + d_y( NULL ), + d_referenceTime( 0.0 ), + d_startIndex( 0 ), + d_offset( 0.0 ) +{ + fill( interval, numPoints ); +} + +void CircularBuffer::fill( double interval, size_t numPoints ) +{ + if ( interval <= 0.0 || numPoints < 2 ) + return; + + d_values.resize( numPoints ); + d_values.fill( 0.0 ); + + if ( d_y ) + { + d_step = interval / ( numPoints - 2 ); + for ( size_t i = 0; i < numPoints; i++ ) + d_values[i] = d_y( i * d_step ); + } + + d_interval = interval; +} + +void CircularBuffer::setFunction( double( *y )( double ) ) +{ + d_y = y; +} + +void CircularBuffer::setReferenceTime( double timeStamp ) +{ + d_referenceTime = timeStamp; + + const double startTime = ::fmod( d_referenceTime, d_values.size() * d_step ); + + d_startIndex = int( startTime / d_step ); // floor + d_offset = ::fmod( startTime, d_step ); +} + +double CircularBuffer::referenceTime() const +{ + return d_referenceTime; +} + +size_t CircularBuffer::size() const +{ + return d_values.size(); +} + +QPointF CircularBuffer::sample( size_t i ) const +{ + const int size = d_values.size(); + + int index = d_startIndex + i; + if ( index >= size ) + index -= size; + + const double x = i * d_step - d_offset - d_interval; + const double y = d_values.data()[index]; + + return QPointF( x, y ); +} + +QRectF CircularBuffer::boundingRect() const +{ + return QRectF( -1.0, -d_interval, 2.0, d_interval ); +} \ No newline at end of file diff --git a/qwt/examples/refreshtest/circularbuffer.h b/qwt/examples/refreshtest/circularbuffer.h new file mode 100644 index 000000000..eae1a9f26 --- /dev/null +++ b/qwt/examples/refreshtest/circularbuffer.h @@ -0,0 +1,35 @@ +#ifndef _CIRCULAR_BUFFER_H_ +#define _CIRCULAR_BUFFER_H_ + +#include +#include + +class CircularBuffer: public QwtSeriesData +{ +public: + CircularBuffer( double interval = 10.0, size_t numPoints = 1000 ); + void fill( double interval, size_t numPoints ); + + void setReferenceTime( double ); + double referenceTime() const; + + virtual size_t size() const; + virtual QPointF sample( size_t i ) const; + + virtual QRectF boundingRect() const; + + void setFunction( double( *y )( double ) ); + +private: + double ( *d_y )( double ); + + double d_referenceTime; + double d_interval; + QVector d_values; + + double d_step; + int d_startIndex; + double d_offset; +}; + +#endif diff --git a/qwt/examples/refreshtest/main.cpp b/qwt/examples/refreshtest/main.cpp new file mode 100644 index 000000000..0a306848e --- /dev/null +++ b/qwt/examples/refreshtest/main.cpp @@ -0,0 +1,30 @@ +#include "mainwindow.h" +#include + +#ifndef QWT_NO_OPENGL +#if QT_VERSION >= 0x040600 && QT_VERSION < 0x050000 +#define USE_OPENGL 1 +#endif +#endif + +#if USE_OPENGL +#include +#endif + +int main( int argc, char **argv ) +{ +#if USE_OPENGL + // on my box QPaintEngine::OpenGL2 has serious problems, f.e: + // the lines of a simple drawRect are wrong. + + QGL::setPreferredPaintEngine( QPaintEngine::OpenGL ); +#endif + + QApplication a( argc, argv ); + + MainWindow mainWindow; + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/refreshtest/mainwindow.cpp b/qwt/examples/refreshtest/mainwindow.cpp new file mode 100644 index 000000000..c3df4cfd5 --- /dev/null +++ b/qwt/examples/refreshtest/mainwindow.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include +#include "panel.h" +#include "plot.h" +#include "mainwindow.h" + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + QWidget *w = new QWidget( this ); + + d_panel = new Panel( w ); + + d_plot = new Plot( w ); + + QHBoxLayout *hLayout = new QHBoxLayout( w ); + hLayout->addWidget( d_panel ); + hLayout->addWidget( d_plot, 10 ); + + setCentralWidget( w ); + + d_frameCount = new QLabel( this ); + statusBar()->addWidget( d_frameCount, 10 ); + + applySettings( d_panel->settings() ); + + connect( d_panel, SIGNAL( settingsChanged( const Settings & ) ), + this, SLOT( applySettings( const Settings & ) ) ); +} + +bool MainWindow::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == d_plot->canvas() && event->type() == QEvent::Paint ) + { + static int counter; + static QTime timeStamp; + + if ( !timeStamp.isValid() ) + { + timeStamp.start(); + counter = 0; + } + else + { + counter++; + + const double elapsed = timeStamp.elapsed() / 1000.0; + if ( elapsed >= 1 ) + { + QString fps; + fps.setNum( qRound( counter / elapsed ) ); + fps += " Fps"; + + d_frameCount->setText( fps ); + + counter = 0; + timeStamp.start(); + } + } + } + + return QMainWindow::eventFilter( object, event ); +} + +void MainWindow::applySettings( const Settings &settings ) +{ + d_plot->setSettings( settings ); + + // the canvas might have been recreated + d_plot->canvas()->removeEventFilter( this ); + d_plot->canvas()->installEventFilter( this ); +} diff --git a/qwt/examples/refreshtest/mainwindow.h b/qwt/examples/refreshtest/mainwindow.h new file mode 100644 index 000000000..47d10bd79 --- /dev/null +++ b/qwt/examples/refreshtest/mainwindow.h @@ -0,0 +1,28 @@ +#ifndef _MAIN_WINDOW_H_ +#define _MAIN_WINDOW_H_ + +#include + +class Plot; +class Panel; +class QLabel; +class Settings; + +class MainWindow: public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = NULL ); + virtual bool eventFilter( QObject *, QEvent * ); + +private Q_SLOTS: + void applySettings( const Settings & ); + +private: + Plot *d_plot; + Panel *d_panel; + QLabel *d_frameCount; +}; + +#endif diff --git a/qwt/examples/refreshtest/panel.cpp b/qwt/examples/refreshtest/panel.cpp new file mode 100644 index 000000000..4189998c2 --- /dev/null +++ b/qwt/examples/refreshtest/panel.cpp @@ -0,0 +1,297 @@ +#include "panel.h" +#include +#include +#include +#include +#include +#include + +class SpinBox: public QSpinBox +{ +public: + SpinBox( int min, int max, int step, QWidget *parent ): + QSpinBox( parent ) + { + setRange( min, max ); + setSingleStep( step ); + } +}; + +class CheckBox: public QCheckBox +{ +public: + CheckBox( const QString &title, QWidget *parent ): + QCheckBox( title, parent ) + { + } + + void setChecked( bool checked ) + { + setCheckState( checked ? Qt::Checked : Qt::Unchecked ); + } + + bool isChecked() const + { + return checkState() == Qt::Checked; + } +}; + +Panel::Panel( QWidget *parent ): + QTabWidget( parent ) +{ + setTabPosition( QTabWidget::West ); + + addTab( createPlotTab( this ), "Plot" ); + addTab( createCanvasTab( this ), "Canvas" ); + addTab( createCurveTab( this ), "Curve" ); + + setSettings( Settings() ); + + connect( d_numPoints, SIGNAL( valueChanged( int ) ), SLOT( edited() ) ); + connect( d_updateInterval, SIGNAL( valueChanged( int ) ), SLOT( edited() ) ); + connect( d_curveWidth, SIGNAL( valueChanged( int ) ), SLOT( edited() ) ); + + connect( d_paintCache, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_paintOnScreen, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_immediatePaint, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); +#ifndef QWT_NO_OPENGL + connect( d_openGL, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); +#endif + + connect( d_curveAntialiasing, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_curveClipping, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_curveFiltering, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_lineSplitting, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_curveFilled, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + + connect( d_updateType, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) ); + connect( d_gridStyle, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) ); + connect( d_curveType, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) ); + connect( d_curvePen, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) ); +} + +QWidget *Panel::createPlotTab( QWidget *parent ) +{ + QWidget *page = new QWidget( parent ); + + d_updateInterval = new SpinBox( 0, 1000, 10, page ); + d_numPoints = new SpinBox( 10, 1000000, 1000, page ); + + d_updateType = new QComboBox( page ); + d_updateType->addItem( "Repaint" ); + d_updateType->addItem( "Replot" ); + + int row = 0; + + QGridLayout *layout = new QGridLayout( page ); + + layout->addWidget( new QLabel( "Updates", page ), row, 0 ); + layout->addWidget( d_updateInterval, row, 1 ); + layout->addWidget( new QLabel( "ms", page ), row++, 2 ); + + layout->addWidget( new QLabel( "Points", page ), row, 0 ); + layout->addWidget( d_numPoints, row++, 1 ); + + layout->addWidget( new QLabel( "Update", page ), row, 0 ); + layout->addWidget( d_updateType, row++, 1 ); + + layout->addLayout( new QHBoxLayout(), row++, 0 ); + + layout->setColumnStretch( 1, 10 ); + layout->setRowStretch( row, 10 ); + + return page; +} + +QWidget *Panel::createCanvasTab( QWidget *parent ) +{ + QWidget *page = new QWidget( parent ); + + d_gridStyle = new QComboBox( page ); + d_gridStyle->addItem( "None" ); + d_gridStyle->addItem( "Solid" ); + d_gridStyle->addItem( "Dashes" ); + + d_paintCache = new CheckBox( "Paint Cache", page ); + d_paintOnScreen = new CheckBox( "Paint On Screen", page ); + d_immediatePaint = new CheckBox( "Immediate Paint", page ); +#ifndef QWT_NO_OPENGL + d_openGL = new CheckBox( "OpenGL", page ); +#endif + + int row = 0; + + QGridLayout *layout = new QGridLayout( page ); + layout->addWidget( new QLabel( "Grid", page ), row, 0 ); + layout->addWidget( d_gridStyle, row++, 1 ); + + layout->addWidget( d_paintCache, row++, 0, 1, -1 ); + layout->addWidget( d_paintOnScreen, row++, 0, 1, -1 ); + layout->addWidget( d_immediatePaint, row++, 0, 1, -1 ); +#ifndef QWT_NO_OPENGL + layout->addWidget( d_openGL, row++, 0, 1, -1 ); +#endif + + layout->addLayout( new QHBoxLayout(), row++, 0 ); + + layout->setColumnStretch( 1, 10 ); + layout->setRowStretch( row, 10 ); + + return page; +} + +QWidget *Panel::createCurveTab( QWidget *parent ) +{ + QWidget *page = new QWidget( parent ); + + d_curveType = new QComboBox( page ); + d_curveType->addItem( "Wave" ); + d_curveType->addItem( "Noise" ); + + d_curveAntialiasing = new CheckBox( "Antialiasing", page ); + d_curveClipping = new CheckBox( "Clipping", page ); + d_curveFiltering = new CheckBox( "Filtering", page ); + d_lineSplitting = new CheckBox( "Split Lines", page ); + + d_curveWidth = new SpinBox( 0, 10, 1, page ); + + d_curvePen = new QComboBox( page ); + d_curvePen->addItem( "Solid" ); + d_curvePen->addItem( "Dotted" ); + + d_curveFilled = new CheckBox( "Filled", page ); + + int row = 0; + + QGridLayout *layout = new QGridLayout( page ); + layout->addWidget( new QLabel( "Type", page ), row, 0 ); + layout->addWidget( d_curveType, row++, 1 ); + + layout->addWidget( d_curveAntialiasing, row++, 0, 1, -1 ); + layout->addWidget( d_curveClipping, row++, 0, 1, -1 ); + layout->addWidget( d_curveFiltering, row++, 0, 1, -1 ); + layout->addWidget( d_lineSplitting, row++, 0, 1, -1 ); + + layout->addWidget( new QLabel( "Width", page ), row, 0 ); + layout->addWidget( d_curveWidth, row++, 1 ); + + layout->addWidget( new QLabel( "Style", page ), row, 0 ); + layout->addWidget( d_curvePen, row++, 1 ); + + layout->addWidget( d_curveFilled, row++, 0, 1, -1 ); + + layout->addLayout( new QHBoxLayout(), row++, 0 ); + + layout->setColumnStretch( 1, 10 ); + layout->setRowStretch( row, 10 ); + + return page; +} + +void Panel::edited() +{ + const Settings s = settings(); + Q_EMIT settingsChanged( s ); +} + + +Settings Panel::settings() const +{ + Settings s; + + s.grid.pen = QPen( Qt::black, 0 ); + + switch( d_gridStyle->currentIndex() ) + { + case 0: + s.grid.pen.setStyle( Qt::NoPen ); + break; + case 2: + s.grid.pen.setStyle( Qt::DashLine ); + break; + } + + s.curve.pen.setStyle( d_curvePen->currentIndex() == 0 ? + Qt::SolidLine : Qt::DotLine ); + s.curve.pen.setWidth( d_curveWidth->value() ); + s.curve.brush.setStyle( ( d_curveFilled->isChecked() ) ? + Qt::SolidPattern : Qt::NoBrush ); + s.curve.numPoints = d_numPoints->value(); + s.curve.functionType = static_cast( + d_curveType->currentIndex() ); + if ( d_curveClipping->isChecked() ) + s.curve.paintAttributes |= QwtPlotCurve::ClipPolygons; + else + s.curve.paintAttributes &= ~QwtPlotCurve::ClipPolygons; + if ( d_curveFiltering->isChecked() ) + s.curve.paintAttributes |= QwtPlotCurve::FilterPoints; + else + s.curve.paintAttributes &= ~QwtPlotCurve::FilterPoints; + + if ( d_curveAntialiasing->isChecked() ) + s.curve.renderHint |= QwtPlotItem::RenderAntialiased; + else + s.curve.renderHint &= ~QwtPlotItem::RenderAntialiased; + + s.curve.lineSplitting = ( d_lineSplitting->isChecked() ); + + s.canvas.useBackingStore = ( d_paintCache->isChecked() ); + s.canvas.paintOnScreen = ( d_paintOnScreen->isChecked() ); + s.canvas.immediatePaint = ( d_immediatePaint->isChecked() ); +#ifndef QWT_NO_OPENGL + s.canvas.openGL = ( d_openGL->isChecked() ); +#endif + + s.updateInterval = d_updateInterval->value(); + s.updateType = static_cast( d_updateType->currentIndex() ); + + return s; +} + +void Panel::setSettings( const Settings &s ) +{ + d_numPoints->setValue( s.curve.numPoints ); + d_updateInterval->setValue( s.updateInterval ); + d_updateType->setCurrentIndex( s.updateType ); + + switch( s.grid.pen.style() ) + { + case Qt::NoPen: + { + d_gridStyle->setCurrentIndex( 0 ); + break; + } + case Qt::DashLine: + { + d_gridStyle->setCurrentIndex( 2 ); + break; + } + default: + { + d_gridStyle->setCurrentIndex( 1 ); // Solid + } + } + + d_paintCache->setChecked( s.canvas.useBackingStore ); + d_paintOnScreen->setChecked( s.canvas.paintOnScreen ); + d_immediatePaint->setChecked( s.canvas.immediatePaint ); +#ifndef QWT_NO_OPENGL + d_openGL->setChecked( s.canvas.openGL ); +#endif + + d_curveType->setCurrentIndex( s.curve.functionType ); + d_curveAntialiasing->setChecked( + s.curve.renderHint & QwtPlotCurve::RenderAntialiased ); + + d_curveClipping->setChecked( + s.curve.paintAttributes & QwtPlotCurve::ClipPolygons ); + d_curveFiltering->setChecked( + s.curve.paintAttributes & QwtPlotCurve::FilterPoints ); + + d_lineSplitting->setChecked( s.curve.lineSplitting ); + + d_curveWidth->setValue( s.curve.pen.width() ); + d_curvePen->setCurrentIndex( + s.curve.pen.style() == Qt::SolidLine ? 0 : 1 ); + d_curveFilled->setChecked( s.curve.brush.style() != Qt::NoBrush ); +} diff --git a/qwt/examples/refreshtest/panel.h b/qwt/examples/refreshtest/panel.h new file mode 100644 index 000000000..9a0fcc5f3 --- /dev/null +++ b/qwt/examples/refreshtest/panel.h @@ -0,0 +1,54 @@ +#ifndef _PANEL_H_ +#define _PANEL_H_ 1 + +#include "settings.h" +#include + +class QComboBox; +class SpinBox; +class CheckBox; + +class Panel: public QTabWidget +{ + Q_OBJECT + +public: + Panel( QWidget * = NULL ); + + Settings settings() const; + void setSettings( const Settings & ); + +Q_SIGNALS: + void settingsChanged( const Settings & ); + +private Q_SLOTS: + void edited(); + +private: + QWidget *createPlotTab( QWidget * ); + QWidget *createCanvasTab( QWidget * ); + QWidget *createCurveTab( QWidget * ); + + SpinBox *d_numPoints; + SpinBox *d_updateInterval; + QComboBox *d_updateType; + + QComboBox *d_gridStyle; + CheckBox *d_paintCache; + CheckBox *d_paintOnScreen; + CheckBox *d_immediatePaint; +#ifndef QWT_NO_OPENGL + CheckBox *d_openGL; +#endif + + QComboBox *d_curveType; + CheckBox *d_curveAntialiasing; + CheckBox *d_curveClipping; + CheckBox *d_curveFiltering; + CheckBox *d_lineSplitting; + SpinBox *d_curveWidth; + QComboBox *d_curvePen; + CheckBox *d_curveFilled; +}; + +#endif diff --git a/qwt/examples/refreshtest/plot.cpp b/qwt/examples/refreshtest/plot.cpp new file mode 100644 index 000000000..8db9348c2 --- /dev/null +++ b/qwt/examples/refreshtest/plot.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef QWT_NO_OPENGL +#include +#include +#endif +#include "plot.h" +#include "circularbuffer.h" +#include "settings.h" + +static double wave( double x ) +{ + const double period = 1.0; + const double c = 5.0; + + double v = ::fmod( x, period ); + + const double amplitude = qAbs( x - qRound( x / c ) * c ) / ( 0.5 * c ); + v = amplitude * qSin( v / period * 2 * M_PI ); + + return v; +} + +static double noise( double ) +{ + return 2.0 * ( qrand() / ( static_cast( RAND_MAX ) + 1 ) ) - 1.0; +} + +#ifndef QWT_NO_OPENGL +class GLCanvas: public QwtPlotGLCanvas +{ +public: + GLCanvas( QwtPlot *parent = NULL ): + QwtPlotGLCanvas( parent ) + { + setContentsMargins( 1, 1, 1, 1 ); + } + +protected: + virtual void paintEvent( QPaintEvent *event ) + { + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QwtPlot *plot = qobject_cast< QwtPlot *>( parent() ); + if ( plot ) + plot->drawCanvas( &painter ); + + painter.setPen( palette().foreground().color() ); + painter.drawRect( rect().adjusted( 0, 0, -1, -1 ) ); + } +}; +#endif + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ), + d_interval( 10.0 ), // seconds + d_timerId( -1 ) +{ + // Assign a title + setTitle( "Testing Refresh Rates" ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setFrameStyle( QFrame::Box | QFrame::Plain ); + canvas->setLineWidth( 1 ); + canvas->setPalette( Qt::white ); + + setCanvas( canvas ); + + alignScales(); + + // Insert grid + d_grid = new QwtPlotGrid(); + d_grid->attach( this ); + + // Insert curve + d_curve = new QwtPlotCurve( "Data Moving Right" ); + d_curve->setPen( Qt::black ); + d_curve->setData( new CircularBuffer( d_interval, 10 ) ); + d_curve->attach( this ); + + // Axis + setAxisTitle( QwtAxis::xBottom, "Seconds" ); + setAxisScale( QwtAxis::xBottom, -d_interval, 0.0 ); + + setAxisTitle( QwtAxis::yLeft, "Values" ); + setAxisScale( QwtAxis::yLeft, -1.0, 1.0 ); + + d_clock.start(); + + setSettings( d_settings ); +} + +// +// Set a plain canvas frame and align the scales to it +// +void Plot::alignScales() +{ + // The code below shows how to align the scales to + // the canvas frame, but is also a good example demonstrating + // why the spreaded API needs polishing. + + for ( int i = 0; i < QwtAxis::PosCount; i++ ) + { + QwtScaleWidget *scaleWidget = axisWidget( i ); + if ( scaleWidget ) + scaleWidget->setMargin( 0 ); + + QwtScaleDraw *scaleDraw = axisScaleDraw( i ); + if ( scaleDraw ) + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + } + + plotLayout()->setAlignCanvasToScales( true ); +} + +void Plot::setSettings( const Settings &s ) +{ + if ( d_timerId >= 0 ) + killTimer( d_timerId ); + + d_timerId = startTimer( s.updateInterval ); + + d_grid->setPen( s.grid.pen ); + d_grid->setVisible( s.grid.pen.style() != Qt::NoPen ); + + CircularBuffer *buffer = static_cast( d_curve->data() ); + if ( s.curve.numPoints != buffer->size() || + s.curve.functionType != d_settings.curve.functionType ) + { + switch( s.curve.functionType ) + { + case Settings::Wave: + buffer->setFunction( wave ); + break; + case Settings::Noise: + buffer->setFunction( noise ); + break; + default: + buffer->setFunction( NULL ); + } + + buffer->fill( d_interval, s.curve.numPoints ); + } + + d_curve->setPen( s.curve.pen ); + d_curve->setBrush( s.curve.brush ); + + d_curve->setPaintAttribute( QwtPlotCurve::ClipPolygons, + s.curve.paintAttributes & QwtPlotCurve::ClipPolygons ); + d_curve->setPaintAttribute( QwtPlotCurve::FilterPoints, + s.curve.paintAttributes & QwtPlotCurve::FilterPoints ); + + d_curve->setRenderHint( QwtPlotItem::RenderAntialiased, + s.curve.renderHint & QwtPlotItem::RenderAntialiased ); + +#ifndef QWT_NO_OPENGL + if ( s.canvas.openGL ) + { + QwtPlotGLCanvas *plotCanvas = qobject_cast( canvas() ); + if ( plotCanvas == NULL ) + { + plotCanvas = new GLCanvas(); + plotCanvas->setPalette( QColor( "khaki" ) ); + + setCanvas( plotCanvas ); + } + } + else +#endif + { + QwtPlotCanvas *plotCanvas = qobject_cast( canvas() ); + if ( plotCanvas == NULL ) + { + plotCanvas = new QwtPlotCanvas(); + plotCanvas->setFrameStyle( QFrame::Box | QFrame::Plain ); + plotCanvas->setLineWidth( 1 ); + plotCanvas->setPalette( Qt::white ); + + setCanvas( plotCanvas ); + } + + plotCanvas->setAttribute( Qt::WA_PaintOnScreen, s.canvas.paintOnScreen ); + + plotCanvas->setPaintAttribute( + QwtPlotCanvas::BackingStore, s.canvas.useBackingStore ); + plotCanvas->setPaintAttribute( + QwtPlotCanvas::ImmediatePaint, s.canvas.immediatePaint ); + } + + QwtPainter::setPolylineSplitting( s.curve.lineSplitting ); + + d_settings = s; +} + +void Plot::timerEvent( QTimerEvent * ) +{ + CircularBuffer *buffer = static_cast( d_curve->data() ); + buffer->setReferenceTime( d_clock.elapsed() / 1000.0 ); + + if ( d_settings.updateType == Settings::RepaintCanvas ) + { + // the axes in this example doesn't change. So all we need to do + // is to repaint the canvas. + + QMetaObject::invokeMethod( canvas(), "replot", Qt::DirectConnection ); + } + else + { + replot(); + } +} diff --git a/qwt/examples/refreshtest/plot.h b/qwt/examples/refreshtest/plot.h new file mode 100644 index 000000000..8e6345077 --- /dev/null +++ b/qwt/examples/refreshtest/plot.h @@ -0,0 +1,38 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ 1 + +#include +#include +#include "settings.h" + +class QwtPlotGrid; +class QwtPlotCurve; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget* = NULL ); + +public Q_SLOTS: + void setSettings( const Settings & ); + +protected: + virtual void timerEvent( QTimerEvent *e ); + +private: + void alignScales(); + + QwtPlotGrid *d_grid; + QwtPlotCurve *d_curve; + + QwtSystemClock d_clock; + double d_interval; + + int d_timerId; + + Settings d_settings; +}; + +#endif diff --git a/qwt/examples/refreshtest/refreshtest.pro b/qwt/examples/refreshtest/refreshtest.pro new file mode 100644 index 000000000..3dca7a251 --- /dev/null +++ b/qwt/examples/refreshtest/refreshtest.pro @@ -0,0 +1,27 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = refreshtest + +HEADERS = \ + settings.h \ + circularbuffer.h \ + panel.h \ + plot.h \ + mainwindow.h + +SOURCES = \ + circularbuffer.cpp \ + panel.cpp \ + plot.cpp \ + mainwindow.cpp \ + main.cpp + diff --git a/qwt/examples/refreshtest/settings.h b/qwt/examples/refreshtest/settings.h new file mode 100644 index 000000000..2140e9496 --- /dev/null +++ b/qwt/examples/refreshtest/settings.h @@ -0,0 +1,78 @@ +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ + +#include +#include + +class Settings +{ +public: + enum FunctionType + { + NoFunction = -1, + + Wave, + Noise + }; + + enum UpdateType + { + RepaintCanvas, + Replot + }; + + Settings() + { + grid.pen = Qt::NoPen; + grid.pen.setCosmetic( true ); + + curve.brush = Qt::NoBrush; + curve.numPoints = 1000; + curve.functionType = Wave; + curve.paintAttributes = 0; + curve.renderHint = 0; + curve.lineSplitting = true; + + canvas.useBackingStore = false; + canvas.paintOnScreen = false; + canvas.immediatePaint = true; +#ifndef QWT_NO_OPENGL + canvas.openGL = false; +#endif + + updateType = RepaintCanvas; + updateInterval = 20; + } + + struct gridSettings + { + QPen pen; + } grid; + + struct curveSettings + { + QPen pen; + QBrush brush; + uint numPoints; + FunctionType functionType; + int paintAttributes; + int renderHint; + bool lineSplitting; + } curve; + + struct canvasSettings + { + bool useBackingStore; + bool paintOnScreen; + bool immediatePaint; + +#ifndef QWT_NO_OPENGL + bool openGL; +#endif + } canvas; + + UpdateType updateType; + int updateInterval; +}; + +#endif diff --git a/qwt/examples/scatterplot/main.cpp b/qwt/examples/scatterplot/main.cpp new file mode 100644 index 000000000..bd70f997f --- /dev/null +++ b/qwt/examples/scatterplot/main.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.resize( 800, 600 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/scatterplot/mainwindow.cpp b/qwt/examples/scatterplot/mainwindow.cpp new file mode 100644 index 000000000..ef24d0c9e --- /dev/null +++ b/qwt/examples/scatterplot/mainwindow.cpp @@ -0,0 +1,35 @@ +#include "mainwindow.h" +#include "plot.h" +#include + +static double randomValue() +{ + // a number between [ 0.0, 1.0 ] + return ( qrand() % 100000 ) / 100000.0; +} + +MainWindow::MainWindow() +{ + d_plot = new Plot( this ); + d_plot->setTitle( "Scatter Plot" ); + setCentralWidget( d_plot ); + + // a million points + setSamples( 100000 ); +} + +void MainWindow::setSamples( int numPoints ) +{ + QPolygonF samples; + + for ( int i = 0; i < numPoints; i++ ) + { + const double x = randomValue() * 24.0 + 1.0; + const double y = ::log( 10.0 * ( x - 1.0 ) + 1.0 ) + * ( randomValue() * 0.5 + 0.9 ); + + samples += QPointF( x, y ); + } + + d_plot->setSamples( samples ); +} diff --git a/qwt/examples/scatterplot/mainwindow.h b/qwt/examples/scatterplot/mainwindow.h new file mode 100644 index 000000000..ed5f2ac39 --- /dev/null +++ b/qwt/examples/scatterplot/mainwindow.h @@ -0,0 +1,22 @@ +#ifndef _MAINWINDOW_H_ +#define _MAINWINDOW_H_ 1 + +#include + +class Plot; + +class MainWindow: public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + +private: + void setSamples( int samples ); + +private: + Plot *d_plot; +}; + +#endif diff --git a/qwt/examples/scatterplot/plot.cpp b/qwt/examples/scatterplot/plot.cpp new file mode 100644 index 000000000..1e65a399a --- /dev/null +++ b/qwt/examples/scatterplot/plot.cpp @@ -0,0 +1,91 @@ +#include "plot.h" +#include +#include +#include +#include +#include + +class DistancePicker: public QwtPlotPicker +{ +public: + DistancePicker( QWidget *canvas ): + QwtPlotPicker( canvas ) + { + setTrackerMode( QwtPicker::ActiveOnly ); + setStateMachine( new QwtPickerDragLineMachine() ); + setRubberBand( QwtPlotPicker::PolygonRubberBand ); + } + + virtual QwtText trackerTextF( const QPointF &pos ) const + { + QwtText text; + + const QPolygon points = selection(); + if ( !points.isEmpty() ) + { + QString num; + num.setNum( QLineF( pos, invTransform( points[0] ) ).length() ); + + QColor bg( Qt::white ); + bg.setAlpha( 200 ); + + text.setBackgroundBrush( QBrush( bg ) ); + text.setText( num ); + } + return text; + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ), + d_curve( NULL ) +{ + canvas()->setStyleSheet( + "border: 2px solid Black;" + "border-radius: 15px;" + "background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1," + "stop: 0 LemonChiffon, stop: 1 PaleGoldenrod );" + ); + + // attach curve + d_curve = new QwtPlotCurve( "Scattered Points" ); + d_curve->setPen( QColor( "Purple" ) ); + + // when using QwtPlotCurve::ImageBuffer simple dots can be + // rendered in parallel on multicore systems. + d_curve->setRenderThreadCount( 0 ); // 0: use QThread::idealThreadCount() + + d_curve->attach( this ); + + setSymbol( NULL ); + + // panning with the left mouse button + (void )new QwtPlotPanner( canvas() ); + + // zoom in/out with the wheel + QwtPlotMagnifier *magnifier = new QwtPlotMagnifier( canvas() ); + magnifier->setMouseButton( Qt::NoButton ); + + // distanve measurement with the right mouse button + DistancePicker *picker = new DistancePicker( canvas() ); + picker->setMousePattern( QwtPlotPicker::MouseSelect1, Qt::RightButton ); + picker->setRubberBandPen( QPen( Qt::blue ) ); +} + +void Plot::setSymbol( QwtSymbol *symbol ) +{ + d_curve->setSymbol( symbol ); + + if ( symbol == NULL ) + { + d_curve->setStyle( QwtPlotCurve::Dots ); + } +} + +void Plot::setSamples( const QVector &samples ) +{ + d_curve->setPaintAttribute( + QwtPlotCurve::ImageBuffer, samples.size() > 1000 ); + + d_curve->setSamples( samples ); +} diff --git a/qwt/examples/scatterplot/plot.h b/qwt/examples/scatterplot/plot.h new file mode 100644 index 000000000..57cf4443a --- /dev/null +++ b/qwt/examples/scatterplot/plot.h @@ -0,0 +1,23 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ 1 + +#include + +class QwtPlotCurve; +class QwtSymbol; + +class Plot : public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent = NULL ); + + void setSymbol( QwtSymbol * ); + void setSamples( const QVector &samples ); + +private: + QwtPlotCurve *d_curve; +}; + +#endif // _PLOT_H_ diff --git a/qwt/examples/scatterplot/scatterplot.pro b/qwt/examples/scatterplot/scatterplot.pro new file mode 100644 index 000000000..67ad530ce --- /dev/null +++ b/qwt/examples/scatterplot/scatterplot.pro @@ -0,0 +1,22 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = scatterplot + +HEADERS = \ + mainwindow.h \ + plot.h + +SOURCES = \ + main.cpp \ + mainwindow.cpp \ + plot.cpp + diff --git a/qwt/examples/simpleplot/simpleplot.cpp b/qwt/examples/simpleplot/simpleplot.cpp new file mode 100644 index 000000000..9796bc767 --- /dev/null +++ b/qwt/examples/simpleplot/simpleplot.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include +#include + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QwtPlot plot; + plot.setTitle( "Plot Demo" ); + plot.setCanvasBackground( Qt::white ); + plot.setAxisScale( QwtAxis::yLeft, 0.0, 10.0 ); + plot.insertLegend( new QwtLegend() ); + + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->attach( &plot ); + + QwtPlotCurve *curve = new QwtPlotCurve(); + curve->setTitle( "Some Points" ); + curve->setPen( Qt::blue, 4 ), + curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + + QwtSymbol *symbol = new QwtSymbol( QwtSymbol::Ellipse, + QBrush( Qt::yellow ), QPen( Qt::red, 2 ), QSize( 8, 8 ) ); + curve->setSymbol( symbol ); + + QPolygonF points; + points << QPointF( 0.0, 4.4 ) << QPointF( 1.0, 3.0 ) + << QPointF( 2.0, 4.5 ) << QPointF( 3.0, 6.8 ) + << QPointF( 4.0, 7.9 ) << QPointF( 5.0, 7.1 ); + curve->setSamples( points ); + + curve->attach( &plot ); + + plot.resize( 600, 400 ); + plot.show(); + + return a.exec(); +} diff --git a/qwt/examples/simpleplot/simpleplot.pro b/qwt/examples/simpleplot/simpleplot.pro new file mode 100644 index 000000000..62dcad194 --- /dev/null +++ b/qwt/examples/simpleplot/simpleplot.pro @@ -0,0 +1,16 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = simpleplot + +SOURCES = \ + simpleplot.cpp + diff --git a/qwt/examples/sinusplot/sinusplot.cpp b/qwt/examples/sinusplot/sinusplot.cpp new file mode 100644 index 000000000..215275b30 --- /dev/null +++ b/qwt/examples/sinusplot/sinusplot.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//----------------------------------------------------------------- +// simple.cpp +// +// A simple example which shows how to use QwtPlot connected +// to a data class without any storage, calculating each values +// on the fly. +//----------------------------------------------------------------- + +class FunctionData: public QwtSyntheticPointData +{ +public: + FunctionData( double( *y )( double ) ): + QwtSyntheticPointData( 100 ), + d_y( y ) + { + } + + virtual double y( double x ) const + { + return d_y( x ); + } + +private: + double( *d_y )( double ); +}; + +class ArrowSymbol: public QwtSymbol +{ +public: + ArrowSymbol() + { + QPen pen( Qt::black, 0 ); + pen.setJoinStyle( Qt::MiterJoin ); + + setPen( pen ); + setBrush( Qt::red ); + + QPainterPath path; + path.moveTo( 0, 8 ); + path.lineTo( 0, 5 ); + path.lineTo( -3, 5 ); + path.lineTo( 0, 0 ); + path.lineTo( 3, 5 ); + path.lineTo( 0, 5 ); + + QTransform transform; + transform.rotate( -30.0 ); + path = transform.map( path ); + + setPath( path ); + setPinPoint( QPointF( 0, 0 ) ); + + setSize( 10, 14 ); + } +}; + +class Plot : public QwtPlot +{ +public: + Plot( QWidget *parent = NULL ); + +protected: + virtual void resizeEvent( QResizeEvent * ); + +private: + void populate(); + void updateGradient(); +}; + + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setAutoFillBackground( true ); + setPalette( QPalette( QColor( 165, 193, 228 ) ) ); + updateGradient(); + + setTitle( "A Simple QwtPlot Demonstration" ); + insertLegend( new QwtLegend(), QwtPlot::RightLegend ); + + // axes + setAxisTitle( QwtAxis::xBottom, "x -->" ); + setAxisScale( QwtAxis::xBottom, 0.0, 10.0 ); + + setAxisTitle( QwtAxis::yLeft, "y -->" ); + setAxisScale( QwtAxis::yLeft, -1.0, 1.0 ); + + // canvas + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setLineWidth( 1 ); + canvas->setFrameStyle( QFrame::Box | QFrame::Plain ); + canvas->setBorderRadius( 15 ); + + QPalette canvasPalette( Qt::white ); + canvasPalette.setColor( QPalette::Foreground, QColor( 133, 190, 232 ) ); + canvas->setPalette( canvasPalette ); + + setCanvas( canvas ); + + // panning with the left mouse button + ( void ) new QwtPlotPanner( canvas ); + + // zoom in/out with the wheel + ( void ) new QwtPlotMagnifier( canvas ); + + populate(); +} + +void Plot::populate() +{ + // Insert new curves + QwtPlotCurve *cSin = new QwtPlotCurve( "y = sin(x)" ); + cSin->setRenderHint( QwtPlotItem::RenderAntialiased ); + cSin->setLegendAttribute( QwtPlotCurve::LegendShowLine, true ); + cSin->setPen( Qt::red ); + cSin->attach( this ); + + QwtPlotCurve *cCos = new QwtPlotCurve( "y = cos(x)" ); + cCos->setRenderHint( QwtPlotItem::RenderAntialiased ); + cCos->setLegendAttribute( QwtPlotCurve::LegendShowLine, true ); + cCos->setPen( Qt::blue ); + cCos->attach( this ); + + // Create sin and cos data + cSin->setData( new FunctionData( ::sin ) ); + cCos->setData( new FunctionData( ::cos ) ); + + // Insert markers + + // ...a horizontal line at y = 0... + QwtPlotMarker *mY = new QwtPlotMarker(); + mY->setLabel( QString::fromLatin1( "y = 0" ) ); + mY->setLabelAlignment( Qt::AlignRight | Qt::AlignTop ); + mY->setLineStyle( QwtPlotMarker::HLine ); + mY->setYValue( 0.0 ); + mY->attach( this ); + + // ...a vertical line at x = 2 * pi + QwtPlotMarker *mX = new QwtPlotMarker(); + mX->setLabel( QString::fromLatin1( "x = 2 pi" ) ); + mX->setLabelAlignment( Qt::AlignLeft | Qt::AlignBottom ); + mX->setLabelOrientation( Qt::Vertical ); + mX->setLineStyle( QwtPlotMarker::VLine ); + mX->setLinePen( Qt::black, 0, Qt::DashDotLine ); + mX->setXValue( 2.0 * M_PI ); + mX->attach( this ); + + const double x = 7.7; + + // an arrow at a specific position + QwtPlotMarker *mPos = new QwtPlotMarker( "Marker" ); + mPos->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + mPos->setItemAttribute( QwtPlotItem::Legend, true ); + mPos->setSymbol( new ArrowSymbol() ); + mPos->setValue( QPointF( x, ::sin( x ) ) ); + mPos->setLabel( QString( "x = %1" ).arg( x ) ); + mPos->setLabelAlignment( Qt::AlignRight | Qt::AlignBottom ); + mPos->attach( this ); +} + +void Plot::updateGradient() +{ + QPalette pal = palette(); + + const QColor buttonColor = pal.color( QPalette::Button ); + + QLinearGradient gradient( rect().topLeft(), rect().bottomLeft() ); + gradient.setColorAt( 0.0, Qt::white ); + gradient.setColorAt( 0.7, buttonColor ); + gradient.setColorAt( 1.0, buttonColor ); + + pal.setBrush( QPalette::Window, gradient ); + setPalette( pal ); +} + +void Plot::resizeEvent( QResizeEvent *event ) +{ + QwtPlot::resizeEvent( event ); + + // Qt 4.7.1: QGradient::StretchToDeviceMode is buggy on X11 + updateGradient(); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + Plot *plot = new Plot(); + + // We put a dummy widget around to have + // so that Qt paints a widget background + // when resizing + + QWidget window; + QHBoxLayout *layout = new QHBoxLayout( &window ); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->addWidget( plot ); + + window.resize( 600, 400 ); + window.show(); + + return a.exec(); +} diff --git a/qwt/examples/sinusplot/sinusplot.pro b/qwt/examples/sinusplot/sinusplot.pro new file mode 100644 index 000000000..fc43e58fb --- /dev/null +++ b/qwt/examples/sinusplot/sinusplot.pro @@ -0,0 +1,16 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = sinusplot + +SOURCES = \ + sinusplot.cpp + diff --git a/qwt/examples/spectrogram/main.cpp b/qwt/examples/spectrogram/main.cpp new file mode 100644 index 000000000..e4d004198 --- /dev/null +++ b/qwt/examples/spectrogram/main.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + Plot *d_plot; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new Plot( this ); + + setCentralWidget( d_plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QToolButton *btnSpectrogram = new QToolButton( toolBar ); + btnSpectrogram->setText( "Spectrogram" ); + btnSpectrogram->setCheckable( true ); + btnSpectrogram->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnSpectrogram ); + connect( btnSpectrogram, SIGNAL( toggled( bool ) ), + d_plot, SLOT( showSpectrogram( bool ) ) ); + + QToolButton *btnContour = new QToolButton( toolBar ); + btnContour->setText( "Contour" ); + btnContour->setCheckable( true ); + btnContour->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnContour ); + connect( btnContour, SIGNAL( toggled( bool ) ), + d_plot, SLOT( showContour( bool ) ) ); + +#ifndef QT_NO_PRINTER + QToolButton *btnPrint = new QToolButton( toolBar ); + btnPrint->setText( "Print" ); + btnPrint->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnPrint ); + connect( btnPrint, SIGNAL( clicked() ), + d_plot, SLOT( printPlot() ) ); +#endif + + QSlider *slider = new QSlider( Qt::Horizontal ); + slider->setRange( 0, 255 ); + slider->setValue( 255 ); + connect( slider, SIGNAL( valueChanged( int ) ), + d_plot, SLOT( setAlpha( int ) ) ); + + toolBar->addWidget( slider ); + + addToolBar( toolBar ); + + btnSpectrogram->setChecked( true ); + btnContour->setChecked( false ); + +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/spectrogram/plot.cpp b/qwt/examples/spectrogram/plot.cpp new file mode 100644 index 000000000..36cbc0b06 --- /dev/null +++ b/qwt/examples/spectrogram/plot.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "plot.h" + +class MyZoomer: public QwtPlotZoomer +{ +public: + MyZoomer( QWidget *canvas ): + QwtPlotZoomer( canvas ) + { + setTrackerMode( AlwaysOn ); + } + + virtual QwtText trackerTextF( const QPointF &pos ) const + { + QColor bg( Qt::white ); + bg.setAlpha( 200 ); + + QwtText text = QwtPlotZoomer::trackerTextF( pos ); + text.setBackgroundBrush( QBrush( bg ) ); + return text; + } +}; + +class SpectrogramData: public QwtRasterData +{ +public: + SpectrogramData() + { + setInterval( Qt::XAxis, QwtInterval( -1.5, 1.5 ) ); + setInterval( Qt::YAxis, QwtInterval( -1.5, 1.5 ) ); + setInterval( Qt::ZAxis, QwtInterval( 0.0, 10.0 ) ); + } + + virtual double value( double x, double y ) const + { + const double c = 0.842; + + const double v1 = x * x + ( y - c ) * ( y + c ); + const double v2 = x * ( y + c ) + x * ( y + c ); + + return 1.0 / ( v1 * v1 + v2 * v2 ); + } +}; + +class ColorMap: public QwtLinearColorMap +{ +public: + ColorMap(): + QwtLinearColorMap( Qt::darkCyan, Qt::red ) + { + addColorStop( 0.1, Qt::cyan ); + addColorStop( 0.6, Qt::green ); + addColorStop( 0.95, Qt::yellow ); + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + d_spectrogram = new QwtPlotSpectrogram(); + d_spectrogram->setRenderThreadCount( 0 ); // use system specific thread count + + d_spectrogram->setColorMap( new ColorMap() ); + d_spectrogram->setCachePolicy( QwtPlotRasterItem::PaintCache ); + + d_spectrogram->setData( new SpectrogramData() ); + d_spectrogram->attach( this ); + + QList contourLevels; + for ( double level = 0.5; level < 10.0; level += 1.0 ) + contourLevels += level; + d_spectrogram->setContourLevels( contourLevels ); + + const QwtInterval zInterval = d_spectrogram->data()->interval( Qt::ZAxis ); + // A color bar on the right axis + QwtScaleWidget *rightAxis = axisWidget( QwtAxis::yRight ); + rightAxis->setTitle( "Intensity" ); + rightAxis->setColorBarEnabled( true ); + rightAxis->setColorMap( zInterval, new ColorMap() ); + + setAxisScale( QwtAxis::yRight, zInterval.minValue(), zInterval.maxValue() ); + setAxisVisible( QwtAxis::yRight ); + + plotLayout()->setAlignCanvasToScales( true ); + replot(); + + // LeftButton for the zooming + // MidButton for the panning + // RightButton: zoom out by 1 + // Ctrl+RighButton: zoom out to full size + + QwtPlotZoomer* zoomer = new MyZoomer( canvas() ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect3, + Qt::RightButton ); + + QwtPlotPanner *panner = new QwtPlotPanner( canvas() ); + panner->setAxisEnabled( QwtAxis::yRight, false ); + panner->setMouseButton( Qt::MidButton ); + + // Avoid jumping when labels with more/less digits + // appear/disappear when scrolling vertically + + const QFontMetrics fm( axisWidget( QwtAxis::yLeft )->font() ); + QwtScaleDraw *sd = axisScaleDraw( QwtAxis::yLeft ); + sd->setMinimumExtent( fm.width( "100.00" ) ); + + const QColor c( Qt::darkBlue ); + zoomer->setRubberBandPen( c ); + zoomer->setTrackerPen( c ); +} + +void Plot::showContour( bool on ) +{ + d_spectrogram->setDisplayMode( QwtPlotSpectrogram::ContourMode, on ); + replot(); +} + +void Plot::showSpectrogram( bool on ) +{ + d_spectrogram->setDisplayMode( QwtPlotSpectrogram::ImageMode, on ); + d_spectrogram->setDefaultContourPen( + on ? QPen( Qt::black, 0 ) : QPen( Qt::NoPen ) ); + + replot(); +} + +void Plot::setAlpha( int alpha ) +{ + d_spectrogram->setAlpha( alpha ); + replot(); +} + +#ifndef QT_NO_PRINTER + +void Plot::printPlot() +{ + QPrinter printer( QPrinter::HighResolution ); + printer.setOrientation( QPrinter::Landscape ); + printer.setOutputFileName( "spectrogram.pdf" ); + + QPrintDialog dialog( &printer ); + if ( dialog.exec() ) + { + QwtPlotRenderer renderer; + + if ( printer.colorMode() == QPrinter::GrayScale ) + { + renderer.setDiscardFlag( QwtPlotRenderer::DiscardBackground ); + renderer.setDiscardFlag( QwtPlotRenderer::DiscardCanvasBackground ); + renderer.setDiscardFlag( QwtPlotRenderer::DiscardCanvasFrame ); + renderer.setLayoutFlag( QwtPlotRenderer::FrameWithScales ); + } + + renderer.renderTo( this, printer ); + } +} + +#endif diff --git a/qwt/examples/spectrogram/plot.h b/qwt/examples/spectrogram/plot.h new file mode 100644 index 000000000..c741a1748 --- /dev/null +++ b/qwt/examples/spectrogram/plot.h @@ -0,0 +1,22 @@ +#include +#include + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + +public Q_SLOTS: + void showContour( bool on ); + void showSpectrogram( bool on ); + void setAlpha( int ); + +#ifndef QT_NO_PRINTER + void printPlot(); +#endif + +private: + QwtPlotSpectrogram *d_spectrogram; +}; diff --git a/qwt/examples/spectrogram/spectrogram.pro b/qwt/examples/spectrogram/spectrogram.pro new file mode 100644 index 000000000..b35b101e8 --- /dev/null +++ b/qwt/examples/spectrogram/spectrogram.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = spectrogram + +HEADERS = \ + plot.h + +SOURCES = \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/stockchart/griditem.cpp b/qwt/examples/stockchart/griditem.cpp new file mode 100644 index 000000000..c73beffd4 --- /dev/null +++ b/qwt/examples/stockchart/griditem.cpp @@ -0,0 +1,278 @@ +#include "griditem.h" +#include +#include +#include + +GridItem::GridItem(): + QwtPlotItem( QwtText( "Grid" ) ), + m_orientations( Qt::Horizontal | Qt::Vertical ), + m_gridAttributes( AutoUpdate | FillCanvas ), + m_isXMinEnabled( false ), + m_isYMinEnabled( false ) +{ + setItemInterest( QwtPlotItem::ScaleInterest, true ); + setZ( 10.0 ); +} + +GridItem::~GridItem() +{ +} + +int GridItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotUserItem + 99; // something +} + +void GridItem::setGridAttribute( GridAttribute attribute, bool on ) +{ + if ( bool( m_gridAttributes & attribute ) == on ) + return; + + if ( on ) + m_gridAttributes |= attribute; + else + m_gridAttributes &= ~attribute; + + itemChanged(); +} + +bool GridItem::testGridAttribute( GridAttribute attribute ) const +{ + return m_gridAttributes & attribute; +} + +void GridItem::setOrientations( Qt::Orientations orientations ) +{ + if ( m_orientations != orientations ) + { + m_orientations = orientations; + itemChanged(); + } +} + +Qt::Orientations GridItem::orientations() const +{ + return m_orientations; +} + +void GridItem::enableXMin( bool enabled ) +{ + if ( enabled != m_isXMinEnabled ) + { + m_isXMinEnabled = enabled; + itemChanged(); + } +} + +bool GridItem::isXMinEnabled() const +{ + return m_isXMinEnabled; +} + +void GridItem::enableYMin( bool enabled ) +{ + if ( enabled != m_isYMinEnabled ) + { + m_isYMinEnabled = enabled; + itemChanged(); + } +} + +bool GridItem::isYMinEnabled() const +{ + return m_isYMinEnabled; +} + +void GridItem::setXDiv( const QwtScaleDiv &scaleDiv ) +{ + if ( m_xScaleDiv != scaleDiv ) + { + m_xScaleDiv = scaleDiv; + itemChanged(); + } +} + +void GridItem::setYDiv( const QwtScaleDiv &scaleDiv ) +{ + if ( m_yScaleDiv != scaleDiv ) + { + m_yScaleDiv = scaleDiv; + itemChanged(); + } +} + +void GridItem::setPalette( const QPalette &palette ) +{ + if ( m_palette != palette ) + { + m_palette = palette; + itemChanged(); + } +} + +QPalette GridItem::palette() const +{ + return m_palette; +} + +void GridItem::draw( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + const QRectF area = QwtScaleMap::invTransform( xMap, yMap, canvasRect ); + + QList xValues; + if ( m_orientations & Qt::Horizontal ) + { + xValues = m_xScaleDiv.ticks( QwtScaleDiv::MajorTick ); + + if ( m_isXMinEnabled ) + { + xValues += m_xScaleDiv.ticks( QwtScaleDiv::MediumTick ); + xValues += m_xScaleDiv.ticks( QwtScaleDiv::MinorTick ); + } + + if ( m_gridAttributes & FillCanvas ) + { + xValues += area.left(); + xValues += area.right(); + } + + qSort( xValues ); + } + + QList yValues; + if ( m_orientations & Qt::Vertical ) + { + yValues = m_yScaleDiv.ticks( QwtScaleDiv::MajorTick ); + + if ( m_isYMinEnabled ) + { + yValues += m_yScaleDiv.ticks( QwtScaleDiv::MediumTick ); + yValues += m_yScaleDiv.ticks( QwtScaleDiv::MinorTick ); + } + + if ( m_gridAttributes & FillCanvas ) + { + yValues += area.top(); + yValues += area.bottom(); + } + + qSort( yValues ); + } + + painter->setPen( Qt::NoPen ); + + if ( ( m_orientations & Qt::Horizontal ) && + ( m_orientations & Qt::Vertical ) ) + { + for ( int i = 1; i < xValues.size(); i++ ) + { + double x1 = xMap.transform( xValues[i - 1] ); + double x2 = xMap.transform( xValues[i] ); + + if ( doAlign ) + { + x1 = qRound( x1 ); + x2 = qRound( x2 ); + } + + for ( int j = 1; j < yValues.size(); j++ ) + { + const QRectF rect( xValues[i - 1], yValues[j - 1], + xValues[i] - xValues[i - 1], yValues[j] - yValues[j - 1] ); + + painter->setBrush( brush( i - 1, j - 1, rect ) ); + + double y1 = yMap.transform( yValues[j - 1] ); + double y2 = yMap.transform( yValues[j] ); + + if ( doAlign ) + { + y1 = qRound( y1 ); + y2 = qRound( y2 ); + } + + QwtPainter::drawRect( painter, x1, y1, x2 - x1, y2 - y1 ); + } + } + } + else if ( m_orientations & Qt::Horizontal ) + { + for ( int i = 1; i < xValues.size(); i++ ) + { + const QRectF rect( xValues[i - 1], area.top(), + xValues[i] - xValues[i - 1], area.bottom() ); + + painter->setBrush( brush( i - 1, 0, rect ) ); + + double x1 = xMap.transform( xValues[i - 1] ); + double x2 = xMap.transform( xValues[i] ); + + if ( doAlign ) + { + x1 = qRound( x1 ); + x2 = qRound( x2 ); + } + + QwtPainter::drawRect( painter, + x1, canvasRect.top(), x2 - x1, canvasRect.height() ); + } + } + else if ( m_orientations & Qt::Vertical ) + { + for ( int i = 1; i < yValues.size(); i++ ) + { + const QRectF rect( area.left(), yValues[i - 1], + area.width(), yValues[i] - yValues[i - 1] ); + + painter->setBrush( brush( 0, i - 1, rect ) ); + + double y1 = yMap.transform( yValues[i - 1] ); + double y2 = yMap.transform( yValues[i] ); + + if ( doAlign ) + { + y1 = qRound( y1 ); + y2 = qRound( y2 ); + } + + QwtPainter::drawRect( painter, canvasRect.left(), y1, + canvasRect.width(), y2 - y1 ); + } + } +} + +const QwtScaleDiv &GridItem::xScaleDiv() const +{ + return m_xScaleDiv; +} + +const QwtScaleDiv &GridItem::yScaleDiv() const +{ + return m_yScaleDiv; +} + +void GridItem::updateScaleDiv( + const QwtScaleDiv& xScaleDiv, const QwtScaleDiv& yScaleDiv ) +{ + if ( m_gridAttributes & AutoUpdate ) + { + setXDiv( xScaleDiv ); + setYDiv( yScaleDiv ); + } +} + +QBrush GridItem::brush( int row, int column, const QRectF & ) const +{ + /* + We need some sort of origin to avoid, that the brush + changes for the same rectangle when panning + */ + if ( ( row + column ) % 2 ) + return QBrush( m_palette.brush( QPalette::Base ) ); + else + return QBrush( m_palette.brush( QPalette::AlternateBase ) ); +} diff --git a/qwt/examples/stockchart/griditem.h b/qwt/examples/stockchart/griditem.h new file mode 100644 index 000000000..f506a64fd --- /dev/null +++ b/qwt/examples/stockchart/griditem.h @@ -0,0 +1,70 @@ +#ifndef _GRID_ITEM_H_ +#define _GRID_ITEM_H_ + +#include +#include +#include + +class GridItem: public QwtPlotItem +{ +public: + enum GridAttribute + { + AutoUpdate = 0x01, + FillCanvas = 0x02 + }; + + typedef QFlags GridAttributes; + + explicit GridItem(); + virtual ~GridItem(); + + virtual int rtti() const; + + void setGridAttribute( GridAttribute, bool on = true ); + bool testGridAttribute( GridAttribute ) const; + + void setOrientations( Qt::Orientations ); + Qt::Orientations orientations() const; + + void enableXMin( bool ); + bool isXMinEnabled() const; + + void enableYMin( bool ); + bool isYMinEnabled() const; + + void setXDiv( const QwtScaleDiv &sx ); + const QwtScaleDiv &xScaleDiv() const; + + void setYDiv( const QwtScaleDiv &sy ); + const QwtScaleDiv &yScaleDiv() const; + + void setPalette( const QPalette & ); + QPalette palette() const; + + virtual void draw( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &rect ) const; + + virtual void updateScaleDiv( + const QwtScaleDiv &xMap, const QwtScaleDiv &yMap ); + +protected: + virtual QBrush brush( int row, int column, const QRectF & ) const; + +private: + Qt::Orientations m_orientations; + GridAttributes m_gridAttributes; + + QwtScaleDiv m_xScaleDiv; + QwtScaleDiv m_yScaleDiv; + + bool m_isXMinEnabled; + bool m_isYMinEnabled; + + QPalette m_palette; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( GridItem::GridAttributes ) + +#endif diff --git a/qwt/examples/stockchart/legend.cpp b/qwt/examples/stockchart/legend.cpp new file mode 100644 index 000000000..2b065d628 --- /dev/null +++ b/qwt/examples/stockchart/legend.cpp @@ -0,0 +1,353 @@ +#include "legend.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void qwtRenderBackground( QPainter *painter, + const QRectF &rect, const QWidget *widget ) +{ + if ( widget->testAttribute( Qt::WA_StyledBackground ) ) + { + QStyleOption opt; + opt.initFrom( widget ); + opt.rect = rect.toAlignedRect(); + + widget->style()->drawPrimitive( + QStyle::PE_Widget, &opt, painter, widget); + } + else + { + const QBrush brush = + widget->palette().brush( widget->backgroundRole() ); + + painter->fillRect( rect, brush ); + } +} + +class LegendTreeView: public QTreeView +{ +public: + LegendTreeView( Legend * ); + + QStandardItem *rootItem( int rtti ); + QStandardItem *insertRootItem( int rtti ); + + QList itemList( const QwtPlotItem * ); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; +}; + +LegendTreeView::LegendTreeView( Legend *legend ): + QTreeView( legend ) +{ + setFrameStyle( NoFrame ); + viewport()->setBackgroundRole(QPalette::Background); + viewport()->setAutoFillBackground( false ); + + setRootIsDecorated( true ); + setHeaderHidden( true ); + + QStandardItemModel *model = new QStandardItemModel(); + + setModel( model ); + + // we want unstyled items + setItemDelegate( new QItemDelegate( this ) ); +} + +QStandardItem *LegendTreeView::rootItem( int rtti ) +{ + QStandardItemModel *mdl = + qobject_cast( model() ); + + for ( int row = 0; row < mdl->rowCount(); row++ ) + { + QStandardItem *item = mdl->item( row ); + if ( item->data() == rtti ) + return item; + } + + return NULL; +} + +QList LegendTreeView::itemList( + const QwtPlotItem *plotItem ) +{ + QList itemList; + + const QStandardItem *rootItem = this->rootItem( plotItem->rtti() ); + if ( rootItem ) + { + for ( int i = 0; i < rootItem->rowCount(); i++ ) + { + QStandardItem *item = rootItem->child( i ); + + const QVariant key = item->data(); + + if ( key.canConvert() ) + { + const qlonglong ptr = key.value(); + if ( ptr == qlonglong( plotItem ) ) + itemList += item; + } + } + } + + return itemList; +} + +QStandardItem *LegendTreeView::insertRootItem( int rtti ) +{ + QStandardItem *item = new QStandardItem(); + item->setEditable( false ); + item->setData( rtti ); + + switch( rtti ) + { + case QwtPlotItem::Rtti_PlotTradingCurve: + { + item->setText( "Curves" ); + break; + } + case QwtPlotItem::Rtti_PlotZone: + { + item->setText( "Zones" ); + break; + } + case QwtPlotItem::Rtti_PlotMarker: + { + item->setText( "Events" ); + break; + } + default: + break; + } + + QStandardItemModel *mdl = + qobject_cast( model() ); + + mdl->appendRow( item ); + setExpanded( mdl->index( mdl->rowCount() - 1, 0 ), true ); + + return item; +} + +QSize LegendTreeView::minimumSizeHint() const +{ + return QSize( -1, -1 ); +} + +QSize LegendTreeView::sizeHint() const +{ + QStyleOptionViewItem styleOption; + styleOption.initFrom( this ); + + const QAbstractItemDelegate *delegate = itemDelegate(); + + const QStandardItemModel *mdl = + qobject_cast( model() ); + + int w = 0; + int h = 0; + + for ( int row = 0; row < mdl->rowCount(); row++ ) + { + const QStandardItem *rootItem = mdl->item( row ); + + int wRow = 0; + for ( int i = 0; i < rootItem->rowCount(); i++ ) + { + const QSize hint = delegate->sizeHint( styleOption, + rootItem->child( i )->index() ); + + wRow = qMax( wRow, hint.width() ); + h += hint.height(); + } + + const QSize rootHint = delegate->sizeHint( + styleOption, rootItem->index() ); + + wRow = qMax( wRow + indentation(), rootHint.width() ); + if ( wRow > w ) + w = wRow; + + if ( rootIsDecorated() ) + w += indentation(); + + h += rootHint.height(); + } + + int left, right, top, bottom; + getContentsMargins( &left, &top, &right, &bottom ); + + w += left + right; + h += top + bottom; + + return QSize( w, h ); +} + +Legend::Legend( QWidget *parent ): + QwtAbstractLegend( parent ) +{ + d_treeView = new LegendTreeView( this ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->addWidget( d_treeView ); + + connect( d_treeView, SIGNAL( clicked( const QModelIndex & ) ), + this, SLOT( handleClick( const QModelIndex & ) ) ); +} + +Legend::~Legend() +{ +} + +void Legend::renderLegend( QPainter *painter, + const QRectF &rect, bool fillBackground ) const +{ + if ( fillBackground ) + { + if ( autoFillBackground() || + testAttribute( Qt::WA_StyledBackground ) ) + { + qwtRenderBackground( painter, rect, d_treeView ); + } + } + + QStyleOptionViewItem styleOption; + styleOption.initFrom( this ); + styleOption.decorationAlignment = Qt::AlignCenter; + + const QAbstractItemDelegate *delegate = d_treeView->itemDelegate(); + + const QStandardItemModel *mdl = + qobject_cast( d_treeView->model() ); + + painter->save(); + painter->translate( rect.topLeft() ); + + for ( int row = 0; row < mdl->rowCount(); row++ ) + { + const QStandardItem *rootItem = mdl->item( row ); + + styleOption.rect = d_treeView->visualRect( rootItem->index() ); + if ( !styleOption.rect.isEmpty() ) + delegate->paint( painter, styleOption, rootItem->index() ); + + for ( int i = 0; i < rootItem->rowCount(); i++ ) + { + const QStandardItem *item = rootItem->child( i ); + + styleOption.rect = d_treeView->visualRect( item->index() ); + if ( !styleOption.rect.isEmpty() ) + { + delegate->paint( painter, styleOption, item->index() ); + } + } + } + painter->restore(); +} + +bool Legend::isEmpty() const +{ + return d_treeView->model()->rowCount() == 0; +} + +int Legend::scrollExtent( Qt::Orientation orientation ) const +{ + Q_UNUSED( orientation ); + + return style()->pixelMetric( QStyle::PM_ScrollBarExtent ); +} + +void Legend::updateLegend( const QVariant &itemInfo, + const QList &data ) +{ + QwtPlotItem *plotItem = qvariant_cast( itemInfo ); + + QStandardItem *rootItem = d_treeView->rootItem( plotItem->rtti() ); + QList itemList = d_treeView->itemList( plotItem ); + + while ( itemList.size() > data.size() ) + { + QStandardItem *item = itemList.takeLast(); + rootItem->removeRow( item->row() ); + } + + if ( !data.isEmpty() ) + { + if ( rootItem == NULL ) + rootItem = d_treeView->insertRootItem( plotItem->rtti() ); + + while ( itemList.size() < data.size() ) + { + QStandardItem *item = new QStandardItem(); + item->setEditable( false ); + item->setData( qlonglong( plotItem ) ); + item->setCheckable( true ); + item->setCheckState( plotItem->isVisible() ? + Qt::Checked : Qt::Unchecked ); + + itemList += item; + rootItem->appendRow( item ); + } + + for ( int i = 0; i < itemList.size(); i++ ) + updateItem( itemList[i], data[i] ); + } + else + { + if ( rootItem && rootItem->rowCount() == 0 ) + d_treeView->model()->removeRow( rootItem->row() ); + } + + d_treeView->updateGeometry(); +} + +void Legend::updateItem( QStandardItem *item, const QwtLegendData &data ) +{ + const QVariant titleValue = data.value( QwtLegendData::TitleRole ); + + QwtText title; + if ( titleValue.canConvert() ) + { + item->setText( title.text() ); + title = titleValue.value(); + } + else if ( titleValue.canConvert() ) + { + title.setText( titleValue.value() ); + } + item->setText( title.text() ); + + const QVariant iconValue = data.value( QwtLegendData::IconRole ); + + QPixmap pm; + if ( iconValue.canConvert() ) + pm = iconValue.value(); + + item->setData(pm, Qt::DecorationRole); +} + +void Legend::handleClick( const QModelIndex &index ) +{ + const QStandardItemModel *model = + qobject_cast( d_treeView->model() ); + + const QStandardItem *item = model->itemFromIndex( index ); + if ( item->isCheckable() ) + { + const qlonglong ptr = item->data().value(); + + Q_EMIT checked( (QwtPlotItem *)ptr, + item->checkState() == Qt::Checked, 0 ); + } +} diff --git a/qwt/examples/stockchart/legend.h b/qwt/examples/stockchart/legend.h new file mode 100644 index 000000000..56cd7cdf2 --- /dev/null +++ b/qwt/examples/stockchart/legend.h @@ -0,0 +1,42 @@ +#ifndef _LEGEND_H_ +#define _LEGEND_H_ + +#include + +class LegendTreeView; +class QStandardItem; +class QModelIndex; +class QwtPlotItem; + +class Legend : public QwtAbstractLegend +{ + Q_OBJECT + +public: + explicit Legend( QWidget *parent = NULL ); + virtual ~Legend(); + + virtual void renderLegend( QPainter *, + const QRectF &, bool fillBackground ) const; + + virtual bool isEmpty() const; + + virtual int scrollExtent( Qt::Orientation ) const; + +Q_SIGNALS: + void checked( QwtPlotItem *plotItem, bool on, int index ); + +public Q_SLOTS: + virtual void updateLegend( const QVariant &, + const QList & ); + +private Q_SLOTS: + void handleClick( const QModelIndex & ); + +private: + void updateItem( QStandardItem *, const QwtLegendData & ); + + LegendTreeView *d_treeView; +}; + +#endif diff --git a/qwt/examples/stockchart/main.cpp b/qwt/examples/stockchart/main.cpp new file mode 100644 index 000000000..14e116a42 --- /dev/null +++ b/qwt/examples/stockchart/main.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + Plot *d_plot; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new Plot( this ); + setCentralWidget( d_plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *typeBox = new QComboBox( toolBar ); + typeBox->addItem( "Bars" ); + typeBox->addItem( "CandleSticks" ); + typeBox->setCurrentIndex( 1 ); + typeBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), d_plot, SLOT( exportPlot() ) ); + + toolBar->addWidget( typeBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + + d_plot->setMode( typeBox->currentIndex() ); + connect( typeBox, SIGNAL( currentIndexChanged( int ) ), + d_plot, SLOT( setMode( int ) ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.resize( 600, 400 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/stockchart/plot.cpp b/qwt/examples/stockchart/plot.cpp new file mode 100644 index 000000000..5dc37c425 --- /dev/null +++ b/qwt/examples/stockchart/plot.cpp @@ -0,0 +1,253 @@ +#include "plot.h" +#include "legend.h" +#include "griditem.h" +#include "quotefactory.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Zoomer: public QwtPlotZoomer +{ +public: + Zoomer( QWidget *canvas ): + QwtPlotZoomer( canvas ) + { + setRubberBandPen( QColor( Qt::darkGreen ) ); + setTrackerMode( QwtPlotPicker::AlwaysOn ); + } + +protected: + virtual QwtText trackerTextF( const QPointF &pos ) const + { + const QDateTime dt = QwtDate::toDateTime( pos.x() ); + + QString s; + s += QwtDate::toString( QwtDate::toDateTime( pos.x() ), + "MMM dd hh:mm ", QwtDate::FirstThursday ); + + QwtText text( s ); + text.setColor( Qt::white ); + + QColor c = rubberBandPen().color(); + text.setBorderPen( QPen( c ) ); + text.setBorderRadius( 6 ); + c.setAlpha( 170 ); + text.setBackgroundBrush( c ); + + return text; + } +}; + +class DateScaleDraw: public QwtDateScaleDraw +{ +public: + DateScaleDraw( Qt::TimeSpec timeSpec ): + QwtDateScaleDraw( timeSpec ) + { + // as we have dates from 2010 only we use + // format strings without the year + + setDateFormat( QwtDate::Millisecond, "hh:mm:ss:zzz\nddd dd MMM" ); + setDateFormat( QwtDate::Second, "hh:mm:ss\nddd dd MMM" ); + setDateFormat( QwtDate::Minute, "hh:mm\nddd dd MMM" ); + setDateFormat( QwtDate::Hour, "hh:mm\nddd dd MMM" ); + setDateFormat( QwtDate::Day, "ddd dd MMM" ); + setDateFormat( QwtDate::Week, "Www" ); + setDateFormat( QwtDate::Month, "MMM" ); + } +}; + +class ZoneItem: public QwtPlotZoneItem +{ +public: + ZoneItem( const QString &title ) + { + setTitle( title ); + setZ( 11 ); // on top the the grid + setOrientation( Qt::Vertical ); + setItemAttribute( QwtPlotItem::Legend, true ); + } + + void setColor( const QColor &color ) + { + QColor c = color; + + c.setAlpha( 100 ); + setPen( c ); + + c.setAlpha( 20 ); + setBrush( c ); + } + + void setInterval( const QDate &date1, const QDate &date2 ) + { + const QDateTime dt1( date1, QTime(), Qt::UTC ); + const QDateTime dt2( date2, QTime(), Qt::UTC ); + + QwtPlotZoneItem::setInterval( QwtDate::toDouble( dt1 ), + QwtDate::toDouble( dt2 ) ); + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setTitle( "Trading Chart" ); + + QwtDateScaleDraw *scaleDraw = new DateScaleDraw( Qt::UTC ); + QwtDateScaleEngine *scaleEngine = new QwtDateScaleEngine( Qt::UTC ); + + setAxisTitle( QwtAxis::xBottom, QString( "2010" ) ); + setAxisScaleDraw( QwtAxis::xBottom, scaleDraw ); + setAxisScaleEngine( QwtAxis::xBottom, scaleEngine ); + setAxisLabelRotation( QwtAxis::xBottom, -50.0 ); + setAxisLabelAlignment( QwtAxis::xBottom, Qt::AlignLeft | Qt::AlignBottom ); + + setAxisTitle( QwtAxis::yLeft, QString( "Price [EUR]" ) ); + +#if 0 + QwtLegend *legend = new QwtLegend; + legend->setDefaultItemMode( QwtLegendData::Checkable ); + insertLegend( legend, QwtPlot::RightLegend ); +#else + Legend *legend = new Legend; + insertLegend( legend, QwtPlot::RightLegend ); +#endif + + populate(); + + // LeftButton for the zooming + // MidButton for the panning + // RightButton: zoom out by 1 + // Ctrl+RighButton: zoom out to full size + + Zoomer* zoomer = new Zoomer( canvas() ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect3, + Qt::RightButton ); + + QwtPlotPanner *panner = new QwtPlotPanner( canvas() ); + panner->setMouseButton( Qt::MidButton ); + + connect( legend, SIGNAL( checked( QwtPlotItem *, bool, int ) ), + SLOT( showItem( QwtPlotItem *, bool ) ) ); +} + +void Plot::populate() +{ + GridItem *gridItem = new GridItem(); +#if 0 + gridItem->setOrientations( Qt::Horizontal ); +#endif + gridItem->attach( this ); + + const Qt::GlobalColor colors[] = + { + Qt::red, + Qt::blue, + Qt::darkCyan, + Qt::darkMagenta, + Qt::darkYellow + }; + + const int numColors = sizeof( colors ) / sizeof( colors[0] ); + + for ( int i = 0; i < QuoteFactory::NumStocks; i++ ) + { + QuoteFactory::Stock stock = static_cast( i ); + + QwtPlotTradingCurve *curve = new QwtPlotTradingCurve(); + curve->setTitle( QuoteFactory::title( stock ) ); + curve->setOrientation( Qt::Vertical ); + curve->setSamples( QuoteFactory::samples2010( stock ) ); + + // as we have one sample per day a symbol width of + // 12h avoids overlapping symbols. We also bound + // the width, so that is is not scaled below 3 and + // above 15 pixels. + + curve->setSymbolExtent( 12 * 3600 * 1000.0 ); + curve->setMinSymbolWidth( 3 ); + curve->setMaxSymbolWidth( 15 ); + + const Qt::GlobalColor color = colors[ i % numColors ]; + + curve->setSymbolPen( color ); + curve->setSymbolBrush( QwtPlotTradingCurve::Decreasing, color ); + curve->setSymbolBrush( QwtPlotTradingCurve::Increasing, Qt::white ); + curve->attach( this ); + + showItem( curve, true ); + } + + for ( int i = 0; i < 2; i++ ) + { + QwtPlotMarker *marker = new QwtPlotMarker(); + + marker->setTitle( QString( "Event %1" ).arg( i + 1 ) ); + marker->setLineStyle( QwtPlotMarker::VLine ); + marker->setLinePen( colors[ i % numColors ], 0, Qt::DashLine ); + marker->setVisible( false ); + + QDateTime dt( QDate( 2010, 1, 1 ) ); + dt = dt.addDays( 77 * ( i + 1 ) ); + + marker->setValue( QwtDate::toDouble( dt ), 0.0 ); + + marker->setItemAttribute( QwtPlotItem::Legend, true ); + + marker->attach( this ); + } + + // to show how QwtPlotZoneItem works + + ZoneItem *zone1 = new ZoneItem( "Zone 1"); + zone1->setColor( Qt::darkBlue ); + zone1->setInterval( QDate( 2010, 3, 10 ), QDate( 2010, 3, 27 ) ); + zone1->setVisible( false ); + zone1->attach( this ); + + ZoneItem *zone2 = new ZoneItem( "Zone 2"); + zone2->setColor( Qt::darkMagenta ); + zone2->setInterval( QDate( 2010, 8, 1 ), QDate( 2010, 8, 24 ) ); + zone2->setVisible( false ); + zone2->attach( this ); + +} + +void Plot::setMode( int style ) +{ + QwtPlotTradingCurve::SymbolStyle symbolStyle = + static_cast( style ); + + QwtPlotItemList curves = itemList( QwtPlotItem::Rtti_PlotTradingCurve ); + for ( int i = 0; i < curves.size(); i++ ) + { + QwtPlotTradingCurve *curve = + static_cast( curves[i] ); + curve->setSymbolStyle( symbolStyle ); + } + + replot(); +} + +void Plot::showItem( QwtPlotItem *item, bool on ) +{ + item->setVisible( on ); + replot(); +} + +void Plot::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "stockchart.pdf" ); +} diff --git a/qwt/examples/stockchart/plot.h b/qwt/examples/stockchart/plot.h new file mode 100644 index 000000000..bc6f883c2 --- /dev/null +++ b/qwt/examples/stockchart/plot.h @@ -0,0 +1,24 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + +public Q_SLOTS: + void setMode( int ); + void exportPlot(); + +private Q_SLOTS: + void showItem( QwtPlotItem *, bool on ); + +private: + void populate(); +}; + +#endif diff --git a/qwt/examples/stockchart/quotefactory.cpp b/qwt/examples/stockchart/quotefactory.cpp new file mode 100644 index 000000000..495660185 --- /dev/null +++ b/qwt/examples/stockchart/quotefactory.cpp @@ -0,0 +1,856 @@ +#include "quotefactory.h" +#include + +typedef struct +{ + int day; + + double open; + double high; + double low; + double close; + +} t_Data2010; + +static t_Data2010 bmwData[] = +{ + { 3, 31.82, 32.46, 31.82, 32.05 }, + { 4, 31.96, 32.41, 31.78, 32.31 }, + { 5, 32.45, 33.04, 32.36, 32.81 }, + { 6, 32.65, 33.20, 32.38, 33.10 }, + { 7, 33.33, 33.43, 32.51, 32.65 }, + { 10, 32.99, 33.05, 32.11, 32.17 }, + { 11, 32.26, 32.26, 31.10, 31.24 }, + { 12, 31.03, 31.52, 31.01, 31.42 }, + { 13, 31.61, 32.18, 31.50, 31.89 }, + { 14, 32.05, 32.13, 31.36, 31.63 }, + { 17, 31.82, 32.12, 31.43, 32.10 }, + { 18, 32.33, 32.45, 31.65, 32.43 }, + { 19, 32.30, 32.39, 31.67, 31.80 }, + { 20, 32.00, 32.19, 31.16, 31.16 }, + { 21, 31.14, 31.37, 30.32, 30.70 }, + { 24, 30.31, 30.79, 30.05, 30.14 }, + { 25, 30.00, 30.53, 29.40, 30.25 }, + { 26, 29.93, 30.14, 29.38, 29.59 }, + { 27, 29.95, 30.28, 29.49, 29.55 }, + { 28, 29.90, 31.30, 29.85, 30.96 }, + { 31, 30.69, 31.31, 30.56, 31.07 }, + { 32, 31.05, 31.28, 30.58, 31.17 }, + { 33, 31.28, 31.77, 31.01, 31.23 }, + { 34, 31.32, 31.53, 30.21, 30.33 }, + { 35, 30.25, 30.28, 29.43, 29.92 }, + { 38, 30.00, 30.45, 29.33, 29.61 }, + { 39, 29.75, 30.07, 29.35, 29.62 }, + { 40, 29.89, 30.12, 29.55, 29.67 }, + { 41, 29.81, 29.87, 29.02, 29.49 }, + { 42, 29.59, 29.84, 28.28, 29.00 }, + { 45, 29.00, 29.29, 28.46, 28.65 }, + { 46, 28.90, 29.45, 28.60, 29.41 }, + { 47, 29.68, 29.77, 29.35, 29.61 }, + { 48, 29.58, 29.76, 28.45, 29.42 }, + { 49, 29.22, 30.43, 29.01, 30.43 }, + { 52, 30.65, 30.67, 30.06, 30.26 }, + { 53, 30.35, 30.52, 29.53, 29.69 }, + { 54, 29.79, 29.87, 29.18, 29.49 }, + { 55, 29.25, 29.82, 29.06, 29.38 }, + { 56, 29.69, 30.00, 29.55, 29.78 }, + { 59, 30.20, 30.58, 29.95, 30.44 }, + { 60, 30.57, 31.47, 30.49, 31.34 }, + { 61, 31.40, 31.76, 31.08, 31.65 }, + { 62, 31.50, 31.80, 31.34, 31.56 }, + { 63, 31.63, 32.45, 31.63, 32.37 }, + { 66, 32.40, 32.54, 31.81, 31.99 }, + { 67, 31.83, 32.29, 31.58, 32.13 }, + { 68, 32.06, 32.33, 31.81, 32.26 }, + { 69, 32.17, 33.26, 32.16, 32.69 }, + { 70, 32.85, 32.94, 32.44, 32.54 }, + { 73, 32.62, 32.92, 32.54, 32.64 }, + { 74, 32.78, 32.97, 32.55, 32.76 }, + { 75, 32.83, 33.04, 32.45, 32.47 }, + { 76, 32.43, 32.56, 31.98, 32.10 }, + { 77, 32.42, 32.49, 32.02, 32.06 }, + { 80, 31.92, 32.65, 31.87, 32.50 }, + { 81, 32.69, 33.44, 32.61, 33.15 }, + { 82, 33.33, 33.51, 32.92, 33.38 }, + { 83, 33.50, 34.10, 33.49, 34.04 }, + { 84, 33.94, 34.35, 33.81, 34.20 }, + { 87, 34.40, 34.73, 34.01, 34.12 }, + { 88, 34.26, 34.43, 33.71, 33.78 }, + { 89, 33.88, 34.29, 33.78, 34.18 }, + { 90, 35.11, 35.49, 34.97, 35.15 }, + { 95, 35.40, 35.45, 35.15, 35.41 }, + { 96, 35.34, 35.41, 34.77, 34.80 }, + { 97, 34.80, 35.06, 34.44, 34.53 }, + { 98, 34.88, 35.05, 34.64, 34.86 }, + { 101, 35.25, 35.39, 34.99, 35.12 }, + { 102, 35.06, 35.38, 34.88, 35.35 }, + { 103, 35.06, 35.58, 34.88, 35.51 }, + { 104, 35.59, 35.61, 35.09, 35.33 }, + { 105, 35.15, 36.19, 35.15, 35.56 }, + { 108, 35.45, 35.78, 35.10, 35.31 }, + { 109, 36.56, 37.08, 36.41, 36.79 }, + { 110, 36.75, 36.99, 36.37, 36.58 }, + { 111, 36.63, 37.12, 35.93, 36.25 }, + { 112, 36.60, 37.40, 36.33, 37.28 }, + { 115, 37.60, 37.85, 37.26, 37.82 }, + { 116, 37.85, 37.96, 37.06, 37.06 }, + { 117, 36.80, 37.28, 36.14, 36.79 }, + { 118, 36.70, 36.90, 36.19, 36.78 }, + { 119, 36.83, 37.62, 36.70, 37.13 }, + { 122, 37.08, 37.50, 36.72, 37.38 }, + { 123, 37.51, 37.56, 35.38, 35.84 }, + { 124, 36.61, 36.62, 35.42, 35.98 }, + { 125, 35.45, 37.38, 35.45, 36.42 }, + { 126, 35.78, 36.90, 35.05, 35.48 }, + { 129, 36.23, 37.74, 36.20, 37.68 }, + { 130, 36.87, 38.19, 36.73, 38.18 }, + { 131, 37.97, 39.35, 37.74, 39.00 }, + { 132, 39.35, 40.06, 39.15, 39.52 }, + { 133, 39.42, 39.88, 38.46, 38.62 }, + { 136, 38.38, 39.59, 38.25, 38.72 }, + { 137, 39.10, 39.65, 38.90, 39.65 }, + { 138, 38.15, 38.70, 36.97, 37.00 }, + { 139, 37.44, 37.55, 35.43, 36.18 }, + { 140, 36.20, 36.57, 35.28, 36.03 }, + { 143, 36.30, 36.38, 35.41, 36.14 }, + { 144, 35.56, 35.67, 34.64, 35.29 }, + { 145, 35.80, 36.32, 35.50, 35.76 }, + { 146, 36.30, 37.33, 36.06, 37.21 }, + { 147, 37.42, 37.88, 37.02, 37.67 }, + { 150, 37.57, 38.09, 37.49, 37.97 }, + { 151, 37.96, 38.38, 36.98, 38.06 }, + { 152, 37.80, 38.46, 37.37, 38.46 }, + { 153, 39.24, 39.55, 38.94, 39.22 }, + { 154, 39.35, 39.40, 37.82, 38.10 }, + { 157, 37.40, 38.55, 37.40, 38.24 }, + { 158, 38.33, 38.54, 37.31, 37.68 }, + { 159, 37.85, 38.98, 37.76, 38.91 }, + { 160, 38.85, 40.92, 38.68, 40.65 }, + { 161, 40.95, 41.27, 39.72, 40.08 }, + { 164, 40.59, 40.85, 39.56, 39.76 }, + { 165, 39.35, 40.05, 39.34, 39.85 }, + { 166, 40.18, 40.41, 38.80, 39.03 }, + { 167, 38.91, 39.96, 38.74, 39.70 }, + { 168, 39.85, 40.87, 39.82, 40.71 }, + { 171, 41.70, 42.33, 41.43, 41.80 }, + { 172, 41.55, 41.88, 41.06, 41.51 }, + { 173, 41.11, 42.01, 41.07, 41.49 }, + { 174, 41.97, 42.19, 41.25, 41.36 }, + { 175, 41.36, 41.38, 40.22, 40.36 }, + { 178, 40.66, 41.64, 40.36, 41.26 }, + { 179, 40.84, 40.88, 39.87, 39.90 }, + { 180, 40.10, 40.61, 39.80, 40.06 }, + { 181, 39.56, 39.56, 38.08, 38.20 }, + { 182, 38.83, 39.20, 37.79, 37.88 }, + { 185, 38.10, 38.53, 37.91, 38.11 }, + { 186, 38.29, 39.27, 38.29, 39.00 }, + { 187, 38.70, 39.87, 38.62, 39.75 }, + { 188, 39.62, 39.97, 38.87, 38.91 }, + { 189, 39.30, 39.39, 38.60, 39.15 }, + { 192, 39.30, 39.30, 38.87, 38.90 }, + { 193, 39.00, 42.14, 39.00, 42.13 }, + { 194, 42.42, 42.71, 40.99, 41.54 }, + { 195, 41.75, 42.94, 41.36, 42.26 }, + { 196, 42.26, 43.29, 41.80, 42.15 }, + { 199, 41.85, 42.09, 41.17, 41.35 }, + { 200, 42.00, 42.12, 40.60, 41.07 }, + { 201, 41.30, 41.80, 40.61, 40.92 }, + { 202, 40.83, 42.35, 40.79, 41.97 }, + { 203, 41.95, 42.24, 41.58, 41.99 }, + { 206, 42.17, 42.29, 41.61, 42.11 }, + { 207, 42.24, 42.49, 41.21, 41.50 }, + { 208, 41.68, 41.88, 40.41, 40.72 }, + { 209, 40.77, 41.22, 40.40, 40.72 }, + { 210, 40.44, 41.40, 39.96, 41.31 }, + { 213, 41.46, 42.01, 41.02, 41.87 }, + { 214, 42.75, 44.04, 42.75, 43.16 }, + { 215, 43.14, 43.83, 42.49, 43.68 }, + { 216, 43.69, 44.99, 43.47, 44.51 }, + { 217, 44.90, 45.38, 43.72, 43.90 }, + { 220, 44.49, 44.60, 43.97, 44.31 }, + { 221, 44.35, 44.40, 43.15, 43.35 }, + { 222, 43.05, 43.08, 42.33, 42.40 }, + { 223, 42.30, 42.92, 40.78, 41.90 }, + { 224, 42.02, 42.22, 41.28, 41.88 }, + { 227, 42.08, 42.29, 41.40, 41.81 }, + { 228, 41.81, 43.10, 41.74, 43.10 }, + { 229, 43.02, 43.59, 42.76, 43.50 }, + { 230, 43.68, 44.07, 42.66, 42.84 }, + { 231, 42.84, 42.92, 41.74, 41.87 }, + { 234, 42.00, 42.31, 41.60, 41.86 }, + { 235, 41.56, 41.76, 41.10, 41.52 }, + { 236, 41.22, 41.97, 40.83, 41.44 }, + { 237, 41.56, 41.96, 41.35, 41.69 }, + { 238, 41.60, 41.81, 40.74, 41.76 }, + { 241, 41.76, 41.90, 40.94, 41.21 }, + { 242, 40.50, 41.67, 40.15, 41.67 }, + { 243, 42.00, 42.99, 41.38, 42.91 }, + { 244, 42.64, 43.89, 42.64, 43.60 }, + { 245, 43.60, 44.53, 43.26, 44.10 }, + { 248, 44.17, 44.20, 43.47, 44.03 }, + { 249, 43.97, 44.31, 43.51, 43.94 }, + { 250, 43.72, 44.99, 43.60, 44.99 }, + { 251, 44.70, 45.74, 44.51, 45.40 }, + { 252, 45.00, 46.87, 44.99, 46.21 }, + { 255, 46.65, 47.05, 45.91, 46.44 }, + { 256, 46.30, 47.12, 46.21, 47.12 }, + { 257, 46.98, 47.56, 46.88, 47.25 }, + { 258, 47.18, 47.45, 46.82, 47.35 }, + { 259, 47.81, 48.03, 47.10, 47.41 }, + { 262, 47.37, 49.12, 47.22, 49.12 }, + { 263, 48.85, 49.42, 48.45, 48.48 }, + { 264, 48.48, 48.70, 47.57, 48.08 }, + { 265, 48.49, 48.69, 47.49, 48.29 }, + { 266, 48.09, 50.53, 48.03, 50.35 }, + { 269, 50.15, 50.35, 49.60, 50.15 }, + { 270, 49.80, 50.69, 49.31, 50.67 }, + { 271, 51.00, 51.84, 50.64, 51.06 }, + { 272, 50.90, 52.15, 50.50, 51.44 }, + { 273, 51.44, 51.44, 49.12, 49.30 }, + { 276, 49.06, 49.19, 47.92, 48.22 }, + { 277, 48.37, 49.96, 47.82, 49.96 }, + { 278, 49.77, 50.05, 49.13, 49.49 }, + { 279, 49.31, 50.25, 48.81, 50.00 }, + { 280, 50.26, 50.29, 49.42, 50.07 }, + { 283, 50.20, 50.62, 49.82, 49.87 }, + { 284, 49.44, 50.49, 49.06, 50.20 }, + { 285, 50.40, 50.49, 49.88, 50.07 }, + { 286, 50.50, 50.50, 49.74, 50.00 }, + { 287, 50.08, 50.25, 49.19, 49.45 }, + { 290, 49.23, 49.42, 48.58, 49.00 }, + { 291, 48.99, 49.69, 48.84, 49.12 }, + { 292, 49.09, 49.60, 48.90, 49.60 }, + { 293, 49.54, 50.09, 49.31, 50.02 }, + { 294, 50.19, 50.44, 49.54, 50.03 }, + { 297, 50.31, 51.02, 50.20, 50.72 }, + { 298, 50.49, 50.94, 50.12, 50.44 }, + { 299, 50.04, 50.45, 49.10, 49.88 }, + { 300, 50.15, 50.48, 49.53, 49.85 }, + { 301, 49.49, 51.65, 49.44, 51.51 }, + { 304, 51.77, 52.99, 51.65, 52.96 }, + { 305, 52.70, 52.70, 52.10, 52.35 }, + { 306, 50.75, 52.38, 50.65, 51.64 }, + { 307, 52.05, 54.15, 52.00, 54.08 }, + { 308, 54.14, 54.99, 53.76, 54.06 }, + { 311, 53.69, 53.77, 52.86, 53.41 }, + { 312, 53.40, 54.98, 53.22, 54.91 }, + { 313, 54.60, 54.70, 53.33, 53.75 }, + { 314, 54.00, 54.49, 53.60, 54.42 }, + { 315, 53.33, 55.90, 52.85, 55.29 }, + { 318, 55.07, 56.52, 54.90, 56.06 }, + { 319, 55.68, 55.83, 54.62, 54.62 }, + { 320, 54.72, 54.73, 53.87, 54.30 }, + { 321, 54.96, 56.30, 54.94, 56.30 }, + { 322, 56.34, 56.73, 55.65, 56.67 }, + { 325, 57.33, 58.90, 57.30, 57.69 }, + { 326, 57.15, 58.62, 56.39, 56.47 }, + { 327, 57.01, 59.12, 56.48, 59.12 }, + { 328, 59.10, 60.00, 58.84, 59.90 }, + { 329, 59.31, 59.76, 58.13, 59.25 }, + { 332, 59.75, 59.91, 57.74, 57.74 }, + { 333, 57.70, 59.24, 57.22, 57.93 }, + { 334, 58.35, 60.90, 58.35, 60.90 }, + { 335, 61.69, 63.80, 61.55, 63.80 }, + { 336, 63.70, 65.49, 63.48, 63.69 }, + { 339, 64.00, 64.53, 62.75, 62.81 }, + { 340, 63.00, 64.49, 62.40, 63.98 }, + { 341, 63.50, 63.50, 61.90, 61.90 }, + { 342, 62.42, 62.66, 58.88, 60.20 }, + { 343, 60.50, 62.99, 60.39, 62.52 }, + { 346, 62.00, 63.44, 62.00, 63.44 }, + { 347, 63.40, 63.44, 62.14, 62.47 }, + { 348, 62.00, 62.83, 61.40, 62.49 }, + { 349, 62.40, 63.26, 61.79, 62.80 }, + { 350, 62.95, 63.15, 61.80, 61.95 }, + { 353, 61.90, 63.23, 61.64, 63.15 }, + { 354, 63.40, 64.80, 62.92, 64.80 }, + { 355, 64.98, 65.11, 64.30, 64.37 }, + { 356, 64.55, 64.69, 63.24, 63.26 }, + { 360, 62.70, 62.70, 59.12, 59.22 }, + { 361, 59.69, 59.98, 57.66, 58.25 }, + { 362, 58.10, 58.92, 58.08, 58.72 }, + { 363, 59.10, 59.47, 58.62, 58.85 } +}; + +static t_Data2010 porscheData[] = +{ + { 3, 43.00, 43.96, 42.80, 43.37 }, + { 4, 43.15, 45.00, 43.00, 44.77 }, + { 5, 45.75, 46.50, 45.41, 45.65 }, + { 6, 45.67, 48.56, 45.32, 48.28 }, + { 7, 48.78, 48.81, 47.39, 48.00 }, + { 10, 48.26, 49.18, 47.86, 48.35 }, + { 11, 48.35, 48.65, 46.73, 47.05 }, + { 12, 46.51, 47.65, 46.35, 47.37 }, + { 13, 48.10, 48.70, 47.00, 48.13 }, + { 14, 48.10, 48.20, 46.79, 47.85 }, + { 17, 47.85, 48.57, 47.58, 48.10 }, + { 18, 47.85, 48.00, 46.51, 47.65 }, + { 19, 47.24, 47.62, 45.86, 46.40 }, + { 20, 46.51, 46.61, 44.87, 45.00 }, + { 21, 45.00, 45.11, 42.92, 43.50 }, + { 24, 43.00, 43.83, 42.48, 42.97 }, + { 25, 42.47, 43.37, 41.90, 43.23 }, + { 26, 43.00, 43.00, 41.55, 42.28 }, + { 27, 42.80, 42.83, 41.65, 41.72 }, + { 28, 40.91, 41.50, 40.10, 41.11 }, + { 31, 40.85, 41.85, 40.81, 41.55 }, + { 32, 41.69, 43.16, 41.28, 42.87 }, + { 33, 43.47, 43.53, 42.30, 42.47 }, + { 34, 42.67, 42.85, 40.95, 41.15 }, + { 35, 40.81, 40.82, 39.56, 40.03 }, + { 38, 40.00, 40.94, 38.45, 38.95 }, + { 39, 38.65, 38.95, 37.83, 38.24 }, + { 40, 38.30, 38.65, 37.92, 38.30 }, + { 41, 38.40, 39.88, 37.91, 38.36 }, + { 42, 38.60, 38.84, 36.06, 36.99 }, + { 45, 37.31, 37.58, 35.85, 36.06 }, + { 46, 36.45, 36.78, 35.90, 36.78 }, + { 47, 37.01, 37.84, 36.14, 37.42 }, + { 48, 37.40, 37.73, 36.03, 37.16 }, + { 49, 36.90, 38.00, 36.72, 37.97 }, + { 52, 37.52, 38.12, 37.14, 37.14 }, + { 53, 37.22, 37.53, 36.34, 36.69 }, + { 54, 36.88, 36.93, 35.94, 36.55 }, + { 55, 36.35, 37.06, 35.75, 36.09 }, + { 56, 36.70, 37.05, 36.10, 36.90 }, + { 59, 37.10, 37.74, 36.78, 37.63 }, + { 60, 37.65, 38.58, 37.65, 38.56 }, + { 61, 38.35, 39.60, 38.35, 39.42 }, + { 62, 39.39, 40.15, 39.10, 39.70 }, + { 63, 39.75, 40.60, 39.10, 40.35 }, + { 66, 40.40, 40.40, 39.55, 39.97 }, + { 67, 40.05, 40.10, 39.13, 39.90 }, + { 68, 39.78, 40.55, 39.52, 40.37 }, + { 69, 39.86, 42.53, 39.62, 42.34 }, + { 70, 42.75, 44.73, 42.66, 43.03 }, + { 73, 43.27, 43.49, 42.60, 42.65 }, + { 74, 42.78, 43.78, 42.78, 43.78 }, + { 75, 43.73, 44.00, 42.57, 43.46 }, + { 76, 44.10, 44.51, 43.50, 44.51 }, + { 77, 44.40, 44.70, 44.04, 44.04 }, + { 80, 44.00, 44.05, 43.03, 43.69 }, + { 81, 43.13, 43.51, 42.08, 43.17 }, + { 82, 42.89, 44.71, 42.65, 44.20 }, + { 83, 44.31, 44.47, 43.59, 44.22 }, + { 84, 44.15, 45.15, 44.00, 45.13 }, + { 87, 45.45, 46.10, 45.20, 45.51 }, + { 88, 45.76, 46.10, 44.83, 45.17 }, + { 89, 45.60, 45.60, 44.90, 45.19 }, + { 90, 45.60, 46.46, 45.60, 46.37 }, + { 95, 46.00, 47.44, 46.00, 47.24 }, + { 96, 47.22, 47.48, 45.76, 46.04 }, + { 97, 45.05, 45.55, 44.04, 44.41 }, + { 98, 44.88, 45.44, 44.44, 44.99 }, + { 101, 45.20, 45.57, 44.88, 45.35 }, + { 102, 45.10, 46.03, 45.02, 45.71 }, + { 103, 46.02, 46.60, 45.54, 46.30 }, + { 104, 46.44, 46.60, 45.72, 46.04 }, + { 105, 45.80, 46.35, 44.67, 44.67 }, + { 108, 44.50, 45.17, 43.79, 43.83 }, + { 109, 45.39, 46.00, 44.66, 45.92 }, + { 110, 46.00, 46.46, 45.26, 46.26 }, + { 111, 46.29, 46.64, 44.94, 45.20 }, + { 112, 45.69, 46.22, 45.22, 45.97 }, + { 115, 46.30, 46.71, 45.85, 46.69 }, + { 116, 46.48, 46.48, 45.01, 45.01 }, + { 117, 44.60, 45.03, 42.97, 44.03 }, + { 118, 43.50, 44.38, 42.80, 43.76 }, + { 119, 43.40, 44.33, 42.85, 43.69 }, + { 122, 43.70, 43.70, 42.38, 42.71 }, + { 123, 42.95, 42.95, 40.39, 40.53 }, + { 124, 39.99, 40.15, 38.76, 39.95 }, + { 125, 39.05, 40.25, 37.17, 37.40 }, + { 126, 36.30, 37.25, 34.80, 35.58 }, + { 129, 37.54, 38.19, 37.12, 37.92 }, + { 130, 37.79, 38.08, 37.30, 38.08 }, + { 131, 37.94, 39.99, 37.78, 39.73 }, + { 132, 39.80, 40.20, 39.19, 39.73 }, + { 133, 39.35, 39.40, 36.61, 36.72 }, + { 136, 36.29, 38.48, 36.29, 37.58 }, + { 137, 38.33, 38.58, 37.64, 38.47 }, + { 138, 37.77, 38.42, 36.63, 36.67 }, + { 139, 36.40, 36.67, 33.83, 34.72 }, + { 140, 33.85, 34.69, 32.89, 34.07 }, + { 143, 34.49, 35.03, 33.15, 34.40 }, + { 144, 33.40, 33.42, 32.15, 32.54 }, + { 145, 33.28, 34.19, 32.76, 33.49 }, + { 146, 33.85, 35.77, 33.78, 35.49 }, + { 147, 35.99, 36.35, 35.08, 35.53 }, + { 150, 35.24, 35.81, 35.17, 35.34 }, + { 151, 35.21, 36.10, 34.42, 35.24 }, + { 152, 34.55, 35.20, 34.29, 34.85 }, + { 153, 35.63, 36.09, 35.29, 35.70 }, + { 154, 35.98, 35.98, 34.38, 34.50 }, + { 157, 34.45, 34.65, 32.94, 33.26 }, + { 158, 33.50, 33.65, 31.60, 31.91 }, + { 159, 32.42, 33.29, 32.00, 33.22 }, + { 160, 33.10, 33.97, 32.50, 33.58 }, + { 161, 33.97, 35.12, 33.83, 34.85 }, + { 164, 34.90, 35.70, 34.87, 34.97 }, + { 165, 34.40, 34.58, 32.86, 33.46 }, + { 166, 33.87, 33.87, 32.51, 32.87 }, + { 167, 33.17, 34.65, 32.62, 34.60 }, + { 168, 35.58, 35.64, 34.69, 35.06 }, + { 171, 36.52, 37.19, 36.00, 37.00 }, + { 172, 36.87, 37.48, 36.44, 36.85 }, + { 173, 36.38, 36.98, 36.05, 36.40 }, + { 174, 36.00, 36.50, 34.81, 35.08 }, + { 175, 35.21, 36.24, 34.53, 36.04 }, + { 178, 36.76, 37.24, 36.35, 37.16 }, + { 179, 36.49, 36.88, 35.75, 35.81 }, + { 180, 35.92, 36.28, 35.08, 35.29 }, + { 181, 35.00, 35.00, 33.49, 33.54 }, + { 182, 34.00, 34.40, 33.51, 33.51 }, + { 185, 33.80, 34.14, 33.60, 33.74 }, + { 186, 33.75, 35.79, 33.75, 34.96 }, + { 187, 34.85, 35.88, 34.48, 35.88 }, + { 188, 36.00, 36.64, 35.75, 35.96 }, + { 189, 36.41, 36.80, 35.85, 36.72 }, + { 192, 36.60, 37.22, 36.55, 37.08 }, + { 193, 37.10, 38.47, 37.10, 38.08 }, + { 194, 38.19, 38.35, 37.15, 37.49 }, + { 195, 37.35, 37.81, 36.60, 36.87 }, + { 196, 36.76, 37.22, 36.50, 36.74 }, + { 199, 36.51, 36.83, 35.95, 36.08 }, + { 200, 36.12, 36.35, 35.07, 35.42 }, + { 201, 35.40, 36.30, 34.87, 35.08 }, + { 202, 35.00, 37.66, 34.75, 37.47 }, + { 203, 37.70, 39.50, 37.55, 39.12 }, + { 206, 39.43, 39.46, 38.78, 39.18 }, + { 207, 39.30, 39.56, 38.69, 38.98 }, + { 208, 39.00, 39.19, 38.00, 38.23 }, + { 209, 38.10, 39.42, 37.13, 38.72 }, + { 210, 38.88, 39.38, 38.22, 38.82 }, + { 213, 39.26, 39.50, 38.72, 39.01 }, + { 214, 39.07, 40.04, 38.74, 39.10 }, + { 215, 38.85, 39.76, 38.71, 39.29 }, + { 216, 39.30, 39.99, 39.13, 39.53 }, + { 217, 39.50, 40.00, 38.06, 38.32 }, + { 220, 38.60, 39.55, 38.37, 39.38 }, + { 221, 39.48, 39.58, 38.18, 38.56 }, + { 222, 38.58, 38.58, 37.01, 37.31 }, + { 223, 37.32, 37.78, 36.42, 36.82 }, + { 224, 37.30, 37.30, 36.04, 36.53 }, + { 227, 37.00, 37.31, 36.30, 37.12 }, + { 228, 37.00, 38.17, 36.88, 38.00 }, + { 229, 38.10, 38.65, 37.60, 38.58 }, + { 230, 38.60, 39.25, 37.50, 37.88 }, + { 231, 37.85, 37.93, 36.92, 37.26 }, + { 234, 37.53, 38.09, 36.99, 37.64 }, + { 235, 37.59, 37.59, 36.35, 36.80 }, + { 236, 36.50, 36.88, 35.10, 35.93 }, + { 237, 36.40, 36.75, 36.00, 36.35 }, + { 238, 36.50, 36.96, 35.79, 36.78 }, + { 241, 36.91, 37.62, 36.80, 37.15 }, + { 242, 36.45, 36.78, 36.00, 36.74 }, + { 243, 36.82, 38.55, 36.34, 38.26 }, + { 244, 38.67, 39.39, 38.12, 39.26 }, + { 245, 39.28, 39.53, 38.83, 39.29 }, + { 248, 39.40, 39.49, 39.03, 39.28 }, + { 249, 39.30, 39.30, 38.56, 38.80 }, + { 250, 38.55, 39.04, 38.06, 38.89 }, + { 251, 39.00, 39.03, 38.50, 38.90 }, + { 252, 38.90, 39.43, 38.15, 38.30 }, + { 255, 38.76, 38.76, 37.88, 38.09 }, + { 256, 38.37, 38.60, 37.90, 38.42 }, + { 257, 38.69, 39.13, 38.11, 38.68 }, + { 258, 38.27, 38.46, 37.31, 37.41 }, + { 259, 37.88, 38.10, 37.31, 37.60 }, + { 262, 37.62, 37.75, 37.12, 37.52 }, + { 263, 37.50, 37.50, 36.82, 36.83 }, + { 264, 36.95, 37.33, 36.12, 36.12 }, + { 265, 36.07, 36.15, 34.55, 35.62 }, + { 266, 35.29, 36.61, 35.07, 36.53 }, + { 269, 36.60, 36.94, 36.12, 36.50 }, + { 270, 36.15, 36.15, 35.19, 35.51 }, + { 271, 35.51, 36.30, 35.13, 35.56 }, + { 272, 35.96, 36.85, 35.42, 36.33 }, + { 273, 36.50, 36.83, 36.04, 36.31 }, + { 276, 36.44, 36.51, 34.64, 34.74 }, + { 277, 34.75, 35.10, 34.45, 34.99 }, + { 278, 35.45, 35.45, 35.01, 35.20 }, + { 279, 35.50, 35.50, 34.72, 35.10 }, + { 280, 34.80, 35.33, 34.66, 35.25 }, + { 283, 35.12, 36.47, 35.12, 36.33 }, + { 284, 36.12, 37.65, 35.83, 37.16 }, + { 285, 37.40, 40.35, 37.18, 39.00 }, + { 286, 39.30, 40.75, 39.03, 40.34 }, + { 287, 40.80, 42.35, 40.30, 41.53 }, + { 290, 42.00, 42.80, 41.35, 42.70 }, + { 291, 42.70, 43.16, 38.75, 38.97 }, + { 292, 38.60, 40.00, 37.70, 39.79 }, + { 293, 39.61, 39.79, 38.00, 38.40 }, + { 294, 38.29, 38.29, 37.25, 37.60 }, + { 297, 37.73, 38.39, 37.49, 38.06 }, + { 298, 38.02, 38.19, 37.60, 37.99 }, + { 299, 37.90, 38.36, 37.31, 37.49 }, + { 300, 37.40, 37.81, 37.05, 37.19 }, + { 301, 37.00, 37.29, 35.92, 36.81 }, + { 304, 37.00, 37.21, 36.69, 36.90 }, + { 305, 37.00, 37.19, 36.83, 37.01 }, + { 306, 37.97, 38.08, 37.38, 37.65 }, + { 307, 38.39, 38.96, 38.01, 38.71 }, + { 308, 38.70, 40.38, 38.60, 40.02 }, + { 311, 40.02, 40.77, 40.01, 40.60 }, + { 312, 40.40, 43.71, 40.40, 43.54 }, + { 313, 43.00, 44.04, 41.28, 43.33 }, + { 314, 43.00, 44.54, 43.00, 43.94 }, + { 315, 43.30, 44.55, 42.54, 44.55 }, + { 318, 44.10, 47.18, 44.05, 46.60 }, + { 319, 46.45, 47.84, 45.97, 46.55 }, + { 320, 46.40, 47.57, 46.22, 47.38 }, + { 321, 47.85, 49.12, 47.62, 48.97 }, + { 322, 49.30, 50.00, 47.94, 50.00 }, + { 325, 50.20, 53.75, 50.10, 52.43 }, + { 326, 51.90, 54.94, 50.21, 53.07 }, + { 327, 53.20, 56.41, 53.20, 56.41 }, + { 328, 56.98, 59.63, 56.76, 59.27 }, + { 329, 59.00, 60.20, 53.65, 57.70 }, + { 332, 58.30, 59.40, 55.65, 55.88 }, + { 333, 55.85, 58.80, 54.50, 57.93 }, + { 334, 59.37, 61.72, 58.86, 61.43 }, + { 335, 62.93, 65.00, 62.20, 64.54 }, + { 336, 65.50, 66.50, 63.82, 64.80 }, + { 339, 66.00, 66.50, 65.05, 65.70 }, + { 340, 67.60, 69.88, 67.29, 68.57 }, + { 341, 67.90, 67.99, 63.65, 64.69 }, + { 342, 65.90, 66.18, 61.14, 62.02 }, + { 343, 61.42, 66.11, 61.29, 66.00 }, + { 346, 65.63, 67.60, 65.20, 67.19 }, + { 347, 66.99, 67.08, 65.07, 65.19 }, + { 348, 64.90, 65.66, 63.35, 65.50 }, + { 349, 65.07, 66.10, 63.21, 63.48 }, + { 350, 64.28, 64.72, 61.50, 62.06 }, + { 353, 62.00, 63.54, 61.88, 62.25 }, + { 354, 63.00, 63.80, 62.53, 63.75 }, + { 355, 63.79, 64.94, 63.00, 63.56 }, + { 356, 63.62, 64.25, 62.34, 62.48 }, + { 360, 62.21, 62.40, 57.51, 59.40 }, + { 361, 60.00, 60.81, 59.41, 60.07 }, + { 362, 60.10, 60.65, 60.00, 60.30 }, + { 363, 60.30, 60.51, 59.40, 59.66 } +}; + +static t_Data2010 daimlerData[] = +{ + { 3, 37.24, 37.60, 36.96, 37.55 }, + { 4, 37.50, 37.56, 36.87, 37.24 }, + { 5, 37.19, 37.33, 36.62, 37.25 }, + { 6, 36.85, 36.95, 36.35, 36.72 }, + { 7, 36.92, 37.15, 36.24, 36.94 }, + { 10, 37.19, 37.67, 37.04, 37.20 }, + { 11, 37.34, 37.40, 36.16, 36.28 }, + { 12, 36.00, 36.38, 35.60, 36.19 }, + { 13, 36.60, 37.19, 36.47, 37.10 }, + { 14, 37.00, 37.26, 36.31, 36.49 }, + { 17, 36.58, 37.25, 36.25, 37.12 }, + { 18, 36.65, 36.87, 35.73, 36.71 }, + { 19, 36.44, 36.52, 35.33, 35.60 }, + { 20, 35.90, 36.17, 34.69, 34.76 }, + { 21, 34.40, 34.66, 33.28, 34.18 }, + { 24, 33.56, 34.08, 33.22, 33.50 }, + { 25, 33.15, 33.95, 32.70, 33.86 }, + { 26, 33.46, 33.58, 32.60, 32.92 }, + { 27, 33.53, 33.80, 32.32, 32.32 }, + { 28, 32.85, 33.97, 32.69, 33.42 }, + { 31, 33.00, 33.74, 32.96, 33.57 }, + { 32, 33.51, 33.88, 32.92, 33.76 }, + { 33, 33.96, 34.95, 33.90, 34.34 }, + { 34, 34.49, 34.69, 33.01, 33.10 }, + { 35, 33.31, 33.31, 32.01, 32.32 }, + { 38, 32.79, 33.54, 32.60, 33.31 }, + { 39, 33.57, 33.85, 32.98, 33.33 }, + { 40, 33.42, 34.10, 33.34, 33.63 }, + { 41, 33.86, 33.90, 32.26, 32.77 }, + { 42, 32.93, 33.21, 31.74, 32.46 }, + { 45, 32.55, 33.11, 31.88, 32.01 }, + { 46, 32.25, 32.69, 31.82, 32.62 }, + { 47, 33.10, 33.47, 32.90, 33.04 }, + { 48, 33.04, 33.35, 29.92, 31.50 }, + { 49, 31.20, 32.32, 30.90, 32.30 }, + { 52, 32.60, 32.62, 31.36, 31.40 }, + { 53, 31.68, 31.90, 31.00, 31.25 }, + { 54, 31.45, 31.49, 30.43, 30.94 }, + { 55, 30.75, 31.23, 30.10, 30.35 }, + { 56, 30.56, 31.09, 30.26, 30.66 }, + { 59, 31.15, 31.55, 30.74, 31.34 }, + { 60, 31.40, 32.06, 31.24, 31.75 }, + { 61, 31.49, 32.25, 31.42, 31.95 }, + { 62, 31.75, 32.29, 31.57, 31.91 }, + { 63, 32.01, 33.10, 32.01, 32.99 }, + { 66, 32.97, 33.20, 32.73, 33.01 }, + { 67, 32.90, 32.99, 32.25, 32.76 }, + { 68, 32.83, 33.26, 32.58, 33.12 }, + { 69, 32.88, 33.56, 32.88, 33.19 }, + { 70, 33.20, 33.60, 33.03, 33.53 }, + { 73, 33.65, 33.83, 33.31, 33.33 }, + { 74, 33.60, 34.26, 33.51, 34.11 }, + { 75, 34.49, 34.60, 33.93, 34.35 }, + { 76, 34.35, 34.78, 34.17, 34.64 }, + { 77, 34.60, 34.89, 34.26, 34.38 }, + { 80, 34.19, 34.56, 33.92, 34.40 }, + { 81, 34.49, 34.74, 34.10, 34.45 }, + { 82, 34.55, 34.65, 33.78, 34.49 }, + { 83, 34.63, 35.19, 34.50, 35.01 }, + { 84, 34.85, 35.34, 34.78, 34.98 }, + { 87, 35.27, 35.53, 34.85, 34.95 }, + { 88, 35.28, 35.35, 34.34, 34.56 }, + { 89, 34.73, 34.99, 34.43, 34.85 }, + { 90, 35.08, 35.49, 35.06, 35.40 }, + { 95, 35.52, 35.84, 35.26, 35.51 }, + { 96, 35.60, 35.85, 35.28, 35.42 }, + { 97, 35.29, 35.36, 34.79, 35.18 }, + { 98, 35.50, 35.69, 35.10, 35.36 }, + { 101, 35.68, 35.74, 35.10, 35.41 }, + { 102, 35.43, 36.05, 34.98, 36.00 }, + { 103, 36.25, 36.74, 36.00, 36.67 }, + { 104, 36.80, 36.90, 36.19, 36.72 }, + { 105, 36.74, 37.38, 36.30, 36.54 }, + { 108, 36.49, 36.76, 36.12, 36.31 }, + { 109, 38.80, 39.24, 38.48, 39.00 }, + { 110, 39.06, 39.30, 38.38, 38.45 }, + { 111, 38.46, 38.96, 37.72, 37.85 }, + { 112, 38.21, 39.11, 37.81, 38.87 }, + { 115, 39.26, 39.52, 38.75, 39.47 }, + { 116, 39.26, 39.90, 37.93, 37.93 }, + { 117, 37.92, 38.18, 36.82, 37.76 }, + { 118, 37.85, 39.00, 37.68, 38.79 }, + { 119, 38.80, 39.30, 38.37, 38.81 }, + { 122, 38.31, 38.83, 38.08, 38.62 }, + { 123, 38.87, 38.88, 37.00, 37.37 }, + { 124, 37.72, 37.72, 36.60, 37.02 }, + { 125, 36.84, 37.74, 36.63, 36.93 }, + { 126, 36.25, 36.96, 35.30, 35.85 }, + { 129, 36.75, 38.01, 36.63, 38.01 }, + { 130, 37.33, 38.74, 37.17, 38.65 }, + { 131, 38.35, 40.17, 38.07, 39.79 }, + { 132, 40.20, 41.54, 40.02, 41.37 }, + { 133, 41.00, 41.38, 40.15, 40.58 }, + { 136, 40.21, 41.54, 40.01, 41.08 }, + { 137, 41.32, 41.92, 40.88, 41.92 }, + { 138, 41.40, 41.85, 39.85, 40.20 }, + { 139, 40.60, 40.64, 37.37, 38.40 }, + { 140, 38.15, 38.95, 37.11, 38.83 }, + { 143, 39.00, 39.13, 37.38, 38.49 }, + { 144, 37.26, 37.56, 36.67, 37.19 }, + { 145, 37.50, 38.87, 37.50, 38.24 }, + { 146, 38.60, 40.18, 38.60, 39.94 }, + { 147, 40.55, 40.67, 39.95, 40.30 }, + { 150, 40.35, 41.08, 40.31, 41.00 }, + { 151, 40.75, 41.43, 40.02, 40.99 }, + { 152, 40.50, 40.99, 39.97, 40.78 }, + { 153, 41.75, 41.99, 41.12, 41.26 }, + { 154, 41.55, 41.66, 39.87, 40.38 }, + { 157, 39.65, 40.61, 39.54, 40.08 }, + { 158, 40.15, 40.41, 39.47, 40.24 }, + { 159, 40.60, 42.05, 40.38, 41.94 }, + { 160, 41.72, 43.74, 41.64, 43.26 }, + { 161, 43.44, 43.88, 42.14, 42.74 }, + { 164, 43.20, 43.36, 42.38, 42.52 }, + { 165, 42.00, 42.79, 41.72, 42.33 }, + { 166, 42.46, 42.60, 40.78, 41.14 }, + { 167, 41.00, 42.44, 40.94, 42.31 }, + { 168, 42.47, 43.39, 42.35, 43.12 }, + { 171, 44.07, 44.79, 44.03, 44.50 }, + { 172, 44.15, 44.49, 43.74, 44.47 }, + { 173, 43.92, 44.65, 43.78, 43.95 }, + { 174, 44.49, 44.69, 43.25, 43.42 }, + { 175, 42.98, 42.98, 41.74, 42.01 }, + { 178, 42.14, 43.36, 41.86, 43.24 }, + { 179, 42.50, 42.72, 41.07, 41.28 }, + { 180, 41.62, 42.49, 41.32, 41.92 }, + { 181, 41.47, 41.82, 40.17, 40.42 }, + { 182, 40.90, 41.49, 40.17, 40.28 }, + { 185, 40.56, 40.85, 39.95, 39.95 }, + { 186, 40.15, 41.95, 40.15, 41.21 }, + { 187, 41.00, 41.99, 40.56, 41.94 }, + { 188, 42.01, 42.33, 41.15, 41.58 }, + { 189, 41.80, 41.97, 41.39, 41.63 }, + { 192, 41.80, 41.95, 41.38, 41.56 }, + { 193, 41.40, 43.81, 41.40, 43.81 }, + { 194, 44.03, 44.48, 43.06, 43.54 }, + { 195, 43.47, 44.35, 42.82, 43.28 }, + { 196, 43.39, 44.70, 42.94, 43.05 }, + { 199, 43.04, 43.22, 42.20, 42.63 }, + { 200, 42.65, 42.88, 41.30, 41.42 }, + { 201, 41.63, 42.05, 40.72, 40.96 }, + { 202, 40.79, 42.51, 40.76, 42.26 }, + { 203, 42.03, 42.83, 41.74, 42.49 }, + { 206, 42.74, 43.17, 42.36, 43.15 }, + { 207, 43.18, 43.38, 40.81, 41.34 }, + { 208, 41.90, 41.97, 41.27, 41.47 }, + { 209, 41.60, 42.11, 41.13, 41.44 }, + { 210, 41.31, 41.67, 40.79, 41.38 }, + { 213, 41.14, 41.58, 40.64, 41.40 }, + { 214, 41.33, 42.10, 41.33, 42.00 }, + { 215, 41.90, 42.10, 41.48, 41.68 }, + { 216, 41.63, 42.44, 41.56, 42.12 }, + { 217, 42.43, 42.75, 40.84, 40.97 }, + { 220, 41.53, 41.99, 41.10, 41.97 }, + { 221, 41.79, 41.79, 40.84, 41.22 }, + { 222, 40.85, 40.97, 39.92, 40.19 }, + { 223, 40.01, 40.40, 38.53, 39.07 }, + { 224, 39.48, 39.90, 38.89, 39.15 }, + { 227, 39.41, 40.14, 39.23, 39.96 }, + { 228, 40.26, 41.10, 40.13, 41.05 }, + { 229, 40.87, 41.10, 40.50, 40.62 }, + { 230, 40.97, 41.22, 39.65, 39.88 }, + { 231, 39.71, 39.95, 39.10, 39.23 }, + { 234, 39.18, 39.53, 38.90, 39.15 }, + { 235, 38.94, 39.05, 38.17, 38.66 }, + { 236, 38.46, 38.82, 37.64, 38.29 }, + { 237, 38.50, 38.63, 37.85, 38.08 }, + { 238, 37.99, 38.40, 37.60, 38.40 }, + { 241, 38.55, 38.83, 37.77, 38.12 }, + { 242, 37.50, 38.36, 37.03, 38.36 }, + { 243, 38.50, 40.49, 38.30, 40.46 }, + { 244, 40.29, 41.42, 40.22, 41.00 }, + { 245, 41.06, 42.24, 40.94, 41.65 }, + { 248, 41.72, 41.86, 41.08, 41.25 }, + { 249, 40.99, 41.36, 40.65, 41.35 }, + { 250, 41.25, 42.06, 41.12, 42.00 }, + { 251, 41.99, 43.15, 41.79, 43.08 }, + { 252, 43.00, 44.02, 42.86, 43.78 }, + { 255, 44.31, 44.65, 43.66, 43.67 }, + { 256, 43.57, 44.20, 43.54, 44.15 }, + { 257, 44.09, 44.49, 43.94, 44.12 }, + { 258, 43.70, 44.22, 43.62, 44.06 }, + { 259, 44.30, 44.74, 43.62, 44.47 }, + { 262, 44.47, 45.50, 44.35, 45.50 }, + { 263, 45.38, 45.90, 45.26, 45.63 }, + { 264, 45.01, 45.19, 44.23, 44.85 }, + { 265, 44.83, 45.13, 43.78, 44.35 }, + { 266, 44.08, 46.17, 44.00, 46.13 }, + { 269, 46.12, 46.69, 45.85, 46.41 }, + { 270, 46.25, 46.64, 45.55, 46.23 }, + { 271, 46.38, 46.85, 46.01, 46.28 }, + { 272, 45.98, 47.59, 45.88, 46.46 }, + { 273, 46.32, 46.86, 45.11, 45.51 }, + { 276, 45.19, 45.19, 43.66, 43.78 }, + { 277, 43.74, 44.88, 43.59, 44.69 }, + { 278, 45.05, 45.19, 44.03, 44.24 }, + { 279, 44.50, 45.49, 44.19, 45.37 }, + { 280, 45.20, 45.61, 44.62, 45.44 }, + { 283, 45.65, 46.30, 45.45, 45.92 }, + { 284, 46.02, 47.73, 45.66, 47.42 }, + { 285, 47.80, 48.21, 47.22, 47.94 }, + { 286, 48.00, 48.04, 47.17, 47.40 }, + { 287, 47.50, 48.13, 47.18, 47.72 }, + { 290, 47.64, 48.10, 47.24, 47.78 }, + { 291, 47.50, 47.80, 46.86, 47.04 }, + { 292, 46.80, 47.90, 46.75, 47.80 }, + { 293, 47.66, 49.12, 47.65, 49.03 }, + { 294, 48.97, 49.51, 48.63, 49.31 }, + { 297, 49.70, 50.05, 49.37, 49.70 }, + { 298, 49.22, 49.56, 48.30, 48.74 }, + { 299, 48.40, 49.10, 47.53, 47.62 }, + { 300, 48.10, 49.05, 46.74, 46.98 }, + { 301, 46.76, 47.73, 46.35, 47.43 }, + { 304, 48.10, 48.37, 47.33, 47.65 }, + { 305, 47.42, 48.62, 47.22, 48.41 }, + { 306, 48.50, 49.15, 48.10, 48.34 }, + { 307, 49.10, 50.14, 48.80, 50.00 }, + { 308, 50.08, 50.45, 48.85, 48.94 }, + { 311, 49.08, 49.10, 48.52, 48.94 }, + { 312, 48.72, 50.28, 48.65, 50.04 }, + { 313, 49.76, 49.91, 48.70, 49.17 }, + { 314, 49.56, 49.75, 48.98, 49.56 }, + { 315, 48.60, 50.23, 47.92, 49.88 }, + { 318, 49.26, 51.40, 49.26, 50.89 }, + { 319, 50.50, 50.93, 49.47, 49.47 }, + { 320, 49.46, 49.69, 48.98, 49.27 }, + { 321, 50.04, 50.96, 49.80, 50.81 }, + { 322, 50.88, 51.00, 50.18, 50.74 }, + { 325, 50.99, 51.59, 50.31, 50.56 }, + { 326, 50.16, 51.26, 49.51, 49.51 }, + { 327, 50.12, 52.04, 49.73, 52.04 }, + { 328, 52.00, 52.63, 51.52, 51.93 }, + { 329, 51.47, 51.93, 50.65, 51.54 }, + { 332, 51.93, 52.06, 49.79, 49.79 }, + { 333, 50.00, 50.93, 49.01, 49.87 }, + { 334, 50.51, 51.96, 50.09, 51.94 }, + { 335, 52.30, 53.64, 51.88, 53.49 }, + { 336, 53.00, 54.78, 52.68, 53.95 }, + { 339, 53.95, 54.18, 53.40, 53.62 }, + { 340, 53.68, 54.37, 52.61, 54.15 }, + { 341, 53.84, 54.05, 52.97, 53.26 }, + { 342, 53.85, 54.14, 52.15, 53.30 }, + { 343, 53.54, 54.93, 53.35, 54.87 }, + { 346, 55.00, 55.00, 54.42, 54.76 }, + { 347, 54.50, 54.87, 53.72, 54.11 }, + { 348, 53.81, 54.20, 53.17, 53.90 }, + { 349, 53.71, 54.58, 53.71, 54.41 }, + { 350, 54.07, 54.48, 53.57, 53.68 }, + { 353, 53.88, 54.50, 53.78, 53.88 }, + { 354, 54.02, 54.94, 53.95, 54.66 }, + { 355, 54.70, 55.05, 54.33, 54.33 }, + { 356, 54.30, 54.52, 54.04, 54.07 }, + { 360, 53.30, 53.45, 51.29, 51.57 }, + { 361, 51.67, 51.84, 51.02, 51.50 }, + { 362, 51.50, 51.62, 51.23, 51.32 }, + { 363, 51.50, 51.70, 50.61, 50.73 } +}; + +QVector QuoteFactory::samples2010( Stock stock ) +{ + const t_Data2010 *data = NULL; + int numSamples = 0; + + switch( stock ) + { + case BMW: + { + data = bmwData; + numSamples = sizeof( bmwData ) / sizeof( t_Data2010 ); + break; + } + case Daimler: + { + data = daimlerData; + numSamples = sizeof( daimlerData ) / sizeof( t_Data2010 ); + break; + } + case Porsche: + { + data = porscheData; + numSamples = sizeof( porscheData ) / sizeof( t_Data2010 ); + break; + } + default: + break; + } + + QVector samples; + samples.reserve( numSamples ); + + QDateTime year2010( QDate( 2010, 1, 1 ), QTime( 0, 0 ), Qt::UTC ); + + for ( int i = 0; i < numSamples; i++ ) + { + const t_Data2010 &ohlc = data[ i ]; + + samples += QwtOHLCSample( + QwtDate::toDouble( year2010.addDays( ohlc.day ) ), + ohlc.open, ohlc.high, ohlc.low, ohlc.close ); + } + + return samples; +} + +QString QuoteFactory::title( Stock stock ) +{ + switch( stock ) + { + case BMW: + return "BMW"; + case Daimler: + return "Daimler"; + case Porsche: + return "Porsche"; + default: + break; + } + + return "Unknown"; +} diff --git a/qwt/examples/stockchart/quotefactory.h b/qwt/examples/stockchart/quotefactory.h new file mode 100644 index 000000000..ddaac2682 --- /dev/null +++ b/qwt/examples/stockchart/quotefactory.h @@ -0,0 +1,22 @@ +#ifndef _QUOTE_FACTORY_H_ +#define _QUOTE_FACTORY_H_ + +#include + +class QuoteFactory +{ +public: + enum Stock + { + BMW, + Daimler, + Porsche, + + NumStocks + }; + + static QVector samples2010( Stock ); + static QString title( Stock ); +}; + +#endif diff --git a/qwt/examples/stockchart/stockchart.pro b/qwt/examples/stockchart/stockchart.pro new file mode 100644 index 000000000..36719d727 --- /dev/null +++ b/qwt/examples/stockchart/stockchart.pro @@ -0,0 +1,25 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = stockchart + +HEADERS = \ + legend.h \ + griditem.h \ + plot.h \ + quotefactory.h + +SOURCES = \ + legend.cpp \ + griditem.cpp \ + quotefactory.cpp \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/stylesheets/blue.css b/qwt/examples/stylesheets/blue.css new file mode 100644 index 000000000..6a7c77367 --- /dev/null +++ b/qwt/examples/stylesheets/blue.css @@ -0,0 +1,66 @@ +QwtPlot +{ + border: 1px solid white; + border-radius: 10px; + padding: 10px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #657383, stop: 0.4 #8395AA, stop: 1 #657383 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #657383, stop: 1 #8395AA ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #8395AA, stop: 1 #657383 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #8395AA, stop: 0.4 #657383 stop: 1 #8395AA ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #7C8DA0, stop: 0.4 #657383 stop: 1 #7C8DA0 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #657383, stop: 0.25 #7C8DA0 stop: 0.55 #7C8DA0 stop: 1 #657383 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #8FA3B9, stop: 0.25 #7C8DA0 stop: 0.55 #7C8DA0 stop: 1 #8FA3B9 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #7C8DA0, stop: 0.4 #8FA3B9 stop: 0.55 #8FA3B9 stop: 1 #7C8DA0 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #7C8DA0, stop: 0.4 #8395AA stop: 0.55 #8395AA stop: 1 #7C8DA0 ); +} + +QwtPlotCanvas +{ + border: 1px solid white; + border-radius: 10px; + background-color: #616d7e; +} + +QwtPlotGLCanvas +{ + border: 1px solid white; + background-color: #616d7e; +} + +QwtScaleWidget +{ + color: white; +} + +QwtTextLabel#QwtPlotTitle +{ + color: white; +} + +QwtTextLabel#QwtPlotFooter +{ + color: white; +} + +QwtLegend +{ + border: 1px solid white; + border-radius: 10px; + padding: 2px; + background: #616d7e; +} + +QwtLegendLabel +{ + color: white; +} + diff --git a/qwt/examples/stylesheets/choco.css b/qwt/examples/stylesheets/choco.css new file mode 100644 index 000000000..e819fecc9 --- /dev/null +++ b/qwt/examples/stylesheets/choco.css @@ -0,0 +1,50 @@ +QwtPlot +{ + border: 1px solid white; + border-radius: 10px; + padding: 10px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 Brown, stop: 0.5 Chocolate, stop: 1 Brown ); +} + +QwtPlotCanvas +{ + border: 1px solid White; + border-radius: 10px; + background-color: Tan; +} + +QwtPlotGLCanvas +{ + border: 1px solid White; + background-color: Tan; +} + +QwtScaleWidget +{ + color: white; +} + +QwtTextLabel#QwtPlotTitle +{ + color: white; +} + +QwtTextLabel#QwtPlotFooter +{ + color: white; +} + +QwtLegend +{ + border: 1px solid white; + border-radius: 10px; + padding: 2px; + background: brown; +} + +QwtLegendLabel +{ + color: white; +} + diff --git a/qwt/examples/stylesheets/oily.css b/qwt/examples/stylesheets/oily.css new file mode 100644 index 000000000..5c561e82c --- /dev/null +++ b/qwt/examples/stylesheets/oily.css @@ -0,0 +1,51 @@ +QwtPlot +{ + border: 1px solid white; + border-radius: 10px; + padding: 10px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #31312C, stop: 1 #808080 ); +} + +QwtPlotCanvas +{ + border: 1px solid White; + border-radius: 10px; + background-color: #101010; +} + +QwtPlotGLCanvas +{ + border: 1px solid White; + background-color: #101010; +} + +QwtScaleWidget +{ + color: white; +} + +QwtTextLabel#QwtPlotTitle +{ + color: white; +} + +QwtTextLabel#QwtPlotFooter +{ + color: white; +} + +QwtLegend +{ + border: 1px solid white; + border-radius: 10px; + padding: 2px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #808080, stop: 1 #31312C ); +} + +QwtLegendLabel +{ + color: white; +} + diff --git a/qwt/examples/stylesheets/rosy.css b/qwt/examples/stylesheets/rosy.css new file mode 100644 index 000000000..0bb9f0526 --- /dev/null +++ b/qwt/examples/stylesheets/rosy.css @@ -0,0 +1,50 @@ +QwtPlot +{ + border: 1px solid white; + border-radius: 10px; + padding: 10px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #7e354d, stop: 0.5 #7f5a58, stop: 1 #7e354d ); +} + +QwtPlotCanvas +{ + border: 1px solid White; + border-radius: 10px; + background-color: #7f5a58; +} + +QwtPlotGLCanvas +{ + border: 1px solid White; + background-color: #7f5a58; +} + +QwtScaleWidget +{ + color: white; +} + +QwtTextLabel#QwtPlotTitle +{ + color: white; +} + +QwtTextLabel#QwtPlotFooter +{ + color: white; +} + +QwtLegend +{ + border: 1px solid white; + border-radius: 10px; + padding: 2px; + background: #7f5a58; +} + +QwtLegendLabel +{ + color: white; +} + diff --git a/qwt/examples/sysinfo/sysinfo.cpp b/qwt/examples/sysinfo/sysinfo.cpp new file mode 100644 index 000000000..640202b8b --- /dev/null +++ b/qwt/examples/sysinfo/sysinfo.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +class ValueBar: public QWidget +{ +public: + ValueBar( Qt::Orientation orientation, + const QString &text, QWidget *parent, double value = 0.0 ): + QWidget( parent ) + { + d_label = new QLabel( text, this ); + d_label->setFont( QFont( "Helvetica", 10 ) ); + + d_thermo = new QwtThermo( this ); + d_thermo->setOrientation( orientation ); + d_thermo->setScale( 0.0, 100.0 ); + d_thermo->setValue( value ); + d_thermo->setFont( QFont( "Helvetica", 8 ) ); + d_thermo->setPipeWidth( 6 ); + d_thermo->setScaleMaxMajor( 6 ); + d_thermo->setScaleMaxMinor( 5 ); + d_thermo->setFillBrush( Qt::darkMagenta ); + +#if 0 + QwtLinearColorMap *colorMap = + new QwtLinearColorMap( Qt::blue, Qt::red ); + + colorMap->addColorStop( 0.2, Qt::yellow ); + colorMap->addColorStop( 0.3, Qt::cyan ); + colorMap->addColorStop( 0.4, Qt::green ); + colorMap->addColorStop( 0.5, Qt::magenta ); + colorMap->setMode( QwtLinearColorMap::FixedColors ); + d_thermo->setColorMap( colorMap ); +#endif + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setMargin( 0 ); + layout->setSpacing( 0 ); + + if ( orientation == Qt::Horizontal ) + { + d_label->setAlignment( Qt::AlignCenter ); + d_thermo->setScalePosition( QwtThermo::LeadingScale ); + layout->addWidget( d_label ); + layout->addWidget( d_thermo ); + } + else + { + d_label->setAlignment( Qt::AlignRight ); + d_thermo->setScalePosition( QwtThermo::TrailingScale ); + layout->addWidget( d_thermo, 10, Qt::AlignHCenter ); + layout->addWidget( d_label, 0 ); + } + } + + void setValue( double value ) + { + d_thermo->setValue( value ); + } + +private: + QLabel *d_label; + QwtThermo *d_thermo; +}; + +class SysInfo : public QFrame +{ +public: + SysInfo( QWidget *parent = NULL ): + QFrame( parent ) + { + QGroupBox *memBox = new QGroupBox( "Memory Usage", this ); + memBox->setFont( QFont( "Helvetica", 10 ) ); + + QVBoxLayout *memLayout = new QVBoxLayout( memBox ); + memLayout->setMargin( 15 ); + memLayout->setSpacing( 5 ); + + Qt::Orientation o = Qt::Horizontal; + memLayout->addWidget( new ValueBar( o, "Used", memBox, 57 ) ); + memLayout->addWidget( new ValueBar( o, "Shared", memBox, 17 ) ); + memLayout->addWidget( new ValueBar( o, "Cache", memBox, 30 ) ); + memLayout->addWidget( new ValueBar( o, "Buffers", memBox, 22 ) ); + memLayout->addWidget( new ValueBar( o, "Swap Used", memBox, 57 ) ); + memLayout->addWidget( new QWidget( memBox ), 10 ); // spacer + + QGroupBox *cpuBox = new QGroupBox( "Cpu Usage", this ); + cpuBox->setFont( QFont( "Helvetica", 10 ) ); + + QHBoxLayout *cpuLayout = new QHBoxLayout( cpuBox ); + cpuLayout->setMargin( 15 ); + cpuLayout->setSpacing( 5 ); + + o = Qt::Vertical; + cpuLayout->addWidget( new ValueBar( o, "User", cpuBox, 57 ) ); + cpuLayout->addWidget( new ValueBar( o, "Total", cpuBox, 73 ) ); + cpuLayout->addWidget( new ValueBar( o, "System", cpuBox, 16 ) ); + cpuLayout->addWidget( new ValueBar( o, "Idle", cpuBox, 27 ) ); + + QHBoxLayout *layout = new QHBoxLayout( this ); + layout->setMargin( 10 ); + layout->addWidget( memBox, 10 ); + layout->addWidget( cpuBox, 0 ); + } +}; + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + SysInfo info; + info.resize( info.sizeHint().expandedTo( QSize( 600, 400 ) ) ); + info.show(); + + int rv = a.exec(); + return rv; +} diff --git a/qwt/examples/sysinfo/sysinfo.pro b/qwt/examples/sysinfo/sysinfo.pro new file mode 100644 index 000000000..f93624b12 --- /dev/null +++ b/qwt/examples/sysinfo/sysinfo.pro @@ -0,0 +1,15 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = sysinfo + +SOURCES = \ + sysinfo.cpp diff --git a/qwt/examples/tvplot/main.cpp b/qwt/examples/tvplot/main.cpp new file mode 100644 index 000000000..2ce089613 --- /dev/null +++ b/qwt/examples/tvplot/main.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#include "tvplot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + TVPlot *d_plot; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new TVPlot( this ); + setCentralWidget( d_plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *typeBox = new QComboBox( toolBar ); + typeBox->addItem( "Outline" ); + typeBox->addItem( "Columns" ); + typeBox->addItem( "Lines" ); + typeBox->addItem( "Column Symbol" ); + typeBox->setCurrentIndex( typeBox->count() - 1 ); + typeBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), d_plot, SLOT( exportPlot() ) ); + + toolBar->addWidget( typeBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + + d_plot->setMode( typeBox->currentIndex() ); + connect( typeBox, SIGNAL( currentIndexChanged( int ) ), + d_plot, SLOT( setMode( int ) ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/tvplot/tvplot.cpp b/qwt/examples/tvplot/tvplot.cpp new file mode 100644 index 000000000..9da1a839b --- /dev/null +++ b/qwt/examples/tvplot/tvplot.cpp @@ -0,0 +1,169 @@ +#include "tvplot.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Histogram: public QwtPlotHistogram +{ +public: + Histogram( const QString &, const QColor & ); + + void setColor( const QColor & ); + void setValues( uint numValues, const double * ); +}; + +Histogram::Histogram( const QString &title, const QColor &symbolColor ): + QwtPlotHistogram( title ) +{ + setStyle( QwtPlotHistogram::Columns ); + + setColor( symbolColor ); +} + +void Histogram::setColor( const QColor &color ) +{ + QColor c = color; + c.setAlpha( 180 ); + setBrush( QBrush( c ) ); +} + +void Histogram::setValues( uint numValues, const double *values ) +{ + QVector samples( numValues ); + for ( uint i = 0; i < numValues; i++ ) + { + QwtInterval interval( double( i ), i + 1.0 ); + interval.setBorderFlags( QwtInterval::ExcludeMaximum ); + + samples[i] = QwtIntervalSample( values[i], interval ); + } + + setData( new QwtIntervalSeriesData( samples ) ); +} + +TVPlot::TVPlot( QWidget *parent ): + QwtPlot( parent ) +{ + setTitle( "Watching TV during a weekend" ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setPalette( Qt::gray ); + canvas->setBorderRadius( 10 ); + setCanvas( canvas ); + + plotLayout()->setAlignCanvasToScales( true ); + + setAxisTitle( QwtAxis::yLeft, "Number of People" ); + setAxisTitle( QwtAxis::xBottom, "Number of Hours" ); + + QwtLegend *legend = new QwtLegend; + legend->setDefaultItemMode( QwtLegendData::Checkable ); + insertLegend( legend, QwtPlot::RightLegend ); + + populate(); + + connect( legend, SIGNAL( checked( const QVariant &, bool, int ) ), + SLOT( showItem( const QVariant &, bool ) ) ); + + replot(); // creating the legend items + + QwtPlotItemList items = itemList( QwtPlotItem::Rtti_PlotHistogram ); + for ( int i = 0; i < items.size(); i++ ) + { + if ( i == 0 ) + { + const QVariant itemInfo = itemToInfo( items[i] ); + + QwtLegendLabel *legendLabel = + qobject_cast( legend->legendWidget( itemInfo ) ); + if ( legendLabel ) + legendLabel->setChecked( true ); + + items[i]->setVisible( true ); + } + else + { + items[i]->setVisible( false ); + } + } + + setAutoReplot( true ); +} + +void TVPlot::populate() +{ + QwtPlotGrid *grid = new QwtPlotGrid; + grid->enableX( false ); + grid->enableY( true ); + grid->enableXMin( false ); + grid->enableYMin( false ); + grid->setMajorPen( Qt::black, 0, Qt::DotLine ); + grid->attach( this ); + + const double juneValues[] = { 7, 19, 24, 32, 10, 5, 3 }; + const double novemberValues[] = { 4, 15, 22, 34, 13, 8, 4 }; + + Histogram *histogramJune = new Histogram( "Summer", Qt::red ); + histogramJune->setValues( + sizeof( juneValues ) / sizeof( double ), juneValues ); + histogramJune->attach( this ); + + Histogram *histogramNovember = new Histogram( "Winter", Qt::blue ); + histogramNovember->setValues( + sizeof( novemberValues ) / sizeof( double ), novemberValues ); + histogramNovember->attach( this ); +} + +void TVPlot::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "tvplot.pdf" ); +} + +void TVPlot::setMode( int mode ) +{ + QwtPlotItemList items = itemList( QwtPlotItem::Rtti_PlotHistogram ); + + for ( int i = 0; i < items.size(); i++ ) + { + QwtPlotHistogram *histogram = static_cast( items[i] ); + if ( mode < 3 ) + { + histogram->setStyle( static_cast( mode ) ); + histogram->setSymbol( NULL ); + + QPen pen( Qt::black, 0 ); + if ( mode == QwtPlotHistogram::Lines ) + pen.setBrush( histogram->brush() ); + + histogram->setPen( pen ); + } + else + { + histogram->setStyle( QwtPlotHistogram::Columns ); + + QwtColumnSymbol *symbol = new QwtColumnSymbol( QwtColumnSymbol::Box ); + symbol->setFrameStyle( QwtColumnSymbol::Raised ); + symbol->setLineWidth( 2 ); + symbol->setPalette( QPalette( histogram->brush().color() ) ); + + histogram->setSymbol( symbol ); + } + } +} + +void TVPlot::showItem( const QVariant &itemInfo, bool on ) +{ + QwtPlotItem *plotItem = infoToItem( itemInfo ); + if ( plotItem ) + plotItem->setVisible( on ); +} + diff --git a/qwt/examples/tvplot/tvplot.h b/qwt/examples/tvplot/tvplot.h new file mode 100644 index 000000000..ee0995132 --- /dev/null +++ b/qwt/examples/tvplot/tvplot.h @@ -0,0 +1,23 @@ +#ifndef _TV_PLOT_H_ + +#include + +class TVPlot: public QwtPlot +{ + Q_OBJECT + +public: + TVPlot( QWidget * = NULL ); + +public Q_SLOTS: + void setMode( int ); + void exportPlot(); + +private: + void populate(); + +private Q_SLOTS: + void showItem( const QVariant &, bool on ); +}; + +#endif diff --git a/qwt/examples/tvplot/tvplot.pro b/qwt/examples/tvplot/tvplot.pro new file mode 100644 index 000000000..f91401551 --- /dev/null +++ b/qwt/examples/tvplot/tvplot.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = tvplot + +SOURCES = \ + tvplot.cpp \ + main.cpp + +HEADERS = \ + tvplot.h diff --git a/qwt/playground/curvetracker/curvetracker.cpp b/qwt/playground/curvetracker/curvetracker.cpp new file mode 100644 index 000000000..ab540aac0 --- /dev/null +++ b/qwt/playground/curvetracker/curvetracker.cpp @@ -0,0 +1,129 @@ +#include "curvetracker.h" +#include +#include +#include +#include + +struct compareX +{ + inline bool operator()( const double x, const QPointF &pos ) const + { + return ( x < pos.x() ); + } +}; + +CurveTracker::CurveTracker( QWidget *canvas ): + QwtPlotPicker( canvas ) +{ + setTrackerMode( QwtPlotPicker::ActiveOnly ); + setRubberBand( VLineRubberBand ); + + setStateMachine( new QwtPickerDragPointMachine() ); +} + +QRect CurveTracker::trackerRect( const QFont &font ) const +{ + QRect r = QwtPlotPicker::trackerRect( font ); + + // align r to the first curve + + const QwtPlotItemList curves = plot()->itemList( QwtPlotItem::Rtti_PlotCurve ); + if ( curves.size() > 0 ) + { + QPointF pos = invTransform( trackerPosition() ); + + const QLineF line = curveLineAt( + static_cast( curves[0] ), pos.x() ); + if ( !line.isNull() ) + { + const double curveY = line.pointAt( + ( pos.x() - line.p1().x() ) / line.dx() ).y(); + + pos.setY( curveY ); + pos = transform( pos ); + + r.moveBottom( pos.y() ); + } + } + + return r; +} + +QwtText CurveTracker::trackerTextF( const QPointF &pos ) const +{ + QwtText trackerText; + + trackerText.setColor( Qt::black ); + + QColor c( "#333333" ); + trackerText.setBorderPen( QPen( c, 2 ) ); + c.setAlpha( 200 ); + trackerText.setBackgroundBrush( c ); + + QString info; + + const QwtPlotItemList curves = + plot()->itemList( QwtPlotItem::Rtti_PlotCurve ); + + for ( int i = 0; i < curves.size(); i++ ) + { + const QString curveInfo = curveInfoAt( + static_cast( curves[i] ), pos ); + + if ( !curveInfo.isEmpty() ) + { + if ( !info.isEmpty() ) + info += "
"; + + info += curveInfo; + } + } + + trackerText.setText( info ); + return trackerText; +} + +QString CurveTracker::curveInfoAt( + const QwtPlotCurve *curve, const QPointF &pos ) const +{ + const QLineF line = curveLineAt( curve, pos.x() ); + if ( line.isNull() ) + return QString::null; + + const double y = line.pointAt( + ( pos.x() - line.p1().x() ) / line.dx() ).y(); + + QString info( "%2" ); + return info.arg( curve->pen().color().name() ).arg( y ); +} + +QLineF CurveTracker::curveLineAt( + const QwtPlotCurve *curve, double x ) const +{ + QLineF line; + + if ( curve->dataSize() >= 2 ) + { + const QRectF br = curve->boundingRect(); + if ( br.isValid() && x >= br.left() && x <= br.right() ) + { + int index = qwtUpperSampleIndex( + *curve->data(), x, compareX() ); + + if ( index == -1 && + x == curve->sample( curve->dataSize() - 1 ).x() ) + { + // the last sample is excluded from qwtUpperSampleIndex + index = curve->dataSize() - 1; + } + + if ( index > 0 ) + { + line.setP1( curve->sample( index - 1 ) ); + line.setP2( curve->sample( index ) ); + } + } + } + + return line; +} diff --git a/qwt/playground/curvetracker/curvetracker.h b/qwt/playground/curvetracker/curvetracker.h new file mode 100644 index 000000000..3fddd4814 --- /dev/null +++ b/qwt/playground/curvetracker/curvetracker.h @@ -0,0 +1,22 @@ +#ifndef _CURVE_TRACKER_ +#define _CURVE_TRACKER_H_ + +#include + +class QwtPlotCurve; + +class CurveTracker: public QwtPlotPicker +{ +public: + CurveTracker( QWidget * ); + +protected: + virtual QwtText trackerTextF( const QPointF & ) const; + virtual QRect trackerRect( const QFont & ) const; + +private: + QString curveInfoAt( const QwtPlotCurve *, const QPointF & ) const; + QLineF curveLineAt( const QwtPlotCurve *, double x ) const; +}; + +#endif diff --git a/qwt/playground/curvetracker/curvetracker.pro b/qwt/playground/curvetracker/curvetracker.pro new file mode 100644 index 000000000..3f219d73a --- /dev/null +++ b/qwt/playground/curvetracker/curvetracker.pro @@ -0,0 +1,22 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = curvetracker + +HEADERS = \ + curvetracker.h \ + plot.h + +SOURCES = \ + curvetracker.cpp \ + plot.cpp \ + main.cpp + diff --git a/qwt/playground/curvetracker/main.cpp b/qwt/playground/curvetracker/main.cpp new file mode 100644 index 000000000..cbf2848ed --- /dev/null +++ b/qwt/playground/curvetracker/main.cpp @@ -0,0 +1,13 @@ +#include +#include "plot.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + Plot plot; + plot.resize( 600, 400 ); + plot.show(); + + return a.exec(); +} diff --git a/qwt/playground/curvetracker/plot.cpp b/qwt/playground/curvetracker/plot.cpp new file mode 100644 index 000000000..4cc8b27ed --- /dev/null +++ b/qwt/playground/curvetracker/plot.cpp @@ -0,0 +1,107 @@ +#include "plot.h" +#include "curvetracker.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Plot::Plot( QWidget *parent ): + QwtPlot( parent) +{ + setPalette( Qt::black ); + + // we want to have the axis scales like a frame around the + // canvas + plotLayout()->setAlignCanvasToScales( true ); + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + axisWidget( axis )->setMargin( 0 ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setAutoFillBackground( false ); + canvas->setFrameStyle( QFrame::NoFrame ); + setCanvas( canvas ); + + setAxisScale( QwtAxis::yLeft, 0.0, 10.0 ); + + // a title + QwtText title( "Picker Demo" ); + title.setColor( Qt::white ); + title.setRenderFlags( Qt::AlignHCenter | Qt::AlignTop ); + + QFont font; + font.setBold( true ); + title.setFont( font ); + + QwtPlotTextLabel *titleItem = new QwtPlotTextLabel(); + titleItem->setText( title ); + titleItem->attach( this ); + +#if 1 + // section + + //QColor c( "PaleVioletRed" ); + + QwtPlotZoneItem* zone = new QwtPlotZoneItem(); + zone->setPen( Qt::darkGray ); + zone->setBrush( QColor( "#834358" ) ); + zone->setOrientation( Qt::Horizontal ); + zone->setInterval( 3.8, 5.7 ); + zone->attach( this ); + +#else + // grid + + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->setMajorPen( Qt::white, 0, Qt::DotLine ); + grid->setMinorPen( Qt::gray, 0 , Qt::DotLine ); + grid->attach( this ); +#endif + + // curves + + QPolygonF points1; + points1 << QPointF( 0.2, 4.4 ) << QPointF( 1.2, 3.0 ) + << QPointF( 2.7, 4.5 ) << QPointF( 3.5, 6.8 ) + << QPointF( 4.7, 7.9 ) << QPointF( 5.8, 7.1 ); + + insertCurve( "Curve 1", "DarkOrange", points1 ); + + QPolygonF points2; + points2 << QPointF( 0.4, 8.7 ) << QPointF( 1.4, 7.8 ) + << QPointF( 2.3, 5.5 ) << QPointF( 3.3, 4.1 ) + << QPointF( 4.4, 5.2 ) << QPointF( 5.6, 5.7 ); + + insertCurve( "Curve 2", "DodgerBlue", points2 ); + + CurveTracker* tracker = new CurveTracker( this->canvas() ); + + // for the demo we want the tracker to be active without + // having to click on the canvas + tracker->setStateMachine( new QwtPickerTrackerMachine() ); + tracker->setRubberBandPen( QPen( "MediumOrchid" ) ); +} + +void Plot::insertCurve( const QString &title, + const QColor &color, const QPolygonF &points ) +{ + QwtPlotCurve *curve = new QwtPlotCurve(); + curve->setTitle( title ); + curve->setPen( color, 2 ), + curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + + QwtSymbol *symbol = new QwtSymbol( QwtSymbol::Ellipse, + QBrush( Qt::white ), QPen( color, 2 ), QSize( 8, 8 ) ); + curve->setSymbol( symbol ); + + curve->setSamples( points ); + + curve->attach( this ); +} + + diff --git a/qwt/playground/curvetracker/plot.h b/qwt/playground/curvetracker/plot.h new file mode 100644 index 000000000..c16d734ed --- /dev/null +++ b/qwt/playground/curvetracker/plot.h @@ -0,0 +1,21 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class QPolygonF; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + +private: + void insertCurve( const QString &title, + const QColor &, const QPolygonF & ); +}; + +#endif + diff --git a/qwt/playground/graphicscale/canvas.cpp b/qwt/playground/graphicscale/canvas.cpp new file mode 100644 index 000000000..5cd44e6e3 --- /dev/null +++ b/qwt/playground/graphicscale/canvas.cpp @@ -0,0 +1,72 @@ +#include "canvas.h" +#include +#include + +Canvas::Canvas( Mode mode, QWidget *parent ): + QWidget( parent ), + d_mode( mode ) +{ + const int m = 10; + setContentsMargins( m, m, m, m ); + + if ( d_mode == Svg ) + d_renderer = new QSvgRenderer( this ); + else + d_graphic = new QwtGraphic(); +} + +Canvas::~Canvas() +{ + if ( d_mode == VectorGraphic ) + delete d_graphic; +} + +void Canvas::setSvg( const QByteArray &data ) +{ + if ( d_mode == VectorGraphic ) + { + d_graphic->reset(); + + QSvgRenderer renderer; + renderer.load( data ); + + QPainter p( d_graphic ); + renderer.render( &p, renderer.viewBoxF() ); + p.end(); + } + else + { + d_renderer->load( data ); + } + + update(); +} + +void Canvas::paintEvent( QPaintEvent * ) +{ + QPainter painter( this ); + + painter.save(); + + painter.setPen( Qt::black ); + painter.setBrush( Qt::white ); + painter.drawRect( contentsRect().adjusted( 0, 0, -1, -1 ) ); + + painter.restore(); + + painter.setPen( Qt::NoPen ); + painter.setBrush( Qt::NoBrush ); + render( &painter, contentsRect() ); +} + +void Canvas::render( QPainter *painter, const QRect &rect ) const +{ + if ( d_mode == Svg ) + { + d_renderer->render( painter, rect ); + } + else + { + d_graphic->render( painter, rect ); + } +} diff --git a/qwt/playground/graphicscale/canvas.h b/qwt/playground/graphicscale/canvas.h new file mode 100644 index 000000000..51f2a2988 --- /dev/null +++ b/qwt/playground/graphicscale/canvas.h @@ -0,0 +1,33 @@ +#include + +class QByteArray; +class QSvgRenderer; +class QwtGraphic; + +class Canvas: public QWidget +{ +public: + enum Mode + { + Svg, + VectorGraphic + }; + + Canvas( Mode, QWidget *parent = NULL ); + virtual ~Canvas(); + + void setSvg( const QByteArray & ); + +protected: + virtual void paintEvent( QPaintEvent * ); + +private: + void render( QPainter *, const QRect & ) const; + + const Mode d_mode; + union + { + QSvgRenderer *d_renderer; + QwtGraphic *d_graphic; + }; +}; diff --git a/qwt/playground/graphicscale/graphicscale.pro b/qwt/playground/graphicscale/graphicscale.pro new file mode 100644 index 000000000..64160c99c --- /dev/null +++ b/qwt/playground/graphicscale/graphicscale.pro @@ -0,0 +1,23 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = graphicscale +QT += svg + +HEADERS = \ + canvas.h \ + mainwindow.h + +SOURCES = \ + canvas.cpp \ + mainwindow.cpp \ + main.cpp + diff --git a/qwt/playground/graphicscale/main.cpp b/qwt/playground/graphicscale/main.cpp new file mode 100644 index 000000000..03397ff6c --- /dev/null +++ b/qwt/playground/graphicscale/main.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.resize( 600, 400 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/playground/graphicscale/mainwindow.cpp b/qwt/playground/graphicscale/mainwindow.cpp new file mode 100644 index 000000000..cbcb398de --- /dev/null +++ b/qwt/playground/graphicscale/mainwindow.cpp @@ -0,0 +1,109 @@ +#include "mainwindow.h" +#include "canvas.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow() +{ + QWidget *w = new QWidget( this ); + + d_canvas[0] = new Canvas( Canvas::Svg, this ); + d_canvas[0]->setAutoFillBackground( true ); + d_canvas[0]->setPalette( Qt::gray ); + + d_canvas[1] = new Canvas( Canvas::VectorGraphic, this ); + d_canvas[1]->setAutoFillBackground( true ); + d_canvas[1]->setPalette( Qt::gray ); + + QVBoxLayout *vBox1 = new QVBoxLayout(); + vBox1->setContentsMargins( 0, 0, 0, 0 ); + vBox1->setSpacing( 5 ); + vBox1->addWidget( new QLabel( "SVG" ), 0, Qt::AlignCenter ); + vBox1->addWidget( d_canvas[0], 10 ); + + QVBoxLayout *vBox2 = new QVBoxLayout(); + vBox2->setContentsMargins( 0, 0, 0, 0 ); + vBox2->setSpacing( 5 ); + vBox2->addWidget( new QLabel( "Vector Graphic" ), 0, Qt::AlignCenter ); + vBox2->addWidget( d_canvas[1], 10 ); + + QHBoxLayout *layout = new QHBoxLayout( w ); + layout->addLayout( vBox1 ); + layout->addLayout( vBox2 ); + + setCentralWidget( w ); + + QToolBar *toolBar = new QToolBar( this ); + + QToolButton *btnLoad = new QToolButton( toolBar ); + + btnLoad->setText( "Load SVG" ); + btnLoad->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnLoad ); + + addToolBar( toolBar ); + + connect( btnLoad, SIGNAL( clicked() ), this, SLOT( loadSVG() ) ); + +#if 0 + QPainterPath path; + path.addRect( QRectF( 1.0, 1.0, 2.0, 2.0 ) ); + loadPath( path ); +#endif +}; + +MainWindow::~MainWindow() +{ +} + +void MainWindow::loadSVG() +{ + QString dir = "/home1/uwe/qwt/qwt/tests/svg"; + const QString fileName = QFileDialog::getOpenFileName( NULL, + "Load a Scaleable Vector Graphic (SVG) Document", + dir, "SVG Files (*.svg)" ); + + if ( !fileName.isEmpty() ) + loadSVG( fileName ); + + statusBar()->showMessage( fileName ); +} + +void MainWindow::loadSVG( const QString &fileName ) +{ + QFile file( fileName ); + if ( !file.open(QIODevice::ReadOnly | QIODevice::Text) ) + return; + + const QByteArray document = file.readAll(); + file.close(); + + d_canvas[0]->setSvg( document ); + d_canvas[1]->setSvg( document ); +} + + +void MainWindow::loadPath( const QPainterPath &path ) +{ + QBuffer buf; + + QSvgGenerator generator; + generator.setOutputDevice( &buf ); + + QPainter painter( &generator ); + painter.setRenderHint( QPainter::Antialiasing, false ); + painter.setPen( QPen( Qt::blue, 0 ) ); + painter.setBrush( Qt::darkCyan ); + painter.drawPath( path ); + painter.end(); + + d_canvas[0]->setSvg( buf.data() ); + d_canvas[1]->setSvg( buf.data() ); +} diff --git a/qwt/playground/graphicscale/mainwindow.h b/qwt/playground/graphicscale/mainwindow.h new file mode 100644 index 000000000..6e07ced14 --- /dev/null +++ b/qwt/playground/graphicscale/mainwindow.h @@ -0,0 +1,22 @@ +#include + +class Canvas; +class QPainterPath; + +class MainWindow: public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + virtual ~MainWindow(); + +private Q_SLOTS: + void loadSVG(); + +private: + void loadSVG( const QString & ); + void loadPath( const QPainterPath & ); + + Canvas *d_canvas[2]; +}; diff --git a/qwt/playground/playground.pri b/qwt/playground/playground.pri new file mode 100644 index 000000000..18c2c74de --- /dev/null +++ b/qwt/playground/playground.pri @@ -0,0 +1,78 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################### + +QWT_ROOT = $${PWD}/.. +include( $${QWT_ROOT}/qwtconfig.pri ) +include( $${QWT_ROOT}/qwtbuild.pri ) +include( $${QWT_ROOT}/qwtfunctions.pri ) + +TEMPLATE = app + +INCLUDEPATH += $${QWT_ROOT}/src +DEPENDPATH += $${QWT_ROOT}/src + +!debug_and_release { + + DESTDIR = $${QWT_ROOT}/playground/bin +} +else { + CONFIG(debug, debug|release) { + + DESTDIR = $${QWT_ROOT}/playground/bin_debug + } + else { + + DESTDIR = $${QWT_ROOT}/playground/bin + } +} + + +QMAKE_RPATHDIR *= $${QWT_ROOT}/lib + +contains(QWT_CONFIG, QwtFramework) { + + LIBS += -F$${QWT_ROOT}/lib +} +else { + + LIBS += -L$${QWT_ROOT}/lib +} + +qwtAddLibrary(qwt) + +greaterThan(QT_MAJOR_VERSION, 4) { + + QT += printsupport + QT += concurrent +} + +contains(QWT_CONFIG, QwtOpenGL ) { + + QT += opengl +} +else { + + DEFINES += QWT_NO_OPENGL +} + +contains(QWT_CONFIG, QwtSvg) { + + QT += svg +} +else { + + DEFINES += QWT_NO_SVG +} + + +win32 { + contains(QWT_CONFIG, QwtDll) { + DEFINES += QT_DLL QWT_DLL + } +} diff --git a/qwt/playground/playground.pro b/qwt/playground/playground.pro new file mode 100644 index 000000000..ec6b5d21d --- /dev/null +++ b/qwt/playground/playground.pro @@ -0,0 +1,32 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../qwtconfig.pri ) + +TEMPLATE = subdirs + +contains(QWT_CONFIG, QwtPlot) { + + SUBDIRS += \ + plotmatrix \ + timescale \ + scaleengine \ + graphicscale \ + rescaler \ + shapes \ + curvetracker \ + symbols + + contains(QWT_CONFIG, QwtSvg) { + + SUBDIRS += \ + svgmap + } + +} diff --git a/qwt/playground/plotmatrix/main.cpp b/qwt/playground/plotmatrix/main.cpp new file mode 100644 index 000000000..5754076cf --- /dev/null +++ b/qwt/playground/plotmatrix/main.cpp @@ -0,0 +1,66 @@ +#include "plotmatrix.h" +#include +#include +#include +#include +#include + +class MainWindow: public PlotMatrix +{ +public: + MainWindow(); +}; + +MainWindow::MainWindow(): + PlotMatrix( 3, 4 ) +{ + enableAxis( QwtAxis::yLeft ); + enableAxis( QwtAxis::yRight ); + enableAxis( QwtAxis::xBottom ); + + for ( int row = 0; row < numRows(); row++ ) + { + const double v = qPow( 10.0, row ); + setAxisScale( QwtAxis::yLeft, row, -v, v ); + setAxisScale( QwtAxis::yRight, row, -v, v ); + } + + for ( int col = 0; col < numColumns(); col++ ) + { + const double v = qPow( 10.0, col ); + setAxisScale( QwtAxis::xBottom, col, -v, v ); + setAxisScale( QwtAxis::xTop, col, -v, v ); + } + + for ( int row = 0; row < numRows(); row++ ) + { + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *plot = plotAt( row, col ); + plot->setCanvasBackground( QColor( Qt::darkGray ) ); + + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->enableXMin( true ); + grid->setMajorPen( Qt::white, 0, Qt::DotLine ); + grid->setMinorPen( Qt::gray, 0 , Qt::DotLine ); + grid->attach( plot ); + } + } + + plotAt( 1, 0 )->axisWidget( QwtAxis::yLeft )->setLabelRotation( 45 ); + plotAt( 1, numColumns() - 1 )->axisWidget( QwtAxis::yRight )->setLabelRotation( -45 ); + + updateLayout(); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + + mainWindow.resize( 800, 600 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/playground/plotmatrix/plotmatrix.cpp b/qwt/playground/plotmatrix/plotmatrix.cpp new file mode 100644 index 000000000..81b2070a2 --- /dev/null +++ b/qwt/playground/plotmatrix/plotmatrix.cpp @@ -0,0 +1,428 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +// vim: expandtab + +#include +#include +#include +#include +#include +#include +#include "plotmatrix.h" + +static void enablePlotAxis( QwtPlot *plot, int axis, bool on ) +{ + // when false we still enable the axis to have an effect + // of the minimal extent active. Instead we hide all visible + // parts and margins/spacings. + + plot->setAxisVisible( axis, true ); + + QwtScaleDraw *sd = plot->axisScaleDraw( axis ); + sd->enableComponent( QwtScaleDraw::Backbone, on ); + sd->enableComponent( QwtScaleDraw::Ticks, on ); + sd->enableComponent( QwtScaleDraw::Labels, on ); + + QwtScaleWidget* sw = plot->axisWidget( axis ); + sw->setMargin( on ? 4 : 0 ); + sw->setSpacing( on ? 20 : 0 ); +} + +class Plot: public QwtPlot +{ +public: + Plot( QWidget *parent = NULL ): + QwtPlot( parent ) + { + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setLineWidth( 1 ); + canvas->setFrameStyle( QFrame::Box | QFrame::Plain ); + + setCanvas( canvas ); + } + + virtual QSize sizeHint() const + { + return minimumSizeHint(); + } +}; + +class PlotMatrix::PrivateData +{ +public: + PrivateData(): + inScaleSync( false ) + { + isAxisEnabled[QwtAxis::xBottom] = true; + isAxisEnabled[QwtAxis::xTop] = false; + isAxisEnabled[QwtAxis::yLeft] = true; + isAxisEnabled[QwtAxis::yRight] = false; + } + + bool isAxisEnabled[QwtAxis::PosCount]; + QVector plotWidgets; + mutable bool inScaleSync; +}; + +PlotMatrix::PlotMatrix( int numRows, int numColumns, QWidget *parent ): + QFrame( parent ) +{ + d_data = new PrivateData(); + d_data->plotWidgets.resize( numRows * numColumns ); + + QGridLayout *layout = new QGridLayout( this ); + layout->setSpacing( 5 ); + + for ( int row = 0; row < numRows; row++ ) + { + for ( int col = 0; col < numColumns; col++ ) + { + QwtPlot *plot = new Plot( this ); + layout->addWidget( plot, row, col ); + + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + { + connect( plot->axisWidget( axis ), + SIGNAL( scaleDivChanged() ), SLOT( scaleDivChanged() ) ); + } + d_data->plotWidgets[row * numColumns + col] = plot; + } + } + + updateLayout(); +} + +PlotMatrix::~PlotMatrix() +{ + delete d_data; +} + +int PlotMatrix::numRows() const +{ + const QGridLayout *l = qobject_cast( layout() ); + if ( l ) + return l->rowCount(); + + return 0; +} + +int PlotMatrix::numColumns() const +{ + const QGridLayout *l = qobject_cast( layout() ); + if ( l ) + return l->columnCount(); + return 0; +} + +QwtPlot* PlotMatrix::plotAt( int row, int column ) +{ + const int index = row * numColumns() + column; + if ( index < d_data->plotWidgets.size() ) + return d_data->plotWidgets[index]; + + return NULL; +} + +const QwtPlot* PlotMatrix::plotAt( int row, int column ) const +{ + const int index = row * numColumns() + column; + if ( index < d_data->plotWidgets.size() ) + return d_data->plotWidgets[index]; + + return NULL; +} + +void PlotMatrix::enableAxis( int axis, bool tf ) +{ + if ( QwtAxis::isValid( axis ) ) + { + if ( tf != d_data->isAxisEnabled[axis] ) + { + d_data->isAxisEnabled[axis] = tf; + updateLayout(); + } + } +} + +bool PlotMatrix::axisEnabled( int axis ) const +{ + if ( QwtAxis::isValid( axis ) ) + return d_data->isAxisEnabled[axis]; + + return false; +} + +void PlotMatrix::setAxisScale( int axis, int rowOrColumn, + double min, double max, double step ) +{ + int row = 0; + int col = 0; + + if ( axis == QwtAxis::xBottom || axis == QwtAxis::xTop ) + col = rowOrColumn; + else + row = rowOrColumn; + + QwtPlot *plt = plotAt( row, col ); + if ( plt ) + { + plt->setAxisScale( axis, min, max, step ); + plt->updateAxes(); + } +} + +void PlotMatrix::scaleDivChanged() +{ + if ( d_data->inScaleSync ) + return; + + d_data->inScaleSync = true; + + QwtPlot *plt = NULL; + int axisId = -1; + int rowOrColumn = -1; + + // find the changed axis + for ( int row = 0; row < numRows(); row++ ) + { + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( row, col ); + if ( p ) + { + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + { + if ( p->axisWidget( axis ) == sender() ) + { + plt = p; + axisId = axis; + if ( axisId == QwtAxis::xBottom || axisId == QwtAxis::xTop ) + rowOrColumn = col; + else + rowOrColumn = row; + + } + } + } + } + } + + if ( plt ) + { + const QwtScaleDiv scaleDiv = plt->axisScaleDiv( axisId ); + + // synchronize the axes + if ( axisId == QwtAxis::xBottom || axisId == QwtAxis::xTop ) + { + for ( int row = 0; row < numRows(); row++ ) + { + QwtPlot *p = plotAt( row, rowOrColumn ); + if ( p != plt ) + { + p->setAxisScaleDiv( axisId, scaleDiv ); + } + } + } + else + { + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( rowOrColumn, col ); + if ( p != plt ) + { + p->setAxisScaleDiv( axisId, scaleDiv ); + } + } + } + + updateLayout(); + } + + d_data->inScaleSync = false; +} + +void PlotMatrix::updateLayout() +{ + for ( int row = 0; row < numRows(); row++ ) + { + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( row, col ); + if ( p ) + { + bool showAxis[QwtAxis::PosCount]; + showAxis[QwtAxis::xBottom] = + axisEnabled( QwtAxis::xBottom ) && row == numRows() - 1; + showAxis[QwtAxis::xTop] = + axisEnabled( QwtAxis::xTop ) && row == 0; + showAxis[QwtAxis::yLeft] = + axisEnabled( QwtAxis::yLeft ) && col == 0; + showAxis[QwtAxis::yRight] = + axisEnabled( QwtAxis::yRight ) && col == numColumns() - 1; + + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + { + enablePlotAxis( p, axis, showAxis[axis] ); + } + } + } + } + + for ( int row = 0; row < numRows(); row++ ) + { + alignAxes( row, QwtAxis::xTop ); + alignAxes( row, QwtAxis::xBottom ); + + alignScaleBorder( row, QwtAxis::yLeft ); + alignScaleBorder( row, QwtAxis::yRight ); + } + + for ( int col = 0; col < numColumns(); col++ ) + { + alignAxes( col, QwtAxis::yLeft ); + alignAxes( col, QwtAxis::yRight ); + + alignScaleBorder( col, QwtAxis::xBottom ); + alignScaleBorder( col, QwtAxis::xTop ); + } + + for ( int row = 0; row < numRows(); row++ ) + { + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( row, col ); + if ( p ) + p->replot(); + } + } +} + +void PlotMatrix::alignAxes( int rowOrColumn, int axis ) +{ + if ( QwtAxis::isYAxis( axis ) ) + { + double maxExtent = 0; + + for ( int row = 0; row < numRows(); row++ ) + { + QwtPlot *p = plotAt( row, rowOrColumn ); + if ( p ) + { + QwtScaleWidget *scaleWidget = p->axisWidget( axis ); + + QwtScaleDraw *sd = scaleWidget->scaleDraw(); + sd->setMinimumExtent( 0.0 ); + + const double extent = sd->extent( scaleWidget->font() ); + if ( extent > maxExtent ) + maxExtent = extent; + } + } + + for ( int row = 0; row < numRows(); row++ ) + { + QwtPlot *p = plotAt( row, rowOrColumn ); + if ( p ) + { + QwtScaleWidget *scaleWidget = p->axisWidget( axis ); + scaleWidget->scaleDraw()->setMinimumExtent( maxExtent ); + } + } + } + else + { + double maxExtent = 0; + + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( rowOrColumn, col ); + if ( p ) + { + QwtScaleWidget *scaleWidget = p->axisWidget( axis ); + + QwtScaleDraw *sd = scaleWidget->scaleDraw(); + sd->setMinimumExtent( 0.0 ); + + const double extent = sd->extent( scaleWidget->font() ); + if ( extent > maxExtent ) + maxExtent = extent; + } + } + + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( rowOrColumn, col ); + if ( p ) + { + QwtScaleWidget *scaleWidget = p->axisWidget( axis ); + scaleWidget->scaleDraw()->setMinimumExtent( maxExtent ); + } + } + } +} + +void PlotMatrix::alignScaleBorder( int rowOrColumn, int axis ) +{ + int startDist = 0; + int endDist = 0; + + if ( axis == QwtAxis::yLeft ) + { + QwtPlot *p = plotAt( rowOrColumn, 0 ); + if ( p ) + p->axisWidget( axis )->getBorderDistHint( startDist, endDist ); + + for ( int col = 1; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( rowOrColumn, col ); + if ( p ) + p->axisWidget( axis )->setMinBorderDist( startDist, endDist ); + } + } + else if ( axis == QwtAxis::yRight ) + { + QwtPlot *p = plotAt( rowOrColumn, numColumns() - 1 ); + if ( p ) + p->axisWidget( axis )->getBorderDistHint( startDist, endDist ); + + for ( int col = 0; col < numColumns() - 1; col++ ) + { + QwtPlot *p = plotAt( rowOrColumn, col ); + if ( p ) + p->axisWidget( axis )->setMinBorderDist( startDist, endDist ); + } + } + if ( axis == QwtAxis::xTop ) + { + QwtPlot *p = plotAt( rowOrColumn, 0 ); + if ( p ) + p->axisWidget( axis )->getBorderDistHint( startDist, endDist ); + + for ( int row = 1; row < numRows(); row++ ) + { + QwtPlot *p = plotAt( row, rowOrColumn ); + if ( p ) + p->axisWidget( axis )->setMinBorderDist( startDist, endDist ); + } + } + else if ( axis == QwtAxis::xBottom ) + { + QwtPlot *p = plotAt( numRows() - 1, rowOrColumn ); + if ( p ) + p->axisWidget( axis )->getBorderDistHint( startDist, endDist ); + + for ( int row = 0; row < numRows() - 1; row++ ) + { + QwtPlot *p = plotAt( row, rowOrColumn ); + if ( p ) + p->axisWidget( axis )->setMinBorderDist( startDist, endDist ); + } + } +} diff --git a/qwt/playground/plotmatrix/plotmatrix.h b/qwt/playground/plotmatrix/plotmatrix.h new file mode 100644 index 000000000..81a779b50 --- /dev/null +++ b/qwt/playground/plotmatrix/plotmatrix.h @@ -0,0 +1,41 @@ +#ifndef _PLOT_MATRIX_H_ +#define _PLOT_MATRIX_H_ + +#include +#include + +class PlotMatrix: public QFrame +{ + Q_OBJECT + +public: + PlotMatrix( int rows, int columns, QWidget * parent = NULL ); + virtual ~PlotMatrix(); + + int numRows() const; + int numColumns() const; + + QwtPlot* plotAt( int row, int column ); + const QwtPlot* plotAt( int row, int column ) const; + + void enableAxis( int axisId, bool tf = true ); + bool axisEnabled( int axisId ) const; + + void setAxisScale( int axisId, int rowOrColumn, + double min, double max, double step = 0 ); + +protected: + void updateLayout(); + +private Q_SLOTS: + void scaleDivChanged(); + +private: + void alignAxes( int rowOrColumn, int axis ); + void alignScaleBorder( int rowOrColumn, int axis ); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/playground/plotmatrix/plotmatrix.pro b/qwt/playground/plotmatrix/plotmatrix.pro new file mode 100644 index 000000000..027907e24 --- /dev/null +++ b/qwt/playground/plotmatrix/plotmatrix.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = plotmatrix + +HEADERS = \ + plotmatrix.h + +SOURCES = \ + plotmatrix.cpp \ + main.cpp diff --git a/qwt/playground/rescaler/main.cpp b/qwt/playground/rescaler/main.cpp new file mode 100644 index 000000000..928aa1208 --- /dev/null +++ b/qwt/playground/rescaler/main.cpp @@ -0,0 +1,15 @@ +#include +#include "mainwindow.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + + mainWindow.resize( 800, 600 ); + mainWindow.show(); + + return a.exec(); +} + diff --git a/qwt/playground/rescaler/mainwindow.cpp b/qwt/playground/rescaler/mainwindow.cpp new file mode 100644 index 000000000..8e578b6b3 --- /dev/null +++ b/qwt/playground/rescaler/mainwindow.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "plot.h" +#include "mainwindow.h" + +MainWindow::MainWindow() +{ + QFrame *w = new QFrame( this ); + + QWidget *panel = createPanel( w ); + panel->setFixedWidth( 2 * panel->sizeHint().width() ); + d_plot = createPlot( w ); + + QHBoxLayout *layout = new QHBoxLayout( w ); + layout->setMargin( 0 ); + layout->addWidget( panel, 0 ); + layout->addWidget( d_plot, 10 ); + + setCentralWidget( w ); + + setRescaleMode( 0 ); + + ( void )statusBar(); +} + +QWidget *MainWindow::createPanel( QWidget *parent ) +{ + QGroupBox *panel = new QGroupBox( "Navigation Panel", parent ); + + QComboBox *rescaleBox = new QComboBox( panel ); + rescaleBox->setEditable( false ); + rescaleBox->insertItem( KeepScales, "None" ); + rescaleBox->insertItem( Fixed, "Fixed" ); + rescaleBox->insertItem( Expanding, "Expanding" ); + rescaleBox->insertItem( Fitting, "Fitting" ); + + connect( rescaleBox, SIGNAL( activated( int ) ), SLOT( setRescaleMode( int ) ) ); + + d_rescaleInfo = new QLabel( panel ); + d_rescaleInfo->setSizePolicy( + QSizePolicy::Expanding, QSizePolicy::Expanding ); + d_rescaleInfo->setWordWrap( true ); + + QVBoxLayout *layout = new QVBoxLayout( panel ); + layout->addWidget( rescaleBox ); + layout->addWidget( d_rescaleInfo ); + layout->addStretch( 10 ); + + return panel; +} + +Plot *MainWindow::createPlot( QWidget *parent ) +{ + Plot *plot = new Plot( parent, QwtInterval( 0.0, 1000.0 ) ); + plot->replot(); + + d_rescaler = new QwtPlotRescaler( plot->canvas() ); + d_rescaler->setReferenceAxis( QwtAxis::xBottom ); + d_rescaler->setAspectRatio( QwtAxis::yLeft, 1.0 ); + d_rescaler->setAspectRatio( QwtAxis::yRight, 0.0 ); + d_rescaler->setAspectRatio( QwtAxis::xTop, 0.0 ); + + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + d_rescaler->setIntervalHint( axis, QwtInterval( 0.0, 1000.0 ) ); + + connect( plot, SIGNAL( resized( double, double ) ), + SLOT( showRatio( double, double ) ) ); + return plot; +} + +void MainWindow::setRescaleMode( int mode ) +{ + bool doEnable = true; + QString info; + QRectF rectOfInterest; + QwtPlotRescaler::ExpandingDirection direction = QwtPlotRescaler::ExpandUp; + + switch( mode ) + { + case KeepScales: + { + doEnable = false; + info = "All scales remain unchanged, when the plot is resized"; + break; + } + case Fixed: + { + d_rescaler->setRescalePolicy( QwtPlotRescaler::Fixed ); + info = "The scale of the bottom axis remains unchanged, " + "when the plot is resized. All other scales are changed, " + "so that a pixel on screen means the same distance for" + "all scales."; + break; + } + case Expanding: + { + d_rescaler->setRescalePolicy( QwtPlotRescaler::Expanding ); + info = "The scales of all axis are shrinked/expanded, when " + "resizing the plot, keeping the distance that is represented " + "by one pixel."; + d_rescaleInfo->setText( "Expanding" ); + break; + } + case Fitting: + { + d_rescaler->setRescalePolicy( QwtPlotRescaler::Fitting ); + const QwtInterval xIntv = + d_rescaler->intervalHint( QwtAxis::xBottom ); + const QwtInterval yIntv = + d_rescaler->intervalHint( QwtAxis::yLeft ); + + rectOfInterest = QRectF( xIntv.minValue(), yIntv.minValue(), + xIntv.width(), yIntv.width() ); + direction = QwtPlotRescaler::ExpandBoth; + + info = "Fitting"; + break; + } + } + + d_plot->setRectOfInterest( rectOfInterest ); + + d_rescaleInfo->setText( info ); + d_rescaler->setEnabled( doEnable ); + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + d_rescaler->setExpandingDirection( direction ); + + if ( doEnable ) + d_rescaler->rescale(); + else + d_plot->replot(); +} + +void MainWindow::showRatio( double xRatio, double yRatio ) +{ + const QString msg = QString( "%1, %2" ).arg( xRatio ).arg( yRatio ); + statusBar()->showMessage( msg ); +} + diff --git a/qwt/playground/rescaler/mainwindow.h b/qwt/playground/rescaler/mainwindow.h new file mode 100644 index 000000000..6f52afba2 --- /dev/null +++ b/qwt/playground/rescaler/mainwindow.h @@ -0,0 +1,39 @@ +#ifndef _MAINWINDOW_H_ +#define _MAINWINDOW_H_ 1 + +#include + +class QwtPlotRescaler; +class QLabel; +class Plot; + +class MainWindow: public QMainWindow +{ + Q_OBJECT + +public: + enum RescaleMode + { + KeepScales, + Fixed, + Expanding, + Fitting + }; + + MainWindow(); + +private Q_SLOTS: + void setRescaleMode( int ); + void showRatio( double, double ); + +private: + QWidget *createPanel( QWidget * ); + Plot *createPlot( QWidget * ); + + QwtPlotRescaler *d_rescaler; + QLabel *d_rescaleInfo; + + Plot *d_plot; +}; + +#endif diff --git a/qwt/playground/rescaler/plot.cpp b/qwt/playground/rescaler/plot.cpp new file mode 100644 index 000000000..75a0dfa78 --- /dev/null +++ b/qwt/playground/rescaler/plot.cpp @@ -0,0 +1,181 @@ +#include "plot.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class TextItem: public QwtPlotItem +{ +public: + void setText( const QString &text ) + { + m_text = text; + } + + virtual void draw( QPainter *painter, + const QwtScaleMap &, const QwtScaleMap &, + const QRectF &canvasRect ) const + { + const int margin = 5; + const QRectF textRect = + canvasRect.adjusted( margin, margin, -margin, -margin ); + + painter->setPen( Qt::white ); + painter->drawText( textRect, + Qt::AlignBottom | Qt::AlignRight, m_text ); + } + +private: + QString m_text; +}; + + +// RectItem shows how to implement a simple plot item, +// what wouldn't be necessary as QwtPlotShapeItem +// would do the same +class RectItem: public QwtPlotItem +{ +public: + enum Type + { + Rect, + Ellipse + }; + + RectItem( Type type ): + d_type( type ) + { + } + + void setPen( const QPen &pen ) + { + if ( pen != d_pen ) + { + d_pen = pen; + itemChanged(); + } + } + + void setBrush( const QBrush &brush ) + { + if ( brush != d_brush ) + { + d_brush = brush; + itemChanged(); + } + } + void setRect( const QRectF &rect ) + { + if ( d_rect != rect ) + { + d_rect = rect; + itemChanged(); + } + } + + virtual QRectF boundingRect() const + { + return d_rect; + } + + virtual void draw( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF & ) const + { + if ( d_rect.isValid() ) + { + const QRectF rect = QwtScaleMap::transform( + xMap, yMap, d_rect ); + + painter->setPen( d_pen ); + painter->setBrush( d_brush ); + + if ( d_type == Ellipse ) + QwtPainter::drawEllipse( painter, rect ); + else + QwtPainter::drawRect( painter, rect ); + } + } + +private: + QPen d_pen; + QBrush d_brush; + QRectF d_rect; + Type d_type; +}; + +Plot::Plot( QWidget *parent, const QwtInterval &interval ): + QwtPlot( parent ) +{ + for ( int axis = 0; axis < QwtAxis::PosCount; axis ++ ) + setAxisScale( axis, interval.minValue(), interval.maxValue() ); + + setCanvasBackground( QColor( Qt::darkBlue ) ); + plotLayout()->setAlignCanvasToScales( true ); + + // grid + QwtPlotGrid *grid = new QwtPlotGrid; + //grid->enableXMin(true); + grid->setMajorPen( Qt::white, 0, Qt::DotLine ); + grid->setMinorPen( Qt::gray, 0 , Qt::DotLine ); + grid->attach( this ); + + const int numEllipses = 10; + + for ( int i = 0; i < numEllipses; i++ ) + { + const double x = interval.minValue() + + qrand() % qRound( interval.width() ); + const double y = interval.minValue() + + qrand() % qRound( interval.width() ); + const double r = interval.minValue() + + qrand() % qRound( interval.width() / 6 ); + + const QRectF area( x - r, y - r , 2 * r, 2 * r ); + + RectItem *item = new RectItem( RectItem::Ellipse ); + item->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + item->setRect( area ); + item->setPen( QPen( Qt::yellow ) ); + item->attach( this ); + } + + TextItem *textItem = new TextItem(); + textItem->setText( "Navigation Example" ); + textItem->attach( this ); + + d_rectOfInterest = new RectItem( RectItem::Rect ); + d_rectOfInterest->setPen( Qt::NoPen ); + QColor c = Qt::gray; + c.setAlpha( 100 ); + d_rectOfInterest->setBrush( QBrush( c ) ); + d_rectOfInterest->attach( this ); +} + +void Plot::updateLayout() +{ + QwtPlot::updateLayout(); + + const QwtScaleMap xMap = canvasMap( QwtAxis::xBottom ); + const QwtScaleMap yMap = canvasMap( QwtAxis::yLeft ); + + const QRect cr = canvas()->contentsRect(); + const double x1 = xMap.invTransform( cr.left() ); + const double x2 = xMap.invTransform( cr.right() ); + const double y1 = yMap.invTransform( cr.bottom() ); + const double y2 = yMap.invTransform( cr.top() ); + + const double xRatio = ( x2 - x1 ) / cr.width(); + const double yRatio = ( y2 - y1 ) / cr.height(); + + Q_EMIT resized( xRatio, yRatio ); +} + +void Plot::setRectOfInterest( const QRectF &rect ) +{ + d_rectOfInterest->setRect( rect ); +} diff --git a/qwt/playground/rescaler/plot.h b/qwt/playground/rescaler/plot.h new file mode 100644 index 000000000..0d9656ce4 --- /dev/null +++ b/qwt/playground/rescaler/plot.h @@ -0,0 +1,28 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ 1 + +#include + +class RectItem; +class QwtInterval; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent, const QwtInterval & ); + virtual void updateLayout(); + + void setRectOfInterest( const QRectF & ); + +Q_SIGNALS: + void resized( double xRatio, double yRatio ); + +private: + RectItem *d_rectOfInterest; +}; + +#endif + + diff --git a/qwt/playground/rescaler/rescaler.pro b/qwt/playground/rescaler/rescaler.pro new file mode 100644 index 000000000..c29d9e98a --- /dev/null +++ b/qwt/playground/rescaler/rescaler.pro @@ -0,0 +1,22 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = rescaler + +HEADERS = \ + mainwindow.h \ + plot.h + +SOURCES = \ + mainwindow.cpp \ + plot.cpp \ + main.cpp + diff --git a/qwt/playground/scaleengine/mainwindow.cpp b/qwt/playground/scaleengine/mainwindow.cpp new file mode 100644 index 000000000..894d99149 --- /dev/null +++ b/qwt/playground/scaleengine/mainwindow.cpp @@ -0,0 +1,120 @@ +#include "mainwindow.h" +#include "plot.h" +#include "transformplot.h" +#include +#include +#include + +class TransformPos: public QwtTransform +{ +public: + TransformPos( double pos, double range, double factor ): + d_position( pos ), + d_range( range ), + d_factor( factor ), + d_powRange( qPow( d_range, d_factor ) ) + { + } + + virtual double transform( double value ) const + { + const double v1 = d_position - d_range; + const double v2 = v1 + 2 * d_range; + + if ( value <= v1 ) + { + return value; + } + + if ( value >= v2 ) + { + return v1 + 2 * d_powRange + value - v2; + } + + double v; + + if ( value <= d_position ) + { + v = v1 + qPow( value - v1, d_factor ); + } + else + { + v = v1 + 2 * d_powRange - qPow( v2 - value, d_factor ); + } + + return v; + } + + virtual double invTransform( double value ) const + { + const double v1 = d_position - d_range; + const double v2 = v1 + 2 * d_powRange; + + if ( value < v1 ) + { + return value; + } + + if ( value >= v2 ) + { + return value + 2 * ( d_range - d_powRange ); + } + + double v; + if ( value <= v1 + d_powRange ) + { + v = v1 + qPow( value - v1, 1.0 / d_factor ); + } + else + { + v = d_position + d_range - qPow( v2 - value, 1.0 / d_factor ); + } + + return v; + } + + virtual QwtTransform *copy() const + { + return new TransformPos( d_position, d_range, d_factor ); + } + +private: + const double d_position; + const double d_range; + const double d_factor; + const double d_powRange; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + QSplitter *splitter = new QSplitter( Qt::Vertical ); + + d_transformPlot = new TransformPlot( splitter ); + + d_transformPlot->insertTransformation( "Square Root", + QColor( "DarkSlateGray" ), new QwtPowerTransform( 0.5 ) ); + d_transformPlot->insertTransformation( "Linear", + QColor( "Peru" ), new QwtNullTransform() ); + d_transformPlot->insertTransformation( "Cubic", + QColor( "OliveDrab" ), new QwtPowerTransform( 3.0 ) ); + d_transformPlot->insertTransformation( "Power 10", + QColor( "Indigo" ), new QwtPowerTransform( 10.0 ) ); + d_transformPlot->insertTransformation( "Log", + QColor( "SteelBlue" ), new QwtLogTransform() ); + d_transformPlot->insertTransformation( "At 400", + QColor( "Crimson" ), new TransformPos( 400.0, 100.0, 1.4 ) ); + + const QwtPlotItemList curves = + d_transformPlot->itemList( QwtPlotItem::Rtti_PlotCurve ); + if ( !curves.isEmpty() ) + d_transformPlot->setLegendChecked( curves[ 2 ] ); + + d_plot = new Plot( splitter ); + d_plot->setTransformation( new QwtPowerTransform( 3.0 ) ); + + setCentralWidget( splitter ); + + connect( d_transformPlot, SIGNAL( selected( QwtTransform * ) ), + d_plot, SLOT( setTransformation( QwtTransform * ) ) ); +} diff --git a/qwt/playground/scaleengine/mainwindow.h b/qwt/playground/scaleengine/mainwindow.h new file mode 100644 index 000000000..6eea4844d --- /dev/null +++ b/qwt/playground/scaleengine/mainwindow.h @@ -0,0 +1,16 @@ +#include + +class Plot; +class TransformPlot; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = 0 ); + +private: + Plot *d_plot; + TransformPlot *d_transformPlot; +}; diff --git a/qwt/playground/scaleengine/plot.cpp b/qwt/playground/scaleengine/plot.cpp new file mode 100644 index 000000000..35e2230c1 --- /dev/null +++ b/qwt/playground/scaleengine/plot.cpp @@ -0,0 +1,70 @@ +#include "plot.h" +#include +#include +#include +#include +#include + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setCanvasBackground( Qt::white ); + + setAxisScale(QwtAxis::yLeft, 0.0, 10.0 ); + setTransformation( new QwtNullTransform() ); + + populate(); + + QwtPlotPicker *picker = new QwtPlotPicker( canvas() ); + picker->setTrackerMode( QwtPlotPicker::AlwaysOn ); +} + +void Plot::populate() +{ + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->setMinorPen( Qt::black, 0, Qt::DashLine ); + grid->enableXMin( true ); + grid->attach( this ); + + QwtPlotCurve *curve = new QwtPlotCurve(); + curve->setTitle("Some Points"); + curve->setPen( Qt::blue, 4 ), + curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + + QwtSymbol *symbol = new QwtSymbol( QwtSymbol::Ellipse, + QBrush( Qt::yellow ), QPen( Qt::red, 2 ), QSize( 8, 8 ) ); + curve->setSymbol( symbol ); + + QPolygonF points; + points << QPointF( 10.0, 4.4 ) + << QPointF( 100.0, 3.0 ) << QPointF( 200.0, 4.5 ) + << QPointF( 300.0, 6.8 ) << QPointF( 400.0, 7.9 ) + << QPointF( 500.0, 7.1 ) << QPointF( 600.0, 7.9 ) + << QPointF( 700.0, 7.1 ) << QPointF( 800.0, 5.4 ) + << QPointF( 900.0, 2.8 ) << QPointF( 1000.0, 3.6 ); + curve->setSamples( points ); + curve->attach( this ); +} + +void Plot::setTransformation( QwtTransform *transform ) +{ + QwtLinearScaleEngine *scaleEngine = new QwtLinearScaleEngine(); + scaleEngine->setTransformation( transform ); + + setAxisScaleEngine( QwtAxis::xBottom, scaleEngine ); + + // we have to reassign the axis settinge, because they are + // invalidated, when the scale engine has changed + + QwtScaleDiv scaleDiv = + axisScaleEngine( QwtAxis::xBottom )->divideScale( 10.0, 1000.0, 8, 10 ); + + QList ticks; + ticks += 10.0; + ticks += scaleDiv.ticks( QwtScaleDiv::MajorTick ); + scaleDiv.setTicks( QwtScaleDiv::MajorTick, ticks ); + + setAxisScaleDiv( QwtAxis::xBottom, scaleDiv ); + + replot(); +} diff --git a/qwt/playground/scaleengine/plot.h b/qwt/playground/scaleengine/plot.h new file mode 100644 index 000000000..6548505b0 --- /dev/null +++ b/qwt/playground/scaleengine/plot.h @@ -0,0 +1,23 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class QwtTransform; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent = NULL ); + +public Q_SLOTS: + void setTransformation( QwtTransform * ); + +private: + void populate(); +}; + +#endif + diff --git a/qwt/playground/scaleengine/scaleengine.cpp b/qwt/playground/scaleengine/scaleengine.cpp new file mode 100644 index 000000000..6e02da2cb --- /dev/null +++ b/qwt/playground/scaleengine/scaleengine.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + MainWindow window; + window.resize(800,600); + window.show(); + + return a.exec(); +} diff --git a/qwt/playground/scaleengine/scaleengine.pro b/qwt/playground/scaleengine/scaleengine.pro new file mode 100644 index 000000000..b1704d002 --- /dev/null +++ b/qwt/playground/scaleengine/scaleengine.pro @@ -0,0 +1,24 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = scaleengine + +HEADERS = \ + transformplot.h \ + plot.h \ + mainwindow.h + +SOURCES = \ + transformplot.cpp \ + plot.cpp \ + mainwindow.cpp \ + scaleengine.cpp + diff --git a/qwt/playground/scaleengine/transformplot.cpp b/qwt/playground/scaleengine/transformplot.cpp new file mode 100644 index 000000000..60bcf0f69 --- /dev/null +++ b/qwt/playground/scaleengine/transformplot.cpp @@ -0,0 +1,108 @@ +#include "transformplot.h" +#include +#include +#include +#include +#include +#include + +class TransformData: public QwtSyntheticPointData +{ +public: + TransformData( QwtTransform *transform ): + QwtSyntheticPointData( 200 ), + d_transform( transform ) + { + } + + virtual ~TransformData() + { + delete d_transform; + } + + const QwtTransform *transform() const + { + return d_transform; + } + + virtual double y( double x ) const + { + const double min = 10.0; + const double max = 1000.0; + + const double value = min + x * ( max - min ); + + const double s1 = d_transform->transform( min ); + const double s2 = d_transform->transform( max ); + const double s = d_transform->transform( value ); + + return ( s - s1 ) / ( s2 - s1 ); + } + +private: + QwtTransform *d_transform; +}; + +TransformPlot::TransformPlot( QWidget *parent ): + QwtPlot( parent ) +{ + setTitle( "Transformations" ); + setCanvasBackground( Qt::white ); + + setAxisScale( QwtAxis::xBottom, 0.0, 1.0 ); + setAxisScale( QwtAxis::yLeft, 0.0, 1.0 ); + + QwtLegend *legend = new QwtLegend(); + legend->setDefaultItemMode( QwtLegendData::Checkable ); + insertLegend( legend, QwtPlot::RightLegend ); + + connect( legend, SIGNAL( checked( const QVariant &, bool, int ) ), + this, SLOT( legendChecked( const QVariant &, bool ) ) ); +} + +void TransformPlot::insertTransformation( + const QString &title, const QColor &color, QwtTransform *transform ) +{ + QwtPlotCurve *curve = new QwtPlotCurve( title ); + curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + curve->setPen( color, 2 ); + curve->setData( new TransformData( transform ) ); + curve->attach( this ); +} + +void TransformPlot::legendChecked( const QVariant &itemInfo, bool on ) +{ + QwtPlotItem *plotItem = infoToItem( itemInfo ); + + setLegendChecked( plotItem ); + + if ( on && plotItem->rtti() == QwtPlotItem::Rtti_PlotCurve ) + { + QwtPlotCurve *curve = static_cast( plotItem ); + TransformData *data = static_cast( curve->data() ); + + Q_EMIT selected( data->transform()->copy() ); + } +} + +void TransformPlot::setLegendChecked( QwtPlotItem *plotItem ) +{ + const QwtPlotItemList items = itemList(); + for ( int i = 0; i < items.size(); i++ ) + { + QwtPlotItem *item = items[ i ]; + if ( item->testItemAttribute( QwtPlotItem::Legend ) ) + { + QwtLegend *lgd = qobject_cast( legend() ); + + QwtLegendLabel *label = qobject_cast< QwtLegendLabel *>( + lgd->legendWidget( itemToInfo( item ) ) ); + if ( label ) + { + lgd->blockSignals( true ); + label->setChecked( item == plotItem ); + lgd->blockSignals( false ); + } + } + } +} diff --git a/qwt/playground/scaleengine/transformplot.h b/qwt/playground/scaleengine/transformplot.h new file mode 100644 index 000000000..8dd6c7121 --- /dev/null +++ b/qwt/playground/scaleengine/transformplot.h @@ -0,0 +1,27 @@ +#ifndef _TRANSFORM_PLOT_H_ +#define _TRANSFORM_PLOT_H_ + +#include + +class TransformPlot: public QwtPlot +{ + Q_OBJECT + +public: + TransformPlot( QWidget *parent = NULL ); + void insertTransformation( const QString &, + const QColor &, QwtTransform * ); + + void setLegendChecked( QwtPlotItem * ); + +Q_SIGNALS: + void selected( QwtTransform * ); + +private Q_SLOTS: + void legendChecked( const QVariant &, bool on ); + +private: +}; + +#endif + diff --git a/qwt/playground/shapes/shapes.cpp b/qwt/playground/shapes/shapes.cpp new file mode 100644 index 000000000..a998f2667 --- /dev/null +++ b/qwt/playground/shapes/shapes.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Plot : public QwtPlot +{ +public: + Plot( QWidget *parent = NULL ); + +private: + void populate(); +}; + + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setPalette( QColor( 60, 60, 60 ) ); + canvas()->setPalette( Qt::white ); + + // panning with the left mouse button + ( void ) new QwtPlotPanner( canvas() ); + + // zoom in/out with the wheel + ( void ) new QwtPlotMagnifier( canvas() ); + + setTitle( "Shapes" ); + insertLegend( new QwtLegend(), QwtPlot::RightLegend ); + + // axes + setAxisTitle( QwtAxis::xBottom, "x -->" ); + setAxisTitle( QwtAxis::yLeft, "y -->" ); +#if 0 + setAxisScaleEngine( QwtAxis::xBottom, new QwtLog10ScaleEngine ); + setAxisScaleEngine( QwtAxis::yLeft, new QwtLog10ScaleEngine ); +#endif + + populate(); +} + +void Plot::populate() +{ + const double d = 900.0; + const QRectF rect( 1.0, 1.0, d, d ); + + QPainterPath path; + //path.setFillRule( Qt::WindingFill ); + path.addEllipse( rect ); + + const QRectF rect2 = rect.adjusted( 0.2 * d, 0.3 * d, -0.22 * d, 1.5 * d ); + path.addEllipse( rect2 ); + +#if 0 + QFont font; + font.setPointSizeF( 200 ); + QPainterPath textPath; + textPath.addText( rect.center(), font, "Seppi" ); + + QTransform transform; + transform.translate( rect.center().x() - 600, rect.center().y() + 50 ); + transform.rotate( 180.0, Qt::XAxis ); + + textPath = transform.map( textPath ); + + path.addPath( textPath ); +#endif + + QwtPlotShapeItem *item = new QwtPlotShapeItem( "Shape" ); + item->setItemAttribute( QwtPlotItem::Legend, true ); + item->setRenderHint( QwtPlotItem::RenderAntialiased, true ); +#if 1 + item->setRenderTolerance( 1.0 ); +#endif + item->setShape( path ); + item->setPen( Qt::yellow ); + + QColor c = Qt::darkRed; + c.setAlpha( 100 ); + item->setBrush( c ); + + item->attach( this ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + Plot plot; + plot.resize( 600, 400 ); + plot.show(); + + return a.exec(); +} diff --git a/qwt/playground/shapes/shapes.pro b/qwt/playground/shapes/shapes.pro new file mode 100644 index 000000000..2aaf01645 --- /dev/null +++ b/qwt/playground/shapes/shapes.pro @@ -0,0 +1,16 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = shapes + +SOURCES = \ + shapes.cpp + diff --git a/qwt/playground/svgmap/main.cpp b/qwt/playground/svgmap/main.cpp new file mode 100644 index 000000000..74c334e74 --- /dev/null +++ b/qwt/playground/svgmap/main.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( const QString &fileName ) + { + Plot *plot = new Plot( this ); + if ( !fileName.isEmpty() ) + plot->loadSVG( fileName ); + + setCentralWidget( plot ); + +#ifndef QT_NO_FILEDIALOG + + QToolBar *toolBar = new QToolBar( this ); + + QToolButton *btnLoad = new QToolButton( toolBar ); + + btnLoad->setText( "Load SVG" ); + btnLoad->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnLoad ); + + addToolBar( toolBar ); + + connect( btnLoad, SIGNAL( clicked() ), plot, SLOT( loadSVG() ) ); +#endif + } +}; + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QString fileName; + if ( argc > 1 ) + fileName = argv[1]; + + MainWindow w( fileName ); + w.resize( 600, 400 ); + w.show(); + + int rv = a.exec(); + return rv; +} diff --git a/qwt/playground/svgmap/plot.cpp b/qwt/playground/svgmap/plot.cpp new file mode 100644 index 000000000..1d1884485 --- /dev/null +++ b/qwt/playground/svgmap/plot.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include +#include "plot.h" + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ), + d_mapItem( NULL ), + d_mapRect( 0.0, 0.0, 100.0, 100.0 ) // something +{ +#if 1 + /* + d_mapRect is only a reference for zooming, but + the ranges are nothing useful for the user. So we + hide the axes. + */ + plotLayout()->setCanvasMargin( 0 ); + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + setAxisVisible( axis, false ); +#else + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->attach( this ); +#endif + + /* + Navigation: + + Left Mouse Button: Panning + Mouse Wheel: Zooming In/Out + Right Mouse Button: Reset to initial + */ + + ( void )new QwtPlotPanner( canvas() ); + ( void )new QwtPlotMagnifier( canvas() ); + + canvas()->setFocusPolicy( Qt::WheelFocus ); + rescale(); +} + +#ifndef QT_NO_FILEDIALOG + +void Plot::loadSVG() +{ + QString dir; + const QString fileName = QFileDialog::getOpenFileName( NULL, + "Load a Scaleable Vector Graphic (SVG) Map", + dir, "SVG Files (*.svg)" ); + + if ( !fileName.isEmpty() ) + loadSVG( fileName ); +} + +#endif + +void Plot::loadSVG( const QString &fileName ) +{ + if ( d_mapItem == NULL ) + { + d_mapItem = new QwtPlotSvgItem(); + d_mapItem->attach( this ); + } + + d_mapItem->loadFile( d_mapRect, fileName ); + rescale(); + + replot(); +} + +void Plot::rescale() +{ + setAxisScale( QwtAxis::xBottom, + d_mapRect.left(), d_mapRect.right() ); + setAxisScale( QwtAxis::yLeft, + d_mapRect.top(), d_mapRect.bottom() ); +} diff --git a/qwt/playground/svgmap/plot.h b/qwt/playground/svgmap/plot.h new file mode 100644 index 000000000..b05c32b3e --- /dev/null +++ b/qwt/playground/svgmap/plot.h @@ -0,0 +1,26 @@ +#include +#include + +class QwtPlotSvgItem; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + +public Q_SLOTS: + +#ifndef QT_NO_FILEDIALOG + void loadSVG(); +#endif + + void loadSVG( const QString & ); + +private: + void rescale(); + + QwtPlotSvgItem *d_mapItem; + const QRectF d_mapRect; +}; diff --git a/qwt/playground/svgmap/svgmap.pro b/qwt/playground/svgmap/svgmap.pro new file mode 100644 index 000000000..9f3ad44d1 --- /dev/null +++ b/qwt/playground/svgmap/svgmap.pro @@ -0,0 +1,26 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +!contains(QWT_CONFIG, QwtSvg) { + + message(Are you trying to build Qwt with the Qt Creator as Shadow Build ?) + error(Qwt is configured without SVG support !) +} + +TARGET = svgmap +QT += svg + +HEADERS = \ + plot.h + +SOURCES = \ + plot.cpp \ + main.cpp diff --git a/qwt/playground/symbols/symbols.cpp b/qwt/playground/symbols/symbols.cpp new file mode 100644 index 000000000..9870afcc5 --- /dev/null +++ b/qwt/playground/symbols/symbols.cpp @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class MySymbol: public QwtSymbol +{ +public: + MySymbol( QwtSymbol::Style style, const QBrush &brush ) + { + QPen pen( Qt::black, 0 ); + pen.setJoinStyle( Qt::MiterJoin ); + pen.setCosmetic( true ); + + QPainterPath path = createArrow( QSize( 16, 24 ) ); + + const QSizeF pathSize = path.boundingRect().size(); + + setSize( 0.8 * pathSize.toSize() ); + + setPinPoint( QPointF( 0.0, 0.0 ) ); + + switch( style ) + { + case QwtSymbol::Pixmap: + { + const QSize sz = size(); + + const double ratio = qMin( sz.width() / pathSize.width(), + sz.height() / pathSize.height() ); + + QTransform transform; + transform.scale( ratio, ratio ); + + path = transform.map( path ); + + if ( isPinPointEnabled() ) + { + QPointF pos = transform.map( pinPoint() ); + setPinPoint( pos ); + } + + const QRectF br = path.boundingRect(); + + int m = 2 + qCeil( pen.widthF() ); + + QPixmap pm( sz + QSize( 2 * m, 2 * m ) ); + pm.fill( Qt::transparent ); + + QPainter painter( &pm ); + painter.setRenderHint( QPainter::Antialiasing, true ); + + painter.setPen( pen ); + painter.setBrush( brush ); + + painter.translate( m, m ); + painter.translate( -br.left(), br.top() ); + painter.drawPath( path ); + + setPixmap( pm ); + setSize( pm.size() ); + if ( isPinPointEnabled() ) + setPinPoint( pinPoint() + QPointF( m, m ) ); + + break; + } + case QwtSymbol::Graphic: + { + QwtGraphic graphic; + graphic.setRenderHint( QwtGraphic::RenderPensUnscaled ); + + QPainter painter( &graphic ); + painter.setRenderHint( QPainter::Antialiasing, true ); + painter.setPen( pen ); + painter.setBrush( brush ); + + painter.drawPath( path ); + painter.end(); + + setGraphic( graphic ); + break; + } + case QwtSymbol::SvgDocument: + { + QBuffer buf; + + QSvgGenerator generator; + generator.setOutputDevice( &buf ); + + QPainter painter( &generator ); + painter.setRenderHint( QPainter::Antialiasing, true ); + painter.setPen( pen ); + painter.setBrush( brush ); + + painter.drawPath( path ); + painter.end(); + + setSvgDocument( buf.data() ); + break; + } + case QwtSymbol::Path: + default: + { + setPen( pen ); + setBrush( brush ); + setPath( path ); + } + } + + } + +private: + QPainterPath createArrow( const QSizeF &size ) const + { + const double w = size.width(); + const double h = size.height(); + const double y0 = 0.6 * h; + + QPainterPath path; + path.moveTo( 0, h ); + path.lineTo( 0, y0 ); + path.lineTo( -0.5 * w, y0 ); + path.lineTo( 0, 0 ); + path.lineTo( 0.5 * w, y0 ); + path.lineTo( 0, y0 ); + + QTransform transform; + transform.rotate( -30.0 ); + path = transform.map( path ); + + return path; + } +}; + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QwtPlot plot; + plot.setTitle( "Plot Demo" ); + plot.setCanvasBackground( Qt::white ); + + plot.setAxisScale( QwtAxis::xBottom, -1.0, 6.0 ); + + QwtLegend *legend = new QwtLegend(); + legend->setDefaultItemMode( QwtLegendData::Checkable ); + plot.insertLegend( legend ); + + for ( int i = 0; i < 4; i++ ) + { + QwtPlotCurve *curve = new QwtPlotCurve(); + curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + curve->setPen( Qt::blue ); + + QBrush brush; + QwtSymbol::Style style = QwtSymbol::NoSymbol; + QString title; + if ( i == 0 ) + { + brush = Qt::magenta; + style = QwtSymbol::Path; + title = "Path"; + } + else if ( i == 2 ) + { + brush = Qt::red; + style = QwtSymbol::Graphic; + title = "Graphic"; + } + else if ( i == 1 ) + { + brush = Qt::yellow; + style = QwtSymbol::SvgDocument; + title = "Svg"; + } + else if ( i == 3 ) + { + brush = Qt::cyan; + style = QwtSymbol::Pixmap; + title = "Pixmap"; + } + + MySymbol *symbol = new MySymbol( style, brush ); + + curve->setSymbol( symbol ); + curve->setTitle( title ); + curve->setLegendAttribute( QwtPlotCurve::LegendShowSymbol, true ); + curve->setLegendIconSize( QSize( 15, 18 ) ); + + QPolygonF points; + points << QPointF( 0.0, 4.4 ) << QPointF( 1.0, 3.0 ) + << QPointF( 2.0, 4.5 ) << QPointF( 3.0, 6.8 ) + << QPointF( 4.0, 7.9 ) << QPointF( 5.0, 7.1 ); + + points.translate( 0.0, i * 2.0 ); + + curve->setSamples( points ); + curve->attach( &plot ); + } + + plot.resize( 600, 400 ); + plot.show(); + + return a.exec(); +} diff --git a/qwt/playground/symbols/symbols.pro b/qwt/playground/symbols/symbols.pro new file mode 100644 index 000000000..35f6d5450 --- /dev/null +++ b/qwt/playground/symbols/symbols.pro @@ -0,0 +1,16 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = symbols + +SOURCES = \ + symbols.cpp + diff --git a/qwt/playground/timescale/main.cpp b/qwt/playground/timescale/main.cpp new file mode 100644 index 000000000..14321f756 --- /dev/null +++ b/qwt/playground/timescale/main.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow window; + window.resize( 800, 600 ); + window.show(); + + return a.exec(); +} diff --git a/qwt/playground/timescale/mainwindow.cpp b/qwt/playground/timescale/mainwindow.cpp new file mode 100644 index 000000000..809710afb --- /dev/null +++ b/qwt/playground/timescale/mainwindow.cpp @@ -0,0 +1,58 @@ +#include "plot.h" +#include "panel.h" +#include "mainwindow.h" +#include +#include +#include + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + Settings settings; +#if 1 + settings.startDateTime = QDateTime( QDate( 2012, 10, 27 ), QTime( 18, 5, 0, 0 ) ); + settings.endDateTime = QDateTime( QDate( 2012, 10, 28 ), QTime( 12, 12, 0, 0 ) ); +#else + settings.startDateTime = QDateTime( QDate( 2011, 5, 3 ), QTime( 0, 6, 0, 0 ) ); + settings.endDateTime = QDateTime( QDate( 2012, 3, 10 ), QTime( 0, 5, 0, 0 ) ); +#endif + settings.maxMajorSteps = 10; + settings.maxMinorSteps = 8; + settings.maxWeeks = -1; + + d_plot = new Plot(); + d_panel = new Panel(); + d_panel->setSettings( settings ); + + QWidget *box = new QWidget( this ); + + QHBoxLayout *layout = new QHBoxLayout( box ); + layout->addWidget( d_plot, 10 ); + layout->addWidget( d_panel ); + + setCentralWidget( box ); + + updatePlot(); + + connect( d_panel, SIGNAL( edited() ), SLOT( updatePlot() ) ); + connect( d_plot->axisWidget( QwtAxis::yLeft ), + SIGNAL( scaleDivChanged() ), SLOT( updatePanel() ) ); +} + +void MainWindow::updatePlot() +{ + d_plot->blockSignals( true ); + d_plot->applySettings( d_panel->settings() ); + d_plot->blockSignals( false ); +} + +void MainWindow::updatePanel() +{ + const QwtScaleDiv scaleDiv = d_plot->axisScaleDiv( QwtAxis::yLeft ); + + Settings settings = d_panel->settings(); + settings.startDateTime = QwtDate::toDateTime( scaleDiv.lowerBound(), Qt::LocalTime ); + settings.endDateTime = QwtDate::toDateTime( scaleDiv.upperBound(), Qt::LocalTime ); + + d_panel->setSettings( settings ); +} diff --git a/qwt/playground/timescale/mainwindow.h b/qwt/playground/timescale/mainwindow.h new file mode 100644 index 000000000..d8cef9671 --- /dev/null +++ b/qwt/playground/timescale/mainwindow.h @@ -0,0 +1,20 @@ +#include + +class Plot; +class Panel; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = 0 ); + +private Q_SLOTS: + void updatePlot(); + void updatePanel(); + +private: + Plot *d_plot; + Panel *d_panel; +}; diff --git a/qwt/playground/timescale/panel.cpp b/qwt/playground/timescale/panel.cpp new file mode 100644 index 000000000..65c97e367 --- /dev/null +++ b/qwt/playground/timescale/panel.cpp @@ -0,0 +1,95 @@ +#include "panel.h" +#include "settings.h" +#include +#include +#include +#include +#include + +Panel::Panel( QWidget *parent ): + QWidget( parent ) +{ + // create widgets + + d_startDateTime = new QDateTimeEdit(); + d_startDateTime->setDisplayFormat( "M/d/yyyy h:mm AP :zzz" ); + d_startDateTime->setCalendarPopup( true ); + + d_endDateTime = new QDateTimeEdit(); + d_endDateTime->setDisplayFormat( "M/d/yyyy h:mm AP :zzz" ); + d_endDateTime->setCalendarPopup( true ); + + d_maxMajorSteps = new QSpinBox(); + d_maxMajorSteps->setRange( 0, 50 ); + + d_maxMinorSteps = new QSpinBox(); + d_maxMinorSteps->setRange( 0, 50 ); + + d_maxWeeks = new QSpinBox(); + d_maxWeeks->setRange( -1, 100 ); + d_maxWeeks->setSpecialValueText( "Disabled" ); + + // layout + + QGridLayout *layout = new QGridLayout( this ); + layout->setAlignment( Qt::AlignLeft | Qt::AlignTop ); + + int row = 0; + layout->addWidget( new QLabel( "From" ), row, 0 ); + layout->addWidget( d_startDateTime, row, 1 ); + + row++; + layout->addWidget( new QLabel( "To" ), row, 0 ); + layout->addWidget( d_endDateTime, row, 1 ); + + row++; + layout->addWidget( new QLabel( "Max. Major Steps" ), row, 0 ); + layout->addWidget( d_maxMajorSteps, row, 1 ); + + row++; + layout->addWidget( new QLabel( "Max. Minor Steps" ), row, 0 ); + layout->addWidget( d_maxMinorSteps, row, 1 ); + + row++; + layout->addWidget( new QLabel( "Max Weeks" ), row, 0 ); + layout->addWidget( d_maxWeeks, row, 1 ); + + connect( d_startDateTime, + SIGNAL( dateTimeChanged( const QDateTime & ) ), SIGNAL( edited() ) ); + connect( d_endDateTime, + SIGNAL( dateTimeChanged( const QDateTime & ) ), SIGNAL( edited() ) ); + connect( d_maxMajorSteps, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); + connect( d_maxMinorSteps, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); + connect( d_maxWeeks, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); +} + +void Panel::setSettings( const Settings &settings ) +{ + blockSignals( true ); + + d_startDateTime->setDateTime( settings.startDateTime ); + d_endDateTime->setDateTime( settings.endDateTime ); + + d_maxMajorSteps->setValue( settings.maxMajorSteps ); + d_maxMinorSteps->setValue( settings.maxMinorSteps ); + d_maxWeeks->setValue( settings.maxWeeks ); + + blockSignals( false ); +} + +Settings Panel::settings() const +{ + Settings settings; + + settings.startDateTime = d_startDateTime->dateTime(); + settings.endDateTime = d_endDateTime->dateTime(); + + settings.maxMajorSteps = d_maxMajorSteps->value(); + settings.maxMinorSteps = d_maxMinorSteps->value(); + settings.maxWeeks = d_maxWeeks->value(); + + return settings; +} diff --git a/qwt/playground/timescale/panel.h b/qwt/playground/timescale/panel.h new file mode 100644 index 000000000..458dfce9e --- /dev/null +++ b/qwt/playground/timescale/panel.h @@ -0,0 +1,32 @@ +#ifndef _PANEL_ +#define _PANEL_ + +#include "settings.h" +#include + +class QDateTimeEdit; +class QSpinBox; + +class Panel: public QWidget +{ + Q_OBJECT + +public: + Panel( QWidget *parent = NULL ); + + void setSettings( const Settings &); + Settings settings() const; + +Q_SIGNALS: + void edited(); + +private: + QDateTimeEdit* d_startDateTime; + QDateTimeEdit* d_endDateTime; + + QSpinBox *d_maxMajorSteps; + QSpinBox *d_maxMinorSteps; + QSpinBox *d_maxWeeks; +}; + +#endif diff --git a/qwt/playground/timescale/plot.cpp b/qwt/playground/timescale/plot.cpp new file mode 100644 index 000000000..f2dd39122 --- /dev/null +++ b/qwt/playground/timescale/plot.cpp @@ -0,0 +1,89 @@ +#include "plot.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include +#include + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setAutoFillBackground( true ); + setPalette( Qt::darkGray ); + setCanvasBackground( Qt::white ); + + plotLayout()->setAlignCanvasToScales( true ); + + initAxis( QwtAxis::yLeft, "Local Time", Qt::LocalTime ); + initAxis( QwtAxis::yRight, + "Coordinated Universal Time ( UTC )", Qt::UTC ); + + QwtPlotPanner *panner = new QwtPlotPanner( canvas() ); + QwtPlotMagnifier *magnifier = new QwtPlotMagnifier( canvas() ); + + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + { + const bool on = QwtAxis::isYAxis( axis ); + + setAxisVisible( axis, on ); + panner->setAxisEnabled( axis, on ); + magnifier->setAxisEnabled( axis, on ); + } + + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->setMajorPen( Qt::black, 0, Qt::SolidLine ); + grid->setMinorPen( Qt::gray, 0 , Qt::SolidLine ); + grid->enableX( false ); + grid->enableXMin( false ); + grid->enableY( true ); + grid->enableYMin( true ); + + grid->attach( this ); +} + +void Plot::initAxis( int axis, + const QString& title, Qt::TimeSpec timeSpec ) +{ + setAxisTitle( axis, title ); + + QwtDateScaleDraw *scaleDraw = new QwtDateScaleDraw( timeSpec ); + QwtDateScaleEngine *scaleEngine = new QwtDateScaleEngine( timeSpec ); + +#if 0 + if ( timeSpec == Qt::LocalTime ) + { + scaleDraw->setTimeSpec( Qt::OffsetFromUTC ); + scaleDraw->setUtcOffset( 10 ); + + scaleEngine->setTimeSpec( Qt::OffsetFromUTC ); + scaleEngine->setUtcOffset( 10 ); + } +#endif + setAxisScaleDraw( axis, scaleDraw ); + setAxisScaleEngine( axis, scaleEngine ); +} + +void Plot::applySettings( const Settings &settings ) +{ + applyAxisSettings( QwtAxis::yLeft, settings ); + applyAxisSettings( QwtAxis::yRight, settings ); + + replot(); +} + +void Plot::applyAxisSettings( int axis, const Settings &settings ) +{ + QwtDateScaleEngine *scaleEngine = + static_cast( axisScaleEngine( axis ) ); + + scaleEngine->setMaxWeeks( settings.maxWeeks ); + setAxisMaxMinor( axis, settings.maxMinorSteps ); + setAxisMaxMajor( axis, settings.maxMajorSteps ); + + + setAxisScale( axis, QwtDate::toDouble( settings.startDateTime ), + QwtDate::toDouble( settings.endDateTime ) ); +} diff --git a/qwt/playground/timescale/plot.h b/qwt/playground/timescale/plot.h new file mode 100644 index 000000000..d92b9a676 --- /dev/null +++ b/qwt/playground/timescale/plot.h @@ -0,0 +1,23 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class Settings; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent = NULL ); + +public Q_SLOTS: + void applySettings( const Settings & ); + +private: + void initAxis( int axis, const QString& title, Qt::TimeSpec ); + void applyAxisSettings( int axis, const Settings & ); +}; + +#endif diff --git a/qwt/playground/timescale/settings.h b/qwt/playground/timescale/settings.h new file mode 100644 index 000000000..524190faf --- /dev/null +++ b/qwt/playground/timescale/settings.h @@ -0,0 +1,25 @@ +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ 1 + +#include + +class Settings +{ +public: + Settings(): + maxMajorSteps( 10 ), + maxMinorSteps( 5 ), + maxWeeks( -1 ) + { + }; + + QDateTime startDateTime; + QDateTime endDateTime; + + int maxMajorSteps; + int maxMinorSteps; + + int maxWeeks; +}; + +#endif diff --git a/qwt/playground/timescale/timescale.pro b/qwt/playground/timescale/timescale.pro new file mode 100644 index 000000000..fae1e7f6f --- /dev/null +++ b/qwt/playground/timescale/timescale.pro @@ -0,0 +1,24 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = timescale + +HEADERS = \ + panel.h \ + plot.h \ + mainwindow.h + +SOURCES = \ + panel.cpp \ + plot.cpp \ + mainwindow.cpp \ + main.cpp + diff --git a/qwt/qwt.prf b/qwt/qwt.prf new file mode 100644 index 000000000..ea2737b6f --- /dev/null +++ b/qwt/qwt.prf @@ -0,0 +1,38 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include ( ./qwtconfig.pri ) +include ( ./qwtfunctions.pri ) + +contains(QWT_CONFIG, QwtDll) { + + DEFINES *= QWT_DLL +} + +contains(QWT_CONFIG, QwtSvg) { + + QT *= svg +} +else { + + DEFINES *= QWT_NO_SVG +} + +contains(QWT_CONFIG, QwtFramework) { + + INCLUDEPATH *= $${QWT_INSTALL_LIBS}/qwt.framework/Headers + LIBS *= -F$${QWT_INSTALL_LIBS} +} +else { + + INCLUDEPATH *= $${QWT_INSTALL_HEADERS} + LIBS *= -L$${QWT_INSTALL_LIBS} +} + +qwtAddLibrary(qwt) diff --git a/qwt/qwt.pro b/qwt/qwt.pro new file mode 100644 index 000000000..04b7443a7 --- /dev/null +++ b/qwt/qwt.pro @@ -0,0 +1,36 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( qwtconfig.pri ) + +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS = \ + src \ + textengines \ + doc + +contains(QWT_CONFIG, QwtDesigner ) { + SUBDIRS += designer +} + +contains(QWT_CONFIG, QwtExamples ) { + SUBDIRS += examples +} + +contains(QWT_CONFIG, QwtPlayground ) { + SUBDIRS += playground +} + +qwtspec.files = qwtconfig.pri qwtfunctions.pri qwt.prf +qwtspec.path = $${QWT_INSTALL_FEATURES} + +INSTALLS += qwtspec + diff --git a/qwt/qwtbuild.pri b/qwt/qwtbuild.pri new file mode 100644 index 000000000..a0b865d22 --- /dev/null +++ b/qwt/qwtbuild.pri @@ -0,0 +1,86 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +###################################################################### +# qmake internal options +###################################################################### + +CONFIG += qt +CONFIG += warn_on +CONFIG += no_keywords +CONFIG += silent + +###################################################################### +# release/debug mode +###################################################################### + +win32 { + # On Windows you can't mix release and debug libraries. + # The designer is built in release mode. If you like to use it + # you need a release version. For your own application development you + # might need a debug version. + # Enable debug_and_release + build_all if you want to build both. + + CONFIG += debug_and_release + CONFIG += build_all +} +else { + + CONFIG += debug + + VER_MAJ = $${QWT_VER_MAJ} + VER_MIN = $${QWT_VER_MIN} + VER_PAT = $${QWT_VER_PAT} + VERSION = $${QWT_VERSION} +} + +linux-g++ | linux-g++-64 { + #CONFIG += separate_debug_info + #QMAKE_CXXFLAGS *= -Wfloat-equal + #QMAKE_CXXFLAGS *= -Wshadow + #QMAKE_CXXFLAGS *= -Wpointer-arith + #QMAKE_CXXFLAGS *= -Wconversion + #QMAKE_CXXFLAGS *= -Wsign-compare + #QMAKE_CXXFLAGS *= -Wsign-conversion + #QMAKE_CXXFLAGS *= -Wlogical-op + #QMAKE_CXXFLAGS *= -Werror=format-security + #QMAKE_CXXFLAGS *= -std=c++11 + + # when using the gold linker ( Qt < 4.8 ) - might be + # necessary on non linux systems too + #QMAKE_LFLAGS += -lrt +} + +###################################################################### +# paths for building qwt +###################################################################### + +MOC_DIR = moc +RCC_DIR = resources + +!debug_and_release { + + # in case of debug_and_release object files + # are built in the release and debug subdirectories + OBJECTS_DIR = obj +} + +unix { + + exists( $${QMAKE_LIBDIR_QT}/libqwt.* ) { + + # On some Linux distributions the Qwt libraries are installed + # in the same directory as the Qt libraries. Unfortunately + # qmake always adds QMAKE_LIBDIR_QT at the beginning of the + # linker path, so that the installed libraries will be + # used instead of the local ones. + + error( "local build will conflict with $${QMAKE_LIBDIR_QT}/libqwt.*" ) + } +} diff --git a/qwt/qwtconfig.pri b/qwt/qwtconfig.pri new file mode 100644 index 000000000..40d25659c --- /dev/null +++ b/qwt/qwtconfig.pri @@ -0,0 +1,163 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +QWT_VER_MAJ = 6 +QWT_VER_MIN = 1 +QWT_VER_PAT = 1 +QWT_VERSION = $${QWT_VER_MAJ}.$${QWT_VER_MIN}.$${QWT_VER_PAT} + +###################################################################### +# Install paths +###################################################################### + +QWT_INSTALL_PREFIX = $$[QT_INSTALL_PREFIX] + +unix { + QWT_INSTALL_PREFIX = /usr/local/qwt-$$QWT_VERSION-svn +} + +win32 { + QWT_INSTALL_PREFIX = C:/Qwt-$$QWT_VERSION-svn +} + +QWT_INSTALL_DOCS = $${QWT_INSTALL_PREFIX}/doc +QWT_INSTALL_HEADERS = $${QWT_INSTALL_PREFIX}/include +QWT_INSTALL_LIBS = $${QWT_INSTALL_PREFIX}/lib + +###################################################################### +# Designer plugin +# creator/designer load designer plugins from certain default +# directories ( f.e the path below QT_INSTALL_PREFIX ) and the +# directories listed in the QT_PLUGIN_PATH environment variable. +# When using the path below QWT_INSTALL_PREFIX you need to +# add $${QWT_INSTALL_PREFIX}/plugins to QT_PLUGIN_PATH in the +# runtime environment of designer/creator. +###################################################################### + +QWT_INSTALL_PLUGINS = $${QWT_INSTALL_PREFIX}/plugins/designer + +# linux distributors often organize the Qt installation +# their way and QT_INSTALL_PREFIX doesn't offer a good +# path. Also QT_INSTALL_PREFIX is only one of the default +# search paths of the designer - not the Qt creator + +#QWT_INSTALL_PLUGINS = $$[QT_INSTALL_PREFIX]/plugins/designer + +###################################################################### +# Features +# When building a Qwt application with qmake you might want to load +# the compiler/linker flags, that are required to build a Qwt application +# from qwt.prf. Therefore all you need to do is to add "CONFIG += qwt" +# to your project file and take care, that qwt.prf can be found by qmake. +# ( see http://doc.trolltech.com/4.7/qmake-advanced-usage.html#adding-new-configuration-features ) +# I recommend not to install the Qwt features together with the +# Qt features, because you will have to reinstall the Qwt features, +# with every Qt upgrade. +###################################################################### + +QWT_INSTALL_FEATURES = $${QWT_INSTALL_PREFIX}/features +# QWT_INSTALL_FEATURES = $$[QT_INSTALL_PREFIX]/features + +###################################################################### +# Build the static/shared libraries. +# If QwtDll is enabled, a shared library is built, otherwise +# it will be a static library. +###################################################################### + +QWT_CONFIG += QwtDll + +###################################################################### +# QwtPlot enables all classes, that are needed to use the QwtPlot +# widget. +###################################################################### + +QWT_CONFIG += QwtPlot + +###################################################################### +# QwtWidgets enables all classes, that are needed to use the all other +# widgets (sliders, dials, ...), beside QwtPlot. +###################################################################### + +QWT_CONFIG += QwtWidgets + +###################################################################### +# If you want to display svg images on the plot canvas, or +# export a plot to a SVG document +###################################################################### + +QWT_CONFIG += QwtSvg + +###################################################################### +# If you want to use a OpenGL plot canvas +###################################################################### + +QWT_CONFIG += QwtOpenGL + +###################################################################### +# You can use the MathML renderer of the Qt solutions package to +# enable MathML support in Qwt. Because of license implications +# the ( modified ) code of the MML Widget solution is included and +# linked together with the QwtMathMLTextEngine into an own library. +# To use it you will have to add "CONFIG += qwtmathml" +# to your qmake project file. +###################################################################### + +QWT_CONFIG += QwtMathML + +###################################################################### +# If you want to build the Qwt designer plugin, +# enable the line below. +# Otherwise you have to build it from the designer directory. +###################################################################### + +QWT_CONFIG += QwtDesigner + +###################################################################### +# Compile all Qwt classes into the designer plugin instead +# of linking it against the shared Qwt library. Has no effect +# when QwtDesigner or QwtDll are not both enabled. +# +# On systems where rpath is supported ( all Unixoids ) the +# location of the installed Qwt library is compiled into the plugin, +# but on Windows it might be easier to have a self contained +# plugin to avoid any hassle with configuring the runtime +# environment of the designer/creator. +###################################################################### + +win32 { + QWT_CONFIG += QwtDesignerSelfContained +} + +###################################################################### +# If you want to auto build the examples, enable the line below +# Otherwise you have to build them from the examples directory. +###################################################################### + +QWT_CONFIG += QwtExamples + +###################################################################### +# The playground is primarily intended for the Qwt development +# to explore and test new features. Nevertheless you might find +# ideas or code snippets that help for application development +# If you want to auto build the applications in playground, enable +# the line below. +# Otherwise you have to build them from the playground directory. +###################################################################### + +QWT_CONFIG += QwtPlayground + +###################################################################### +# When Qt has been built as framework qmake wants +# to link frameworks instead of regular libs +###################################################################### + +macx:!static:CONFIG(qt_framework, qt_framework|qt_no_framework) { + + QWT_CONFIG += QwtFramework +} diff --git a/qwt/qwtfunctions.pri b/qwt/qwtfunctions.pri new file mode 100644 index 000000000..022e28010 --- /dev/null +++ b/qwt/qwtfunctions.pri @@ -0,0 +1,71 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +# Copied and modified from qt_functions.prf + +defineReplace(qwtLibraryTarget) { + + unset(LIBRARY_NAME) + LIBRARY_NAME = $$1 + + mac:contains(QWT_CONFIG, QwtFramework) { + + QMAKE_FRAMEWORK_BUNDLE_NAME = $$LIBRARY_NAME + export(QMAKE_FRAMEWORK_BUNDLE_NAME) + } + + contains(TEMPLATE, .*lib):CONFIG(debug, debug|release) { + + !debug_and_release|build_pass { + + mac:RET = $$member(LIBRARY_NAME, 0)_debug + win32:RET = $$member(LIBRARY_NAME, 0)d + } + } + + isEmpty(RET):RET = $$LIBRARY_NAME + return($$RET) +} + +defineTest(qwtAddLibrary) { + + LIB_NAME = $$1 + + unset(LINKAGE) + + mac:contains(QWT_CONFIG, QwtFramework) { + + LINKAGE = -framework $${LIB_NAME} + } + + isEmpty(LINKAGE) { + + if(!debug_and_release|build_pass):CONFIG(debug, debug|release) { + + mac:LINKAGE = -l$${LIB_NAME}_debug + win32:LINKAGE = -l$${LIB_NAME}d + } + } + + isEmpty(LINKAGE) { + + LINKAGE = -l$${LIB_NAME} + } + + !isEmpty(QMAKE_LSB) { + + QMAKE_LFLAGS *= --lsb-shared-libs=$${LIB_NAME} + } + + LIBS += $$LINKAGE + export(LIBS) + export(QMAKE_LFLAGS) + + return(true) +} diff --git a/qwt/src/qwt.h b/qwt/src/qwt.h new file mode 100644 index 000000000..37494933e --- /dev/null +++ b/qwt/src/qwt.h @@ -0,0 +1,22 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_H +#define QWT_H + +#include "qwt_global.h" + +/*! + Some constants for use within Qwt. +*/ +namespace Qwt +{ +}; + +#endif diff --git a/qwt/src/qwt_abstract_legend.cpp b/qwt/src/qwt_abstract_legend.cpp new file mode 100644 index 000000000..4ecaac61d --- /dev/null +++ b/qwt/src/qwt_abstract_legend.cpp @@ -0,0 +1,38 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_abstract_legend.h" + +/*! + Constructor + + \param parent Parent widget +*/ +QwtAbstractLegend::QwtAbstractLegend( QWidget *parent ): + QFrame( parent ) +{ +} + +//! Destructor +QwtAbstractLegend::~QwtAbstractLegend() +{ +} + +/*! + Return the extent, that is needed for elements to scroll + the legend ( usually scrollbars ), + + \param orientation Orientation + \return Extent of the corresponding scroll element +*/ +int QwtAbstractLegend::scrollExtent( Qt::Orientation orientation ) const +{ + Q_UNUSED( orientation ); + return 0; +} diff --git a/qwt/src/qwt_abstract_legend.h b/qwt/src/qwt_abstract_legend.h new file mode 100644 index 000000000..18bd3f4b9 --- /dev/null +++ b/qwt/src/qwt_abstract_legend.h @@ -0,0 +1,71 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ABSTRACT_LEGEND_H +#define QWT_ABSTRACT_LEGEND_H + +#include "qwt_global.h" +#include "qwt_legend_data.h" +#include +#include + +class QVariant; + +/*! + \brief Abstract base class for legend widgets + + Legends, that need to be under control of the QwtPlot layout system + need to be derived from QwtAbstractLegend. + + \note Other type of legends can be implemented by connecting to + the QwtPlot::legendDataChanged() signal. But as these legends + are unknown to the plot layout system the layout code + ( on screen and for QwtPlotRenderer ) need to be organized + in application code. + + \sa QwtLegend + */ +class QWT_EXPORT QwtAbstractLegend : public QFrame +{ + Q_OBJECT + +public: + explicit QwtAbstractLegend( QWidget *parent = NULL ); + virtual ~QwtAbstractLegend(); + + /*! + Render the legend into a given rectangle. + + \param painter Painter + \param rect Bounding rectangle + \param fillBackground When true, fill rect with the widget background + + \sa renderLegend() is used by QwtPlotRenderer + */ + virtual void renderLegend( QPainter *painter, + const QRectF &rect, bool fillBackground ) const = 0; + + //! \return True, when no plot item is inserted + virtual bool isEmpty() const = 0; + + virtual int scrollExtent( Qt::Orientation ) const; + +public Q_SLOTS: + + /*! + \brief Update the entries for a plot item + + \param itemInfo Info about an item + \param data List of legend entry attributes for the item + */ + virtual void updateLegend( const QVariant &itemInfo, + const QList &data ) = 0; +}; + +#endif diff --git a/qwt/src/qwt_abstract_scale.cpp b/qwt/src/qwt_abstract_scale.cpp new file mode 100644 index 000000000..3018b854e --- /dev/null +++ b/qwt/src/qwt_abstract_scale.cpp @@ -0,0 +1,449 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_abstract_scale.h" +#include "qwt_scale_engine.h" +#include "qwt_scale_draw.h" +#include "qwt_scale_div.h" +#include "qwt_scale_map.h" +#include "qwt_interval.h" + +class QwtAbstractScale::PrivateData +{ +public: + PrivateData(): + maxMajor( 5 ), + maxMinor( 3 ), + stepSize( 0.0 ) + { + scaleEngine = new QwtLinearScaleEngine(); + scaleDraw = new QwtScaleDraw(); + } + + ~PrivateData() + { + delete scaleEngine; + delete scaleDraw; + } + + QwtScaleEngine *scaleEngine; + QwtAbstractScaleDraw *scaleDraw; + + int maxMajor; + int maxMinor; + double stepSize; +}; + +/*! + Constructor + + \param parent Parent widget + + Creates a default QwtScaleDraw and a QwtLinearScaleEngine. + The initial scale boundaries are set to [ 0.0, 100.0 ] + + The scaleStepSize() is initialized to 0.0, scaleMaxMajor() to 5 + and scaleMaxMajor to 3. +*/ + +QwtAbstractScale::QwtAbstractScale( QWidget *parent ): + QWidget( parent ) +{ + d_data = new PrivateData; + rescale( 0.0, 100.0, d_data->stepSize ); +} + +//! Destructor +QwtAbstractScale::~QwtAbstractScale() +{ + delete d_data; +} + +/*! + Set the lower bound of the scale + + \param value Lower bound + + \sa lowerBound(), setScale(), setUpperBound() + \note For inverted scales the lower bound + is greater than the upper bound +*/ +void QwtAbstractScale::setLowerBound( double value ) +{ + setScale( value, upperBound() ); +} + +/*! + \return Lower bound of the scale + \sa setLowerBound(), setScale(), upperBound() +*/ +double QwtAbstractScale::lowerBound() const +{ + return d_data->scaleDraw->scaleDiv().lowerBound(); +} + +/*! + Set the upper bound of the scale + + \param value Upper bound + + \sa upperBound(), setScale(), setLowerBound() + \note For inverted scales the lower bound + is greater than the upper bound +*/ +void QwtAbstractScale::setUpperBound( double value ) +{ + setScale( lowerBound(), value ); +} + +/*! + \return Upper bound of the scale + \sa setUpperBound(), setScale(), lowerBound() +*/ +double QwtAbstractScale::upperBound() const +{ + return d_data->scaleDraw->scaleDiv().upperBound(); +} + +/*! + \brief Specify a scale. + + Define a scale by an interval + + The ticks are calculated using scaleMaxMinor(), + scaleMaxMajor() and scaleStepSize(). + + \param lowerBound lower limit of the scale interval + \param upperBound upper limit of the scale interval + + \note For inverted scales the lower bound + is greater than the upper bound +*/ +void QwtAbstractScale::setScale( double lowerBound, double upperBound ) +{ + rescale( lowerBound, upperBound, d_data->stepSize ); +} + +/*! + \brief Specify a scale. + + Define a scale by an interval + + The ticks are calculated using scaleMaxMinor(), + scaleMaxMajor() and scaleStepSize(). + + \param interval Interval +*/ +void QwtAbstractScale::setScale( const QwtInterval &interval ) +{ + setScale( interval.minValue(), interval.maxValue() ); +} + +/*! + \brief Specify a scale. + + scaleMaxMinor(), scaleMaxMajor() and scaleStepSize() and have no effect. + + \param scaleDiv Scale division + \sa setAutoScale() +*/ +void QwtAbstractScale::setScale( const QwtScaleDiv &scaleDiv ) +{ + if ( scaleDiv != d_data->scaleDraw->scaleDiv() ) + { +#if 1 + if ( d_data->scaleEngine ) + { + d_data->scaleDraw->setTransformation( + d_data->scaleEngine->transformation() ); + } +#endif + + d_data->scaleDraw->setScaleDiv( scaleDiv ); + + scaleChange(); + } +} + +/*! + \brief Set the maximum number of major tick intervals. + + The scale's major ticks are calculated automatically such that + the number of major intervals does not exceed ticks. + + The default value is 5. + + \param ticks Maximal number of major ticks. + + \sa scaleMaxMajor(), setScaleMaxMinor(), + setScaleStepSize(), QwtScaleEngine::divideInterval() +*/ +void QwtAbstractScale::setScaleMaxMajor( int ticks ) +{ + if ( ticks != d_data->maxMajor ) + { + d_data->maxMajor = ticks; + updateScaleDraw(); + } +} + +/*! + \return Maximal number of major tick intervals + \sa setScaleMaxMajor(), scaleMaxMinor() +*/ +int QwtAbstractScale::scaleMaxMajor() const +{ + return d_data->maxMajor; +} + +/*! + \brief Set the maximum number of minor tick intervals + + The scale's minor ticks are calculated automatically such that + the number of minor intervals does not exceed ticks. + The default value is 3. + + \param ticks Maximal number of minor ticks. + + \sa scaleMaxMajor(), setScaleMaxMinor(), + setScaleStepSize(), QwtScaleEngine::divideInterval() +*/ +void QwtAbstractScale::setScaleMaxMinor( int ticks ) +{ + if ( ticks != d_data->maxMinor ) + { + d_data->maxMinor = ticks; + updateScaleDraw(); + } +} + +/*! + \return Maximal number of minor tick intervals + \sa setScaleMaxMinor(), scaleMaxMajor() +*/ +int QwtAbstractScale::scaleMaxMinor() const +{ + return d_data->maxMinor; +} + +/*! + \brief Set the step size used for calculating a scale division + + The step size is hint for calculating the intervals for + the major ticks of the scale. A value of 0.0 is interpreted + as no hint. + + \param stepSize Hint for the step size of the scale + + \sa scaleStepSize(), QwtScaleEngine::divideScale() + + \note Position and distance between the major ticks also + depends on scaleMaxMajor(). +*/ +void QwtAbstractScale::setScaleStepSize( double stepSize ) +{ + if ( stepSize != d_data->stepSize ) + { + d_data->stepSize = stepSize; + updateScaleDraw(); + } +} + +/*! + \return Hint for the step size of the scale + \sa setScaleStepSize(), QwtScaleEngine::divideScale() +*/ +double QwtAbstractScale::scaleStepSize() const +{ + return d_data->stepSize; +} + +/*! + \brief Set a scale draw + + scaleDraw has to be created with new and will be deleted in + the destructor or the next call of setAbstractScaleDraw(). + + \sa abstractScaleDraw() +*/ +void QwtAbstractScale::setAbstractScaleDraw( QwtAbstractScaleDraw *scaleDraw ) +{ + if ( scaleDraw == NULL || scaleDraw == d_data->scaleDraw ) + return; + + if ( d_data->scaleDraw != NULL ) + scaleDraw->setScaleDiv( d_data->scaleDraw->scaleDiv() ); + + delete d_data->scaleDraw; + d_data->scaleDraw = scaleDraw; +} + +/*! + \return Scale draw + \sa setAbstractScaleDraw() +*/ +QwtAbstractScaleDraw *QwtAbstractScale::abstractScaleDraw() +{ + return d_data->scaleDraw; +} + +/*! + \return Scale draw + \sa setAbstractScaleDraw() +*/ +const QwtAbstractScaleDraw *QwtAbstractScale::abstractScaleDraw() const +{ + return d_data->scaleDraw; +} + +/*! + \brief Set a scale engine + + The scale engine is responsible for calculating the scale division + and provides a transformation between scale and widget coordinates. + + scaleEngine has to be created with new and will be deleted in + the destructor or the next call of setScaleEngine. +*/ +void QwtAbstractScale::setScaleEngine( QwtScaleEngine *scaleEngine ) +{ + if ( scaleEngine != NULL && scaleEngine != d_data->scaleEngine ) + { + delete d_data->scaleEngine; + d_data->scaleEngine = scaleEngine; + } +} + +/*! + \return Scale engine + \sa setScaleEngine() +*/ +const QwtScaleEngine *QwtAbstractScale::scaleEngine() const +{ + return d_data->scaleEngine; +} + +/*! + \return Scale engine + \sa setScaleEngine() +*/ +QwtScaleEngine *QwtAbstractScale::scaleEngine() +{ + return d_data->scaleEngine; +} + +/*! + \return Scale boundaries and positions of the ticks + + The scale division might have been assigned explicitly + or calculated implicitly by rescale(). + */ +const QwtScaleDiv &QwtAbstractScale::scaleDiv() const +{ + return d_data->scaleDraw->scaleDiv(); +} + +/*! + \return Map to translate between scale and widget coordinates + */ +const QwtScaleMap &QwtAbstractScale::scaleMap() const +{ + return d_data->scaleDraw->scaleMap(); +} + +/*! + Translate a scale value into a widget coordinate + + \param value Scale value + \return Corresponding widget coordinate for value + \sa scaleMap(), invTransform() + */ +int QwtAbstractScale::transform( double value ) const +{ + return qRound( d_data->scaleDraw->scaleMap().transform( value ) ); +} + +/*! + Translate a widget coordinate into a scale value + + \param value Widget coordinate + \return Corresponding scale coordinate for value + \sa scaleMap(), transform() + */ +double QwtAbstractScale::invTransform( int value ) const +{ + return d_data->scaleDraw->scaleMap().invTransform( value ); +} + +/*! + \return True, when the scale is increasing in opposite direction + to the widget coordinates + */ +bool QwtAbstractScale::isInverted() const +{ + return d_data->scaleDraw->scaleMap().isInverting(); +} + +/*! + \return The boundary with the smaller value + \sa maximum(), lowerBound(), upperBound() + */ +double QwtAbstractScale::minimum() const +{ + return qMin( d_data->scaleDraw->scaleDiv().lowerBound(), + d_data->scaleDraw->scaleDiv().upperBound() ); +} + +/*! + \return The boundary with the larger value + \sa minimum(), lowerBound(), upperBound() + */ +double QwtAbstractScale::maximum() const +{ + return qMax( d_data->scaleDraw->scaleDiv().lowerBound(), + d_data->scaleDraw->scaleDiv().upperBound() ); +} + +//! Notify changed scale +void QwtAbstractScale::scaleChange() +{ +} + +/*! + Recalculate the scale division and update the scale. + + \param lowerBound Lower limit of the scale interval + \param upperBound Upper limit of the scale interval + \param stepSize Major step size + + \sa scaleChange() +*/ +void QwtAbstractScale::rescale( + double lowerBound, double upperBound, double stepSize ) +{ + const QwtScaleDiv scaleDiv = d_data->scaleEngine->divideScale( + lowerBound, upperBound, d_data->maxMajor, d_data->maxMinor, stepSize ); + + if ( scaleDiv != d_data->scaleDraw->scaleDiv() ) + { +#if 1 + d_data->scaleDraw->setTransformation( + d_data->scaleEngine->transformation() ); +#endif + + d_data->scaleDraw->setScaleDiv( scaleDiv ); + scaleChange(); + } +} + +void QwtAbstractScale::updateScaleDraw() +{ + rescale( d_data->scaleDraw->scaleDiv().lowerBound(), + d_data->scaleDraw->scaleDiv().upperBound(), d_data->stepSize ); +} diff --git a/qwt/src/qwt_abstract_scale.h b/qwt/src/qwt_abstract_scale.h new file mode 100644 index 000000000..4ed6616aa --- /dev/null +++ b/qwt/src/qwt_abstract_scale.h @@ -0,0 +1,105 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ABSTRACT_SCALE_H +#define QWT_ABSTRACT_SCALE_H + +#include "qwt_global.h" +#include + +class QwtScaleEngine; +class QwtAbstractScaleDraw; +class QwtScaleDiv; +class QwtScaleMap; +class QwtInterval; + +/*! + \brief An abstract base class for widgets having a scale + + The scale of an QwtAbstractScale is determined by a QwtScaleDiv + definition, that contains the boundaries and the ticks of the scale. + The scale is painted using a QwtScaleDraw object. + + The scale division might be assigned explicitly - but usually + it is calculated from the boundaries using a QwtScaleEngine. + + The scale engine also decides the type of transformation of the scale + ( linear, logarithmic ... ). +*/ + +class QWT_EXPORT QwtAbstractScale: public QWidget +{ + Q_OBJECT + + Q_PROPERTY( double lowerBound READ lowerBound WRITE setLowerBound ) + Q_PROPERTY( double upperBound READ upperBound WRITE setUpperBound ) + + Q_PROPERTY( int scaleMaxMajor READ scaleMaxMajor WRITE setScaleMaxMajor ) + Q_PROPERTY( int scaleMaxMinor READ scaleMaxMinor WRITE setScaleMaxMinor ) + + Q_PROPERTY( double scaleStepSize READ scaleStepSize WRITE setScaleStepSize ) + +public: + QwtAbstractScale( QWidget *parent = NULL ); + virtual ~QwtAbstractScale(); + + void setScale( double lowerBound, double upperBound ); + void setScale( const QwtInterval & ); + void setScale( const QwtScaleDiv & ); + + const QwtScaleDiv& scaleDiv() const; + + void setLowerBound( double value ); + double lowerBound() const; + + void setUpperBound( double value ); + double upperBound() const; + + void setScaleStepSize( double stepSize ); + double scaleStepSize() const; + + void setScaleMaxMajor( int ticks ); + int scaleMaxMinor() const; + + void setScaleMaxMinor( int ticks ); + int scaleMaxMajor() const; + + void setScaleEngine( QwtScaleEngine * ); + const QwtScaleEngine *scaleEngine() const; + QwtScaleEngine *scaleEngine(); + + int transform( double ) const; + double invTransform( int ) const; + + bool isInverted() const; + + double minimum() const; + double maximum() const; + + const QwtScaleMap &scaleMap() const; + +protected: + void rescale( double lowerBound, + double upperBound, double stepSize ); + + void setAbstractScaleDraw( QwtAbstractScaleDraw * ); + + const QwtAbstractScaleDraw *abstractScaleDraw() const; + QwtAbstractScaleDraw *abstractScaleDraw(); + + virtual void scaleChange(); + +private: + void updateScaleDraw(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_abstract_scale_draw.cpp b/qwt/src/qwt_abstract_scale_draw.cpp new file mode 100644 index 000000000..201173303 --- /dev/null +++ b/qwt/src/qwt_abstract_scale_draw.cpp @@ -0,0 +1,416 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_abstract_scale_draw.h" +#include "qwt_math.h" +#include "qwt_text.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include +#include +#include +#include + +class QwtAbstractScaleDraw::PrivateData +{ +public: + PrivateData(): + spacing( 4.0 ), + penWidth( 0 ), + minExtent( 0.0 ) + { + components = QwtAbstractScaleDraw::Backbone + | QwtAbstractScaleDraw::Ticks + | QwtAbstractScaleDraw::Labels; + + tickLength[QwtScaleDiv::MinorTick] = 4.0; + tickLength[QwtScaleDiv::MediumTick] = 6.0; + tickLength[QwtScaleDiv::MajorTick] = 8.0; + } + + ScaleComponents components; + + QwtScaleMap map; + QwtScaleDiv scaleDiv; + + double spacing; + double tickLength[QwtScaleDiv::NTickTypes]; + int penWidth; + + double minExtent; + + QMap labelCache; +}; + +/*! + \brief Constructor + + The range of the scale is initialized to [0, 100], + The spacing (distance between ticks and labels) is + set to 4, the tick lengths are set to 4,6 and 8 pixels +*/ +QwtAbstractScaleDraw::QwtAbstractScaleDraw() +{ + d_data = new QwtAbstractScaleDraw::PrivateData; +} + +//! Destructor +QwtAbstractScaleDraw::~QwtAbstractScaleDraw() +{ + delete d_data; +} + +/*! + En/Disable a component of the scale + + \param component Scale component + \param enable On/Off + + \sa hasComponent() +*/ +void QwtAbstractScaleDraw::enableComponent( + ScaleComponent component, bool enable ) +{ + if ( enable ) + d_data->components |= component; + else + d_data->components &= ~component; +} + +/*! + Check if a component is enabled + + \param component Component type + \return true, when component is enabled + \sa enableComponent() +*/ +bool QwtAbstractScaleDraw::hasComponent( ScaleComponent component ) const +{ + return ( d_data->components & component ); +} + +/*! + Change the scale division + \param scaleDiv New scale division +*/ +void QwtAbstractScaleDraw::setScaleDiv( const QwtScaleDiv &scaleDiv ) +{ + d_data->scaleDiv = scaleDiv; + d_data->map.setScaleInterval( scaleDiv.lowerBound(), scaleDiv.upperBound() ); + d_data->labelCache.clear(); +} + +/*! + Change the transformation of the scale + \param transformation New scale transformation +*/ +void QwtAbstractScaleDraw::setTransformation( + QwtTransform *transformation ) +{ + d_data->map.setTransformation( transformation ); +} + +//! \return Map how to translate between scale and pixel values +const QwtScaleMap &QwtAbstractScaleDraw::scaleMap() const +{ + return d_data->map; +} + +//! \return Map how to translate between scale and pixel values +QwtScaleMap &QwtAbstractScaleDraw::scaleMap() +{ + return d_data->map; +} + +//! \return scale division +const QwtScaleDiv& QwtAbstractScaleDraw::scaleDiv() const +{ + return d_data->scaleDiv; +} + +/*! + \brief Specify the width of the scale pen + \param width Pen width + \sa penWidth() +*/ +void QwtAbstractScaleDraw::setPenWidth( int width ) +{ + if ( width < 0 ) + width = 0; + + if ( width != d_data->penWidth ) + d_data->penWidth = width; +} + +/*! + \return Scale pen width + \sa setPenWidth() +*/ +int QwtAbstractScaleDraw::penWidth() const +{ + return d_data->penWidth; +} + +/*! + \brief Draw the scale + + \param painter The painter + + \param palette Palette, text color is used for the labels, + foreground color for ticks and backbone +*/ +void QwtAbstractScaleDraw::draw( QPainter *painter, + const QPalette& palette ) const +{ + painter->save(); + + QPen pen = painter->pen(); + pen.setWidth( d_data->penWidth ); + pen.setCosmetic( false ); + painter->setPen( pen ); + + if ( hasComponent( QwtAbstractScaleDraw::Labels ) ) + { + painter->save(); + painter->setPen( palette.color( QPalette::Text ) ); // ignore pen style + + const QList &majorTicks = + d_data->scaleDiv.ticks( QwtScaleDiv::MajorTick ); + + for ( int i = 0; i < majorTicks.count(); i++ ) + { + const double v = majorTicks[i]; + if ( d_data->scaleDiv.contains( v ) ) + drawLabel( painter, v ); + } + + painter->restore(); + } + + if ( hasComponent( QwtAbstractScaleDraw::Ticks ) ) + { + painter->save(); + + QPen pen = painter->pen(); + pen.setColor( palette.color( QPalette::WindowText ) ); + pen.setCapStyle( Qt::FlatCap ); + + painter->setPen( pen ); + + for ( int tickType = QwtScaleDiv::MinorTick; + tickType < QwtScaleDiv::NTickTypes; tickType++ ) + { + const QList &ticks = d_data->scaleDiv.ticks( tickType ); + for ( int i = 0; i < ticks.count(); i++ ) + { + const double v = ticks[i]; + if ( d_data->scaleDiv.contains( v ) ) + drawTick( painter, v, d_data->tickLength[tickType] ); + } + } + + painter->restore(); + } + + if ( hasComponent( QwtAbstractScaleDraw::Backbone ) ) + { + painter->save(); + + QPen pen = painter->pen(); + pen.setColor( palette.color( QPalette::WindowText ) ); + pen.setCapStyle( Qt::FlatCap ); + + painter->setPen( pen ); + + drawBackbone( painter ); + + painter->restore(); + } + + painter->restore(); +} + +/*! + \brief Set the spacing between tick and labels + + The spacing is the distance between ticks and labels. + The default spacing is 4 pixels. + + \param spacing Spacing + + \sa spacing() +*/ +void QwtAbstractScaleDraw::setSpacing( double spacing ) +{ + if ( spacing < 0 ) + spacing = 0; + + d_data->spacing = spacing; +} + +/*! + \brief Get the spacing + + The spacing is the distance between ticks and labels. + The default spacing is 4 pixels. + + \return Spacing + \sa setSpacing() +*/ +double QwtAbstractScaleDraw::spacing() const +{ + return d_data->spacing; +} + +/*! + \brief Set a minimum for the extent + + The extent is calculated from the components of the + scale draw. In situations, where the labels are + changing and the layout depends on the extent (f.e scrolling + a scale), setting an upper limit as minimum extent will + avoid jumps of the layout. + + \param minExtent Minimum extent + + \sa extent(), minimumExtent() +*/ +void QwtAbstractScaleDraw::setMinimumExtent( double minExtent ) +{ + if ( minExtent < 0.0 ) + minExtent = 0.0; + + d_data->minExtent = minExtent; +} + +/*! + Get the minimum extent + \return Minimum extent + \sa extent(), setMinimumExtent() +*/ +double QwtAbstractScaleDraw::minimumExtent() const +{ + return d_data->minExtent; +} + +/*! + Set the length of the ticks + + \param tickType Tick type + \param length New length + + \warning the length is limited to [0..1000] +*/ +void QwtAbstractScaleDraw::setTickLength( + QwtScaleDiv::TickType tickType, double length ) +{ + if ( tickType < QwtScaleDiv::MinorTick || + tickType > QwtScaleDiv::MajorTick ) + { + return; + } + + if ( length < 0.0 ) + length = 0.0; + + const double maxTickLen = 1000.0; + if ( length > maxTickLen ) + length = maxTickLen; + + d_data->tickLength[tickType] = length; +} + +/*! + \return Length of the ticks + \sa setTickLength(), maxTickLength() +*/ +double QwtAbstractScaleDraw::tickLength( QwtScaleDiv::TickType tickType ) const +{ + if ( tickType < QwtScaleDiv::MinorTick || + tickType > QwtScaleDiv::MajorTick ) + { + return 0; + } + + return d_data->tickLength[tickType]; +} + +/*! + \return Length of the longest tick + + Useful for layout calculations + \sa tickLength(), setTickLength() +*/ +double QwtAbstractScaleDraw::maxTickLength() const +{ + double length = 0.0; + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + length = qMax( length, d_data->tickLength[i] ); + + return length; +} + +/*! + \brief Convert a value into its representing label + + The value is converted to a plain text using + QLocale().toString(value). + This method is often overloaded by applications to have individual + labels. + + \param value Value + \return Label string. +*/ +QwtText QwtAbstractScaleDraw::label( double value ) const +{ + return QLocale().toString( value ); +} + +/*! + \brief Convert a value into its representing label and cache it. + + The conversion between value and label is called very often + in the layout and painting code. Unfortunately the + calculation of the label sizes might be slow (really slow + for rich text in Qt4), so it's necessary to cache the labels. + + \param font Font + \param value Value + + \return Tick label +*/ +const QwtText &QwtAbstractScaleDraw::tickLabel( + const QFont &font, double value ) const +{ + QMap::const_iterator it = d_data->labelCache.find( value ); + if ( it == d_data->labelCache.end() ) + { + QwtText lbl = label( value ); + lbl.setRenderFlags( 0 ); + lbl.setLayoutAttribute( QwtText::MinimumLayout ); + + ( void )lbl.textSize( font ); // initialize the internal cache + + it = d_data->labelCache.insert( value, lbl ); + } + + return ( *it ); +} + +/*! + Invalidate the cache used by tickLabel() + + The cache is invalidated, when a new QwtScaleDiv is set. If + the labels need to be changed. while the same QwtScaleDiv is set, + invalidateCache() needs to be called manually. +*/ +void QwtAbstractScaleDraw::invalidateCache() +{ + d_data->labelCache.clear(); +} diff --git a/qwt/src/qwt_abstract_scale_draw.h b/qwt/src/qwt_abstract_scale_draw.h new file mode 100644 index 000000000..d0f1ec3c3 --- /dev/null +++ b/qwt/src/qwt_abstract_scale_draw.h @@ -0,0 +1,141 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ABSTRACT_SCALE_DRAW_H +#define QWT_ABSTRACT_SCALE_DRAW_H + +#include "qwt_global.h" +#include "qwt_scale_div.h" +#include "qwt_text.h" + +class QPalette; +class QPainter; +class QFont; +class QwtTransform; +class QwtScaleMap; + +/*! + \brief A abstract base class for drawing scales + + QwtAbstractScaleDraw can be used to draw linear or logarithmic scales. + + After a scale division has been specified as a QwtScaleDiv object + using setScaleDiv(), the scale can be drawn with the draw() member. +*/ +class QWT_EXPORT QwtAbstractScaleDraw +{ +public: + + /*! + Components of a scale + \sa enableComponent(), hasComponent + */ + enum ScaleComponent + { + //! Backbone = the line where the ticks are located + Backbone = 0x01, + + //! Ticks + Ticks = 0x02, + + //! Labels + Labels = 0x04 + }; + + //! Scale components + typedef QFlags ScaleComponents; + + QwtAbstractScaleDraw(); + virtual ~QwtAbstractScaleDraw(); + + void setScaleDiv( const QwtScaleDiv &s ); + const QwtScaleDiv& scaleDiv() const; + + void setTransformation( QwtTransform * ); + const QwtScaleMap &scaleMap() const; + QwtScaleMap &scaleMap(); + + void enableComponent( ScaleComponent, bool enable = true ); + bool hasComponent( ScaleComponent ) const; + + void setTickLength( QwtScaleDiv::TickType, double length ); + double tickLength( QwtScaleDiv::TickType ) const; + double maxTickLength() const; + + void setSpacing( double margin ); + double spacing() const; + + void setPenWidth( int width ); + int penWidth() const; + + virtual void draw( QPainter *, const QPalette & ) const; + + virtual QwtText label( double ) const; + + /*! + Calculate the extent + + The extent is the distance from the baseline to the outermost + pixel of the scale draw in opposite to its orientation. + It is at least minimumExtent() pixels. + + \param font Font used for drawing the tick labels + \return Number of pixels + + \sa setMinimumExtent(), minimumExtent() + */ + virtual double extent( const QFont &font ) const = 0; + + void setMinimumExtent( double ); + double minimumExtent() const; + +protected: + /*! + Draw a tick + + \param painter Painter + \param value Value of the tick + \param len Length of the tick + + \sa drawBackbone(), drawLabel() + */ + virtual void drawTick( QPainter *painter, double value, double len ) const = 0; + + /*! + Draws the baseline of the scale + \param painter Painter + + \sa drawTick(), drawLabel() + */ + virtual void drawBackbone( QPainter *painter ) const = 0; + + /*! + Draws the label for a major scale tick + + \param painter Painter + \param value Value + + \sa drawTick(), drawBackbone() + */ + virtual void drawLabel( QPainter *painter, double value ) const = 0; + + void invalidateCache(); + const QwtText &tickLabel( const QFont &, double value ) const; + +private: + QwtAbstractScaleDraw( const QwtAbstractScaleDraw & ); + QwtAbstractScaleDraw &operator=( const QwtAbstractScaleDraw & ); + + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtAbstractScaleDraw::ScaleComponents ) + +#endif diff --git a/qwt/src/qwt_abstract_slider.cpp b/qwt/src/qwt_abstract_slider.cpp new file mode 100644 index 000000000..7bac22e61 --- /dev/null +++ b/qwt/src/qwt_abstract_slider.cpp @@ -0,0 +1,822 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_abstract_slider.h" +#include "qwt_abstract_scale_draw.h" +#include "qwt_math.h" +#include "qwt_scale_map.h" +#include + +#if QT_VERSION < 0x040601 +#define qFabs(x) ::fabs(x) +#endif + +static double qwtAlignToScaleDiv( + const QwtAbstractSlider *slider, double value ) +{ + const QwtScaleDiv &sd = slider->scaleDiv(); + + const int tValue = slider->transform( value ); + + if ( tValue == slider->transform( sd.lowerBound() ) ) + return sd.lowerBound(); + + if ( tValue == slider->transform( sd.lowerBound() ) ) + return sd.upperBound(); + + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + { + const QList ticks = sd.ticks( i ); + for ( int j = 0; j < ticks.size(); j++ ) + { + if ( slider->transform( ticks[ j ] ) == tValue ) + return ticks[ j ]; + } + } + + return value; +} + +class QwtAbstractSlider::PrivateData +{ +public: + PrivateData(): + isScrolling( false ), + isTracking( true ), + pendingValueChanged( false ), + readOnly( false ), + totalSteps( 100 ), + singleSteps( 1 ), + pageSteps( 10 ), + stepAlignment( true ), + isValid( false ), + value( 0.0 ), + wrapping( false ), + invertedControls( false ) + { + } + + bool isScrolling; + bool isTracking; + bool pendingValueChanged; + + bool readOnly; + + uint totalSteps; + uint singleSteps; + uint pageSteps; + bool stepAlignment; + + bool isValid; + double value; + + bool wrapping; + bool invertedControls; +}; + +/*! + \brief Constructor + + The scale is initialized to [0.0, 100.0], the + number of steps is set to 100 with 1 and 10 and single + an page step sizes. Step alignment is enabled. + + The initial value is invalid. + + \param parent Parent widget +*/ +QwtAbstractSlider::QwtAbstractSlider( QWidget *parent ): + QwtAbstractScale( parent ) +{ + d_data = new QwtAbstractSlider::PrivateData; + + setScale( 0.0, 100.0 ); + setFocusPolicy( Qt::StrongFocus ); +} + +//! Destructor +QwtAbstractSlider::~QwtAbstractSlider() +{ + delete d_data; +} + +/*! + Set the value to be valid/invalid + + \param on When true, the value is invalidated + + \sa setValue() +*/ +void QwtAbstractSlider::setValid( bool on ) +{ + if ( on != d_data->isValid ) + { + d_data->isValid = on; + sliderChange(); + + Q_EMIT valueChanged( d_data->value ); + } +} + +//! \return True, when the value is invalid +bool QwtAbstractSlider::isValid() const +{ + return d_data->isValid; +} + +/*! + En/Disable read only mode + + In read only mode the slider can't be controlled by mouse + or keyboard. + + \param on Enables in case of true + \sa isReadOnly() + + \warning The focus policy is set to Qt::StrongFocus or Qt::NoFocus +*/ +void QwtAbstractSlider::setReadOnly( bool on ) +{ + if ( d_data->readOnly != on ) + { + d_data->readOnly = on; + setFocusPolicy( on ? Qt::StrongFocus : Qt::NoFocus ); + + update(); + } +} + +/*! + In read only mode the slider can't be controlled by mouse + or keyboard. + + \return true if read only + \sa setReadOnly() +*/ +bool QwtAbstractSlider::isReadOnly() const +{ + return d_data->readOnly; +} + +/*! + \brief Enables or disables tracking. + + If tracking is enabled, the slider emits the valueChanged() + signal while the movable part of the slider is being dragged. + If tracking is disabled, the slider emits the valueChanged() signal + only when the user releases the slider. + + Tracking is enabled by default. + \param on \c true (enable) or \c false (disable) tracking. + + \sa isTracking(), sliderMoved() +*/ +void QwtAbstractSlider::setTracking( bool on ) +{ + d_data->isTracking = on; +} + +/*! + \return True, when tracking has been enabled + \sa setTracking() +*/ +bool QwtAbstractSlider::isTracking() const +{ + return d_data->isTracking; +} + +/*! + Mouse press event handler + \param event Mouse event +*/ +void QwtAbstractSlider::mousePressEvent( QMouseEvent *event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( !d_data->isValid || lowerBound() == upperBound() ) + return; + + d_data->isScrolling = isScrollPosition( event->pos() ); + + if ( d_data->isScrolling ) + { + d_data->pendingValueChanged = false; + + Q_EMIT sliderPressed(); + } +} + +/*! + Mouse Move Event handler + \param event Mouse event +*/ +void QwtAbstractSlider::mouseMoveEvent( QMouseEvent *event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( d_data->isValid && d_data->isScrolling ) + { + double value = scrolledTo( event->pos() ); + if ( value != d_data->value ) + { + value = boundedValue( value ); + + if ( d_data->stepAlignment ) + { + value = alignedValue( value ); + } + else + { + value = qwtAlignToScaleDiv( this, value ); + } + + if ( value != d_data->value ) + { + d_data->value = value; + + sliderChange(); + + Q_EMIT sliderMoved( d_data->value ); + + if ( d_data->isTracking ) + Q_EMIT valueChanged( d_data->value ); + else + d_data->pendingValueChanged = true; + } + } + } +} + +/*! + Mouse Release Event handler + \param event Mouse event +*/ +void QwtAbstractSlider::mouseReleaseEvent( QMouseEvent *event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( d_data->isScrolling && d_data->isValid ) + { + d_data->isScrolling = false; + + if ( d_data->pendingValueChanged ) + Q_EMIT valueChanged( d_data->value ); + + Q_EMIT sliderReleased(); + } +} + +/*! + Wheel Event handler + + In/decreases the value by s number of steps. The direction + depends on the invertedControls() property. + + When the control or shift modifier is pressed the wheel delta + ( divided by 120 ) is mapped to an increment according to + pageSteps(). Otherwise it is mapped to singleSteps(). + + \param event Wheel event +*/ +void QwtAbstractSlider::wheelEvent( QWheelEvent *event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( !d_data->isValid || d_data->isScrolling ) + return; + + int numSteps = 0; + + if ( ( event->modifiers() & Qt::ControlModifier) || + ( event->modifiers() & Qt::ShiftModifier ) ) + { + // one page regardless of delta + numSteps = d_data->pageSteps; + if ( event->delta() < 0 ) + numSteps = -numSteps; + } + else + { + const int numTurns = ( event->delta() / 120 ); + numSteps = numTurns * d_data->singleSteps; + } + + if ( d_data->invertedControls ) + numSteps = -numSteps; + + const double value = incrementedValue( d_data->value, numSteps ); + if ( value != d_data->value ) + { + d_data->value = value; + sliderChange(); + + Q_EMIT sliderMoved( d_data->value ); + Q_EMIT valueChanged( d_data->value ); + } +} + +/*! + Handles key events + + QwtAbstractSlider handles the following keys: + + - Qt::Key_Left\n + Add/Subtract singleSteps() in direction to lowerBound(); + - Qt::Key_Right\n + Add/Subtract singleSteps() in direction to upperBound(); + - Qt::Key_Down\n + Subtract singleSteps(), when invertedControls() is false + - Qt::Key_Up\n + Add singleSteps(), when invertedControls() is false + - Qt::Key_PageDown\n + Subtract pageSteps(), when invertedControls() is false + - Qt::Key_PageUp\n + Add pageSteps(), when invertedControls() is false + - Qt::Key_Home\n + Set the value to the minimum() + - Qt::Key_End\n + Set the value to the maximum() + + \param event Key event + \sa isReadOnly() +*/ +void QwtAbstractSlider::keyPressEvent( QKeyEvent *event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( !d_data->isValid || d_data->isScrolling ) + return; + + int numSteps = 0; + double value = d_data->value; + + switch ( event->key() ) + { + case Qt::Key_Left: + { + numSteps = -static_cast( d_data->singleSteps ); + if ( isInverted() ) + numSteps = -numSteps; + + break; + } + case Qt::Key_Right: + { + numSteps = d_data->singleSteps; + if ( isInverted() ) + numSteps = -numSteps; + + break; + } + case Qt::Key_Down: + { + numSteps = -static_cast( d_data->singleSteps ); + if ( d_data->invertedControls ) + numSteps = -numSteps; + break; + } + case Qt::Key_Up: + { + numSteps = d_data->singleSteps; + if ( d_data->invertedControls ) + numSteps = -numSteps; + + break; + } + case Qt::Key_PageUp: + { + numSteps = d_data->pageSteps; + if ( d_data->invertedControls ) + numSteps = -numSteps; + break; + } + case Qt::Key_PageDown: + { + numSteps = -static_cast( d_data->pageSteps ); + if ( d_data->invertedControls ) + numSteps = -numSteps; + break; + } + case Qt::Key_Home: + { + value = minimum(); + break; + } + case Qt::Key_End: + { + value = maximum(); + break; + } + default:; + { + event->ignore(); + } + } + + if ( numSteps != 0 ) + { + value = incrementedValue( d_data->value, numSteps ); + } + + if ( value != d_data->value ) + { + d_data->value = value; + sliderChange(); + + Q_EMIT sliderMoved( d_data->value ); + Q_EMIT valueChanged( d_data->value ); + } +} + +/*! + \brief Set the number of steps + + The range of the slider is divided into a number of steps from + which the value increments according to user inputs depend. + + The default setting is 100. + + \param stepCount Number of steps + + \sa totalSteps(), setSingleSteps(), setPageSteps() + */ +void QwtAbstractSlider::setTotalSteps( uint stepCount ) +{ + d_data->totalSteps = stepCount; +} + +/*! + \return Number of steps + \sa setTotalSteps(), singleSteps(), pageSteps() + */ +uint QwtAbstractSlider::totalSteps() const +{ + return d_data->totalSteps; +} + +/*! + \brief Set the number of steps for a single increment + + The range of the slider is divided into a number of steps from + which the value increments according to user inputs depend. + + \param stepCount Number of steps + + \sa singleSteps(), setTotalSteps(), setPageSteps() + */ + +void QwtAbstractSlider::setSingleSteps( uint stepCount ) +{ + d_data->singleSteps = stepCount; +} + +/*! + \return Number of steps + \sa setSingleSteps(), totalSteps(), pageSteps() + */ +uint QwtAbstractSlider::singleSteps() const +{ + return d_data->singleSteps; +} + +/*! + \brief Set the number of steps for a page increment + + The range of the slider is divided into a number of steps from + which the value increments according to user inputs depend. + + \param stepCount Number of steps + + \sa pageSteps(), setTotalSteps(), setSingleSteps() + */ + +void QwtAbstractSlider::setPageSteps( uint stepCount ) +{ + d_data->pageSteps = stepCount; +} + +/*! + \return Number of steps + \sa setPageSteps(), totalSteps(), singleSteps() + */ +uint QwtAbstractSlider::pageSteps() const +{ + return d_data->pageSteps; +} + +/*! + \brief Enable step alignment + + When step alignment is enabled values resulting from slider + movements are aligned to the step size. + + \param on Enable step alignment when true + \sa stepAlignment() +*/ +void QwtAbstractSlider::setStepAlignment( bool on ) +{ + if ( on != d_data->stepAlignment ) + { + d_data->stepAlignment = on; + } +} + +/*! + \return True, when step alignment is enabled + \sa setStepAlignment() + */ +bool QwtAbstractSlider::stepAlignment() const +{ + return d_data->stepAlignment; +} + +/*! + Set the slider to the specified value + + \param value New value + \sa setValid(), sliderChange(), valueChanged() +*/ +void QwtAbstractSlider::setValue( double value ) +{ + value = qBound( minimum(), value, maximum() ); + + const bool changed = ( d_data->value != value ) || !d_data->isValid; + + d_data->value = value; + d_data->isValid = true; + + if ( changed ) + { + sliderChange(); + Q_EMIT valueChanged( d_data->value ); + } +} + +//! Returns the current value. +double QwtAbstractSlider::value() const +{ + return d_data->value; +} + +/*! + If wrapping is true stepping up from upperBound() value will + take you to the minimum() value and vice versa. + + \param on En/Disable wrapping + \sa wrapping() +*/ +void QwtAbstractSlider::setWrapping( bool on ) +{ + d_data->wrapping = on; +} + +/*! + \return True, when wrapping is set + \sa setWrapping() + */ +bool QwtAbstractSlider::wrapping() const +{ + return d_data->wrapping; +} + +/*! + Invert wheel and key events + + Usually scrolling the mouse wheel "up" and using keys like page + up will increase the slider's value towards its maximum. + When invertedControls() is enabled the value is scrolled + towards its minimum. + + Inverting the controls might be f.e. useful for a vertical slider + with an inverted scale ( decreasing from top to bottom ). + + \param on Invert controls, when true + + \sa invertedControls(), keyEvent(), wheelEvent() + */ +void QwtAbstractSlider::setInvertedControls( bool on ) +{ + d_data->invertedControls = on; +} + +/*! + \return True, when the controls are inverted + \sa setInvertedControls() + */ +bool QwtAbstractSlider::invertedControls() const +{ + return d_data->invertedControls; +} + +/*! + Increment the slider + + The step size depends on the number of totalSteps() + + \param stepCount Number of steps + \sa setTotalSteps(), incrementedValue() + */ +void QwtAbstractSlider::incrementValue( int stepCount ) +{ + const double value = incrementedValue( + d_data->value, stepCount ); + + if ( value != d_data->value ) + { + d_data->value = value; + sliderChange(); + } +} + +/*! + Increment a value + + \param value Value + \param stepCount Number of steps + + \return Incremented value + */ +double QwtAbstractSlider::incrementedValue( + double value, int stepCount ) const +{ + if ( d_data->totalSteps == 0 ) + return value; + + const QwtTransform *transformation = + scaleMap().transformation(); + + if ( transformation == NULL ) + { + const double range = maximum() - minimum(); + value += stepCount * range / d_data->totalSteps; + } + else + { + QwtScaleMap map = scaleMap(); + map.setPaintInterval( 0, d_data->totalSteps ); + + // we need equidant steps according to + // paint device coordinates + const double range = transformation->transform( maximum() ) + - transformation->transform( minimum() ); + + const double stepSize = range / d_data->totalSteps; + + double v = transformation->transform( value ); + + v = qRound( v / stepSize ) * stepSize; + v += stepCount * range / d_data->totalSteps; + + value = transformation->invTransform( v ); + } + + value = boundedValue( value ); + + if ( d_data->stepAlignment ) + value = alignedValue( value ); + + return value; +} + +double QwtAbstractSlider::boundedValue( double value ) const +{ + const double vmin = minimum(); + const double vmax = maximum(); + + if ( d_data->wrapping && vmin != vmax ) + { + const int fullCircle = 360 * 16; + + const double pd = scaleMap().pDist(); + if ( int( pd / fullCircle ) * fullCircle == pd ) + { + // full circle scales: min and max are the same + const double range = vmax - vmin; + + if ( value < vmin ) + { + value += ::ceil( ( vmin - value ) / range ) * range; + } + else if ( value > vmax ) + { + value -= ::ceil( ( value - vmax ) / range ) * range; + } + } + else + { + if ( value < vmin ) + value = vmax; + else if ( value > vmax ) + value = vmin; + } + } + else + { + value = qBound( vmin, value, vmax ); + } + + return value; +} + +double QwtAbstractSlider::alignedValue( double value ) const +{ + if ( d_data->totalSteps == 0 ) + return value; + + double stepSize; + + if ( scaleMap().transformation() == NULL ) + { + stepSize = ( maximum() - minimum() ) / d_data->totalSteps; + if ( stepSize > 0.0 ) + { + value = lowerBound() + + qRound( ( value - lowerBound() ) / stepSize ) * stepSize; + } + } + else + { + stepSize = ( scaleMap().p2() - scaleMap().p1() ) / d_data->totalSteps; + + if ( stepSize > 0.0 ) + { + double v = scaleMap().transform( value ); + + v = scaleMap().p1() + + qRound( ( v - scaleMap().p1() ) / stepSize ) * stepSize; + + value = scaleMap().invTransform( v ); + } + } + + if ( qAbs( stepSize ) > 1e-12 ) + { + if ( qFuzzyCompare( value + 1.0, 1.0 ) ) + { + // correct rounding error if value = 0 + value = 0.0; + } + else + { + // correct rounding error at the border + if ( qFuzzyCompare( value, upperBound() ) ) + value = upperBound(); + else if ( qFuzzyCompare( value, lowerBound() ) ) + value = lowerBound(); + } + } + + return value; +} + +/*! + Update the slider according to modifications of the scale + */ +void QwtAbstractSlider::scaleChange() +{ + const double value = qBound( minimum(), d_data->value, maximum() ); + + const bool changed = ( value != d_data->value ); + if ( changed ) + { + d_data->value = value; + } + + if ( d_data->isValid || changed ) + Q_EMIT valueChanged( d_data->value ); + + updateGeometry(); + update(); +} + +//! Calling update() +void QwtAbstractSlider::sliderChange() +{ + update(); +} diff --git a/qwt/src/qwt_abstract_slider.h b/qwt/src/qwt_abstract_slider.h new file mode 100644 index 000000000..c91dcb604 --- /dev/null +++ b/qwt/src/qwt_abstract_slider.h @@ -0,0 +1,167 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ABSTRACT_SLIDER_H +#define QWT_ABSTRACT_SLIDER_H + +#include "qwt_global.h" +#include "qwt_abstract_scale.h" + +/*! + \brief An abstract base class for slider widgets with a scale + + A slider widget displays a value according to a scale. + The class is designed as a common super class for widgets like + QwtKnob, QwtDial and QwtSlider. + + When the slider is nor readOnly() its value can be modified + by keyboard, mouse and wheel inputs. + + The range of the slider is divided into a number of steps from + which the value increments according to user inputs depend. + Only for linear scales the number of steps correspond with + a fixed step size. +*/ + +class QWT_EXPORT QwtAbstractSlider: public QwtAbstractScale +{ + Q_OBJECT + + Q_PROPERTY( double value READ value WRITE setValue ) + + Q_PROPERTY( uint totalSteps READ totalSteps WRITE setTotalSteps ) + Q_PROPERTY( uint singleSteps READ singleSteps WRITE setSingleSteps ) + Q_PROPERTY( uint pageSteps READ pageSteps WRITE setPageSteps ) + Q_PROPERTY( bool stepAlignment READ stepAlignment WRITE setStepAlignment ) + + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly ) + Q_PROPERTY( bool tracking READ isTracking WRITE setTracking ) + Q_PROPERTY( bool wrapping READ wrapping WRITE setWrapping ) + + Q_PROPERTY( bool invertedControls READ invertedControls WRITE setInvertedControls ) + +public: + explicit QwtAbstractSlider( QWidget *parent = NULL ); + virtual ~QwtAbstractSlider(); + + void setValid( bool ); + bool isValid() const; + + double value() const; + + void setWrapping( bool ); + bool wrapping() const; + + void setTotalSteps( uint ); + uint totalSteps() const; + + void setSingleSteps( uint ); + uint singleSteps() const; + + void setPageSteps( uint ); + uint pageSteps() const; + + void setStepAlignment( bool ); + bool stepAlignment() const; + + void setTracking( bool ); + bool isTracking() const; + + void setReadOnly( bool ); + bool isReadOnly() const; + + void setInvertedControls( bool ); + bool invertedControls() const; + +public Q_SLOTS: + void setValue( double val ); + +Q_SIGNALS: + + /*! + \brief Notify a change of value. + + When tracking is enabled (default setting), + this signal will be emitted every time the value changes. + + \param value New value + + \sa setTracking(), sliderMoved() + */ + void valueChanged( double value ); + + /*! + This signal is emitted when the user presses the + movable part of the slider. + */ + void sliderPressed(); + + /*! + This signal is emitted when the user releases the + movable part of the slider. + */ + void sliderReleased(); + + /*! + This signal is emitted when the user moves the + slider with the mouse. + + \param value New value + + \sa valueChanged() + */ + void sliderMoved( double value ); + +protected: + virtual void mousePressEvent( QMouseEvent * ); + virtual void mouseReleaseEvent( QMouseEvent * ); + virtual void mouseMoveEvent( QMouseEvent * ); + virtual void keyPressEvent( QKeyEvent * ); + virtual void wheelEvent( QWheelEvent * ); + + /*! + \brief Determine what to do when the user presses a mouse button. + + \param pos Mouse position + + \retval True, when pos is a valid scroll position + \sa scrolledTo() + */ + virtual bool isScrollPosition( const QPoint &pos ) const = 0; + + /*! + \brief Determine the value for a new position of the + movable part of the slider + + \param pos Mouse position + + \return Value for the mouse position + \sa isScrollPosition() + */ + virtual double scrolledTo( const QPoint &pos ) const = 0; + + void incrementValue( int numSteps ); + + virtual void scaleChange(); + +protected: + virtual void sliderChange(); + + double incrementedValue( + double value, int stepCount ) const; + +private: + double alignedValue( double ) const; + double boundedValue( double ) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_analog_clock.cpp b/qwt/src/qwt_analog_clock.cpp new file mode 100644 index 000000000..1d44a95cb --- /dev/null +++ b/qwt/src/qwt_analog_clock.cpp @@ -0,0 +1,244 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_analog_clock.h" +#include "qwt_round_scale_draw.h" +#include +#include + +class QwtAnalogClockScaleDraw: public QwtRoundScaleDraw +{ +public: + QwtAnalogClockScaleDraw() + { + setSpacing( 8 ); + + enableComponent( QwtAbstractScaleDraw::Backbone, false ); + + setTickLength( QwtScaleDiv::MinorTick, 2 ); + setTickLength( QwtScaleDiv::MediumTick, 4 ); + setTickLength( QwtScaleDiv::MajorTick, 8 ); + + setPenWidth( 1 ); + } + + virtual QwtText label( double value ) const + { + if ( qFuzzyCompare( value + 1.0, 1.0 ) ) + value = 60.0 * 60.0 * 12.0; + + return QLocale().toString( qRound( value / ( 60.0 * 60.0 ) ) ); + } +}; + +/*! + Constructor + \param parent Parent widget +*/ +QwtAnalogClock::QwtAnalogClock( QWidget *parent ): + QwtDial( parent ) +{ + setWrapping( true ); + setReadOnly( true ); + + setOrigin( 270.0 ); + setScaleDraw( new QwtAnalogClockScaleDraw() ); + + setTotalSteps( 60 ); + + const int secondsPerHour = 60.0 * 60.0; + + QList majorTicks; + QList minorTicks; + + for ( int i = 0; i < 12; i++ ) + { + majorTicks += i * secondsPerHour; + + for ( int j = 1; j < 5; j++ ) + minorTicks += i * secondsPerHour + j * secondsPerHour / 5.0; + } + + QwtScaleDiv scaleDiv; + scaleDiv.setInterval( 0.0, 12.0 * secondsPerHour ); + scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks ); + scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks ); + setScale( scaleDiv ); + + QColor knobColor = palette().color( QPalette::Active, QPalette::Text ); + knobColor = knobColor.dark( 120 ); + + QColor handColor; + int width; + + for ( int i = 0; i < NHands; i++ ) + { + if ( i == SecondHand ) + { + width = 2; + handColor = knobColor.dark( 120 ); + } + else + { + width = 8; + handColor = knobColor; + } + + QwtDialSimpleNeedle *hand = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, handColor, knobColor ); + hand->setWidth( width ); + + d_hand[i] = NULL; + setHand( static_cast( i ), hand ); + } +} + +//! Destructor +QwtAnalogClock::~QwtAnalogClock() +{ + for ( int i = 0; i < NHands; i++ ) + delete d_hand[i]; +} + +/*! + Nop method, use setHand() instead + \sa setHand() +*/ +void QwtAnalogClock::setNeedle( QwtDialNeedle * ) +{ + // no op + return; +} + +/*! + Set a clock hand + \param hand Specifies the type of hand + \param needle Hand + \sa hand() +*/ +void QwtAnalogClock::setHand( Hand hand, QwtDialNeedle *needle ) +{ + if ( hand >= 0 && hand < NHands ) + { + delete d_hand[hand]; + d_hand[hand] = needle; + } +} + +/*! + \return Clock hand + \param hd Specifies the type of hand + \sa setHand() +*/ +QwtDialNeedle *QwtAnalogClock::hand( Hand hd ) +{ + if ( hd < 0 || hd >= NHands ) + return NULL; + + return d_hand[hd]; +} + +/*! + \return Clock hand + \param hd Specifies the type of hand + \sa setHand() +*/ +const QwtDialNeedle *QwtAnalogClock::hand( Hand hd ) const +{ + return const_cast( this )->hand( hd ); +} + +/*! + \brief Set the current time +*/ +void QwtAnalogClock::setCurrentTime() +{ + setTime( QTime::currentTime() ); +} + +/*! + Set a time + \param time Time to display +*/ +void QwtAnalogClock::setTime( const QTime &time ) +{ + if ( time.isValid() ) + { + setValue( ( time.hour() % 12 ) * 60.0 * 60.0 + + time.minute() * 60.0 + time.second() ); + } + else + setValid( false ); +} + +/*! + \brief Draw the needle + + A clock has no single needle but three hands instead. drawNeedle() + translates value() into directions for the hands and calls + drawHand(). + + \param painter Painter + \param center Center of the clock + \param radius Maximum length for the hands + \param dir Dummy, not used. + \param colorGroup ColorGroup + + \sa drawHand() +*/ +void QwtAnalogClock::drawNeedle( QPainter *painter, const QPointF ¢er, + double radius, double dir, QPalette::ColorGroup colorGroup ) const +{ + Q_UNUSED( dir ); + + if ( isValid() ) + { + const double hours = value() / ( 60.0 * 60.0 ); + const double minutes = + ( value() - qFloor(hours) * 60.0 * 60.0 ) / 60.0; + const double seconds = value() - qFloor(hours) * 60.0 * 60.0 + - qFloor(minutes) * 60.0; + + double angle[NHands]; + angle[HourHand] = 360.0 * hours / 12.0; + angle[MinuteHand] = 360.0 * minutes / 60.0; + angle[SecondHand] = 360.0 * seconds / 60.0; + + for ( int hand = 0; hand < NHands; hand++ ) + { + const double d = 360.0 - angle[hand] - origin(); + drawHand( painter, static_cast( hand ), + center, radius, d, colorGroup ); + } + } +} + +/*! + Draw a clock hand + + \param painter Painter + \param hd Specify the type of hand + \param center Center of the clock + \param radius Maximum length for the hands + \param direction Direction of the hand in degrees, counter clockwise + \param cg ColorGroup +*/ +void QwtAnalogClock::drawHand( QPainter *painter, Hand hd, + const QPointF ¢er, double radius, double direction, + QPalette::ColorGroup cg ) const +{ + const QwtDialNeedle *needle = hand( hd ); + if ( needle ) + { + if ( hd == HourHand ) + radius = qRound( 0.8 * radius ); + + needle->draw( painter, center, radius, direction, cg ); + } +} diff --git a/qwt/src/qwt_analog_clock.h b/qwt/src/qwt_analog_clock.h new file mode 100644 index 000000000..ffe27e2cd --- /dev/null +++ b/qwt/src/qwt_analog_clock.h @@ -0,0 +1,93 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ANALOG_CLOCK_H +#define QWT_ANALOG_CLOCK_H + +#include "qwt_global.h" +#include "qwt_dial.h" +#include "qwt_dial_needle.h" +#include + +/*! + \brief An analog clock + + \image html analogclock.png + + \par Example + \code + #include + + QwtAnalogClock *clock = new QwtAnalogClock(...); + clock->scaleDraw()->setPenWidth(3); + clock->setLineWidth(6); + clock->setFrameShadow(QwtDial::Sunken); + clock->setTime(); + + // update the clock every second + QTimer *timer = new QTimer(clock); + timer->connect(timer, SIGNAL(timeout()), clock, SLOT(setCurrentTime())); + timer->start(1000); + + \endcode + + \note The examples/dials example shows how to use QwtAnalogClock. +*/ + +class QWT_EXPORT QwtAnalogClock: public QwtDial +{ + Q_OBJECT + +public: + /*! + Hand type + \sa setHand(), hand() + */ + enum Hand + { + //! Needle displaying the seconds + SecondHand, + + //! Needle displaying the minutes + MinuteHand, + + //! Needle displaying the hours + HourHand, + + //! Number of needles + NHands + }; + + explicit QwtAnalogClock( QWidget* parent = NULL ); + virtual ~QwtAnalogClock(); + + void setHand( Hand, QwtDialNeedle * ); + + const QwtDialNeedle *hand( Hand ) const; + QwtDialNeedle *hand( Hand ); + +public Q_SLOTS: + void setCurrentTime(); + void setTime( const QTime & ); + +protected: + virtual void drawNeedle( QPainter *, const QPointF &, + double radius, double direction, QPalette::ColorGroup ) const; + + virtual void drawHand( QPainter *, Hand, const QPointF &, + double radius, double direction, QPalette::ColorGroup ) const; + +private: + // use setHand instead + void setNeedle( QwtDialNeedle * ); + + QwtDialNeedle *d_hand[NHands]; +}; + +#endif diff --git a/qwt/src/qwt_arrow_button.cpp b/qwt/src/qwt_arrow_button.cpp new file mode 100644 index 000000000..bd20f91e9 --- /dev/null +++ b/qwt/src/qwt_arrow_button.cpp @@ -0,0 +1,333 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_arrow_button.h" +#include "qwt_math.h" +#include +#include +#include +#include +#include + +static const int MaxNum = 3; +static const int Margin = 2; +static const int Spacing = 1; + +class QwtArrowButton::PrivateData +{ +public: + int num; + Qt::ArrowType arrowType; +}; + +static QStyleOptionButton styleOpt( const QwtArrowButton* btn ) +{ + QStyleOptionButton option; + option.init( btn ); + option.features = QStyleOptionButton::None; + if ( btn->isFlat() ) + option.features |= QStyleOptionButton::Flat; + if ( btn->menu() ) + option.features |= QStyleOptionButton::HasMenu; + if ( btn->autoDefault() || btn->isDefault() ) + option.features |= QStyleOptionButton::AutoDefaultButton; + if ( btn->isDefault() ) + option.features |= QStyleOptionButton::DefaultButton; + if ( btn->isDown() ) + option.state |= QStyle::State_Sunken; + if ( !btn->isFlat() && !btn->isDown() ) + option.state |= QStyle::State_Raised; + + return option; +} + +/*! + \param num Number of arrows + \param arrowType see Qt::ArrowType in the Qt docs. + \param parent Parent widget +*/ +QwtArrowButton::QwtArrowButton( int num, + Qt::ArrowType arrowType, QWidget *parent ): + QPushButton( parent ) +{ + d_data = new PrivateData; + d_data->num = qBound( 1, num, MaxNum ); + d_data->arrowType = arrowType; + + setAutoRepeat( true ); + setAutoDefault( false ); + + switch ( d_data->arrowType ) + { + case Qt::LeftArrow: + case Qt::RightArrow: + setSizePolicy( QSizePolicy::Expanding, + QSizePolicy::Fixed ); + break; + default: + setSizePolicy( QSizePolicy::Fixed, + QSizePolicy::Expanding ); + } +} + +//! Destructor +QwtArrowButton::~QwtArrowButton() +{ + delete d_data; + d_data = NULL; +} + +/*! + \brief The direction of the arrows +*/ +Qt::ArrowType QwtArrowButton::arrowType() const +{ + return d_data->arrowType; +} + +/*! + \brief The number of arrows +*/ +int QwtArrowButton::num() const +{ + return d_data->num; +} + +/*! + \return the bounding rectangle for the label +*/ +QRect QwtArrowButton::labelRect() const +{ + const int m = Margin; + + QRect r = rect(); + r.setRect( r.x() + m, r.y() + m, + r.width() - 2 * m, r.height() - 2 * m ); + + if ( isDown() ) + { + QStyleOptionButton option = styleOpt( this ); + const int ph = style()->pixelMetric( + QStyle::PM_ButtonShiftHorizontal, &option, this ); + const int pv = style()->pixelMetric( + QStyle::PM_ButtonShiftVertical, &option, this ); + + r.translate( ph, pv ); + } + + return r; +} + +/*! + Paint event handler + \param event Paint event +*/ +void QwtArrowButton::paintEvent( QPaintEvent *event ) +{ + QPushButton::paintEvent( event ); + QPainter painter( this ); + drawButtonLabel( &painter ); +} + +/*! + \brief Draw the button label + + \param painter Painter + \sa The Qt Manual for QPushButton +*/ +void QwtArrowButton::drawButtonLabel( QPainter *painter ) +{ + const bool isVertical = d_data->arrowType == Qt::UpArrow || + d_data->arrowType == Qt::DownArrow; + + const QRect r = labelRect(); + QSize boundingSize = labelRect().size(); + if ( isVertical ) + boundingSize.transpose(); + + const int w = + ( boundingSize.width() - ( MaxNum - 1 ) * Spacing ) / MaxNum; + + QSize arrow = arrowSize( Qt::RightArrow, + QSize( w, boundingSize.height() ) ); + + if ( isVertical ) + arrow.transpose(); + + QRect contentsSize; // aligned rect where to paint all arrows + if ( d_data->arrowType == Qt::LeftArrow || d_data->arrowType == Qt::RightArrow ) + { + contentsSize.setWidth( d_data->num * arrow.width() + + ( d_data->num - 1 ) * Spacing ); + contentsSize.setHeight( arrow.height() ); + } + else + { + contentsSize.setWidth( arrow.width() ); + contentsSize.setHeight( d_data->num * arrow.height() + + ( d_data->num - 1 ) * Spacing ); + } + + QRect arrowRect( contentsSize ); + arrowRect.moveCenter( r.center() ); + arrowRect.setSize( arrow ); + + painter->save(); + for ( int i = 0; i < d_data->num; i++ ) + { + drawArrow( painter, arrowRect, d_data->arrowType ); + + int dx = 0; + int dy = 0; + + if ( isVertical ) + dy = arrow.height() + Spacing; + else + dx = arrow.width() + Spacing; + + arrowRect.translate( dx, dy ); + } + painter->restore(); + + if ( hasFocus() ) + { + QStyleOptionFocusRect option; + option.init( this ); + option.backgroundColor = palette().color( QPalette::Window ); + + style()->drawPrimitive( QStyle::PE_FrameFocusRect, + &option, painter, this ); + } +} + +/*! + Draw an arrow int a bounding rectangle + + \param painter Painter + \param r Rectangle where to paint the arrow + \param arrowType Arrow type +*/ +void QwtArrowButton::drawArrow( QPainter *painter, + const QRect &r, Qt::ArrowType arrowType ) const +{ + QPolygon pa( 3 ); + + switch ( arrowType ) + { + case Qt::UpArrow: + pa.setPoint( 0, r.bottomLeft() ); + pa.setPoint( 1, r.bottomRight() ); + pa.setPoint( 2, r.center().x(), r.top() ); + break; + case Qt::DownArrow: + pa.setPoint( 0, r.topLeft() ); + pa.setPoint( 1, r.topRight() ); + pa.setPoint( 2, r.center().x(), r.bottom() ); + break; + case Qt::RightArrow: + pa.setPoint( 0, r.topLeft() ); + pa.setPoint( 1, r.bottomLeft() ); + pa.setPoint( 2, r.right(), r.center().y() ); + break; + case Qt::LeftArrow: + pa.setPoint( 0, r.topRight() ); + pa.setPoint( 1, r.bottomRight() ); + pa.setPoint( 2, r.left(), r.center().y() ); + break; + default: + break; + } + + painter->save(); + + painter->setRenderHint( QPainter::Antialiasing, true ); + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().brush( QPalette::ButtonText ) ); + painter->drawPolygon( pa ); + + painter->restore(); +} + +/*! + \return a size hint +*/ +QSize QwtArrowButton::sizeHint() const +{ + const QSize hint = minimumSizeHint(); + return hint.expandedTo( QApplication::globalStrut() ); +} + +/*! + \brief Return a minimum size hint +*/ +QSize QwtArrowButton::minimumSizeHint() const +{ + const QSize asz = arrowSize( Qt::RightArrow, QSize() ); + + QSize sz( + 2 * Margin + ( MaxNum - 1 ) * Spacing + MaxNum * asz.width(), + 2 * Margin + asz.height() + ); + + if ( d_data->arrowType == Qt::UpArrow || d_data->arrowType == Qt::DownArrow ) + sz.transpose(); + + QStyleOption styleOption; + styleOption.init( this ); + + sz = style()->sizeFromContents( QStyle::CT_PushButton, + &styleOption, sz, this ); + + return sz; +} + +/*! + Calculate the size for a arrow that fits into a rectangle of a given size + + \param arrowType Arrow type + \param boundingSize Bounding size + \return Size of the arrow +*/ +QSize QwtArrowButton::arrowSize( Qt::ArrowType arrowType, + const QSize &boundingSize ) const +{ + QSize bs = boundingSize; + if ( arrowType == Qt::UpArrow || arrowType == Qt::DownArrow ) + bs.transpose(); + + const int MinLen = 2; + const QSize sz = bs.expandedTo( + QSize( MinLen, 2 * MinLen - 1 ) ); // minimum + + int w = sz.width(); + int h = 2 * w - 1; + + if ( h > sz.height() ) + { + h = sz.height(); + w = ( h + 1 ) / 2; + } + + QSize arrSize( w, h ); + if ( arrowType == Qt::UpArrow || arrowType == Qt::DownArrow ) + arrSize.transpose(); + + return arrSize; +} + +/*! + \brief autoRepeat for the space keys +*/ +void QwtArrowButton::keyPressEvent( QKeyEvent *event ) +{ + if ( event->isAutoRepeat() && event->key() == Qt::Key_Space ) + Q_EMIT clicked(); + + QPushButton::keyPressEvent( event ); +} diff --git a/qwt/src/qwt_arrow_button.h b/qwt/src/qwt_arrow_button.h new file mode 100644 index 000000000..ae436fec0 --- /dev/null +++ b/qwt/src/qwt_arrow_button.h @@ -0,0 +1,52 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ARROW_BUTTON_H +#define QWT_ARROW_BUTTON_H + +#include "qwt_global.h" +#include + +/*! + \brief Arrow Button + + A push button with one or more filled triangles on its front. + An Arrow button can have 1 to 3 arrows in a row, pointing + up, down, left or right. +*/ +class QWT_EXPORT QwtArrowButton : public QPushButton +{ +public: + explicit QwtArrowButton ( int num, Qt::ArrowType, QWidget *parent = NULL ); + virtual ~QwtArrowButton(); + + Qt::ArrowType arrowType() const; + int num() const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + +protected: + virtual void paintEvent( QPaintEvent *event ); + + virtual void drawButtonLabel( QPainter *p ); + virtual void drawArrow( QPainter *, + const QRect &, Qt::ArrowType ) const; + virtual QRect labelRect() const; + virtual QSize arrowSize( Qt::ArrowType, + const QSize &boundingSize ) const; + + virtual void keyPressEvent( QKeyEvent * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_axes_mask.cpp b/qwt/src/qwt_axes_mask.cpp new file mode 100644 index 000000000..46b038e5b --- /dev/null +++ b/qwt/src/qwt_axes_mask.cpp @@ -0,0 +1,82 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot.h" +#include "qwt_axes_mask.h" +#include +#include + +class QwtAxesMask::PrivateData +{ +public: + QList disabledAxes[ QwtAxis::PosCount ]; +}; + +QwtAxesMask::QwtAxesMask() +{ + d_data = new PrivateData(); +} + +QwtAxesMask::~QwtAxesMask() +{ + delete d_data; +} + +/*! + \brief En/Disable an axis + + Only Axes that are enabled will be zoomed. + All other axes will remain unchanged. + + \param axisId Axis id + \param on On/Off + + \sa isAxisEnabled() +*/ +void QwtAxesMask::setEnabled( QwtAxisId axisId, bool on ) +{ + if ( !QwtAxis::isValid( axisId.pos ) ) + return; + + QList &axes = d_data->disabledAxes[ axisId.pos ]; + + QList::iterator it = qLowerBound( axes.begin(), axes.end(), axisId.id ); + + const bool isEnabled = ( it != axes.end() ) && ( *it != axisId.id ); + + if ( on ) + { + if ( !isEnabled ) + axes.insert( it, axisId.id ); + } + else + { + if ( isEnabled ) + axes.erase( it ); + } +} + +/*! + Test if an axis is enabled + + \param axisId Axis id + \return True, if the axis is enabled + + \sa setAxisEnabled() +*/ +bool QwtAxesMask::isEnabled( QwtAxisId axisId ) const +{ + if ( QwtAxis::isValid( axisId.pos ) ) + { + const QList &axes = d_data->disabledAxes[ axisId.pos ]; + return qFind( axes, axisId.id ) != axes.end(); + } + + return true; +} diff --git a/qwt/src/qwt_axes_mask.h b/qwt/src/qwt_axes_mask.h new file mode 100644 index 000000000..2df9fd072 --- /dev/null +++ b/qwt/src/qwt_axes_mask.h @@ -0,0 +1,30 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_AXES_MASK_H +#define QWT_AXES_MASK_H 1 + +#include "qwt_global.h" +#include "qwt_axis_id.h" + +class QWT_EXPORT QwtAxesMask +{ +public: + QwtAxesMask(); + ~QwtAxesMask(); + + void setEnabled( QwtAxisId, bool ); + bool isEnabled( QwtAxisId ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_axis_id.cpp b/qwt/src/qwt_axis_id.cpp new file mode 100644 index 000000000..3e3cbf614 --- /dev/null +++ b/qwt/src/qwt_axis_id.cpp @@ -0,0 +1,32 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_axis_id.h" + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<( QDebug debug, const QwtAxisId &axisId ) +{ + static const char *posNames[] = { "yLeft", "yRight", "xBottom", "xTop" }; + + debug.nospace(); + + debug << "QwtAxisId("; + + if ( axisId.pos >= 0 && axisId.pos < 4 ) + debug << posNames[axisId.pos]; + else + debug << axisId.pos; + + debug << "," << axisId.id << ")"; + + return debug.space(); +} + +#endif diff --git a/qwt/src/qwt_axis_id.h b/qwt/src/qwt_axis_id.h new file mode 100644 index 000000000..1a779f78e --- /dev/null +++ b/qwt/src/qwt_axis_id.h @@ -0,0 +1,106 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_AXIS_ID_H +#define QWT_AXIS_ID_H + +#include "qwt_global.h" + +#ifndef QT_NO_DEBUG_STREAM +#include +#endif + +namespace QwtAxis +{ + //! \brief Axis position + enum Position + { + //! Y axis left of the canvas + yLeft, + + //! Y axis right of the canvas + yRight, + + //! X axis below the canvas + xBottom, + + //! X axis above the canvas + xTop + }; + + //! \brief Number of axis positions + enum { PosCount = xTop + 1 }; + + bool isValid( int axisPos ); + bool isYAxis( int axisPos ); + bool isXAxis( int axisPos ); +}; + +inline bool QwtAxis::isValid( int axisPos ) +{ + return ( axisPos >= 0 && axisPos < PosCount ); +} + +inline bool QwtAxis::isXAxis( int axisPos ) +{ + return ( axisPos == xBottom ) || ( axisPos == xTop ); +} + +inline bool QwtAxis::isYAxis( int axisPos ) +{ + return ( axisPos == yLeft ) || ( axisPos == yRight ); +} + +class QWT_EXPORT QwtAxisId +{ +public: + QwtAxisId( int position, int index = 0 ); + + bool operator==( const QwtAxisId & ) const; + bool operator!=( const QwtAxisId & ) const; + + bool isXAxis() const; + bool isYAxis() const; + +public: + int pos; + int id; +}; + +inline QwtAxisId::QwtAxisId( int position, int index ): + pos( position ), + id( index ) +{ +} + +inline bool QwtAxisId::operator==( const QwtAxisId &other ) const +{ + return ( pos == other.pos ) && ( id == other.id ); +} + +inline bool QwtAxisId::operator!=( const QwtAxisId &other ) const +{ + return !operator==( other ); +} + +inline bool QwtAxisId::isXAxis() const +{ + return QwtAxis::isXAxis( pos ); +} + +inline bool QwtAxisId::isYAxis() const +{ + return QwtAxis::isYAxis( pos ); +} + +#ifndef QT_NO_DEBUG_STREAM +QWT_EXPORT QDebug operator<<( QDebug, const QwtAxisId & ); +#endif + +#endif diff --git a/qwt/src/qwt_clipper.cpp b/qwt/src/qwt_clipper.cpp new file mode 100644 index 000000000..51614aa37 --- /dev/null +++ b/qwt/src/qwt_clipper.cpp @@ -0,0 +1,510 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_clipper.h" +#include "qwt_point_polar.h" +#include +#include +#include + +#if QT_VERSION < 0x040601 +#define qAtan(x) ::atan(x) +#endif + +namespace QwtClip +{ + // some templates used for inlining + template class LeftEdge; + template class RightEdge; + template class TopEdge; + template class BottomEdge; + + template class PointBuffer; +} + +template +class QwtClip::LeftEdge +{ +public: + inline LeftEdge( Value x1, Value, Value, Value ): + d_x1( x1 ) + { + } + + inline bool isInside( const Point &p ) const + { + return p.x() >= d_x1; + } + + inline Point intersection( const Point &p1, const Point &p2 ) const + { + double dy = ( p1.y() - p2.y() ) / double( p1.x() - p2.x() ); + return Point( d_x1, static_cast< Value >( p2.y() + ( d_x1 - p2.x() ) * dy ) ); + } +private: + const Value d_x1; +}; + +template +class QwtClip::RightEdge +{ +public: + inline RightEdge( Value, Value x2, Value, Value ): + d_x2( x2 ) + { + } + + inline bool isInside( const Point &p ) const + { + return p.x() <= d_x2; + } + + inline Point intersection( const Point &p1, const Point &p2 ) const + { + double dy = ( p1.y() - p2.y() ) / double( p1.x() - p2.x() ); + return Point( d_x2, static_cast( p2.y() + ( d_x2 - p2.x() ) * dy ) ); + } + +private: + const Value d_x2; +}; + +template +class QwtClip::TopEdge +{ +public: + inline TopEdge( Value, Value, Value y1, Value ): + d_y1( y1 ) + { + } + + inline bool isInside( const Point &p ) const + { + return p.y() >= d_y1; + } + + inline Point intersection( const Point &p1, const Point &p2 ) const + { + double dx = ( p1.x() - p2.x() ) / double( p1.y() - p2.y() ); + return Point( static_cast( p2.x() + ( d_y1 - p2.y() ) * dx ), d_y1 ); + } + +private: + const Value d_y1; +}; + +template +class QwtClip::BottomEdge +{ +public: + inline BottomEdge( Value, Value, Value, Value y2 ): + d_y2( y2 ) + { + } + + inline bool isInside( const Point &p ) const + { + return p.y() <= d_y2; + } + + inline Point intersection( const Point &p1, const Point &p2 ) const + { + double dx = ( p1.x() - p2.x() ) / double( p1.y() - p2.y() ); + return Point( static_cast( p2.x() + ( d_y2 - p2.y() ) * dx ), d_y2 ); + } + +private: + const Value d_y2; +}; + +template +class QwtClip::PointBuffer +{ +public: + PointBuffer( int capacity = 0 ): + m_capacity( 0 ), + m_size( 0 ), + m_buffer( NULL ) + { + if ( capacity > 0 ) + reserve( capacity ); + } + + ~PointBuffer() + { + if ( m_buffer ) + ::free( m_buffer ); + } + + inline void setPoints( int numPoints, const Point *points ) + { + reserve( numPoints ); + + m_size = numPoints; + ::memcpy( m_buffer, points, m_size * sizeof( Point ) ); + } + + inline void reset() + { + m_size = 0; + } + + inline int size() const + { + return m_size; + } + + inline Point *data() const + { + return m_buffer; + } + + inline Point &operator[]( int i ) + { + return m_buffer[i]; + } + + inline const Point &operator[]( int i ) const + { + return m_buffer[i]; + } + + inline void add( const Point &point ) + { + if ( m_capacity <= m_size ) + reserve( m_size + 1 ); + + m_buffer[m_size++] = point; + } + +private: + inline void reserve( int size ) + { + if ( m_capacity == 0 ) + m_capacity = 1; + + while ( m_capacity < size ) + m_capacity *= 2; + + m_buffer = static_cast( + ::realloc( m_buffer, m_capacity * sizeof( Point ) ) ); + } + + int m_capacity; + int m_size; + Point *m_buffer; +}; + +using namespace QwtClip; + +template +class QwtPolygonClipper +{ +public: + QwtPolygonClipper( const Rect &clipRect ): + d_clipRect( clipRect ) + { + } + + Polygon clipPolygon( const Polygon &polygon, bool closePolygon ) const + { +#if 0 + if ( d_clipRect.contains( polygon.boundingRect() ) ) + return polygon; +#endif + + PointBuffer points1; + PointBuffer points2( qMin( 256, polygon.size() ) ); + + points1.setPoints( polygon.size(), polygon.data() ); + + clipEdge< LeftEdge >( closePolygon, points1, points2 ); + clipEdge< RightEdge >( closePolygon, points2, points1 ); + clipEdge< TopEdge >( closePolygon, points1, points2 ); + clipEdge< BottomEdge >( closePolygon, points2, points1 ); + + Polygon p; + p.resize( points1.size() ); + ::memcpy( p.data(), points1.data(), points1.size() * sizeof( Point ) ); + + return p; + } + +private: + template + inline void clipEdge( bool closePolygon, + PointBuffer &points, PointBuffer &clippedPoints ) const + { + clippedPoints.reset(); + + if ( points.size() < 2 ) + { + if ( points.size() == 1 ) + clippedPoints.add( points[0] ); + return; + } + + const Edge edge( d_clipRect.x(), d_clipRect.x() + d_clipRect.width(), + d_clipRect.y(), d_clipRect.y() + d_clipRect.height() ); + + int lastPos, start; + if ( closePolygon ) + { + start = 0; + lastPos = points.size() - 1; + } + else + { + start = 1; + lastPos = 0; + + if ( edge.isInside( points[0] ) ) + clippedPoints.add( points[0] ); + } + + const uint nPoints = points.size(); + for ( uint i = start; i < nPoints; i++ ) + { + const Point &p1 = points[i]; + const Point &p2 = points[lastPos]; + + if ( edge.isInside( p1 ) ) + { + if ( edge.isInside( p2 ) ) + { + clippedPoints.add( p1 ); + } + else + { + clippedPoints.add( edge.intersection( p1, p2 ) ); + clippedPoints.add( p1 ); + } + } + else + { + if ( edge.isInside( p2 ) ) + { + clippedPoints.add( edge.intersection( p1, p2 ) ); + } + } + lastPos = i; + } + } + + const Rect d_clipRect; +}; + +class QwtCircleClipper +{ +public: + QwtCircleClipper( const QRectF &r ); + QVector clipCircle( const QPointF &, double radius ) const; + +private: + enum Edge + { + Left, + Top, + Right, + Bottom, + + NEdges + }; + + QList cuttingPoints( + Edge, const QPointF &pos, double radius ) const; + + double toAngle( const QPointF &, const QPointF & ) const; + + const QRectF d_rect; +}; + + +QwtCircleClipper::QwtCircleClipper( const QRectF &r ): + d_rect( r ) +{ +} + +QVector QwtCircleClipper::clipCircle( + const QPointF &pos, double radius ) const +{ + QList points; + for ( int edge = 0; edge < NEdges; edge++ ) + points += cuttingPoints( static_cast(edge), pos, radius ); + + QVector intv; + if ( points.size() <= 0 ) + { + QRectF cRect( 0, 0, 2 * radius, 2 * radius ); + cRect.moveCenter( pos ); + if ( d_rect.contains( cRect ) ) + intv += QwtInterval( 0.0, 2 * M_PI ); + } + else + { + QList angles; + for ( int i = 0; i < points.size(); i++ ) + angles += toAngle( pos, points[i] ); + qSort( angles ); + + const int in = d_rect.contains( qwtPolar2Pos( pos, radius, + angles[0] + ( angles[1] - angles[0] ) / 2 ) ); + + if ( in ) + { + for ( int i = 0; i < angles.size() - 1; i += 2 ) + intv += QwtInterval( angles[i], angles[i+1] ); + } + else + { + for ( int i = 1; i < angles.size() - 1; i += 2 ) + intv += QwtInterval( angles[i], angles[i+1] ); + intv += QwtInterval( angles.last(), angles.first() ); + } + } + + return intv; +} + +double QwtCircleClipper::toAngle( + const QPointF &from, const QPointF &to ) const +{ + if ( from.x() == to.x() ) + return from.y() <= to.y() ? M_PI / 2.0 : 3 * M_PI / 2.0; + + const double m = qAbs( ( to.y() - from.y() ) / ( to.x() - from.x() ) ); + + double angle = qAtan( m ); + if ( to.x() > from.x() ) + { + if ( to.y() > from.y() ) + angle = 2 * M_PI - angle; + } + else + { + if ( to.y() > from.y() ) + angle = M_PI + angle; + else + angle = M_PI - angle; + } + + return angle; +} + +QList QwtCircleClipper::cuttingPoints( + Edge edge, const QPointF &pos, double radius ) const +{ + QList points; + + if ( edge == Left || edge == Right ) + { + const double x = ( edge == Left ) ? d_rect.left() : d_rect.right(); + if ( qAbs( pos.x() - x ) < radius ) + { + const double off = qSqrt( qwtSqr( radius ) - qwtSqr( pos.x() - x ) ); + const double m_y1 = pos.y() + off; + if ( m_y1 >= d_rect.top() && m_y1 <= d_rect.bottom() ) + points += QPointF( x, m_y1 ); + + const double m_y2 = pos.y() - off; + if ( m_y2 >= d_rect.top() && m_y2 <= d_rect.bottom() ) + points += QPointF( x, m_y2 ); + } + } + else + { + const double y = ( edge == Top ) ? d_rect.top() : d_rect.bottom(); + if ( qAbs( pos.y() - y ) < radius ) + { + const double off = qSqrt( qwtSqr( radius ) - qwtSqr( pos.y() - y ) ); + const double x1 = pos.x() + off; + if ( x1 >= d_rect.left() && x1 <= d_rect.right() ) + points += QPointF( x1, y ); + + const double m_x2 = pos.x() - off; + if ( m_x2 >= d_rect.left() && m_x2 <= d_rect.right() ) + points += QPointF( m_x2, y ); + } + } + return points; +} + +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon + \param closePolygon True, when the polygon is closed + + \return Clipped polygon +*/ +QPolygon QwtClipper::clipPolygon( + const QRectF &clipRect, const QPolygon &polygon, bool closePolygon ) +{ + const int minX = qCeil( clipRect.left() ); + const int maxX = qFloor( clipRect.right() ); + const int minY = qCeil( clipRect.top() ); + const int maxY = qFloor( clipRect.bottom() ); + + const QRect r( minX, minY, maxX - minX, maxY - minY ); + + QwtPolygonClipper clipper( r ); + return clipper.clipPolygon( polygon, closePolygon ); +} +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon + \param closePolygon True, when the polygon is closed + + \return Clipped polygon +*/ +QPolygon QwtClipper::clipPolygon( + const QRect &clipRect, const QPolygon &polygon, bool closePolygon ) +{ + QwtPolygonClipper clipper( clipRect ); + return clipper.clipPolygon( polygon, closePolygon ); +} + +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon + \param closePolygon True, when the polygon is closed + + \return Clipped polygon +*/ +QPolygonF QwtClipper::clipPolygonF( + const QRectF &clipRect, const QPolygonF &polygon, bool closePolygon ) +{ + QwtPolygonClipper clipper( clipRect ); + return clipper.clipPolygon( polygon, closePolygon ); +} + +/*! + Circle clipping + + clipCircle() divides a circle into intervals of angles representing arcs + of the circle. When the circle is completely inside the clip rectangle + an interval [0.0, 2 * M_PI] is returned. + + \param clipRect Clip rectangle + \param center Center of the circle + \param radius Radius of the circle + + \return Arcs of the circle +*/ +QVector QwtClipper::clipCircle( const QRectF &clipRect, + const QPointF ¢er, double radius ) +{ + QwtCircleClipper clipper( clipRect ); + return clipper.clipCircle( center, radius ); +} diff --git a/qwt/src/qwt_clipper.h b/qwt/src/qwt_clipper.h new file mode 100644 index 000000000..1b1820bb0 --- /dev/null +++ b/qwt/src/qwt_clipper.h @@ -0,0 +1,40 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_CLIPPER_H +#define QWT_CLIPPER_H + +#include "qwt_global.h" +#include "qwt_interval.h" +#include +#include + +class QRect; +class QRectF; + +/*! + \brief Some clipping algorithms +*/ + +class QWT_EXPORT QwtClipper +{ +public: + static QPolygon clipPolygon( const QRect &, + const QPolygon &, bool closePolygon = false ); + static QPolygon clipPolygon( const QRectF &, + const QPolygon &, bool closePolygon = false ); + + static QPolygonF clipPolygonF( const QRectF &, + const QPolygonF &, bool closePolygon = false ); + + static QVector clipCircle( + const QRectF &, const QPointF &, double radius ); +}; + +#endif diff --git a/qwt/src/qwt_color_map.cpp b/qwt/src/qwt_color_map.cpp new file mode 100644 index 000000000..571c1380d --- /dev/null +++ b/qwt/src/qwt_color_map.cpp @@ -0,0 +1,444 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_color_map.h" +#include "qwt_math.h" +#include "qwt_interval.h" +#include + +class QwtLinearColorMap::ColorStops +{ +public: + ColorStops() + { + _stops.reserve( 256 ); + } + + void insert( double pos, const QColor &color ); + QRgb rgb( QwtLinearColorMap::Mode, double pos ) const; + + QVector stops() const; + +private: + + class ColorStop + { + public: + ColorStop(): + pos( 0.0 ), + rgb( 0 ) + { + }; + + ColorStop( double p, const QColor &c ): + pos( p ), + rgb( c.rgb() ) + { + r = qRed( rgb ); + g = qGreen( rgb ); + b = qBlue( rgb ); + } + + double pos; + QRgb rgb; + int r, g, b; + }; + + inline int findUpper( double pos ) const; + QVector _stops; +}; + +void QwtLinearColorMap::ColorStops::insert( double pos, const QColor &color ) +{ + // Lookups need to be very fast, insertions are not so important. + // Anyway, a balanced tree is what we need here. TODO ... + + if ( pos < 0.0 || pos > 1.0 ) + return; + + int index; + if ( _stops.size() == 0 ) + { + index = 0; + _stops.resize( 1 ); + } + else + { + index = findUpper( pos ); + if ( index == _stops.size() || + qAbs( _stops[index].pos - pos ) >= 0.001 ) + { + _stops.resize( _stops.size() + 1 ); + for ( int i = _stops.size() - 1; i > index; i-- ) + _stops[i] = _stops[i-1]; + } + } + + _stops[index] = ColorStop( pos, color ); +} + +inline QVector QwtLinearColorMap::ColorStops::stops() const +{ + QVector positions( _stops.size() ); + for ( int i = 0; i < _stops.size(); i++ ) + positions[i] = _stops[i].pos; + return positions; +} + +inline int QwtLinearColorMap::ColorStops::findUpper( double pos ) const +{ + int index = 0; + int n = _stops.size(); + + const ColorStop *stops = _stops.data(); + + while ( n > 0 ) + { + const int half = n >> 1; + const int middle = index + half; + + if ( stops[middle].pos <= pos ) + { + index = middle + 1; + n -= half + 1; + } + else + n = half; + } + + return index; +} + +inline QRgb QwtLinearColorMap::ColorStops::rgb( + QwtLinearColorMap::Mode mode, double pos ) const +{ + if ( pos <= 0.0 ) + return _stops[0].rgb; + if ( pos >= 1.0 ) + return _stops[ _stops.size() - 1 ].rgb; + + const int index = findUpper( pos ); + if ( mode == FixedColors ) + { + return _stops[index-1].rgb; + } + else + { + const ColorStop &s1 = _stops[index-1]; + const ColorStop &s2 = _stops[index]; + + const double ratio = ( pos - s1.pos ) / ( s2.pos - s1.pos ); + + const int r = s1.r + qRound( ratio * ( s2.r - s1.r ) ); + const int g = s1.g + qRound( ratio * ( s2.g - s1.g ) ); + const int b = s1.b + qRound( ratio * ( s2.b - s1.b ) ); + + return qRgb( r, g, b ); + } +} + +//! Constructor +QwtColorMap::QwtColorMap( Format format ): + d_format( format ) +{ +} + +//! Destructor +QwtColorMap::~QwtColorMap() +{ +} + +/*! + Build and return a color map of 256 colors + + The color table is needed for rendering indexed images in combination + with using colorIndex(). + + \param interval Range for the values + \return A color table, that can be used for a QImage +*/ +QVector QwtColorMap::colorTable( const QwtInterval &interval ) const +{ + QVector table( 256 ); + + if ( interval.isValid() ) + { + const double step = interval.width() / ( table.size() - 1 ); + for ( int i = 0; i < table.size(); i++ ) + table[i] = rgb( interval, interval.minValue() + step * i ); + } + + return table; +} + +class QwtLinearColorMap::PrivateData +{ +public: + ColorStops colorStops; + QwtLinearColorMap::Mode mode; +}; + +/*! + Build a color map with two stops at 0.0 and 1.0. The color + at 0.0 is Qt::blue, at 1.0 it is Qt::yellow. + + \param format Preferred format of the color map +*/ +QwtLinearColorMap::QwtLinearColorMap( QwtColorMap::Format format ): + QwtColorMap( format ) +{ + d_data = new PrivateData; + d_data->mode = ScaledColors; + + setColorInterval( Qt::blue, Qt::yellow ); +} + +/*! + Build a color map with two stops at 0.0 and 1.0. + + \param color1 Color used for the minimum value of the value interval + \param color2 Color used for the maximum value of the value interval + \param format Preferred format for the color map +*/ +QwtLinearColorMap::QwtLinearColorMap( const QColor &color1, + const QColor &color2, QwtColorMap::Format format ): + QwtColorMap( format ) +{ + d_data = new PrivateData; + d_data->mode = ScaledColors; + setColorInterval( color1, color2 ); +} + +//! Destructor +QwtLinearColorMap::~QwtLinearColorMap() +{ + delete d_data; +} + +/*! + \brief Set the mode of the color map + + FixedColors means the color is calculated from the next lower + color stop. ScaledColors means the color is calculated + by interpolating the colors of the adjacent stops. + + \sa mode() +*/ +void QwtLinearColorMap::setMode( Mode mode ) +{ + d_data->mode = mode; +} + +/*! + \return Mode of the color map + \sa setMode() +*/ +QwtLinearColorMap::Mode QwtLinearColorMap::mode() const +{ + return d_data->mode; +} + +/*! + Set the color range + + Add stops at 0.0 and 1.0. + + \param color1 Color used for the minimum value of the value interval + \param color2 Color used for the maximum value of the value interval + + \sa color1(), color2() +*/ +void QwtLinearColorMap::setColorInterval( + const QColor &color1, const QColor &color2 ) +{ + d_data->colorStops = ColorStops(); + d_data->colorStops.insert( 0.0, color1 ); + d_data->colorStops.insert( 1.0, color2 ); +} + +/*! + Add a color stop + + The value has to be in the range [0.0, 1.0]. + F.e. a stop at position 17.0 for a range [10.0,20.0] must be + passed as: (17.0 - 10.0) / (20.0 - 10.0) + + \param value Value between [0.0, 1.0] + \param color Color stop +*/ +void QwtLinearColorMap::addColorStop( double value, const QColor& color ) +{ + if ( value >= 0.0 && value <= 1.0 ) + d_data->colorStops.insert( value, color ); +} + +/*! + \return Positions of color stops in increasing order +*/ +QVector QwtLinearColorMap::colorStops() const +{ + return d_data->colorStops.stops(); +} + +/*! + \return the first color of the color range + \sa setColorInterval() +*/ +QColor QwtLinearColorMap::color1() const +{ + return QColor( d_data->colorStops.rgb( d_data->mode, 0.0 ) ); +} + +/*! + \return the second color of the color range + \sa setColorInterval() +*/ +QColor QwtLinearColorMap::color2() const +{ + return QColor( d_data->colorStops.rgb( d_data->mode, 1.0 ) ); +} + +/*! + Map a value of a given interval into a RGB value + + \param interval Range for all values + \param value Value to map into a RGB value + + \return RGB value for value +*/ +QRgb QwtLinearColorMap::rgb( + const QwtInterval &interval, double value ) const +{ + if ( qIsNaN(value) ) + return qRgba(0, 0, 0, 0); + + const double width = interval.width(); + + double ratio = 0.0; + if ( width > 0.0 ) + ratio = ( value - interval.minValue() ) / width; + + return d_data->colorStops.rgb( d_data->mode, ratio ); +} + +/*! + \brief Map a value of a given interval into a color index + + \param interval Range for all values + \param value Value to map into a color index + + \return Index, between 0 and 255 +*/ +unsigned char QwtLinearColorMap::colorIndex( + const QwtInterval &interval, double value ) const +{ + const double width = interval.width(); + + if ( qIsNaN(value) || width <= 0.0 || value <= interval.minValue() ) + return 0; + + if ( value >= interval.maxValue() ) + return 255; + + const double ratio = ( value - interval.minValue() ) / width; + + unsigned char index; + if ( d_data->mode == FixedColors ) + index = static_cast( ratio * 255 ); // always floor + else + index = static_cast( qRound( ratio * 255 ) ); + + return index; +} + +class QwtAlphaColorMap::PrivateData +{ +public: + QColor color; + QRgb rgb; +}; + + +/*! + Constructor + \param color Color of the map +*/ +QwtAlphaColorMap::QwtAlphaColorMap( const QColor &color ): + QwtColorMap( QwtColorMap::RGB ) +{ + d_data = new PrivateData; + d_data->color = color; + d_data->rgb = color.rgb() & qRgba( 255, 255, 255, 0 ); +} + +//! Destructor +QwtAlphaColorMap::~QwtAlphaColorMap() +{ + delete d_data; +} + +/*! + Set the color + + \param color Color + \sa color() +*/ +void QwtAlphaColorMap::setColor( const QColor &color ) +{ + d_data->color = color; + d_data->rgb = color.rgb(); +} + +/*! + \return the color + \sa setColor() +*/ +QColor QwtAlphaColorMap::color() const +{ + return d_data->color; +} + +/*! + \brief Map a value of a given interval into a alpha value + + alpha := (value - interval.minValue()) / interval.width(); + + \param interval Range for all values + \param value Value to map into a RGB value + \return RGB value, with an alpha value +*/ +QRgb QwtAlphaColorMap::rgb( const QwtInterval &interval, double value ) const +{ + const double width = interval.width(); + if ( !qIsNaN(value) && width >= 0.0 ) + { + const double ratio = ( value - interval.minValue() ) / width; + int alpha = qRound( 255 * ratio ); + if ( alpha < 0 ) + alpha = 0; + if ( alpha > 255 ) + alpha = 255; + + return d_data->rgb | ( alpha << 24 ); + } + return d_data->rgb; +} + +/*! + Dummy function, needed to be implemented as it is pure virtual + in QwtColorMap. Color indices make no sense in combination with + an alpha channel. + + \return Always 0 +*/ +unsigned char QwtAlphaColorMap::colorIndex( + const QwtInterval &, double ) const +{ + return 0; +} diff --git a/qwt/src/qwt_color_map.h b/qwt/src/qwt_color_map.h new file mode 100644 index 000000000..91a92bd0c --- /dev/null +++ b/qwt/src/qwt_color_map.h @@ -0,0 +1,200 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_COLOR_MAP_H +#define QWT_COLOR_MAP_H + +#include "qwt_global.h" +#include "qwt_interval.h" +#include +#include + +/*! + \brief QwtColorMap is used to map values into colors. + + For displaying 3D data on a 2D plane the 3rd dimension is often + displayed using colors, like f.e in a spectrogram. + + Each color map is optimized to return colors for only one of the + following image formats: + + - QImage::Format_Indexed8\n + - QImage::Format_ARGB32\n + + \sa QwtPlotSpectrogram, QwtScaleWidget +*/ + +class QWT_EXPORT QwtColorMap +{ +public: + /*! + Format for color mapping + \sa rgb(), colorIndex(), colorTable() + */ + + enum Format + { + //! The map is intended to map into RGB values. + RGB, + + /*! + The map is intended to map into 8 bit values, that + are indices into the color table. + */ + Indexed + }; + + QwtColorMap( Format = QwtColorMap::RGB ); + virtual ~QwtColorMap(); + + Format format() const; + + /*! + Map a value of a given interval into a RGB value. + + \param interval Range for the values + \param value Value + \return RGB value, corresponding to value + */ + virtual QRgb rgb( const QwtInterval &interval, + double value ) const = 0; + + /*! + Map a value of a given interval into a color index + + \param interval Range for the values + \param value Value + \return color index, corresponding to value + */ + virtual unsigned char colorIndex( + const QwtInterval &interval, double value ) const = 0; + + QColor color( const QwtInterval &, double value ) const; + virtual QVector colorTable( const QwtInterval & ) const; + +private: + Format d_format; +}; + +/*! + \brief QwtLinearColorMap builds a color map from color stops. + + A color stop is a color at a specific position. The valid + range for the positions is [0.0, 1.0]. When mapping a value + into a color it is translated into this interval according to mode(). +*/ +class QWT_EXPORT QwtLinearColorMap: public QwtColorMap +{ +public: + /*! + Mode of color map + \sa setMode(), mode() + */ + enum Mode + { + //! Return the color from the next lower color stop + FixedColors, + + //! Interpolating the colors of the adjacent stops. + ScaledColors + }; + + QwtLinearColorMap( QwtColorMap::Format = QwtColorMap::RGB ); + QwtLinearColorMap( const QColor &from, const QColor &to, + QwtColorMap::Format = QwtColorMap::RGB ); + + virtual ~QwtLinearColorMap(); + + void setMode( Mode ); + Mode mode() const; + + void setColorInterval( const QColor &color1, const QColor &color2 ); + void addColorStop( double value, const QColor& ); + QVector colorStops() const; + + QColor color1() const; + QColor color2() const; + + virtual QRgb rgb( const QwtInterval &, double value ) const; + virtual unsigned char colorIndex( + const QwtInterval &, double value ) const; + + class ColorStops; + +private: + // Disabled copy constructor and operator= + QwtLinearColorMap( const QwtLinearColorMap & ); + QwtLinearColorMap &operator=( const QwtLinearColorMap & ); + + class PrivateData; + PrivateData *d_data; +}; + +/*! + \brief QwtAlphaColorMap varies the alpha value of a color +*/ +class QWT_EXPORT QwtAlphaColorMap: public QwtColorMap +{ +public: + QwtAlphaColorMap( const QColor & = QColor( Qt::gray ) ); + virtual ~QwtAlphaColorMap(); + + void setColor( const QColor & ); + QColor color() const; + + virtual QRgb rgb( const QwtInterval &, double value ) const; + +private: + QwtAlphaColorMap( const QwtAlphaColorMap & ); + QwtAlphaColorMap &operator=( const QwtAlphaColorMap & ); + + virtual unsigned char colorIndex( + const QwtInterval &, double value ) const; + + class PrivateData; + PrivateData *d_data; +}; + + +/*! + Map a value into a color + + \param interval Valid interval for values + \param value Value + + \return Color corresponding to value + + \warning This method is slow for Indexed color maps. If it is + necessary to map many values, its better to get the + color table once and find the color using colorIndex(). +*/ +inline QColor QwtColorMap::color( + const QwtInterval &interval, double value ) const +{ + if ( d_format == RGB ) + { + return QColor( rgb( interval, value ) ); + } + else + { + const unsigned int index = colorIndex( interval, value ); + return colorTable( interval )[index]; // slow + } +} + +/*! + \return Intended format of the color map + \sa Format +*/ +inline QwtColorMap::Format QwtColorMap::format() const +{ + return d_format; +} + +#endif diff --git a/qwt/src/qwt_column_symbol.cpp b/qwt/src/qwt_column_symbol.cpp new file mode 100644 index 000000000..d6f0f1a63 --- /dev/null +++ b/qwt/src/qwt_column_symbol.cpp @@ -0,0 +1,293 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_column_symbol.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include +#include + +static void qwtDrawBox( QPainter *p, const QRectF &rect, + const QPalette &pal, double lw ) +{ + if ( lw > 0.0 ) + { + if ( rect.width() == 0.0 ) + { + p->setPen( pal.dark().color() ); + p->drawLine( rect.topLeft(), rect.bottomLeft() ); + return; + } + + if ( rect.height() == 0.0 ) + { + p->setPen( pal.dark().color() ); + p->drawLine( rect.topLeft(), rect.topRight() ); + return; + } + + lw = qMin( lw, rect.height() / 2.0 - 1.0 ); + lw = qMin( lw, rect.width() / 2.0 - 1.0 ); + + const QRectF outerRect = rect.adjusted( 0, 0, 1, 1 ); + QPolygonF polygon( outerRect ); + + if ( outerRect.width() > 2 * lw && + outerRect.height() > 2 * lw ) + { + const QRectF innerRect = outerRect.adjusted( lw, lw, -lw, -lw ); + polygon = polygon.subtracted( innerRect ); + } + + p->setPen( Qt::NoPen ); + + p->setBrush( pal.dark() ); + p->drawPolygon( polygon ); + } + + const QRectF windowRect = rect.adjusted( lw, lw, -lw + 1, -lw + 1 ); + if ( windowRect.isValid() ) + p->fillRect( windowRect, pal.window() ); +} + +static void qwtDrawPanel( QPainter *painter, const QRectF &rect, + const QPalette &pal, double lw ) +{ + if ( lw > 0.0 ) + { + if ( rect.width() == 0.0 ) + { + painter->setPen( pal.window().color() ); + painter->drawLine( rect.topLeft(), rect.bottomLeft() ); + return; + } + + if ( rect.height() == 0.0 ) + { + painter->setPen( pal.window().color() ); + painter->drawLine( rect.topLeft(), rect.topRight() ); + return; + } + + lw = qMin( lw, rect.height() / 2.0 - 1.0 ); + lw = qMin( lw, rect.width() / 2.0 - 1.0 ); + + const QRectF outerRect = rect.adjusted( 0, 0, 1, 1 ); + const QRectF innerRect = outerRect.adjusted( lw, lw, -lw, -lw ); + + QPolygonF lines[2]; + + lines[0] += outerRect.bottomLeft(); + lines[0] += outerRect.topLeft(); + lines[0] += outerRect.topRight(); + lines[0] += innerRect.topRight(); + lines[0] += innerRect.topLeft(); + lines[0] += innerRect.bottomLeft(); + + lines[1] += outerRect.topRight(); + lines[1] += outerRect.bottomRight(); + lines[1] += outerRect.bottomLeft(); + lines[1] += innerRect.bottomLeft(); + lines[1] += innerRect.bottomRight(); + lines[1] += innerRect.topRight(); + + painter->setPen( Qt::NoPen ); + + painter->setBrush( pal.light() ); + painter->drawPolygon( lines[0] ); + painter->setBrush( pal.dark() ); + painter->drawPolygon( lines[1] ); + } + + painter->fillRect( rect.adjusted( lw, lw, -lw + 1, -lw + 1 ), pal.window() ); +} + +class QwtColumnSymbol::PrivateData +{ +public: + PrivateData(): + style( QwtColumnSymbol::Box ), + frameStyle( QwtColumnSymbol::Raised ), + lineWidth( 2 ) + { + palette = QPalette( Qt::gray ); + } + + QwtColumnSymbol::Style style; + QwtColumnSymbol::FrameStyle frameStyle; + + QPalette palette; + int lineWidth; +}; + +/*! + Constructor + + \param style Style of the symbol + \sa setStyle(), style(), Style +*/ +QwtColumnSymbol::QwtColumnSymbol( Style style ) +{ + d_data = new PrivateData(); + d_data->style = style; +} + +//! Destructor +QwtColumnSymbol::~QwtColumnSymbol() +{ + delete d_data; +} + +/*! + Specify the symbol style + + \param style Style + \sa style(), setPalette() +*/ +void QwtColumnSymbol::setStyle( Style style ) +{ + d_data->style = style; +} + +/*! + \return Current symbol style + \sa setStyle() +*/ +QwtColumnSymbol::Style QwtColumnSymbol::style() const +{ + return d_data->style; +} + +/*! + Assign a palette for the symbol + + \param palette Palette + \sa palette(), setStyle() +*/ +void QwtColumnSymbol::setPalette( const QPalette &palette ) +{ + d_data->palette = palette; +} + +/*! + \return Current palette + \sa setPalette() +*/ +const QPalette& QwtColumnSymbol::palette() const +{ + return d_data->palette; +} + +/*! + Set the frame, that is used for the Box style. + + \param frameStyle Frame style + \sa frameStyle(), setLineWidth(), setStyle() +*/ +void QwtColumnSymbol::setFrameStyle( FrameStyle frameStyle ) +{ + d_data->frameStyle = frameStyle; +} + +/*! + \return Current frame style, that is used for the Box style. + \sa setFrameStyle(), lineWidth(), setStyle() +*/ +QwtColumnSymbol::FrameStyle QwtColumnSymbol::frameStyle() const +{ + return d_data->frameStyle; +} + +/*! + Set the line width of the frame, that is used for the Box style. + + \param width Width + \sa lineWidth(), setFrameStyle() +*/ +void QwtColumnSymbol::setLineWidth( int width ) +{ + if ( width < 0 ) + width = 0; + + d_data->lineWidth = width; +} + +/*! + \return Line width of the frame, that is used for the Box style. + \sa setLineWidth(), frameStyle(), setStyle() +*/ +int QwtColumnSymbol::lineWidth() const +{ + return d_data->lineWidth; +} + +/*! + Draw the symbol depending on its style. + + \param painter Painter + \param rect Directed rectangle + + \sa drawBox() +*/ +void QwtColumnSymbol::draw( QPainter *painter, + const QwtColumnRect &rect ) const +{ + painter->save(); + + switch ( d_data->style ) + { + case QwtColumnSymbol::Box: + { + drawBox( painter, rect ); + break; + } + default:; + } + + painter->restore(); +} + +/*! + Draw the symbol when it is in Box style. + + \param painter Painter + \param rect Directed rectangle + + \sa draw() +*/ +void QwtColumnSymbol::drawBox( QPainter *painter, + const QwtColumnRect &rect ) const +{ + QRectF r = rect.toRect(); + if ( QwtPainter::roundingAlignment( painter ) ) + { + r.setLeft( qRound( r.left() ) ); + r.setRight( qRound( r.right() ) ); + r.setTop( qRound( r.top() ) ); + r.setBottom( qRound( r.bottom() ) ); + } + + switch ( d_data->frameStyle ) + { + case QwtColumnSymbol::Raised: + { + qwtDrawPanel( painter, r, d_data->palette, d_data->lineWidth ); + break; + } + case QwtColumnSymbol::Plain: + { + qwtDrawBox( painter, r, d_data->palette, d_data->lineWidth ); + break; + } + default: + { + painter->fillRect( r, d_data->palette.window() ); + } + } +} diff --git a/qwt/src/qwt_column_symbol.h b/qwt/src/qwt_column_symbol.h new file mode 100644 index 000000000..918fe4a3c --- /dev/null +++ b/qwt/src/qwt_column_symbol.h @@ -0,0 +1,161 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_COLUMN_SYMBOL_H +#define QWT_COLUMN_SYMBOL_H + +#include "qwt_global.h" +#include "qwt_interval.h" +#include +#include +#include + +class QPainter; +class QPalette; +class QRect; +class QwtText; + +/*! + \brief Directed rectangle representing bounding rectangle and orientation + of a column. +*/ +class QWT_EXPORT QwtColumnRect +{ +public: + //! Direction of the column + enum Direction + { + //! From left to right + LeftToRight, + + //! From right to left + RightToLeft, + + //! From bottom to top + BottomToTop, + + //! From top to bottom + TopToBottom + }; + + //! Build an rectangle with invalid intervals directed BottomToTop. + QwtColumnRect(): + direction( BottomToTop ) + { + } + + //! \return A normalized QRect built from the intervals + QRectF toRect() const + { + QRectF r( hInterval.minValue(), vInterval.minValue(), + hInterval.maxValue() - hInterval.minValue(), + vInterval.maxValue() - vInterval.minValue() ); + r = r.normalized(); + + if ( hInterval.borderFlags() & QwtInterval::ExcludeMinimum ) + r.adjust( 1, 0, 0, 0 ); + if ( hInterval.borderFlags() & QwtInterval::ExcludeMaximum ) + r.adjust( 0, 0, -1, 0 ); + if ( vInterval.borderFlags() & QwtInterval::ExcludeMinimum ) + r.adjust( 0, 1, 0, 0 ); + if ( vInterval.borderFlags() & QwtInterval::ExcludeMaximum ) + r.adjust( 0, 0, 0, -1 ); + + return r; + } + + //! \return Orientation + Qt::Orientation orientation() const + { + if ( direction == LeftToRight || direction == RightToLeft ) + return Qt::Horizontal; + + return Qt::Vertical; + } + + //! Interval for the horizontal coordinates + QwtInterval hInterval; + + //! Interval for the vertical coordinates + QwtInterval vInterval; + + //! Direction + Direction direction; +}; + +//! A drawing primitive for columns +class QWT_EXPORT QwtColumnSymbol +{ +public: + /*! + Style + \sa setStyle(), style() + */ + enum Style + { + //! No Style, the symbol draws nothing + NoStyle = -1, + + /*! + The column is painted with a frame depending on the frameStyle() + and lineWidth() using the palette(). + */ + Box, + + /*! + Styles >= QwtColumnSymbol::UserStyle are reserved for derived + classes of QwtColumnSymbol that overload draw() with + additional application specific symbol types. + */ + UserStyle = 1000 + }; + + /*! + Frame Style used in Box style(). + \sa Style, setFrameStyle(), frameStyle(), setStyle(), setPalette() + */ + enum FrameStyle + { + //! No frame + NoFrame, + + //! A plain frame style + Plain, + + //! A raised frame style + Raised + }; + +public: + QwtColumnSymbol( Style = NoStyle ); + virtual ~QwtColumnSymbol(); + + void setFrameStyle( FrameStyle style ); + FrameStyle frameStyle() const; + + void setLineWidth( int width ); + int lineWidth() const; + + void setPalette( const QPalette & ); + const QPalette &palette() const; + + void setStyle( Style ); + Style style() const; + + virtual void draw( QPainter *, const QwtColumnRect & ) const; + +protected: + void drawBox( QPainter *, const QwtColumnRect & ) const; + +private: + class PrivateData; + PrivateData* d_data; +}; + +#endif diff --git a/qwt/src/qwt_compass.cpp b/qwt/src/qwt_compass.cpp new file mode 100644 index 000000000..4e2c9ffdf --- /dev/null +++ b/qwt/src/qwt_compass.cpp @@ -0,0 +1,308 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_compass.h" +#include "qwt_compass_rose.h" +#include "qwt_math.h" +#include "qwt_scale_draw.h" +#include "qwt_painter.h" +#include "qwt_dial_needle.h" +#include +#include +#include + +/*! + \brief Constructor + + Initializes a label map for multiples of 45 degrees + */ +QwtCompassScaleDraw::QwtCompassScaleDraw() +{ + enableComponent( QwtAbstractScaleDraw::Backbone, false ); + enableComponent( QwtAbstractScaleDraw::Ticks, false ); + + d_labelMap.insert( 0.0, QString::fromLatin1( "N" ) ); + d_labelMap.insert( 45.0, QString::fromLatin1( "NE" ) ); + d_labelMap.insert( 90.0, QString::fromLatin1( "E" ) ); + d_labelMap.insert( 135.0, QString::fromLatin1( "SE" ) ); + d_labelMap.insert( 180.0, QString::fromLatin1( "S" ) ); + d_labelMap.insert( 225.0, QString::fromLatin1( "SW" ) ); + d_labelMap.insert( 270.0, QString::fromLatin1( "W" ) ); + d_labelMap.insert( 315.0, QString::fromLatin1( "NW" ) ); + +#if 0 + d_labelMap.insert( 22.5, QString::fromLatin1( "NNE" ) ); + d_labelMap.insert( 67.5, QString::fromLatin1( "NEE" ) ); + d_labelMap.insert( 112.5, QString::fromLatin1( "SEE" ) ); + d_labelMap.insert( 157.5, QString::fromLatin1( "SSE" ) ); + d_labelMap.insert( 202.5, QString::fromLatin1( "SSW" ) ); + d_labelMap.insert( 247.5, QString::fromLatin1( "SWW" ) ); + d_labelMap.insert( 292.5, QString::fromLatin1( "NWW" ) ); + d_labelMap.insert( 337.5, QString::fromLatin1( "NNW" ) ); +#endif +} + +/*! + \brief Constructor + + \param map Value to label map + */ +QwtCompassScaleDraw::QwtCompassScaleDraw( const QMap &map ): + d_labelMap( map ) +{ + enableComponent( QwtAbstractScaleDraw::Backbone, false ); + enableComponent( QwtAbstractScaleDraw::Ticks, false ); +} + +/*! + \brief Set a map, mapping values to labels + \param map Value to label map + + The values of the major ticks are found by looking into this + map. The default map consists of the labels N, NE, E, SE, S, SW, W, NW. + + \warning The map will have no effect for values that are no major + tick values. Major ticks can be changed by QwtScaleDraw::setScale + + \sa labelMap(), scaleDraw(), setScale() +*/ +void QwtCompassScaleDraw::setLabelMap( const QMap &map ) +{ + d_labelMap = map; +} + + +/*! + \return map, mapping values to labels + \sa setLabelMap() +*/ +QMap QwtCompassScaleDraw::labelMap() const +{ + return d_labelMap; +} + +/*! + Map a value to a corresponding label + + \param value Value that will be mapped + + label() looks in the labelMap() for a corresponding label for value + or returns an null text. + + \return Label, or QString::null + \sa labelMap(), setLabelMap() +*/ + +QwtText QwtCompassScaleDraw::label( double value ) const +{ + if ( qFuzzyCompare( value + 1.0, 1.0 ) ) + value = 0.0; + + if ( value < 0.0 ) + value += 360.0; + + if ( d_labelMap.contains( value ) ) + return d_labelMap[value]; + + return QwtText(); +} + +class QwtCompass::PrivateData +{ +public: + PrivateData(): + rose( NULL ) + { + } + + ~PrivateData() + { + delete rose; + } + + QwtCompassRose *rose; +}; + +/*! + \brief Constructor + \param parent Parent widget + + Create a compass widget with a scale, no needle and no rose. + The default origin is 270.0 with no valid value. It accepts + mouse and keyboard inputs and has no step size. The default mode + is QwtDial::RotateNeedle. +*/ +QwtCompass::QwtCompass( QWidget* parent ): + QwtDial( parent ) +{ + d_data = new PrivateData; + + setScaleDraw( new QwtCompassScaleDraw() ); + + setOrigin( 270.0 ); + setWrapping( true ); + + setScaleMaxMajor( 36 ); + setScaleMaxMinor( 10 ); + + setScale( 0.0, 360.0 ); // degrees as default + setTotalSteps( 360 ); +} + +//! Destructor +QwtCompass::~QwtCompass() +{ + delete d_data; +} + + +/*! + Draw the contents of the scale + + \param painter Painter + \param center Center of the content circle + \param radius Radius of the content circle +*/ +void QwtCompass::drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const +{ + QPalette::ColorGroup cg; + if ( isEnabled() ) + cg = hasFocus() ? QPalette::Active : QPalette::Inactive; + else + cg = QPalette::Disabled; + + double north = origin(); + if ( isValid() ) + { + if ( mode() == RotateScale ) + north -= value(); + } + + const int margin = 4; + drawRose( painter, center, radius - margin, 360.0 - north, cg ); +} + +/*! + Draw the compass rose + + \param painter Painter + \param center Center of the compass + \param radius of the circle, where to paint the rose + \param north Direction pointing north, in degrees counter clockwise + \param cg Color group +*/ +void QwtCompass::drawRose( QPainter *painter, const QPointF ¢er, + double radius, double north, QPalette::ColorGroup cg ) const +{ + if ( d_data->rose ) + d_data->rose->draw( painter, center, radius, north, cg ); +} + +/*! + Set a rose for the compass + \param rose Compass rose + \warning The rose will be deleted, when a different rose is + set or in ~QwtCompass + \sa rose() +*/ +void QwtCompass::setRose( QwtCompassRose *rose ) +{ + if ( rose != d_data->rose ) + { + if ( d_data->rose ) + delete d_data->rose; + + d_data->rose = rose; + update(); + } +} + +/*! + \return rose + \sa setRose() +*/ +const QwtCompassRose *QwtCompass::rose() const +{ + return d_data->rose; +} + +/*! + \return rose + \sa setRose() +*/ +QwtCompassRose *QwtCompass::rose() +{ + return d_data->rose; +} + +/*! + Handles key events + + Beside the keys described in QwtDial::keyPressEvent numbers + from 1-9 (without 5) set the direction according to their + position on the num pad. + + \sa isReadOnly() +*/ +void QwtCompass::keyPressEvent( QKeyEvent *kev ) +{ + if ( isReadOnly() ) + return; + +#if 0 + if ( kev->key() == Key_5 ) + { + invalidate(); // signal ??? + return; + } +#endif + + double newValue = value(); + + if ( kev->key() >= Qt::Key_1 && kev->key() <= Qt::Key_9 ) + { + if ( mode() != RotateNeedle || kev->key() == Qt::Key_5 ) + return; + + switch ( kev->key() ) + { + case Qt::Key_6: + newValue = 180.0 * 0.0; + break; + case Qt::Key_3: + newValue = 180.0 * 0.25; + break; + case Qt::Key_2: + newValue = 180.0 * 0.5; + break; + case Qt::Key_1: + newValue = 180.0 * 0.75; + break; + case Qt::Key_4: + newValue = 180.0 * 1.0; + break; + case Qt::Key_7: + newValue = 180.0 * 1.25; + break; + case Qt::Key_8: + newValue = 180.0 * 1.5; + break; + case Qt::Key_9: + newValue = 180.0 * 1.75; + break; + } + newValue -= origin(); + setValue( newValue ); + } + else + { + QwtDial::keyPressEvent( kev ); + } +} diff --git a/qwt/src/qwt_compass.h b/qwt/src/qwt_compass.h new file mode 100644 index 000000000..b9a3c95bf --- /dev/null +++ b/qwt/src/qwt_compass.h @@ -0,0 +1,83 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_COMPASS_H +#define QWT_COMPASS_H 1 + +#include "qwt_global.h" +#include "qwt_dial.h" +#include "qwt_round_scale_draw.h" +#include +#include + +class QwtCompassRose; + +/*! + \brief A special scale draw made for QwtCompass + + QwtCompassScaleDraw maps values to strings using + a special map, that can be modified by the application + + The default map consists of the labels N, NE, E, SE, S, SW, W, NW. + + \sa QwtCompass +*/ +class QWT_EXPORT QwtCompassScaleDraw: public QwtRoundScaleDraw +{ +public: + explicit QwtCompassScaleDraw(); + explicit QwtCompassScaleDraw( const QMap &map ); + + void setLabelMap( const QMap &map ); + QMap labelMap() const; + + virtual QwtText label( double value ) const; + +private: + QMap d_labelMap; +}; + +/*! + \brief A Compass Widget + + QwtCompass is a widget to display and enter directions. It consists + of a scale, an optional needle and rose. + + \image html dials1.png + + \note The examples/dials example shows how to use QwtCompass. +*/ + +class QWT_EXPORT QwtCompass: public QwtDial +{ + Q_OBJECT + +public: + explicit QwtCompass( QWidget* parent = NULL ); + virtual ~QwtCompass(); + + void setRose( QwtCompassRose *rose ); + const QwtCompassRose *rose() const; + QwtCompassRose *rose(); + +protected: + virtual void drawRose( QPainter *, const QPointF ¢er, + double radius, double north, QPalette::ColorGroup ) const; + + virtual void drawScaleContents( QPainter *, + const QPointF ¢er, double radius ) const; + + virtual void keyPressEvent( QKeyEvent * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_compass_rose.cpp b/qwt/src/qwt_compass_rose.cpp new file mode 100644 index 000000000..21a35f244 --- /dev/null +++ b/qwt/src/qwt_compass_rose.cpp @@ -0,0 +1,269 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_compass_rose.h" +#include "qwt_point_polar.h" +#include "qwt_painter.h" +#include + +static QPointF qwtIntersection( + QPointF p11, QPointF p12, QPointF p21, QPointF p22 ) +{ + const QLineF line1( p11, p12 ); + const QLineF line2( p21, p22 ); + + QPointF pos; + if ( line1.intersect( line2, &pos ) == QLineF::NoIntersection ) + return QPointF(); + + return pos; +} + +class QwtSimpleCompassRose::PrivateData +{ +public: + PrivateData(): + width( 0.2 ), + numThorns( 8 ), + numThornLevels( -1 ), + shrinkFactor( 0.9 ) + { + } + + double width; + int numThorns; + int numThornLevels; + double shrinkFactor; +}; + +/*! + Constructor + + \param numThorns Number of thorns + \param numThornLevels Number of thorn levels +*/ +QwtSimpleCompassRose::QwtSimpleCompassRose( + int numThorns, int numThornLevels ) +{ + d_data = new PrivateData(); + d_data->numThorns = numThorns; + d_data->numThornLevels = numThornLevels; + + const QColor dark( 128, 128, 255 ); + const QColor light( 192, 255, 255 ); + + QPalette palette; + palette.setColor( QPalette::Dark, dark ); + palette.setColor( QPalette::Light, light ); + + setPalette( palette ); +} + +//! Destructor +QwtSimpleCompassRose::~QwtSimpleCompassRose() +{ + delete d_data; +} + +/*! + Set the Factor how to shrink the thorns with each level + The default value is 0.9. + + \param factor Shrink factor + \sa shrinkFactor() +*/ +void QwtSimpleCompassRose::setShrinkFactor( double factor ) +{ + d_data->shrinkFactor = factor; +} + +/*! + \return Factor how to shrink the thorns with each level + \sa setShrinkFactor() +*/ +double QwtSimpleCompassRose::shrinkFactor() const +{ + return d_data->shrinkFactor; +} + +/*! + Draw the rose + + \param painter Painter + \param center Center point + \param radius Radius of the rose + \param north Position + \param cg Color group +*/ +void QwtSimpleCompassRose::draw( QPainter *painter, const QPointF ¢er, + double radius, double north, QPalette::ColorGroup cg ) const +{ + QPalette pal = palette(); + pal.setCurrentColorGroup( cg ); + + drawRose( painter, pal, center, radius, north, d_data->width, + d_data->numThorns, d_data->numThornLevels, d_data->shrinkFactor ); +} + +/*! + Draw the rose + + \param painter Painter + \param palette Palette + \param center Center of the rose + \param radius Radius of the rose + \param north Position pointing to north + \param width Width of the rose + \param numThorns Number of thorns + \param numThornLevels Number of thorn levels + \param shrinkFactor Factor to shrink the thorns with each level +*/ +void QwtSimpleCompassRose::drawRose( + QPainter *painter, + const QPalette &palette, + const QPointF ¢er, double radius, double north, double width, + int numThorns, int numThornLevels, double shrinkFactor ) +{ + if ( numThorns < 4 ) + numThorns = 4; + + if ( numThorns % 4 ) + numThorns += 4 - numThorns % 4; + + if ( numThornLevels <= 0 ) + numThornLevels = numThorns / 4; + + if ( shrinkFactor >= 1.0 ) + shrinkFactor = 1.0; + + if ( shrinkFactor <= 0.5 ) + shrinkFactor = 0.5; + + painter->save(); + + painter->setPen( Qt::NoPen ); + + for ( int j = 1; j <= numThornLevels; j++ ) + { + double step = qPow( 2.0, j ) * M_PI / numThorns; + if ( step > M_PI_2 ) + break; + + double r = radius; + for ( int k = 0; k < 3; k++ ) + { + if ( j + k < numThornLevels ) + r *= shrinkFactor; + } + + double leafWidth = r * width; + if ( 2.0 * M_PI / step > 32 ) + leafWidth = 16; + + const double origin = qwtRadians( north ); + for ( double angle = origin; + angle < 2.0 * M_PI + origin; angle += step ) + { + const QPointF p = qwtPolar2Pos( center, r, angle ); + const QPointF p1 = qwtPolar2Pos( center, leafWidth, angle + M_PI_2 ); + const QPointF p2 = qwtPolar2Pos( center, leafWidth, angle - M_PI_2 ); + const QPointF p3 = qwtPolar2Pos( center, r, angle + step / 2.0 ); + const QPointF p4 = qwtPolar2Pos( center, r, angle - step / 2.0 ); + + QPainterPath darkPath; + darkPath.moveTo( center ); + darkPath.lineTo( p ); + darkPath.lineTo( qwtIntersection( center, p3, p1, p ) ); + + painter->setBrush( palette.brush( QPalette::Dark ) ); + painter->drawPath( darkPath ); + + QPainterPath lightPath; + lightPath.moveTo( center ); + lightPath.lineTo( p ); + lightPath.lineTo( qwtIntersection( center, p4, p2, p ) ); + + painter->setBrush( palette.brush( QPalette::Light ) ); + painter->drawPath( lightPath ); + } + } + painter->restore(); +} + +/*! + Set the width of the rose heads. Lower value make thinner heads. + The range is limited from 0.03 to 0.4. + + \param width Width +*/ +void QwtSimpleCompassRose::setWidth( double width ) +{ + d_data->width = width; + if ( d_data->width < 0.03 ) + d_data->width = 0.03; + + if ( d_data->width > 0.4 ) + d_data->width = 0.4; +} + +/*! + \return Width of the rose + \sa setWidth() + */ +double QwtSimpleCompassRose::width() const +{ + return d_data->width; +} + +/*! + Set the number of thorns on one level + The number is aligned to a multiple of 4, with a minimum of 4 + + \param numThorns Number of thorns + \sa numThorns(), setNumThornLevels() +*/ +void QwtSimpleCompassRose::setNumThorns( int numThorns ) +{ + if ( numThorns < 4 ) + numThorns = 4; + + if ( numThorns % 4 ) + numThorns += 4 - numThorns % 4; + + d_data->numThorns = numThorns; +} + +/*! + \return Number of thorns + \sa setNumThorns(), setNumThornLevels() +*/ +int QwtSimpleCompassRose::numThorns() const +{ + return d_data->numThorns; +} + +/*! + Set the of thorns levels + + \param numThornLevels Number of thorns levels + \sa setNumThorns(), numThornLevels() +*/ +void QwtSimpleCompassRose::setNumThornLevels( int numThornLevels ) +{ + d_data->numThornLevels = numThornLevels; +} + +/*! + \return Number of thorn levels + \sa setNumThorns(), setNumThornLevels() +*/ +int QwtSimpleCompassRose::numThornLevels() const +{ + return d_data->numThornLevels; +} diff --git a/qwt/src/qwt_compass_rose.h b/qwt/src/qwt_compass_rose.h new file mode 100644 index 000000000..9b715dfe0 --- /dev/null +++ b/qwt/src/qwt_compass_rose.h @@ -0,0 +1,89 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_COMPASS_ROSE_H +#define QWT_COMPASS_ROSE_H 1 + +#include "qwt_global.h" +#include + +class QPainter; + +/*! + \brief Abstract base class for a compass rose +*/ +class QWT_EXPORT QwtCompassRose +{ +public: + //! Destructor + virtual ~QwtCompassRose() {}; + + //! Assign a palette + virtual void setPalette( const QPalette &p ) + { + d_palette = p; + } + + //! \return Current palette + const QPalette &palette() const + { + return d_palette; + } + + /*! + Draw the rose + + \param painter Painter + \param center Center point + \param radius Radius of the rose + \param north Position + \param colorGroup Color group + */ + virtual void draw( QPainter *painter, + const QPointF ¢er, double radius, double north, + QPalette::ColorGroup colorGroup = QPalette::Active ) const = 0; + +private: + QPalette d_palette; +}; + +/*! + \brief A simple rose for QwtCompass +*/ +class QWT_EXPORT QwtSimpleCompassRose: public QwtCompassRose +{ +public: + QwtSimpleCompassRose( int numThorns = 8, int numThornLevels = -1 ); + virtual ~QwtSimpleCompassRose(); + + void setWidth( double w ); + double width() const; + + void setNumThorns( int count ); + int numThorns() const; + + void setNumThornLevels( int count ); + int numThornLevels() const; + + void setShrinkFactor( double factor ); + double shrinkFactor() const; + + virtual void draw( QPainter *, const QPointF ¢er, double radius, + double north, QPalette::ColorGroup = QPalette::Active ) const; + + static void drawRose( QPainter *, const QPalette &, + const QPointF ¢er, double radius, double origin, double width, + int numThorns, int numThornLevels, double shrinkFactor ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_compat.h b/qwt/src/qwt_compat.h new file mode 100644 index 000000000..c97cf6b9c --- /dev/null +++ b/qwt/src/qwt_compat.h @@ -0,0 +1,42 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef _QWT_COMPAT_H_ +#define _QWT_COMPAT_H_ + +#include "qwt_global.h" +#include "qwt_interval.h" +#include "qwt_point_3d.h" +#include +#include +#include +#include +#include +#include + +// A couple of definition for Qwt5 compatibility + +#define qwtMax qMax +#define qwtMin qMin +#define qwtAbs qAbs +#define qwtRound qRound + +#define QwtArray QVector + +typedef QList QwtValueList; +typedef QPointF QwtDoublePoint; +typedef QSizeF QwtDoubleSize; +typedef QRectF QwtDoubleRect; + +typedef QPolygon QwtPolygon; +typedef QPolygonF QwtPolygonF; +typedef QwtInterval QwtDoubleInterval; +typedef QwtPoint3D QwtDoublePoint3D; + +#endif diff --git a/qwt/src/qwt_counter.cpp b/qwt/src/qwt_counter.cpp new file mode 100644 index 000000000..31c05c8df --- /dev/null +++ b/qwt/src/qwt_counter.cpp @@ -0,0 +1,785 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_arrow_button.h" +#include "qwt_math.h" +#include "qwt_counter.h" +#include +#include +#include +#include +#include + +class QwtCounter::PrivateData +{ +public: + PrivateData(): + minimum( 0.0 ), + maximum( 0.0 ), + singleStep( 1.0 ), + isValid( false ), + value( 0.0 ), + wrapping( false ) + { + increment[Button1] = 1; + increment[Button2] = 10; + increment[Button3] = 100; + } + + QwtArrowButton *buttonDown[ButtonCnt]; + QwtArrowButton *buttonUp[ButtonCnt]; + QLineEdit *valueEdit; + + int increment[ButtonCnt]; + int numButtons; + + double minimum; + double maximum; + double singleStep; + + bool isValid; + double value; + + bool wrapping; +}; + +/*! + The counter is initialized with a range is set to [0.0, 1.0] with + 0.01 as single step size. The value is invalid. + + The default number of buttons is set to 2. The default increments are: + \li Button 1: 1 step + \li Button 2: 10 steps + \li Button 3: 100 steps + + \param parent + */ +QwtCounter::QwtCounter( QWidget *parent ): + QWidget( parent ) +{ + initCounter(); +} + +void QwtCounter::initCounter() +{ + d_data = new PrivateData; + + QHBoxLayout *layout = new QHBoxLayout( this ); + layout->setSpacing( 0 ); + layout->setMargin( 0 ); + + for ( int i = ButtonCnt - 1; i >= 0; i-- ) + { + QwtArrowButton *btn = + new QwtArrowButton( i + 1, Qt::DownArrow, this ); + btn->setFocusPolicy( Qt::NoFocus ); + btn->installEventFilter( this ); + layout->addWidget( btn ); + + connect( btn, SIGNAL( released() ), SLOT( btnReleased() ) ); + connect( btn, SIGNAL( clicked() ), SLOT( btnClicked() ) ); + + d_data->buttonDown[i] = btn; + } + + d_data->valueEdit = new QLineEdit( this ); + d_data->valueEdit->setReadOnly( false ); + d_data->valueEdit->setValidator( new QDoubleValidator( d_data->valueEdit ) ); + layout->addWidget( d_data->valueEdit ); + + connect( d_data->valueEdit, SIGNAL( editingFinished() ), + SLOT( textChanged() ) ); + + layout->setStretchFactor( d_data->valueEdit, 10 ); + + for ( int i = 0; i < ButtonCnt; i++ ) + { + QwtArrowButton *btn = + new QwtArrowButton( i + 1, Qt::UpArrow, this ); + btn->setFocusPolicy( Qt::NoFocus ); + btn->installEventFilter( this ); + layout->addWidget( btn ); + + connect( btn, SIGNAL( released() ), SLOT( btnReleased() ) ); + connect( btn, SIGNAL( clicked() ), SLOT( btnClicked() ) ); + + d_data->buttonUp[i] = btn; + } + + setNumButtons( 2 ); + setRange( 0.0, 1.0 ); + setSingleStep( 0.001 ); + setValue( 0.0 ); + + setSizePolicy( + QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); + + setFocusProxy( d_data->valueEdit ); + setFocusPolicy( Qt::StrongFocus ); +} + +//! Destructor +QwtCounter::~QwtCounter() +{ + delete d_data; +} + +/*! + Set the counter to be in valid/invalid state + + When the counter is set to invalid, no numbers are displayed and + the buttons are disabled. + + \param on If true the counter will be set as valid + + \sa setValue(), isValid() +*/ +void QwtCounter::setValid( bool on ) +{ + if ( on != d_data->isValid ) + { + d_data->isValid = on; + + updateButtons(); + + if ( d_data->isValid ) + { + showNumber( value() ); + Q_EMIT valueChanged( value() ); + } + else + { + d_data->valueEdit->setText( QString::null ); + } + } +} + +/*! + \return True, if the value is valid + \sa setValid(), setValue() + */ +bool QwtCounter::isValid() const +{ + return d_data->isValid; +} + +/*! + \brief Allow/disallow the user to manually edit the value + + \param on True disable editing + \sa isReadOnly() +*/ +void QwtCounter::setReadOnly( bool on ) +{ + d_data->valueEdit->setReadOnly( on ); +} + +/*! + \return True, when the line line edit is read only. (default is no) + \sa setReadOnly() + */ +bool QwtCounter::isReadOnly() const +{ + return d_data->valueEdit->isReadOnly(); +} + +/*! + \brief Set a new value without adjusting to the step raster + + The state of the counter is set to be valid. + + \param value New value + + \sa isValid(), value(), valueChanged() + \warning The value is clipped when it lies outside the range. +*/ + +void QwtCounter::setValue( double value ) +{ + const double vmin = qMin( d_data->minimum, d_data->maximum ); + const double vmax = qMax( d_data->minimum, d_data->maximum ); + + value = qBound( vmin, value, vmax ); + + if ( !d_data->isValid || value != d_data->value ) + { + d_data->isValid = true; + d_data->value = value; + + showNumber( value ); + updateButtons(); + + Q_EMIT valueChanged( value ); + } +} + +/*! + \return Current value of the counter + \sa setValue(), valueChanged() + */ +double QwtCounter::value() const +{ + return d_data->value; +} + +/*! + \brief Set the minimum and maximum values + + The maximum is adjusted if necessary to ensure that the range remains valid. + The value might be modified to be inside of the range. + + \param min Minimum value + \param max Maximum value + + \sa minimum(), maximum() + */ +void QwtCounter::setRange( double min, double max ) +{ + max = qMax( min, max ); + + if ( d_data->maximum == max && d_data->minimum == min ) + return; + + d_data->minimum = min; + d_data->maximum = max; + + setSingleStep( singleStep() ); + + const double value = qBound( min, d_data->value, max ); + + if ( value != d_data->value ) + { + d_data->value = value; + + if ( d_data->isValid ) + { + showNumber( value ); + Q_EMIT valueChanged( value ); + } + } + + updateButtons(); +} + +/*! + Set the minimum value of the range + + \param value Minimum value + \sa setRange(), setMaximum(), minimum() + + \note The maximum is adjusted if necessary to ensure that the range remains valid. +*/ +void QwtCounter::setMinimum( double value ) +{ + setRange( value, maximum() ); +} + +/*! + \return The minimum of the range + \sa setRange(), setMinimum(), maximum() +*/ +double QwtCounter::minimum() const +{ + return d_data->minimum; +} + +/*! + Set the maximum value of the range + + \param value Maximum value + \sa setRange(), setMinimum(), maximum() +*/ +void QwtCounter::setMaximum( double value ) +{ + setRange( minimum(), value ); +} + +/*! + \return The maximum of the range + \sa setRange(), setMaximum(), minimum() +*/ +double QwtCounter::maximum() const +{ + return d_data->maximum; +} + +/*! + \brief Set the step size of the counter + + A value <= 0.0 disables stepping + + \param stepSize Single step size + \sa singleStep() +*/ +void QwtCounter::setSingleStep( double stepSize ) +{ + d_data->singleStep = qMax( stepSize, 0.0 ); +} + +/*! + \return Single step size + \sa setSingleStep() + */ +double QwtCounter::singleStep() const +{ + return d_data->singleStep; +} + +/*! + \brief En/Disable wrapping + + If wrapping is true stepping up from maximum() value will take + you to the minimum() value and vice versa. + + \param on En/Disable wrapping + \sa wrapping() + */ +void QwtCounter::setWrapping( bool on ) +{ + d_data->wrapping = on; +} + +/*! + \return True, when wrapping is set + \sa setWrapping() + */ +bool QwtCounter::wrapping() const +{ + return d_data->wrapping; +} + +/*! + Specify the number of buttons on each side of the label + + \param numButtons Number of buttons + \sa numButtons() +*/ +void QwtCounter::setNumButtons( int numButtons ) +{ + if ( numButtons < 0 || numButtons > QwtCounter::ButtonCnt ) + return; + + for ( int i = 0; i < QwtCounter::ButtonCnt; i++ ) + { + if ( i < numButtons ) + { + d_data->buttonDown[i]->show(); + d_data->buttonUp[i]->show(); + } + else + { + d_data->buttonDown[i]->hide(); + d_data->buttonUp[i]->hide(); + } + } + + d_data->numButtons = numButtons; +} + +/*! + \return The number of buttons on each side of the widget. + \sa setNumButtons() +*/ +int QwtCounter::numButtons() const +{ + return d_data->numButtons; +} + +/*! + Specify the number of steps by which the value + is incremented or decremented when a specified button + is pushed. + + \param button Button index + \param numSteps Number of steps + + \sa incSteps() +*/ +void QwtCounter::setIncSteps( QwtCounter::Button button, int numSteps ) +{ + if ( button >= 0 && button < QwtCounter::ButtonCnt ) + d_data->increment[ button ] = numSteps; +} + +/*! + \return The number of steps by which a specified button increments the value + or 0 if the button is invalid. + \param button Button index + + \sa setIncSteps() +*/ +int QwtCounter::incSteps( QwtCounter::Button button ) const +{ + if ( button >= 0 && button < QwtCounter::ButtonCnt ) + return d_data->increment[ button ]; + + return 0; +} + + +/*! + Set the number of increment steps for button 1 + \param nSteps Number of steps +*/ +void QwtCounter::setStepButton1( int nSteps ) +{ + setIncSteps( QwtCounter::Button1, nSteps ); +} + +//! returns the number of increment steps for button 1 +int QwtCounter::stepButton1() const +{ + return incSteps( QwtCounter::Button1 ); +} + +/*! + Set the number of increment steps for button 2 + \param nSteps Number of steps +*/ +void QwtCounter::setStepButton2( int nSteps ) +{ + setIncSteps( QwtCounter::Button2, nSteps ); +} + +//! returns the number of increment steps for button 2 +int QwtCounter::stepButton2() const +{ + return incSteps( QwtCounter::Button2 ); +} + +/*! + Set the number of increment steps for button 3 + \param nSteps Number of steps +*/ +void QwtCounter::setStepButton3( int nSteps ) +{ + setIncSteps( QwtCounter::Button3, nSteps ); +} + +//! returns the number of increment steps for button 3 +int QwtCounter::stepButton3() const +{ + return incSteps( QwtCounter::Button3 ); +} + +//! Set from lineedit +void QwtCounter::textChanged() +{ + bool converted = false; + + const double value = d_data->valueEdit->text().toDouble( &converted ); + if ( converted ) + setValue( value ); +} + +/*! + Handle QEvent::PolishRequest events + \param event Event + \return see QWidget::event() +*/ +bool QwtCounter::event( QEvent *event ) +{ + if ( event->type() == QEvent::PolishRequest ) + { + const int w = d_data->valueEdit->fontMetrics().width( "W" ) + 8; + for ( int i = 0; i < ButtonCnt; i++ ) + { + d_data->buttonDown[i]->setMinimumWidth( w ); + d_data->buttonUp[i]->setMinimumWidth( w ); + } + } + + return QWidget::event( event ); +} + +/*! + Handle key events + + - Ctrl + Qt::Key_Home\n + Step to minimum() + - Ctrl + Qt::Key_End\n + Step to maximum() + - Qt::Key_Up\n + Increment by incSteps(QwtCounter::Button1) + - Qt::Key_Down\n + Decrement by incSteps(QwtCounter::Button1) + - Qt::Key_PageUp\n + Increment by incSteps(QwtCounter::Button2) + - Qt::Key_PageDown\n + Decrement by incSteps(QwtCounter::Button2) + - Shift + Qt::Key_PageUp\n + Increment by incSteps(QwtCounter::Button3) + - Shift + Qt::Key_PageDown\n + Decrement by incSteps(QwtCounter::Button3) + + \param event Key event +*/ +void QwtCounter::keyPressEvent ( QKeyEvent *event ) +{ + bool accepted = true; + + switch ( event->key() ) + { + case Qt::Key_Home: + { + if ( event->modifiers() & Qt::ControlModifier ) + setValue( minimum() ); + else + accepted = false; + break; + } + case Qt::Key_End: + { + if ( event->modifiers() & Qt::ControlModifier ) + setValue( maximum() ); + else + accepted = false; + break; + } + case Qt::Key_Up: + { + incrementValue( d_data->increment[0] ); + break; + } + case Qt::Key_Down: + { + incrementValue( -d_data->increment[0] ); + break; + } + case Qt::Key_PageUp: + case Qt::Key_PageDown: + { + int increment = d_data->increment[0]; + if ( d_data->numButtons >= 2 ) + increment = d_data->increment[1]; + if ( d_data->numButtons >= 3 ) + { + if ( event->modifiers() & Qt::ShiftModifier ) + increment = d_data->increment[2]; + } + if ( event->key() == Qt::Key_PageDown ) + increment = -increment; + incrementValue( increment ); + break; + } + default: + { + accepted = false; + } + } + + if ( accepted ) + { + event->accept(); + return; + } + + QWidget::keyPressEvent ( event ); +} + +/*! + Handle wheel events + \param event Wheel event +*/ +void QwtCounter::wheelEvent( QWheelEvent *event ) +{ + event->accept(); + + if ( d_data->numButtons <= 0 ) + return; + + int increment = d_data->increment[0]; + if ( d_data->numButtons >= 2 ) + { + if ( event->modifiers() & Qt::ControlModifier ) + increment = d_data->increment[1]; + } + if ( d_data->numButtons >= 3 ) + { + if ( event->modifiers() & Qt::ShiftModifier ) + increment = d_data->increment[2]; + } + + for ( int i = 0; i < d_data->numButtons; i++ ) + { + if ( d_data->buttonDown[i]->geometry().contains( event->pos() ) || + d_data->buttonUp[i]->geometry().contains( event->pos() ) ) + { + increment = d_data->increment[i]; + } + } + + const int wheel_delta = 120; + +#if 1 + int delta = event->delta(); + if ( delta >= 2 * wheel_delta ) + delta /= 2; // Never saw an abs(delta) < 240 +#endif + + incrementValue( delta / wheel_delta * increment ); +} + +void QwtCounter::incrementValue( int numSteps ) +{ + const double min = d_data->minimum; + const double max = d_data->maximum; + double stepSize = d_data->singleStep; + + if ( !d_data->isValid || min >= max || stepSize <= 0.0 ) + return; + + +#if 1 + stepSize = qMax( stepSize, 1.0e-10 * ( max - min ) ); +#endif + + double value = d_data->value + numSteps * stepSize; + + if ( d_data->wrapping ) + { + const double range = max - min; + + if ( value < min ) + { + value += ::ceil( ( min - value ) / range ) * range; + } + else if ( value > max ) + { + value -= ::ceil( ( value - max ) / range ) * range; + } + } + else + { + value = qBound( min, value, max ); + } + + value = min + qRound( ( value - min ) / stepSize ) * stepSize; + + if ( stepSize > 1e-12 ) + { + if ( qFuzzyCompare( value + 1.0, 1.0 ) ) + { + // correct rounding error if value = 0 + value = 0.0; + } + else if ( qFuzzyCompare( value, max ) ) + { + // correct rounding error at the border + value = max; + } + } + + if ( value != d_data->value ) + { + d_data->value = value; + showNumber( d_data->value ); + updateButtons(); + + Q_EMIT valueChanged( d_data->value ); + } +} + + +/*! + \brief Update buttons according to the current value + + When the QwtCounter under- or over-flows, the focus is set to the smallest + up- or down-button and counting is disabled. + + Counting is re-enabled on a button release event (mouse or space bar). +*/ +void QwtCounter::updateButtons() +{ + if ( d_data->isValid ) + { + // 1. save enabled state of the smallest down- and up-button + // 2. change enabled state on under- or over-flow + + for ( int i = 0; i < QwtCounter::ButtonCnt; i++ ) + { + d_data->buttonDown[i]->setEnabled( value() > minimum() ); + d_data->buttonUp[i]->setEnabled( value() < maximum() ); + } + } + else + { + for ( int i = 0; i < QwtCounter::ButtonCnt; i++ ) + { + d_data->buttonDown[i]->setEnabled( false ); + d_data->buttonUp[i]->setEnabled( false ); + } + } +} +/*! + Display number string + + \param number Number +*/ +void QwtCounter::showNumber( double number ) +{ + QString text; + text.setNum( number ); + + const int cursorPos = d_data->valueEdit->cursorPosition(); + d_data->valueEdit->setText( text ); + d_data->valueEdit->setCursorPosition( cursorPos ); +} + +//! Button clicked +void QwtCounter::btnClicked() +{ + for ( int i = 0; i < ButtonCnt; i++ ) + { + if ( d_data->buttonUp[i] == sender() ) + incrementValue( d_data->increment[i] ); + + if ( d_data->buttonDown[i] == sender() ) + incrementValue( -d_data->increment[i] ); + } +} + +//! Button released +void QwtCounter::btnReleased() +{ + Q_EMIT buttonReleased( value() ); +} + +//! A size hint +QSize QwtCounter::sizeHint() const +{ + QString tmp; + + int w = tmp.setNum( minimum() ).length(); + int w1 = tmp.setNum( maximum() ).length(); + if ( w1 > w ) + w = w1; + w1 = tmp.setNum( minimum() + singleStep() ).length(); + if ( w1 > w ) + w = w1; + w1 = tmp.setNum( maximum() - singleStep() ).length(); + if ( w1 > w ) + w = w1; + + tmp.fill( '9', w ); + + QFontMetrics fm( d_data->valueEdit->font() ); + w = fm.width( tmp ) + 2; + if ( d_data->valueEdit->hasFrame() ) + w += 2 * style()->pixelMetric( QStyle::PM_DefaultFrameWidth ); + + // Now we replace default sizeHint contribution of d_data->valueEdit by + // what we really need. + + w += QWidget::sizeHint().width() - d_data->valueEdit->sizeHint().width(); + + const int h = qMin( QWidget::sizeHint().height(), + d_data->valueEdit->minimumSizeHint().height() ); + return QSize( w, h ); +} diff --git a/qwt/src/qwt_counter.h b/qwt/src/qwt_counter.h new file mode 100644 index 000000000..8799eddca --- /dev/null +++ b/qwt/src/qwt_counter.h @@ -0,0 +1,161 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_COUNTER_H +#define QWT_COUNTER_H + +#include "qwt_global.h" +#include + +/*! + \brief The Counter Widget + + A Counter consists of a label displaying a number and + one ore more (up to three) push buttons on each side + of the label which can be used to increment or decrement + the counter's value. + + A counter has a range from a minimum value to a maximum value + and a step size. When the wrapping property is set + the counter is circular. + + The number of steps by which a button increments or decrements the value + can be specified using setIncSteps(). The number of buttons can be + changed with setNumButtons(). + + Example: +\code +#include + +QwtCounter *counter = new QwtCounter(parent); + +counter->setRange(0.0, 100.0); // From 0.0 to 100 +counter->setSingleStep( 1.0 ); // Step size 1.0 +counter->setNumButtons(2); // Two buttons each side +counter->setIncSteps(QwtCounter::Button1, 1); // Button 1 increments 1 step +counter->setIncSteps(QwtCounter::Button2, 20); // Button 2 increments 20 steps + +connect(counter, SIGNAL(valueChanged(double)), myClass, SLOT(newValue(double))); +\endcode + */ + +class QWT_EXPORT QwtCounter : public QWidget +{ + Q_OBJECT + + Q_PROPERTY( double value READ value WRITE setValue ) + Q_PROPERTY( double minimum READ minimum WRITE setMinimum ) + Q_PROPERTY( double maximum READ maximum WRITE setMaximum ) + Q_PROPERTY( double singleStep READ singleStep WRITE setSingleStep ) + + Q_PROPERTY( int numButtons READ numButtons WRITE setNumButtons ) + Q_PROPERTY( int stepButton1 READ stepButton1 WRITE setStepButton1 ) + Q_PROPERTY( int stepButton2 READ stepButton2 WRITE setStepButton2 ) + Q_PROPERTY( int stepButton3 READ stepButton3 WRITE setStepButton3 ) + + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly ) + Q_PROPERTY( bool wrapping READ wrapping WRITE setWrapping ) + +public: + //! Button index + enum Button + { + //! Button intended for minor steps + Button1, + + //! Button intended for medium steps + Button2, + + //! Button intended for large steps + Button3, + + //! Number of buttons + ButtonCnt + }; + + explicit QwtCounter( QWidget *parent = NULL ); + virtual ~QwtCounter(); + + void setValid( bool ); + bool isValid() const; + + void setWrapping( bool ); + bool wrapping() const; + + bool isReadOnly() const; + void setReadOnly( bool ); + + void setNumButtons( int n ); + int numButtons() const; + + void setIncSteps( QwtCounter::Button btn, int nSteps ); + int incSteps( QwtCounter::Button btn ) const; + + virtual QSize sizeHint() const; + + double singleStep() const; + void setSingleStep( double s ); + + void setRange( double min, double max ); + + double minimum() const; + void setMinimum( double min ); + + double maximum() const; + void setMaximum( double max ); + + void setStepButton1( int nSteps ); + int stepButton1() const; + + void setStepButton2( int nSteps ); + int stepButton2() const; + + void setStepButton3( int nSteps ); + int stepButton3() const; + + double value() const; + +public Q_SLOTS: + void setValue( double ); + + +Q_SIGNALS: + /*! + This signal is emitted when a button has been released + \param value The new value + */ + void buttonReleased ( double value ); + + /*! + This signal is emitted when the counter's value has changed + \param value The new value + */ + void valueChanged ( double value ); + +protected: + virtual bool event( QEvent * ); + virtual void wheelEvent( QWheelEvent * ); + virtual void keyPressEvent( QKeyEvent * ); + +private Q_SLOTS: + void btnReleased(); + void btnClicked(); + void textChanged(); + +private: + void incrementValue( int numSteps ); + void initCounter(); + void updateButtons(); + void showNumber( double ); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_curve_fitter.cpp b/qwt/src/qwt_curve_fitter.cpp new file mode 100644 index 000000000..5f09d5d8d --- /dev/null +++ b/qwt/src/qwt_curve_fitter.cpp @@ -0,0 +1,453 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_curve_fitter.h" +#include "qwt_math.h" +#include "qwt_spline.h" +#include +#include + +#if QT_VERSION < 0x040601 +#define qFabs(x) ::fabs(x) +#endif + +//! Constructor +QwtCurveFitter::QwtCurveFitter() +{ +} + +//! Destructor +QwtCurveFitter::~QwtCurveFitter() +{ +} + +class QwtSplineCurveFitter::PrivateData +{ +public: + PrivateData(): + fitMode( QwtSplineCurveFitter::Auto ), + splineSize( 250 ) + { + } + + QwtSpline spline; + QwtSplineCurveFitter::FitMode fitMode; + int splineSize; +}; + +//! Constructor +QwtSplineCurveFitter::QwtSplineCurveFitter() +{ + d_data = new PrivateData; +} + +//! Destructor +QwtSplineCurveFitter::~QwtSplineCurveFitter() +{ + delete d_data; +} + +/*! + Select the algorithm used for building the spline + + \param mode Mode representing a spline algorithm + \sa fitMode() +*/ +void QwtSplineCurveFitter::setFitMode( FitMode mode ) +{ + d_data->fitMode = mode; +} + +/*! + \return Mode representing a spline algorithm + \sa setFitMode() +*/ +QwtSplineCurveFitter::FitMode QwtSplineCurveFitter::fitMode() const +{ + return d_data->fitMode; +} + +/*! + Assign a spline + + \param spline Spline + \sa spline() +*/ +void QwtSplineCurveFitter::setSpline( const QwtSpline &spline ) +{ + d_data->spline = spline; + d_data->spline.reset(); +} + +/*! + \return Spline + \sa setSpline() +*/ +const QwtSpline &QwtSplineCurveFitter::spline() const +{ + return d_data->spline; +} + +/*! + \return Spline + \sa setSpline() +*/ +QwtSpline &QwtSplineCurveFitter::spline() +{ + return d_data->spline; +} + +/*! + Assign a spline size ( has to be at least 10 points ) + + \param splineSize Spline size + \sa splineSize() +*/ +void QwtSplineCurveFitter::setSplineSize( int splineSize ) +{ + d_data->splineSize = qMax( splineSize, 10 ); +} + +/*! + \return Spline size + \sa setSplineSize() +*/ +int QwtSplineCurveFitter::splineSize() const +{ + return d_data->splineSize; +} + +/*! + Find a curve which has the best fit to a series of data points + + \param points Series of data points + \return Curve points +*/ +QPolygonF QwtSplineCurveFitter::fitCurve( const QPolygonF &points ) const +{ + const int size = points.size(); + if ( size <= 2 ) + return points; + + FitMode fitMode = d_data->fitMode; + if ( fitMode == Auto ) + { + fitMode = Spline; + + const QPointF *p = points.data(); + for ( int i = 1; i < size; i++ ) + { + if ( p[i].x() <= p[i-1].x() ) + { + fitMode = ParametricSpline; + break; + } + }; + } + + if ( fitMode == ParametricSpline ) + return fitParametric( points ); + else + return fitSpline( points ); +} + +QPolygonF QwtSplineCurveFitter::fitSpline( const QPolygonF &points ) const +{ + d_data->spline.setPoints( points ); + if ( !d_data->spline.isValid() ) + return points; + + QPolygonF fittedPoints( d_data->splineSize ); + + const double x1 = points[0].x(); + const double x2 = points[int( points.size() - 1 )].x(); + const double dx = x2 - x1; + const double delta = dx / ( d_data->splineSize - 1 ); + + for ( int i = 0; i < d_data->splineSize; i++ ) + { + QPointF &p = fittedPoints[i]; + + const double v = x1 + i * delta; + const double sv = d_data->spline.value( v ); + + p.setX( v ); + p.setY( sv ); + } + d_data->spline.reset(); + + return fittedPoints; +} + +QPolygonF QwtSplineCurveFitter::fitParametric( const QPolygonF &points ) const +{ + int i; + const int size = points.size(); + + QPolygonF fittedPoints( d_data->splineSize ); + QPolygonF splinePointsX( size ); + QPolygonF splinePointsY( size ); + + const QPointF *p = points.data(); + QPointF *spX = splinePointsX.data(); + QPointF *spY = splinePointsY.data(); + + double param = 0.0; + for ( i = 0; i < size; i++ ) + { + const double x = p[i].x(); + const double y = p[i].y(); + if ( i > 0 ) + { + const double delta = qSqrt( qwtSqr( x - spX[i-1].y() ) + + qwtSqr( y - spY[i-1].y() ) ); + param += qMax( delta, 1.0 ); + } + spX[i].setX( param ); + spX[i].setY( x ); + spY[i].setX( param ); + spY[i].setY( y ); + } + + d_data->spline.setPoints( splinePointsX ); + if ( !d_data->spline.isValid() ) + return points; + + const double deltaX = + splinePointsX[size - 1].x() / ( d_data->splineSize - 1 ); + for ( i = 0; i < d_data->splineSize; i++ ) + { + const double dtmp = i * deltaX; + fittedPoints[i].setX( d_data->spline.value( dtmp ) ); + } + + d_data->spline.setPoints( splinePointsY ); + if ( !d_data->spline.isValid() ) + return points; + + const double deltaY = + splinePointsY[size - 1].x() / ( d_data->splineSize - 1 ); + for ( i = 0; i < d_data->splineSize; i++ ) + { + const double dtmp = i * deltaY; + fittedPoints[i].setY( d_data->spline.value( dtmp ) ); + } + + return fittedPoints; +} + +class QwtWeedingCurveFitter::PrivateData +{ +public: + PrivateData(): + tolerance( 1.0 ), + chunkSize( 0 ) + { + } + + double tolerance; + uint chunkSize; +}; + +class QwtWeedingCurveFitter::Line +{ +public: + Line( int i1 = 0, int i2 = 0 ): + from( i1 ), + to( i2 ) + { + } + + int from; + int to; +}; + +/*! + Constructor + + \param tolerance Tolerance + \sa setTolerance(), tolerance() +*/ +QwtWeedingCurveFitter::QwtWeedingCurveFitter( double tolerance ) +{ + d_data = new PrivateData; + setTolerance( tolerance ); +} + +//! Destructor +QwtWeedingCurveFitter::~QwtWeedingCurveFitter() +{ + delete d_data; +} + +/*! + Assign the tolerance + + The tolerance is the maximum distance, that is acceptable + between the original curve and the smoothed curve. + + Increasing the tolerance will reduce the number of the + resulting points. + + \param tolerance Tolerance + + \sa tolerance() +*/ +void QwtWeedingCurveFitter::setTolerance( double tolerance ) +{ + d_data->tolerance = qMax( tolerance, 0.0 ); +} + +/*! + \return Tolerance + \sa setTolerance() +*/ +double QwtWeedingCurveFitter::tolerance() const +{ + return d_data->tolerance; +} + +/*! + Limit the number of points passed to a run of the algorithm + + The runtime of the Douglas Peucker algorithm increases non linear + with the number of points. For a chunk size > 0 the polygon + is split into pieces passed to the algorithm one by one. + + \param numPoints Maximum for the number of points passed to the algorithm + + \sa chunkSize() +*/ +void QwtWeedingCurveFitter::setChunkSize( uint numPoints ) +{ + if ( numPoints > 0 ) + numPoints = qMax( numPoints, 3U ); + + d_data->chunkSize = numPoints; +} + +/*! + + \return Maximum for the number of points passed to a run + of the algorithm - or 0, when unlimited + \sa setChunkSize() +*/ +uint QwtWeedingCurveFitter::chunkSize() const +{ + return d_data->chunkSize; +} + +/*! + \param points Series of data points + \return Curve points +*/ +QPolygonF QwtWeedingCurveFitter::fitCurve( const QPolygonF &points ) const +{ + QPolygonF fittedPoints; + + if ( d_data->chunkSize == 0 ) + { + fittedPoints = simplify( points ); + } + else + { + for ( int i = 0; i < points.size(); i += d_data->chunkSize ) + { + const QPolygonF p = points.mid( i, d_data->chunkSize ); + fittedPoints += simplify( p ); + } + } + + return fittedPoints; +} + +QPolygonF QwtWeedingCurveFitter::simplify( const QPolygonF &points ) const +{ + const double toleranceSqr = d_data->tolerance * d_data->tolerance; + + QStack stack; + stack.reserve( 500 ); + + const QPointF *p = points.data(); + const int nPoints = points.size(); + + QVector usePoint( nPoints, false ); + + stack.push( Line( 0, nPoints - 1 ) ); + + while ( !stack.isEmpty() ) + { + const Line r = stack.pop(); + + // initialize line segment + const double vecX = p[r.to].x() - p[r.from].x(); + const double vecY = p[r.to].y() - p[r.from].y(); + + const double vecLength = qSqrt( vecX * vecX + vecY * vecY ); + + const double unitVecX = ( vecLength != 0.0 ) ? vecX / vecLength : 0.0; + const double unitVecY = ( vecLength != 0.0 ) ? vecY / vecLength : 0.0; + + double maxDistSqr = 0.0; + int nVertexIndexMaxDistance = r.from + 1; + for ( int i = r.from + 1; i < r.to; i++ ) + { + //compare to anchor + const double fromVecX = p[i].x() - p[r.from].x(); + const double fromVecY = p[i].y() - p[r.from].y(); + + double distToSegmentSqr; + if ( fromVecX * unitVecX + fromVecY * unitVecY < 0.0 ) + { + distToSegmentSqr = fromVecX * fromVecX + fromVecY * fromVecY; + } + else + { + const double toVecX = p[i].x() - p[r.to].x(); + const double toVecY = p[i].y() - p[r.to].y(); + const double toVecLength = toVecX * toVecX + toVecY * toVecY; + + const double s = toVecX * ( -unitVecX ) + toVecY * ( -unitVecY ); + if ( s < 0.0 ) + { + distToSegmentSqr = toVecLength; + } + else + { + distToSegmentSqr = qFabs( toVecLength - s * s ); + } + } + + if ( maxDistSqr < distToSegmentSqr ) + { + maxDistSqr = distToSegmentSqr; + nVertexIndexMaxDistance = i; + } + } + if ( maxDistSqr <= toleranceSqr ) + { + usePoint[r.from] = true; + usePoint[r.to] = true; + } + else + { + stack.push( Line( r.from, nVertexIndexMaxDistance ) ); + stack.push( Line( nVertexIndexMaxDistance, r.to ) ); + } + } + + QPolygonF stripped; + for ( int i = 0; i < nPoints; i++ ) + { + if ( usePoint[i] ) + stripped += p[i]; + } + + return stripped; +} diff --git a/qwt/src/qwt_curve_fitter.h b/qwt/src/qwt_curve_fitter.h new file mode 100644 index 000000000..eac376a34 --- /dev/null +++ b/qwt/src/qwt_curve_fitter.h @@ -0,0 +1,139 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_CURVE_FITTER_H +#define QWT_CURVE_FITTER_H + +#include "qwt_global.h" +#include +#include + +class QwtSpline; + +/*! + \brief Abstract base class for a curve fitter +*/ +class QWT_EXPORT QwtCurveFitter +{ +public: + virtual ~QwtCurveFitter(); + + /*! + Find a curve which has the best fit to a series of data points + + \param polygon Series of data points + \return Curve points + */ + virtual QPolygonF fitCurve( const QPolygonF &polygon ) const = 0; + +protected: + QwtCurveFitter(); + +private: + QwtCurveFitter( const QwtCurveFitter & ); + QwtCurveFitter &operator=( const QwtCurveFitter & ); +}; + +/*! + \brief A curve fitter using cubic splines +*/ +class QWT_EXPORT QwtSplineCurveFitter: public QwtCurveFitter +{ +public: + /*! + Spline type + The default setting is Auto + \sa setFitMode(), FitMode() + */ + enum FitMode + { + /*! + Use the default spline algorithm for polygons with + increasing x values ( p[i-1] < p[i] ), otherwise use + a parametric spline algorithm. + */ + Auto, + + //! Use a default spline algorithm + Spline, + + //! Use a parametric spline algorithm + ParametricSpline + }; + + QwtSplineCurveFitter(); + virtual ~QwtSplineCurveFitter(); + + void setFitMode( FitMode ); + FitMode fitMode() const; + + void setSpline( const QwtSpline& ); + const QwtSpline &spline() const; + QwtSpline &spline(); + + void setSplineSize( int size ); + int splineSize() const; + + virtual QPolygonF fitCurve( const QPolygonF & ) const; + +private: + QPolygonF fitSpline( const QPolygonF & ) const; + QPolygonF fitParametric( const QPolygonF & ) const; + + class PrivateData; + PrivateData *d_data; +}; + +/*! + \brief A curve fitter implementing Douglas and Peucker algorithm + + The purpose of the Douglas and Peucker algorithm is that given a 'curve' + composed of line segments to find a curve not too dissimilar but that + has fewer points. The algorithm defines 'too dissimilar' based on the + maximum distance (tolerance) between the original curve and the + smoothed curve. + + The runtime of the algorithm increases non linear ( worst case O( n*n ) ) + and might be very slow for huge polygons. To avoid performance issues + it might be useful to split the polygon ( setChunkSize() ) and to run the algorithm + for these smaller parts. The disadvantage of having no interpolation + at the borders is for most use cases irrelevant. + + The smoothed curve consists of a subset of the points that defined the + original curve. + + In opposite to QwtSplineCurveFitter the Douglas and Peucker algorithm reduces + the number of points. By adjusting the tolerance parameter according to the + axis scales QwtSplineCurveFitter can be used to implement different + level of details to speed up painting of curves of many points. +*/ +class QWT_EXPORT QwtWeedingCurveFitter: public QwtCurveFitter +{ +public: + QwtWeedingCurveFitter( double tolerance = 1.0 ); + virtual ~QwtWeedingCurveFitter(); + + void setTolerance( double ); + double tolerance() const; + + void setChunkSize( uint ); + uint chunkSize() const; + + virtual QPolygonF fitCurve( const QPolygonF & ) const; + +private: + virtual QPolygonF simplify( const QPolygonF & ) const; + + class Line; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_date.cpp b/qwt/src/qwt_date.cpp new file mode 100644 index 000000000..0cf5ca0d2 --- /dev/null +++ b/qwt/src/qwt_date.cpp @@ -0,0 +1,654 @@ +#include "qwt_date.h" +#include +#include +#include +#include +#include + +#if QT_VERSION >= 0x050000 + +typedef qint64 QwtJulianDay; +static const QwtJulianDay minJulianDayD = Q_INT64_C( -784350574879 ); +static const QwtJulianDay maxJulianDayD = Q_INT64_C( 784354017364 ); + +#else + +// QDate stores the Julian day as unsigned int, but +// but it is QDate::fromJulianDay( int ). That's why +// we have the range [ 1, INT_MAX ] +typedef int QwtJulianDay; +static const QwtJulianDay minJulianDayD = 1; +static const QwtJulianDay maxJulianDayD = std::numeric_limits::max(); + +#endif + +static inline Qt::DayOfWeek qwtFirstDayOfWeek() +{ +#if QT_VERSION >= 0x040800 + return QLocale().firstDayOfWeek(); +#else + + switch( QLocale().country() ) + { + case QLocale::Maldives: + return Qt::Friday; + + case QLocale::Afghanistan: + case QLocale::Algeria: + case QLocale::Bahrain: + case QLocale::Djibouti: + case QLocale::Egypt: + case QLocale::Eritrea: + case QLocale::Ethiopia: + case QLocale::Iran: + case QLocale::Iraq: + case QLocale::Jordan: + case QLocale::Kenya: + case QLocale::Kuwait: + case QLocale::LibyanArabJamahiriya: + case QLocale::Morocco: + case QLocale::Oman: + case QLocale::Qatar: + case QLocale::SaudiArabia: + case QLocale::Somalia: + case QLocale::Sudan: + case QLocale::Tunisia: + case QLocale::Yemen: + return Qt::Saturday; + + case QLocale::AmericanSamoa: + case QLocale::Argentina: + case QLocale::Azerbaijan: + case QLocale::Botswana: + case QLocale::Canada: + case QLocale::China: + case QLocale::FaroeIslands: + case QLocale::Georgia: + case QLocale::Greenland: + case QLocale::Guam: + case QLocale::HongKong: + case QLocale::Iceland: + case QLocale::India: + case QLocale::Ireland: + case QLocale::Israel: + case QLocale::Jamaica: + case QLocale::Japan: + case QLocale::Kyrgyzstan: + case QLocale::Lao: + case QLocale::Malta: + case QLocale::MarshallIslands: + case QLocale::Macau: + case QLocale::Mongolia: + case QLocale::NewZealand: + case QLocale::NorthernMarianaIslands: + case QLocale::Pakistan: + case QLocale::Philippines: + case QLocale::RepublicOfKorea: + case QLocale::Singapore: + case QLocale::SyrianArabRepublic: + case QLocale::Taiwan: + case QLocale::Thailand: + case QLocale::TrinidadAndTobago: + case QLocale::UnitedStates: + case QLocale::UnitedStatesMinorOutlyingIslands: + case QLocale::USVirginIslands: + case QLocale::Uzbekistan: + case QLocale::Zimbabwe: + return Qt::Sunday; + + default: + return Qt::Monday; + } +#endif +} + +static inline void qwtFloorTime( + QwtDate::IntervalType intervalType, QDateTime &dt ) +{ + // when dt is inside the special hour where DST is ending + // an hour is no unique. Therefore we have to + // use UTC time. + + const Qt::TimeSpec timeSpec = dt.timeSpec(); + + if ( timeSpec == Qt::LocalTime ) + dt = dt.toTimeSpec( Qt::UTC ); + + const QTime t = dt.time(); + switch( intervalType ) + { + case QwtDate::Second: + { + dt.setTime( QTime( t.hour(), t.minute(), t.second() ) ); + break; + } + case QwtDate::Minute: + { + dt.setTime( QTime( t.hour(), t.minute(), 0 ) ); + break; + } + case QwtDate::Hour: + { + dt.setTime( QTime( t.hour(), 0, 0 ) ); + break; + } + default: + break; + } + + if ( timeSpec == Qt::LocalTime ) + dt = dt.toTimeSpec( Qt::LocalTime ); +} + +static inline QDateTime qwtToTimeSpec( + const QDateTime &dt, Qt::TimeSpec spec ) +{ + if ( dt.timeSpec() == spec ) + return dt; + + const qint64 jd = dt.date().toJulianDay(); + if ( jd < 0 || jd >= INT_MAX ) + { + // the conversion between local time and UTC + // is internally limited. To avoid + // overflows we simply ignore the difference + // for those dates + + QDateTime dt2 = dt; + dt2.setTimeSpec( spec ); + return dt2; + } + + return dt.toTimeSpec( spec ); +} + +static inline double qwtToJulianDay( int year, int month, int day ) +{ + // code from QDate but using doubles to avoid overflows + // for large values + + const int m1 = ( month - 14 ) / 12; + const int m2 = ( 367 * ( month - 2 - 12 * m1 ) ) / 12; + const double y1 = ::floor( ( 4900.0 + year + m1 ) / 100 ); + + return ::floor( ( 1461.0 * ( year + 4800 + m1 ) ) / 4 ) + m2 + - ::floor( ( 3 * y1 ) / 4 ) + day - 32075; +} + +static inline qint64 qwtFloorDiv64( qint64 a, int b ) +{ + if ( a < 0 ) + a -= b - 1; + + return a / b; +} + +static inline qint64 qwtFloorDiv( int a, int b ) +{ + if ( a < 0 ) + a -= b - 1; + + return a / b; +} + +static inline QDate qwtToDate( int year, int month = 1, int day = 1 ) +{ +#if QT_VERSION >= 0x050000 + return QDate( year, month, day ); +#else + if ( year > 100000 ) + { + // code from QDate but using doubles to avoid overflows + // for large values + + const int m1 = ( month - 14 ) / 12; + const int m2 = ( 367 * ( month - 2 - 12 * m1 ) ) / 12; + const double y1 = ::floor( ( 4900.0 + year + m1 ) / 100 ); + + const double jd = ::floor( ( 1461.0 * ( year + 4800 + m1 ) ) / 4 ) + m2 + - ::floor( ( 3 * y1 ) / 4 ) + day - 32075; + + if ( jd > maxJulianDayD ) + { + qWarning() << "qwtToDate: overflow"; + return QDate(); + } + + return QDate::fromJulianDay( static_cast( jd ) ); + } + else + { + return QDate( year, month, day ); + } +#endif +} + +/*! + Translate from double to QDateTime + + \param value Number of milliseconds since the epoch, + 1970-01-01T00:00:00 UTC + \param timeSpec Time specification + \return Datetime value + + \sa toDouble(), QDateTime::setMSecsSinceEpoch() + \note The return datetime for Qt::OffsetFromUTC will be Qt::UTC + */ +QDateTime QwtDate::toDateTime( double value, Qt::TimeSpec timeSpec ) +{ + const int msecsPerDay = 86400000; + + const double days = static_cast( ::floor( value / msecsPerDay ) ); + + const double jd = QwtDate::JulianDayForEpoch + days; + if ( ( jd > maxJulianDayD ) || ( jd < minJulianDayD ) ) + { + qWarning() << "QwtDate::toDateTime: overflow"; + return QDateTime(); + } + + const QDate d = QDate::fromJulianDay( static_cast( jd ) ); + + const int msecs = static_cast( value - days * msecsPerDay ); + + static const QTime timeNull( 0, 0, 0, 0 ); + + QDateTime dt( d, timeNull.addMSecs( msecs ), Qt::UTC ); + + if ( timeSpec == Qt::LocalTime ) + dt = qwtToTimeSpec( dt, timeSpec ); + + return dt; +} + +/*! + Translate from QDateTime to double + + \param dateTime Datetime value + \return Number of milliseconds since 1970-01-01T00:00:00 UTC has passed. + + \sa toDateTime(), QDateTime::toMSecsSinceEpoch() + \warning For values very far below or above 1970-01-01 UTC rounding errors + will happen due to the limited significance of a double. + */ +double QwtDate::toDouble( const QDateTime &dateTime ) +{ + const int msecsPerDay = 86400000; + + const QDateTime dt = qwtToTimeSpec( dateTime, Qt::UTC ); + + const double days = dt.date().toJulianDay() - QwtDate::JulianDayForEpoch; + + const QTime time = dt.time(); + const double secs = 3600.0 * time.hour() + + 60.0 * time.minute() + time.second(); + + return days * msecsPerDay + time.msec() + 1000.0 * secs; +} + +/*! + Ceil a datetime according the interval type + + \param dateTime Datetime value + \param intervalType Interval type, how to ceil. + F.e. when intervalType = QwtDate::Months, the result + will be ceiled to the next beginning of a month + \return Ceiled datetime + \sa floor() + */ +QDateTime QwtDate::ceil( const QDateTime &dateTime, IntervalType intervalType ) +{ + if ( dateTime.date() >= QwtDate::maxDate() ) + return dateTime; + + QDateTime dt = dateTime; + + switch ( intervalType ) + { + case QwtDate::Millisecond: + { + break; + } + case QwtDate::Second: + { + qwtFloorTime( QwtDate::Second, dt ); + if ( dt < dateTime ) + dt.addSecs( 1 ); + + break; + } + case QwtDate::Minute: + { + qwtFloorTime( QwtDate::Minute, dt ); + if ( dt < dateTime ) + dt.addSecs( 60 ); + + break; + } + case QwtDate::Hour: + { + qwtFloorTime( QwtDate::Hour, dt ); + if ( dt < dateTime ) + dt.addSecs( 3600 ); + + break; + } + case QwtDate::Day: + { + dt.setTime( QTime( 0, 0 ) ); + if ( dt < dateTime ) + dt = dt.addDays( 1 ); + + break; + } + case QwtDate::Week: + { + dt.setTime( QTime( 0, 0 ) ); + if ( dt < dateTime ) + dt = dt.addDays( 1 ); + + int days = qwtFirstDayOfWeek() - dt.date().dayOfWeek(); + if ( days < 0 ) + days += 7; + + dt = dt.addDays( days ); + + break; + } + case QwtDate::Month: + { + dt.setTime( QTime( 0, 0 ) ); + dt.setDate( qwtToDate( dateTime.date().year(), + dateTime.date().month() ) ); + + if ( dt < dateTime ) + dt.addMonths( 1 ); + + break; + } + case QwtDate::Year: + { + dt.setTime( QTime( 0, 0 ) ); + + const QDate d = dateTime.date(); + + int year = d.year(); + if ( d.month() > 1 || d.day() > 1 || !dateTime.time().isNull() ) + year++; + + if ( year == 0 ) + year++; // there is no year 0 + + dt.setDate( qwtToDate( year ) ); + break; + } + } + + return dt; +} + +/*! + Floor a datetime according the interval type + + \param dateTime Datetime value + \param intervalType Interval type, how to ceil. + F.e. when intervalType = QwtDate::Months, + the result will be ceiled to the next + beginning of a month + \return Floored datetime + \sa floor() + */ +QDateTime QwtDate::floor( const QDateTime &dateTime, + IntervalType intervalType ) +{ + if ( dateTime.date() <= QwtDate::minDate() ) + return dateTime; + + QDateTime dt = dateTime; + + switch ( intervalType ) + { + case QwtDate::Millisecond: + { + break; + } + case QwtDate::Second: + case QwtDate::Minute: + case QwtDate::Hour: + { + qwtFloorTime( intervalType, dt ); + break; + } + case QwtDate::Day: + { + dt.setTime( QTime( 0, 0 ) ); + break; + } + case QwtDate::Week: + { + dt.setTime( QTime( 0, 0 ) ); + + int days = dt.date().dayOfWeek() - qwtFirstDayOfWeek(); + if ( days < 0 ) + days += 7; + + dt = dt.addDays( -days ); + + break; + } + case QwtDate::Month: + { + dt.setTime( QTime( 0, 0 ) ); + + const QDate date = qwtToDate( dt.date().year(), + dt.date().month() ); + dt.setDate( date ); + + break; + } + case QwtDate::Year: + { + dt.setTime( QTime( 0, 0 ) ); + + const QDate date = qwtToDate( dt.date().year() ); + dt.setDate( date ); + + break; + } + } + + return dt; +} + +/*! + Minimum for the supported date range + + The range of valid dates depends on how QDate stores the + Julian day internally. + + - For Qt4 it is "Tue Jan 2 -4713" + - For Qt5 it is "Thu Jan 1 -2147483648" + + \return minimum of the date range + \sa maxDate() + */ +QDate QwtDate::minDate() +{ + static QDate date; + if ( !date.isValid() ) + date = QDate::fromJulianDay( minJulianDayD ); + + return date; +} + +/*! + Maximum for the supported date range + + The range of valid dates depends on how QDate stores the + Julian day internally. + + - For Qt4 it is "Tue Jun 3 5874898" + - For Qt5 it is "Tue Dec 31 2147483647" + + \return maximum of the date range + \sa minDate() + \note The maximum differs between Qt4 and Qt5 + */ +QDate QwtDate::maxDate() +{ + static QDate date; + if ( !date.isValid() ) + date = QDate::fromJulianDay( maxJulianDayD ); + + return date; +} + +/*! + \brief Date of the first day of the first week for a year + + The first day of a week depends on the current locale + ( QLocale::firstDayOfWeek() ). + + \param year Year + \param type Option how to identify the first week + \return First day of week 0 + + \sa QLocale::firstDayOfWeek(), weekNumber() + */ +QDate QwtDate::dateOfWeek0( int year, Week0Type type ) +{ + const Qt::DayOfWeek firstDayOfWeek = qwtFirstDayOfWeek(); + + QDate dt0( year, 1, 1 ); + + // floor to the first day of the week + int days = dt0.dayOfWeek() - firstDayOfWeek; + if ( days < 0 ) + days += 7; + + dt0 = dt0.addDays( -days ); + + if ( type == QwtDate::FirstThursday ) + { + // according to ISO 8601 the first week is defined + // by the first thursday. + + int d = Qt::Thursday - firstDayOfWeek; + if ( d < 0 ) + d += 7; + + if ( dt0.addDays( d ).year() < year ) + dt0 = dt0.addDays( 7 ); + } + + return dt0; +} + +/*! + Find the week number of a date + + - QwtDate::FirstThursday\n + Corresponding to ISO 8601 ( see QDate::weekNumber() ). + + - QwtDate::FirstDay\n + Number of weeks that have begun since dateOfWeek0(). + + \param date Date + \param type Option how to identify the first week + + \return Week number, starting with 1 + */ +int QwtDate::weekNumber( const QDate &date, Week0Type type ) +{ + int weekNo; + + if ( type == QwtDate::FirstDay ) + { + const QDate day0 = dateOfWeek0( date.year(), type ); + weekNo = day0.daysTo( date ) / 7 + 1; + } + else + { + weekNo = date.weekNumber(); + } + + return weekNo; +} + +/*! + Offset in seconds from Coordinated Universal Time + + The offset depends on the time specification of dateTime: + + - Qt::UTC + 0, dateTime has no offset + - Qt::OffsetFromUTC + returns dateTime.utcOffset() + - Qt::LocalTime: + number of seconds from the UTC + + For Qt::LocalTime the offset depends on the timezone and + daylight savings. + + \param dateTime Datetime value + \return Offset in seconds + */ +int QwtDate::utcOffset( const QDateTime &dateTime ) +{ + int seconds = 0; + + switch( dateTime.timeSpec() ) + { + case Qt::UTC: + { + break; + } + case Qt::OffsetFromUTC: + { + seconds = dateTime.utcOffset(); + } + default: + { + const QDateTime dt1( dateTime.date(), dateTime.time(), Qt::UTC ); + seconds = dateTime.secsTo( dt1 ); + } + } + + return seconds; +} + +/*! + Translate a datetime into a string + + Beside the format expressions documented in QDateTime::toString() + the following expressions are supported: + + - w\n + week number: ( 1 - 53 ) + - ww\n + week number with a leading zero ( 01 - 53 ) + + \param dateTime Datetime value + \param format Format string + \param week0Type Specification of week 0 + + \return Datetime string + \sa QDateTime::toString(), weekNumber(), QwtDateScaleDraw + */ +QString QwtDate::toString( const QDateTime &dateTime, + const QString & format, Week0Type week0Type ) +{ + QString weekNo; + weekNo.setNum( QwtDate::weekNumber( dateTime.date(), week0Type ) ); + + QString weekNoWW; + if ( weekNo.length() == 1 ) + weekNoWW += "0"; + weekNoWW += weekNo; + + QString fmt = format; + fmt.replace( "ww", weekNoWW ); + fmt.replace( "w", weekNo ); + + return dateTime.toString( fmt ); +} diff --git a/qwt/src/qwt_date.h b/qwt/src/qwt_date.h new file mode 100644 index 000000000..30422a1c1 --- /dev/null +++ b/qwt/src/qwt_date.h @@ -0,0 +1,128 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef _QWT_DATE_H_ +#define _QWT_DATE_H_ + +#include "qwt_global.h" +#include + +/*! + \brief A collection of methods around date/time values + + Qt offers convenient classes for dealing with date/time values, + but Qwt uses coordinate systems that are based on doubles. + QwtDate offers methods to translate from QDateTime to double and v.v. + + A double is interpreted as the number of milliseconds since + 1970-01-01T00:00:00 Universal Coordinated Time - also known + as "The Epoch". + + While the range of the Julian day in Qt4 is limited to [0, MAX_INT], + Qt5 stores it as qint64 offering a huge range of valid dates. + As the significance of a double is below this ( assuming a + fraction of 52 bits ) the translation is not + bijective with rounding errors for dates very far from Epoch. + For a resolution of 1 ms those start to happen for dates above the + year 144683. + + An axis for a date/time interval is expected to be aligned + and divided in time/date units like seconds, minutes, ... + QwtDate offers several algorithms that are needed to + calculate these axes. + + \sa QwtDateScaleEngine, QwtDateScaleDraw, QDate, QTime +*/ +class QWT_EXPORT QwtDate +{ +public: + /*! + How to identify the first week of year differs between + countries. + */ + enum Week0Type + { + /*! + According to ISO 8601 the first week of a year is defined + as "the week with the year's first Thursday in it". + + FirstThursday corresponds to the numbering that is + implemented in QDate::weekNumber(). + */ + FirstThursday, + + /*! + "The week with January 1.1 in it." + + In the U.S. this definition is more common than + FirstThursday. + */ + FirstDay + }; + + /*! + Classification of an time interval + + Time intervals needs to be classified to decide how to + align and divide it. + */ + enum IntervalType + { + //! The interval is related to milliseconds + Millisecond, + + //! The interval is related to seconds + Second, + + //! The interval is related to minutes + Minute, + + //! The interval is related to hours + Hour, + + //! The interval is related to days + Day, + + //! The interval is related to weeks + Week, + + //! The interval is related to months + Month, + + //! The interval is related to years + Year + }; + + enum + { + //! The Julian day of "The Epoch" + JulianDayForEpoch = 2440588 + }; + + static QDate minDate(); + static QDate maxDate(); + + static QDateTime toDateTime( double value, + Qt::TimeSpec = Qt::UTC ); + + static double toDouble( const QDateTime & ); + + static QDateTime ceil( const QDateTime &, IntervalType ); + static QDateTime floor( const QDateTime &, IntervalType ); + + static QDate dateOfWeek0( int year, Week0Type ); + static int weekNumber( const QDate &, Week0Type ); + + static int utcOffset( const QDateTime & ); + + static QString toString( const QDateTime &, + const QString & format, Week0Type ); +}; + +#endif diff --git a/qwt/src/qwt_date_scale_draw.cpp b/qwt/src/qwt_date_scale_draw.cpp new file mode 100644 index 000000000..7cfc6dec0 --- /dev/null +++ b/qwt/src/qwt_date_scale_draw.cpp @@ -0,0 +1,269 @@ +#include "qwt_date_scale_draw.h" + +class QwtDateScaleDraw::PrivateData +{ +public: + PrivateData( Qt::TimeSpec spec ): + timeSpec( spec ), + utcOffset( 0 ), + week0Type( QwtDate::FirstThursday ) + { + dateFormats[ QwtDate::Millisecond ] = "hh:mm:ss:zzz\nddd dd MMM yyyy"; + dateFormats[ QwtDate::Second ] = "hh:mm:ss\nddd dd MMM yyyy"; + dateFormats[ QwtDate::Minute ] = "hh:mm\nddd dd MMM yyyy"; + dateFormats[ QwtDate::Hour ] = "hh:mm\nddd dd MMM yyyy"; + dateFormats[ QwtDate::Day ] = "ddd dd MMM yyyy"; + dateFormats[ QwtDate::Week ] = "Www yyyy"; + dateFormats[ QwtDate::Month ] = "MMM yyyy"; + dateFormats[ QwtDate::Year ] = "yyyy"; + } + + Qt::TimeSpec timeSpec; + int utcOffset; + QwtDate::Week0Type week0Type; + QString dateFormats[ QwtDate::Year + 1 ]; +}; + +/*! + \brief Constructor + + The default setting is to display tick labels for the + given time specification. The first week of a year is defined like + for QwtDate::FirstThursday. + + \param timeSpec Time specification + + \sa setTimeSpec(), setWeek0Type() + */ +QwtDateScaleDraw::QwtDateScaleDraw( Qt::TimeSpec timeSpec ) +{ + d_data = new PrivateData( timeSpec ); +} + +//! Destructor +QwtDateScaleDraw::~QwtDateScaleDraw() +{ + delete d_data; +} + +/*! + Set the time specification used for the tick labels + + \param timeSpec Time specification + \sa timeSpec(), setUtcOffset(), toDateTime() + */ +void QwtDateScaleDraw::setTimeSpec( Qt::TimeSpec timeSpec ) +{ + d_data->timeSpec = timeSpec; +} + +/*! + \return Time specification used for the tick labels + \sa setTimeSpec(), utcOffset(), toDateTime() + */ +Qt::TimeSpec QwtDateScaleDraw::timeSpec() const +{ + return d_data->timeSpec; +} + +/*! + Set the offset in seconds from Coordinated Universal Time + + \param seconds Offset in seconds + + \note The offset has no effect beside for the time specification + Qt::OffsetFromUTC. + + \sa QDate::utcOffset(), setTimeSpec(), toDateTime() + */ +void QwtDateScaleDraw::setUtcOffset( int seconds ) +{ + d_data->utcOffset = seconds; +} + +/*! + \return Offset in seconds from Coordinated Universal Time + \note The offset has no effect beside for the time specification + Qt::OffsetFromUTC. + + \sa QDate::setUtcOffset(), setTimeSpec(), toDateTime() + */ +int QwtDateScaleDraw::utcOffset() const +{ + return d_data->utcOffset; +} + +/*! + Sets how to identify the first week of a year. + + \param week0Type Mode how to identify the first week of a year + + \sa week0Type(). + \note week0Type has no effect beside for intervals classified as + QwtDate::Week. + */ +void QwtDateScaleDraw::setWeek0Type( QwtDate::Week0Type week0Type ) +{ + d_data->week0Type = week0Type; +} + +/*! + \return Setting how to identify the first week of a year. + \sa setWeek0Type() + */ +QwtDate::Week0Type QwtDateScaleDraw::week0Type() const +{ + return d_data->week0Type; +} + +/*! + Set the default format string for an datetime interval type + + \param intervalType Interval type + \param format Default format string + + \sa dateFormat(), dateFormatOfDate(), QwtDate::toString() + */ +void QwtDateScaleDraw::setDateFormat( + QwtDate::IntervalType intervalType, const QString &format ) +{ + if ( intervalType >= QwtDate::Millisecond && + intervalType <= QwtDate::Year ) + { + d_data->dateFormats[ intervalType ] = format; + } +} + +/*! + \param intervalType Interval type + \return Default format string for an datetime interval type + \sa setDateFormat(), dateFormatOfDate() + */ +QString QwtDateScaleDraw::dateFormat( + QwtDate::IntervalType intervalType ) const +{ + if ( intervalType >= QwtDate::Millisecond && + intervalType <= QwtDate::Year ) + { + return d_data->dateFormats[ intervalType ]; + } + + return QString::null; +} + +/*! + Format string for the representation of a datetime + + dateFormatOfDate() is intended to be overloaded for + situations, where formats are individual for specific + datetime values. + + The default setting ignores dateTime and return + the default format for the interval type. + + \param dateTime Datetime value + \param intervalType Interval type + \return Format string + + \sa setDateFormat(), QwtDate::toString() + */ +QString QwtDateScaleDraw::dateFormatOfDate( const QDateTime &dateTime, + QwtDate::IntervalType intervalType ) const +{ + Q_UNUSED( dateTime ) + + if ( intervalType >= QwtDate::Millisecond && + intervalType <= QwtDate::Year ) + { + return d_data->dateFormats[ intervalType ]; + } + + return d_data->dateFormats[ QwtDate::Second ]; +} + +/*! + \brief Convert a value into its representing label + + The value is converted to a datetime value using toDateTime() + and converted to a plain text using QwtDate::toString(). + + \param value Value + \return Label string. + + \sa dateFormatOfDate() +*/ +QwtText QwtDateScaleDraw::label( double value ) const +{ + const QDateTime dt = toDateTime( value ); + const QString fmt = dateFormatOfDate( + dt, intervalType( scaleDiv() ) ); + + return QwtDate::toString( dt, fmt, d_data->week0Type ); +} + +/*! + Find the less detailed datetime unit, where no rounding + errors happen. + + \param scaleDiv Scale division + \return Interval type + + \sa dateFormatOfDate() + */ +QwtDate::IntervalType QwtDateScaleDraw::intervalType( + const QwtScaleDiv &scaleDiv ) const +{ + int intvType = QwtDate::Year; + + bool alignedToWeeks = true; + + const QList ticks = scaleDiv.ticks( QwtScaleDiv::MajorTick ); + for ( int i = 0; i < ticks.size(); i++ ) + { + const QDateTime dt = toDateTime( ticks[i] ); + for ( int j = QwtDate::Second; j <= intvType; j++ ) + { + const QDateTime dt0 = QwtDate::floor( dt, + static_cast( j ) ); + + if ( dt0 != dt ) + { + if ( j == QwtDate::Week ) + { + alignedToWeeks = false; + } + else + { + intvType = j - 1; + break; + } + } + } + + if ( intvType == QwtDate::Millisecond ) + break; + } + + if ( intvType == QwtDate::Week && !alignedToWeeks ) + intvType = QwtDate::Day; + + return static_cast( intvType ); +} + +/*! + Translate a double value into a QDateTime object. + + \return QDateTime object initialized with timeSpec() and utcOffset(). + \sa timeSpec(), utcOffset(), QwtDate::toDateTime() + */ +QDateTime QwtDateScaleDraw::toDateTime( double value ) const +{ + QDateTime dt = QwtDate::toDateTime( value, d_data->timeSpec ); + if ( d_data->timeSpec == Qt::OffsetFromUTC ) + { + dt = dt.addSecs( d_data->utcOffset ); + dt.setUtcOffset( d_data->utcOffset ); + } + + return dt; +} diff --git a/qwt/src/qwt_date_scale_draw.h b/qwt/src/qwt_date_scale_draw.h new file mode 100644 index 000000000..92589e890 --- /dev/null +++ b/qwt/src/qwt_date_scale_draw.h @@ -0,0 +1,77 @@ +#ifndef _QWT_DATE_SCALE_DRAW_H_ +#define _QWT_DATE_SCALE_DRAW_H_ 1 + +#include "qwt_global.h" +#include "qwt_scale_draw.h" +#include "qwt_date.h" + +/*! + \brief A class for drawing datetime scales + + QwtDateScaleDraw displays values as datetime labels. + The format of the labels depends on the alignment of + the major tick labels. + + The default format strings are: + + - Millisecond\n + "hh:mm:ss:zzz\nddd dd MMM yyyy" + - Second\n + "hh:mm:ss\nddd dd MMM yyyy" + - Minute\n + "hh:mm\nddd dd MMM yyyy" + - Hour\n + "hh:mm\nddd dd MMM yyyy" + - Day\n + "ddd dd MMM yyyy" + - Week\n + "Www yyyy" + - Month\n + "MMM yyyy" + - Year\n + "yyyy" + + The format strings can be modified using setDateFormat() + or individually for each tick label by overloading dateFormatOfDate(), + + Usually QwtDateScaleDraw is used in combination with + QwtDateScaleEngine, that calculates scales for datetime + intervals. + + \sa QwtDateScaleEngine, QwtPlot::setAxisScaleDraw() +*/ +class QWT_EXPORT QwtDateScaleDraw: public QwtScaleDraw +{ +public: + QwtDateScaleDraw( Qt::TimeSpec = Qt::LocalTime ); + virtual ~QwtDateScaleDraw(); + + void setDateFormat( QwtDate::IntervalType, const QString & ); + QString dateFormat( QwtDate::IntervalType ) const; + + void setTimeSpec( Qt::TimeSpec ); + Qt::TimeSpec timeSpec() const; + + void setUtcOffset( int seconds ); + int utcOffset() const; + + void setWeek0Type( QwtDate::Week0Type ); + QwtDate::Week0Type week0Type() const; + + virtual QwtText label( double ) const; + + QDateTime toDateTime( double ) const; + +protected: + virtual QwtDate::IntervalType + intervalType( const QwtScaleDiv & ) const; + + virtual QString dateFormatOfDate( const QDateTime &, + QwtDate::IntervalType ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_date_scale_engine.cpp b/qwt/src/qwt_date_scale_engine.cpp new file mode 100644 index 000000000..85c1f175a --- /dev/null +++ b/qwt/src/qwt_date_scale_engine.cpp @@ -0,0 +1,1300 @@ +#include "qwt_date_scale_engine.h" +#include "qwt_math.h" +#include "qwt_transform.h" +#include +#include + +static inline double qwtMsecsForType( QwtDate::IntervalType type ) +{ + static const double msecs[] = + { + 1.0, + 1000.0, + 60.0 * 1000.0, + 3600.0 * 1000.0, + 24.0 * 3600.0 * 1000.0, + 7.0 * 24.0 * 3600.0 * 1000.0, + 30.0 * 24.0 * 3600.0 * 1000.0, + 365.0 * 24.0 * 3600.0 * 1000.0, + }; + + if ( type < 0 || type >= static_cast( sizeof( msecs ) / sizeof( msecs[0] ) ) ) + return 1.0; + + return msecs[ type ]; +} + +static inline int qwtAlignValue( + double value, double stepSize, bool up ) +{ + double d = value / stepSize; + d = up ? ::ceil( d ) : ::floor( d ); + + return static_cast( d * stepSize ); +} + +static double qwtIntervalWidth( const QDateTime &minDate, + const QDateTime &maxDate, QwtDate::IntervalType intervalType ) +{ + switch( intervalType ) + { + case QwtDate::Millisecond: + { + const double secsTo = minDate.secsTo( maxDate ); + const double msecs = maxDate.time().msec() - + minDate.time().msec(); + + return secsTo * 1000 + msecs; + } + case QwtDate::Second: + { + return minDate.secsTo( maxDate ); + } + case QwtDate::Minute: + { + const double secsTo = minDate.secsTo( maxDate ); + return ::floor( secsTo / 60 ); + } + case QwtDate::Hour: + { + const double secsTo = minDate.secsTo( maxDate ); + return ::floor( secsTo / 3600 ); + } + case QwtDate::Day: + { + return minDate.daysTo( maxDate ); + } + case QwtDate::Week: + { + return ::floor( minDate.daysTo( maxDate ) / 7.0 ); + } + case QwtDate::Month: + { + const double years = + double( maxDate.date().year() ) - minDate.date().year(); + + int months = maxDate.date().month() - minDate.date().month(); + if ( maxDate.date().day() < minDate.date().day() ) + months--; + + return years * 12 + months; + } + case QwtDate::Year: + { + double years = + double( maxDate.date().year() ) - minDate.date().year(); + + if ( maxDate.date().month() < minDate.date().month() ) + years -= 1.0; + + return years; + } + } + + return 0.0; +} + +static double qwtRoundedIntervalWidth( + const QDateTime &minDate, const QDateTime &maxDate, + QwtDate::IntervalType intervalType ) +{ + const QDateTime minD = QwtDate::floor( minDate, intervalType ); + const QDateTime maxD = QwtDate::ceil( maxDate, intervalType ); + + return qwtIntervalWidth( minD, maxD, intervalType ); +} + +static inline int qwtStepCount( int intervalSize, int maxSteps, + const int limits[], size_t numLimits ) +{ + for ( uint i = 0; i < numLimits; i++ ) + { + const int numSteps = intervalSize / limits[ i ]; + + if ( numSteps > 1 && numSteps <= maxSteps && + numSteps * limits[ i ] == intervalSize ) + { + return numSteps; + } + } + + return 0; +} + +static int qwtStepSize( int intervalSize, int maxSteps, uint base ) +{ + if ( maxSteps <= 0 ) + return 0; + + if ( maxSteps > 2 ) + { + for ( int numSteps = maxSteps; numSteps > 1; numSteps-- ) + { + const double stepSize = double( intervalSize ) / numSteps; + + const double p = ::floor( ::log( stepSize ) / ::log( double( base ) ) ); + const double fraction = qPow( base, p ); + + for ( uint n = base; n >= 1; n /= 2 ) + { + if ( qFuzzyCompare( stepSize, n * fraction ) ) + return qRound( stepSize ); + + if ( n == 3 && ( base % 2 ) == 0 ) + { + if ( qFuzzyCompare( stepSize, 2 * fraction ) ) + return qRound( stepSize ); + } + } + } + } + + return 0; +} + +static int qwtDivideInterval( double intervalSize, int numSteps, + const int limits[], size_t numLimits ) +{ + const int v = qCeil( intervalSize / double( numSteps ) ); + + for ( uint i = 0; i < numLimits - 1; i++ ) + { + if ( v <= limits[i] ) + return limits[i]; + } + + return limits[ numLimits - 1 ]; +} + +static double qwtDivideScale( double intervalSize, int numSteps, + QwtDate::IntervalType intervalType ) +{ + if ( intervalType != QwtDate::Day ) + { + if ( ( intervalSize > numSteps ) && + ( intervalSize <= 2 * numSteps ) ) + { + return 2.0; + } + } + + double stepSize; + + switch( intervalType ) + { + case QwtDate::Second: + case QwtDate::Minute: + { + static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 }; + + stepSize = qwtDivideInterval( intervalSize, numSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + break; + } + case QwtDate::Hour: + { + static int limits[] = { 1, 2, 3, 4, 6, 12, 24 }; + + stepSize = qwtDivideInterval( intervalSize, numSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + break; + } + case QwtDate::Day: + { + const double v = intervalSize / double( numSteps ); + if ( v <= 5.0 ) + stepSize = qCeil( v ); + else + stepSize = qCeil( v / 7 ) * 7; + + break; + } + case QwtDate::Week: + { + static int limits[] = { 1, 2, 4, 8, 12, 26, 52 }; + + stepSize = qwtDivideInterval( intervalSize, numSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + break; + } + case QwtDate::Month: + { + static int limits[] = { 1, 2, 3, 4, 6, 12 }; + + stepSize = qwtDivideInterval( intervalSize, numSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + break; + } + case QwtDate::Year: + case QwtDate::Millisecond: + default: + { + stepSize = QwtScaleArithmetic::divideInterval( + intervalSize, numSteps, 10 ); + } + } + + return stepSize; +} + +static double qwtDivideMajorStep( double stepSize, int maxMinSteps, + QwtDate::IntervalType intervalType ) +{ + double minStepSize = 0.0; + + switch( intervalType ) + { + case QwtDate::Second: + { + minStepSize = qwtStepSize( stepSize, maxMinSteps, 10 ); + if ( minStepSize == 0.0 ) + minStepSize = 0.5 * stepSize; + + break; + } + case QwtDate::Minute: + { + static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 }; + + int numSteps; + + if ( stepSize > maxMinSteps ) + { + numSteps = qwtStepCount( stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + } + else + { + numSteps = qwtStepCount( stepSize * 60, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + } + + if ( numSteps > 0 ) + minStepSize = double( stepSize ) / numSteps; + + break; + } + case QwtDate::Hour: + { + int numSteps = 0; + + if ( stepSize > maxMinSteps ) + { + static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 }; + + numSteps = qwtStepCount( stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + } + else + { + static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 }; + + numSteps = qwtStepCount( stepSize * 60, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + } + + if ( numSteps > 0 ) + minStepSize = double( stepSize ) / numSteps; + + break; + } + case QwtDate::Day: + { + int numSteps = 0; + + if ( stepSize > maxMinSteps ) + { + static int limits[] = { 1, 2, 3, 7, 14, 28 }; + + numSteps = qwtStepCount( stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + } + else + { + static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 }; + + numSteps = qwtStepCount( stepSize * 24, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + } + + if ( numSteps > 0 ) + minStepSize = double( stepSize ) / numSteps; + + break; + } + case QwtDate::Week: + { + const int daysInStep = stepSize * 7; + + if ( maxMinSteps >= daysInStep ) + { + // we want to have one tick per day + minStepSize = 1.0 / 7.0; + } + else + { + // when the stepSize is more than a week we want to + // have a tick for each week + + const int stepSizeInWeeks = stepSize; + + if ( stepSizeInWeeks <= maxMinSteps ) + { + minStepSize = 1; + } + else + { + minStepSize = QwtScaleArithmetic::divideInterval( + stepSizeInWeeks, maxMinSteps, 10 ); + } + } + break; + } + case QwtDate::Month: + { + // fractions of months doesn't make any sense + + if ( stepSize < maxMinSteps ) + maxMinSteps = static_cast( stepSize ); + + static int limits[] = { 1, 2, 3, 4, 6, 12 }; + + int numSteps = qwtStepCount( stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + if ( numSteps > 0 ) + minStepSize = double( stepSize ) / numSteps; + + break; + } + case QwtDate::Year: + { + if ( stepSize >= maxMinSteps ) + { + minStepSize = QwtScaleArithmetic::divideInterval( + stepSize, maxMinSteps, 10 ); + } + else + { + // something in months + + static int limits[] = { 1, 2, 3, 4, 6, 12 }; + + int numSteps = qwtStepCount( 12 * stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + if ( numSteps > 0 ) + minStepSize = double( stepSize ) / numSteps; + } + + break; + } + default: + break; + } + + if ( intervalType != QwtDate::Month + && minStepSize == 0.0 ) + { + minStepSize = 0.5 * stepSize; + } + + return minStepSize; +} + +static QList qwtDstTicks( const QDateTime &dateTime, + int secondsMajor, int secondsMinor ) +{ + if ( secondsMinor <= 0 ) + QList(); + + QDateTime minDate = dateTime.addSecs( -secondsMajor ); + minDate = QwtDate::floor( minDate, QwtDate::Hour ); + + const double utcOffset = QwtDate::utcOffset( dateTime ); + + // find the hours where daylight saving time happens + + double dstMin = QwtDate::toDouble( minDate ); + while ( minDate < dateTime && + QwtDate::utcOffset( minDate ) != utcOffset ) + { + minDate = minDate.addSecs( 3600 ); + dstMin += 3600 * 1000.0; + } + + QList ticks; + for ( int i = 0; i < 3600; i += secondsMinor ) + ticks += dstMin + i * 1000.0; + + return ticks; +} + +static QwtScaleDiv qwtDivideToSeconds( + const QDateTime &minDate, const QDateTime &maxDate, + double stepSize, int maxMinSteps, + QwtDate::IntervalType intervalType ) +{ + // calculate the min step size + double minStepSize = 0; + + if ( maxMinSteps > 1 ) + { + minStepSize = qwtDivideMajorStep( stepSize, + maxMinSteps, intervalType ); + } + + bool daylightSaving = false; + if ( minDate.timeSpec() == Qt::LocalTime ) + { + daylightSaving = intervalType > QwtDate::Hour; + if ( intervalType == QwtDate::Hour ) + { + daylightSaving = stepSize > 1; + } + } + + const double s = qwtMsecsForType( intervalType ) / 1000; + const int secondsMajor = static_cast( stepSize * s ); + const double secondsMinor = minStepSize * s; + + // UTC excludes daylight savings. So from the difference + // of a date and its UTC counterpart we can find out + // the daylight saving hours + + const double utcOffset = QwtDate::utcOffset( minDate ); + double dstOff = 0; + + QList majorTicks; + QList mediumTicks; + QList minorTicks; + + for ( QDateTime dt = minDate; dt <= maxDate; + dt = dt.addSecs( secondsMajor ) ) + { + if ( !dt.isValid() ) + break; + + double majorValue = QwtDate::toDouble( dt ); + + if ( daylightSaving ) + { + const double offset = utcOffset - QwtDate::utcOffset( dt ); + majorValue += offset * 1000.0; + + if ( offset > dstOff ) + { + // we add some minor ticks for the DST hour, + // otherwise the ticks will be unaligned: 0, 2, 3, 5 ... + minorTicks += qwtDstTicks( + dt, secondsMajor, qRound( secondsMinor ) ); + } + + dstOff = offset; + } + + if ( majorTicks.isEmpty() || majorTicks.last() != majorValue ) + majorTicks += majorValue; + + if ( secondsMinor > 0.0 ) + { + const int numMinorSteps = qFloor( secondsMajor / secondsMinor ); + + for ( int i = 1; i < numMinorSteps; i++ ) + { + const QDateTime mt = dt.addMSecs( + qRound64( i * secondsMinor * 1000 ) ); + + double minorValue = QwtDate::toDouble( mt ); + if ( daylightSaving ) + { + const double offset = utcOffset - QwtDate::utcOffset( mt ); + minorValue += offset * 1000.0; + } + + if ( minorTicks.isEmpty() || minorTicks.last() != minorValue ) + { + const bool isMedium = ( numMinorSteps % 2 == 0 ) + && ( i != 1 ) && ( i == numMinorSteps / 2 ); + + if ( isMedium ) + mediumTicks += minorValue; + else + minorTicks += minorValue; + } + } + } + } + + QwtScaleDiv scaleDiv; + + scaleDiv.setInterval( QwtDate::toDouble( minDate ), + QwtDate::toDouble( maxDate ) ); + + scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks ); + scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks ); + scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks ); + + return scaleDiv; +} + +static QwtScaleDiv qwtDivideToMonths( + QDateTime &minDate, const QDateTime &maxDate, + double stepSize, int maxMinSteps ) +{ + // months are intervals with non + // equidistant ( in ms ) steps: we have to build the + // scale division manually + + int minStepDays = 0; + int minStepSize = 0.0; + + if ( maxMinSteps > 1 ) + { + if ( stepSize == 1 ) + { + if ( maxMinSteps >= 30 ) + minStepDays = 1; + else if ( maxMinSteps >= 6 ) + minStepDays = 5; + else if ( maxMinSteps >= 3 ) + minStepDays = 10; + + minStepDays = 15; + } + else + { + minStepSize = qwtDivideMajorStep( + stepSize, maxMinSteps, QwtDate::Month ); + } + } + + QList majorTicks; + QList mediumTicks; + QList minorTicks; + + for ( QDateTime dt = minDate; + dt <= maxDate; dt = dt.addMonths( stepSize ) ) + { + if ( !dt.isValid() ) + break; + + majorTicks += QwtDate::toDouble( dt ); + + if ( minStepDays > 0 ) + { + for ( int days = minStepDays; + days < 30; days += minStepDays ) + { + const double tick = QwtDate::toDouble( dt.addDays( days ) ); + + if ( days == 15 && minStepDays != 15 ) + mediumTicks += tick; + else + minorTicks += tick; + } + } + else if ( minStepSize > 0.0 ) + { + const int numMinorSteps = qRound( stepSize / (double) minStepSize ); + + for ( int i = 1; i < numMinorSteps; i++ ) + { + const double minorValue = + QwtDate::toDouble( dt.addMonths( i * minStepSize ) ); + + if ( ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 ) ) + mediumTicks += minorValue; + else + minorTicks += minorValue; + } + } + } + + QwtScaleDiv scaleDiv; + scaleDiv.setInterval( QwtDate::toDouble( minDate ), + QwtDate::toDouble( maxDate ) ); + + scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks ); + scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks ); + scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks ); + + return scaleDiv; +} + +static QwtScaleDiv qwtDivideToYears( + const QDateTime &minDate, const QDateTime &maxDate, + double stepSize, int maxMinSteps ) +{ + QList majorTicks; + QList mediumTicks; + QList minorTicks; + + double minStepSize = 0.0; + + if ( maxMinSteps > 1 ) + { + minStepSize = qwtDivideMajorStep( + stepSize, maxMinSteps, QwtDate::Year ); + } + + int numMinorSteps = 0; + if ( minStepSize > 0.0 ) + numMinorSteps = qFloor( stepSize / minStepSize ); + + bool dateBC = minDate.date().year() < -1; + + for ( QDateTime dt = minDate; dt <= maxDate; + dt = dt.addYears( stepSize ) ) + { + if ( dateBC && dt.date().year() > 1 ) + { + // there is no year 0 in the Julian calendar + dt = dt.addYears( -1 ); + dateBC = false; + } + + if ( !dt.isValid() ) + break; + + majorTicks += QwtDate::toDouble( dt ); + + for ( int i = 1; i < numMinorSteps; i++ ) + { + QDateTime tickDate; + + const double years = qRound( i * minStepSize ); + if ( years >= INT_MAX / 12 ) + { + tickDate = dt.addYears( years ); + } + else + { + tickDate = dt.addMonths( qRound( years * 12 ) ); + } + + const bool isMedium = ( numMinorSteps > 2 ) && + ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 ); + + const double minorValue = QwtDate::toDouble( tickDate ); + if ( isMedium ) + mediumTicks += minorValue; + else + minorTicks += minorValue; + } + + if ( QwtDate::maxDate().addYears( -stepSize ) < dt.date() ) + { + break; + } + } + + QwtScaleDiv scaleDiv; + scaleDiv.setInterval( QwtDate::toDouble( minDate ), + QwtDate::toDouble( maxDate ) ); + + scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks ); + scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks ); + scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks ); + + return scaleDiv; +} + +class QwtDateScaleEngine::PrivateData +{ +public: + PrivateData( Qt::TimeSpec spec ): + timeSpec( spec ), + utcOffset( 0 ), + week0Type( QwtDate::FirstThursday ), + maxWeeks( 4 ) + { + } + + Qt::TimeSpec timeSpec; + int utcOffset; + QwtDate::Week0Type week0Type; + int maxWeeks; +}; + + +/*! + \brief Constructor + + The engine is initialized to build scales for the + given time specification. It classifies intervals > 4 weeks + as >= Qt::Month. The first week of a year is defined like + for QwtDate::FirstThursday. + + \param timeSpec Time specification + + \sa setTimeSpec(), setMaxWeeks(), setWeek0Type() + */ +QwtDateScaleEngine::QwtDateScaleEngine( Qt::TimeSpec timeSpec ): + QwtLinearScaleEngine( 10 ) +{ + d_data = new PrivateData( timeSpec ); +} + +//! Destructor +QwtDateScaleEngine::~QwtDateScaleEngine() +{ + delete d_data; +} + +/*! + Set the time specification used by the engine + + \param timeSpec Time specification + \sa timeSpec(), setUtcOffset(), toDateTime() + */ +void QwtDateScaleEngine::setTimeSpec( Qt::TimeSpec timeSpec ) +{ + d_data->timeSpec = timeSpec; +} + +/*! + \return Time specification used by the engine + \sa setTimeSpec(), utcOffset(), toDateTime() + */ +Qt::TimeSpec QwtDateScaleEngine::timeSpec() const +{ + return d_data->timeSpec; +} + +/*! + Set the offset in seconds from Coordinated Universal Time + + \param seconds Offset in seconds + + \note The offset has no effect beside for the time specification + Qt::OffsetFromUTC. + + \sa QDate::utcOffset(), setTimeSpec(), toDateTime() + */ +void QwtDateScaleEngine::setUtcOffset( int seconds ) +{ + d_data->utcOffset = seconds; +} + +/*! + \return Offset in seconds from Coordinated Universal Time + \note The offset has no effect beside for the time specification + Qt::OffsetFromUTC. + + \sa QDate::setUtcOffset(), setTimeSpec(), toDateTime() + */ +int QwtDateScaleEngine::utcOffset() const +{ + return d_data->utcOffset; +} + +/*! + Sets how to identify the first week of a year. + + \param week0Type Mode how to identify the first week of a year + + \sa week0Type(), setMaxWeeks() + \note week0Type has no effect beside for intervals classified as + QwtDate::Week. + */ +void QwtDateScaleEngine::setWeek0Type( QwtDate::Week0Type week0Type ) +{ + d_data->week0Type = week0Type; +} + +/*! + \return Setting how to identify the first week of a year. + \sa setWeek0Type(), maxWeeks() + */ +QwtDate::Week0Type QwtDateScaleEngine::week0Type() const +{ + return d_data->week0Type; +} + +/*! + Set a upper limit for the number of weeks, when an interval + can be classified as Qt::Week. + + The default setting is 4 weeks. + + \param weeks Upper limit for the number of weeks + + \note In business charts a year is often devided + into weeks [1-52] + \sa maxWeeks(), setWeek0Type() + */ +void QwtDateScaleEngine::setMaxWeeks( int weeks ) +{ + d_data->maxWeeks = qMax( weeks, 0 ); +} + +/*! + \return Upper limit for the number of weeks, when an interval + can be classified as Qt::Week. + \sa setMaxWeeks(), week0Type() + */ +int QwtDateScaleEngine::maxWeeks() const +{ + return d_data->maxWeeks; +} + +/*! + Classification of a date/time interval division + + \param minDate Minimum ( = earlier ) of the interval + \param maxDate Maximum ( = later ) of the interval + \param maxSteps Maximum for the number of steps + + \return Interval classification + */ +QwtDate::IntervalType QwtDateScaleEngine::intervalType( + const QDateTime &minDate, const QDateTime &maxDate, + int maxSteps ) const +{ + const double jdMin = minDate.date().toJulianDay(); + const double jdMax = maxDate.date().toJulianDay(); + + if ( ( jdMax - jdMin ) / 365 > maxSteps ) + return QwtDate::Year; + + const int months = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Month ); + if ( months > maxSteps * 6 ) + return QwtDate::Year; + + const int days = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Day ); + const int weeks = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Week ); + + if ( weeks > d_data->maxWeeks ) + { + if ( days > 4 * maxSteps * 7 ) + return QwtDate::Month; + } + + if ( days > maxSteps * 7 ) + return QwtDate::Week; + + const int hours = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Hour ); + if ( hours > maxSteps * 24 ) + return QwtDate::Day; + + const int seconds = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Second ); + + if ( seconds >= maxSteps * 3600 ) + return QwtDate::Hour; + + if ( seconds >= maxSteps * 60 ) + return QwtDate::Minute; + + if ( seconds >= maxSteps ) + return QwtDate::Second; + + return QwtDate::Millisecond; +} + +/*! + Align and divide an interval + + The algorithm aligns and divides the interval into steps. + + Datetime interval divisions are usually not equidistant and the + calculated stepSize can only be used as an approximation + for the steps calculated by divideScale(). + + \param maxNumSteps Max. number of steps + \param x1 First limit of the interval (In/Out) + \param x2 Second limit of the interval (In/Out) + \param stepSize Step size (Out) + + \sa QwtScaleEngine::setAttribute() +*/ +void QwtDateScaleEngine::autoScale( int maxNumSteps, + double &x1, double &x2, double &stepSize ) const +{ + stepSize = 0.0; + + QwtInterval interval( x1, x2 ); + interval = interval.normalized(); + + interval.setMinValue( interval.minValue() - lowerMargin() ); + interval.setMaxValue( interval.maxValue() + upperMargin() ); + + if ( testAttribute( QwtScaleEngine::Symmetric ) ) + interval = interval.symmetrize( reference() ); + + if ( testAttribute( QwtScaleEngine::IncludeReference ) ) + interval = interval.extend( reference() ); + + if ( interval.width() == 0.0 ) + interval = buildInterval( interval.minValue() ); + + const QDateTime from = toDateTime( interval.minValue() ); + const QDateTime to = toDateTime( interval.maxValue() ); + + if ( from.isValid() && to.isValid() ) + { + if ( maxNumSteps < 1 ) + maxNumSteps = 1; + + const QwtDate::IntervalType intvType = + intervalType( from, to, maxNumSteps ); + + const double width = qwtIntervalWidth( from, to, intvType ); + + const double stepWidth = qwtDivideScale( width, maxNumSteps, intvType ); + if ( stepWidth != 0.0 && !testAttribute( QwtScaleEngine::Floating ) ) + { + const QDateTime d1 = alignDate( from, stepWidth, intvType, false ); + const QDateTime d2 = alignDate( to, stepWidth, intvType, true ); + + interval.setMinValue( QwtDate::toDouble( d1 ) ); + interval.setMaxValue( QwtDate::toDouble( d2 ) ); + } + + stepSize = stepWidth * qwtMsecsForType( intvType ); + } + + x1 = interval.minValue(); + x2 = interval.maxValue(); + + if ( testAttribute( QwtScaleEngine::Inverted ) ) + { + qSwap( x1, x2 ); + stepSize = -stepSize; + } +} + +/*! + \brief Calculate a scale division for a date/time interval + + \param x1 First interval limit + \param x2 Second interval limit + \param maxMajorSteps Maximum for the number of major steps + \param maxMinorSteps Maximum number of minor steps + \param stepSize Step size. If stepSize == 0, the scaleEngine + calculates one. + \return Calculated scale division +*/ +QwtScaleDiv QwtDateScaleEngine::divideScale( double x1, double x2, + int maxMajorSteps, int maxMinorSteps, double stepSize ) const +{ + if ( maxMajorSteps < 1 ) + maxMajorSteps = 1; + + const double min = qMin( x1, x2 ); + const double max = qMax( x1, x2 ); + + const QDateTime from = toDateTime( min ); + const QDateTime to = toDateTime( max ); + + if ( from == to ) + return QwtScaleDiv(); + + stepSize = qAbs( stepSize ); + if ( stepSize > 0.0 ) + { + // as interval types above hours are not equidistant + // ( even days might have 23/25 hours because of daylight saving ) + // the stepSize is used as a hint only + + maxMajorSteps = qCeil( ( max - min ) / stepSize ); + } + + const QwtDate::IntervalType intvType = + intervalType( from, to, maxMajorSteps ); + + QwtScaleDiv scaleDiv; + + if ( intvType == QwtDate::Millisecond ) + { + // for milliseconds and below we can use the decimal system + scaleDiv = QwtLinearScaleEngine::divideScale( min, max, + maxMajorSteps, maxMinorSteps, stepSize ); + } + else + { + const QDateTime minDate = QwtDate::floor( from, intvType ); + const QDateTime maxDate = QwtDate::ceil( to, intvType ); + + scaleDiv = buildScaleDiv( minDate, maxDate, + maxMajorSteps, maxMinorSteps, intvType ); + + // scaleDiv has been calculated from an extended interval + // adjusted to the step size. We have to shrink it again. + + scaleDiv = scaleDiv.bounded( min, max ); + } + + if ( x1 > x2 ) + scaleDiv.invert(); + + return scaleDiv; +} + +QwtScaleDiv QwtDateScaleEngine::buildScaleDiv( + const QDateTime &minDate, const QDateTime &maxDate, + int maxMajorSteps, int maxMinorSteps, + QwtDate::IntervalType intervalType ) const +{ + // calculate the step size + const double stepSize = qwtDivideScale( + qwtIntervalWidth( minDate, maxDate, intervalType ), + maxMajorSteps, intervalType ); + + // align minDate to the step size + QDateTime dt0 = alignDate( minDate, stepSize, intervalType, false ); + if ( !dt0.isValid() ) + { + // the floored date is out of the range of a + // QDateTime - we ceil instead. + dt0 = alignDate( minDate, stepSize, intervalType, true ); + } + + QwtScaleDiv scaleDiv; + + if ( intervalType <= QwtDate::Week ) + { + scaleDiv = qwtDivideToSeconds( dt0, maxDate, + stepSize, maxMinorSteps, intervalType ); + } + else + { + if( intervalType == QwtDate::Month ) + { + scaleDiv = qwtDivideToMonths( dt0, maxDate, + stepSize, maxMinorSteps ); + } + else if ( intervalType == QwtDate::Year ) + { + scaleDiv = qwtDivideToYears( dt0, maxDate, + stepSize, maxMinorSteps ); + } + } + + + return scaleDiv; +} + +/*! + Align a date/time value for a step size + + For Qt::Day alignments there is no "natural day 0" - + instead the first day of the year is used to avoid jumping + major ticks positions when panning a scale. For other alignments + ( f.e according to the first day of the month ) alignDate() + has to be overloaded. + + \param dateTime Date/time value + \param stepSize Step size + \param intervalType Interval type + \param up When true dateTime is ceiled - otherwise it is floored + + \return Aligned date/time value + */ +QDateTime QwtDateScaleEngine::alignDate( + const QDateTime &dateTime, double stepSize, + QwtDate::IntervalType intervalType, bool up ) const +{ + // what about: (year == 1582 && month == 10 && day > 4 && day < 15) ?? + + QDateTime dt = dateTime; + + if ( dateTime.timeSpec() == Qt::OffsetFromUTC ) + { + dt.setUtcOffset( 0 ); + } + + switch( intervalType ) + { + case QwtDate::Millisecond: + { + const int ms = qwtAlignValue( + dt.time().msec(), stepSize, up ) ; + + dt = QwtDate::floor( dateTime, QwtDate::Second ); + dt = dt.addMSecs( ms ); + + break; + } + case QwtDate::Second: + { + int second = dt.time().second(); + if ( up ) + { + if ( dt.time().msec() > 0 ) + second++; + } + + const int s = qwtAlignValue( second, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Minute ); + dt = dt.addSecs( s ); + + break; + } + case QwtDate::Minute: + { + int minute = dt.time().minute(); + if ( up ) + { + if ( dt.time().msec() > 0 || dt.time().second() > 0 ) + minute++; + } + + const int m = qwtAlignValue( minute, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Hour ); + dt = dt.addSecs( m * 60 ); + + break; + } + case QwtDate::Hour: + { + int hour = dt.time().hour(); + if ( up ) + { + if ( dt.time().msec() > 0 || dt.time().second() > 0 + || dt.time().minute() > 0 ) + { + hour++; + } + } + const int h = qwtAlignValue( hour, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Day ); + dt = dt.addSecs( h * 3600 ); + + break; + } + case QwtDate::Day: + { + // What date do we expect f.e. from an alignment of 5 days ?? + // Aligning them to the beginning of the year avoids at least + // jumping major ticks when panning + + int day = dt.date().dayOfYear(); + if ( up ) + { + if ( dt.time() > QTime( 0, 0 ) ) + day++; + } + + const int d = qwtAlignValue( day, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Year ); + dt = dt.addDays( d - 1 ); + + break; + } + case QwtDate::Week: + { + const QDate date = QwtDate::dateOfWeek0( + dt.date().year(), d_data->week0Type ); + + int numWeeks = date.daysTo( dt.date() ) / 7; + if ( up ) + { + if ( dt.time() > QTime( 0, 0 ) || + date.daysTo( dt.date() ) % 7 ) + { + numWeeks++; + } + } + + const int d = qwtAlignValue( numWeeks, stepSize, up ) * 7; + + dt = QwtDate::floor( dt, QwtDate::Day ); + dt.setDate( date ); + dt = dt.addDays( d ); + + break; + } + case QwtDate::Month: + { + int month = dt.date().month(); + if ( up ) + { + if ( dt.date().day() > 1 || + dt.time() > QTime( 0, 0 ) ) + { + month++; + } + } + + const int m = qwtAlignValue( month - 1, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Year ); + dt = dt.addMonths( m ); + + break; + } + case QwtDate::Year: + { + int year = dateTime.date().year(); + if ( up ) + { + if ( dateTime.date().dayOfYear() > 1 || + dt.time() > QTime( 0, 0 ) ) + { + year++; + } + } + + const int y = qwtAlignValue( year, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Day ); + if ( y == 0 ) + { + // there is no year 0 in the Julian calendar + dt.setDate( QDate( stepSize, 1, 1 ).addYears( -stepSize ) ); + } + else + { + dt.setDate( QDate( y, 1, 1 ) ); + } + + break; + } + } + + if ( dateTime.timeSpec() == Qt::OffsetFromUTC ) + { + dt.setUtcOffset( dateTime.utcOffset() ); + } + + return dt; +} + +/*! + Translate a double value into a QDateTime object. + + For QDateTime result is bounded by QwtDate::minDate() and QwtDate::maxDate() + + \return QDateTime object initialized with timeSpec() and utcOffset(). + \sa timeSpec(), utcOffset(), QwtDate::toDateTime() + */ +QDateTime QwtDateScaleEngine::toDateTime( double value ) const +{ + QDateTime dt = QwtDate::toDateTime( value, d_data->timeSpec ); + if ( !dt.isValid() ) + { + const QDate date = ( value <= 0.0 ) + ? QwtDate::minDate() : QwtDate::maxDate(); + + dt = QDateTime( date, QTime( 0, 0 ), d_data->timeSpec ); + } + + if ( d_data->timeSpec == Qt::OffsetFromUTC ) + { + dt = dt.addSecs( d_data->utcOffset ); + dt.setUtcOffset( d_data->utcOffset ); + } + + return dt; +} + diff --git a/qwt/src/qwt_date_scale_engine.h b/qwt/src/qwt_date_scale_engine.h new file mode 100644 index 000000000..db1d0f134 --- /dev/null +++ b/qwt/src/qwt_date_scale_engine.h @@ -0,0 +1,77 @@ +#ifndef _QWT_DATE_SCALE_ENGINE_H_ +#define _QWT_DATE_SCALE_ENGINE_H_ 1 + +#include "qwt_date.h" +#include "qwt_scale_engine.h" + +/*! + \brief A scale engine for date/time values + + QwtDateScaleEngine builds scales from a time intervals. + Together with QwtDateScaleDraw it can be used for + axes according to date/time values. + + Years, months, weeks, days, hours and minutes are organized + in steps with non constant intervals. QwtDateScaleEngine + classifies intervals and aligns the boundaries and tick positions + according to this classification. + + QwtDateScaleEngine supports representations depending + on Qt::TimeSpec specifications. The valid range for scales + is limited by the range of QDateTime, that differs + between Qt4 and Qt5. + + Datetime values are expected as the number of milliseconds since + 1970-01-01T00:00:00 Universal Coordinated Time - also known + as "The Epoch", that can be converted to QDateTime using + QwtDate::toDateTime(). + + \sa QwtDate, QwtPlot::setAxisScaleEngine(), + QwtAbstractScale::setScaleEngine() +*/ +class QWT_EXPORT QwtDateScaleEngine: public QwtLinearScaleEngine +{ +public: + QwtDateScaleEngine( Qt::TimeSpec = Qt::LocalTime ); + virtual ~QwtDateScaleEngine(); + + void setTimeSpec( Qt::TimeSpec ); + Qt::TimeSpec timeSpec() const; + + void setUtcOffset( int seconds ); + int utcOffset() const; + + void setWeek0Type( QwtDate::Week0Type ); + QwtDate::Week0Type week0Type() const; + + void setMaxWeeks( int ); + int maxWeeks() const; + + virtual void autoScale( int maxNumSteps, + double &x1, double &x2, double &stepSize ) const; + + virtual QwtScaleDiv divideScale( + double x1, double x2, + int maxMajorSteps, int maxMinorSteps, + double stepSize = 0.0 ) const; + + virtual QwtDate::IntervalType intervalType( + const QDateTime &, const QDateTime &, int maxSteps ) const; + + QDateTime toDateTime( double ) const; + +protected: + virtual QDateTime alignDate( const QDateTime &, double stepSize, + QwtDate::IntervalType, bool up ) const; + +private: + QwtScaleDiv buildScaleDiv( const QDateTime &, const QDateTime &, + int maxMajorSteps, int maxMinorSteps, + QwtDate::IntervalType ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_dial.cpp b/qwt/src/qwt_dial.cpp new file mode 100644 index 000000000..1440eb14a --- /dev/null +++ b/qwt/src/qwt_dial.cpp @@ -0,0 +1,872 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_dial.h" +#include "qwt_dial_needle.h" +#include "qwt_math.h" +#include "qwt_scale_engine.h" +#include "qwt_scale_map.h" +#include "qwt_round_scale_draw.h" +#include "qwt_painter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline double qwtAngleDist( double a1, double a2 ) +{ + double dist = qAbs( a2 - a1 ); + if ( dist > 360.0 ) + dist -= 360.0; + + return dist; +} + +static inline bool qwtIsOnArc( double angle, double min, double max ) +{ + if ( min < max ) + { + return ( angle >= min ) && ( angle <= max ); + } + else + { + return ( angle >= min ) || ( angle <= max ); + } +} + +static inline double qwtBoundedAngle( double min, double angle, double max ) +{ + double from = qwtNormalizeDegrees( min ); + double to = qwtNormalizeDegrees( max ); + + double a; + + if ( qwtIsOnArc( angle, from, to ) ) + { + a = angle; + if ( a < min ) + a += 360.0; + } + else + { + if ( qwtAngleDist( angle, from ) < + qwtAngleDist( angle, to ) ) + { + a = min; + } + else + { + a = max; + } + } + + return a; +} + +class QwtDial::PrivateData +{ +public: + PrivateData(): + frameShadow( Sunken ), + lineWidth( 0 ), + mode( RotateNeedle ), + origin( 90.0 ), + minScaleArc( 0.0 ), + maxScaleArc( 0.0 ), + needle( NULL ), + arcOffset( 0.0 ), + mouseOffset( 0.0 ) + { + } + + ~PrivateData() + { + delete needle; + } + Shadow frameShadow; + int lineWidth; + + QwtDial::Mode mode; + + double origin; + double minScaleArc; + double maxScaleArc; + + double scalePenWidth; + QwtDialNeedle *needle; + + double arcOffset; + double mouseOffset; + + QPixmap pixmapCache; +}; + +/*! + \brief Constructor + \param parent Parent widget + + Create a dial widget with no needle. The scale is initialized + to [ 0.0, 360.0 ] and 360 steps ( QwtAbstractSlider::setTotalSteps() ). + The origin of the scale is at 90°, + + The value is set to 0.0. + + The default mode is QwtDial::RotateNeedle. +*/ +QwtDial::QwtDial( QWidget* parent ): + QwtAbstractSlider( parent ) +{ + d_data = new PrivateData; + + setFocusPolicy( Qt::TabFocus ); + + QPalette p = palette(); + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + const QPalette::ColorGroup colorGroup = + static_cast( i ); + + // Base: background color of the circle inside the frame. + // WindowText: background color of the circle inside the scale + + p.setColor( colorGroup, QPalette::WindowText, + p.color( colorGroup, QPalette::Base ) ); + } + setPalette( p ); + + QwtRoundScaleDraw* scaleDraw = new QwtRoundScaleDraw(); + scaleDraw->setRadius( 0 ); + + setScaleDraw( scaleDraw ); + + setScaleArc( 0.0, 360.0 ); // scale as a full circle + + setScaleMaxMajor( 10 ); + setScaleMaxMinor( 5 ); + + setValue( 0.0 ); +} + +//! Destructor +QwtDial::~QwtDial() +{ + delete d_data; +} + +/*! + Sets the frame shadow value from the frame style. + + \param shadow Frame shadow + \sa setLineWidth(), QFrame::setFrameShadow() +*/ +void QwtDial::setFrameShadow( Shadow shadow ) +{ + if ( shadow != d_data->frameShadow ) + { + invalidateCache(); + + d_data->frameShadow = shadow; + if ( lineWidth() > 0 ) + update(); + } +} + +/*! + \return Frame shadow + /sa setFrameShadow(), lineWidth(), QFrame::frameShadow() +*/ +QwtDial::Shadow QwtDial::frameShadow() const +{ + return d_data->frameShadow; +} + +/*! + Sets the line width of the frame + + \param lineWidth Line width + \sa setFrameShadow() +*/ +void QwtDial::setLineWidth( int lineWidth ) +{ + if ( lineWidth < 0 ) + lineWidth = 0; + + if ( d_data->lineWidth != lineWidth ) + { + invalidateCache(); + + d_data->lineWidth = lineWidth; + update(); + } +} + +/*! + \return Line width of the frame + \sa setLineWidth(), frameShadow(), lineWidth() +*/ +int QwtDial::lineWidth() const +{ + return d_data->lineWidth; +} + +/*! + \return bounding rectangle of the circle inside the frame + \sa setLineWidth(), scaleInnerRect(), boundingRect() +*/ +QRect QwtDial::innerRect() const +{ + const int lw = lineWidth(); + return boundingRect().adjusted( lw, lw, -lw, -lw ); +} + +/*! + \return bounding rectangle of the dial including the frame + \sa setLineWidth(), scaleInnerRect(), innerRect() +*/ +QRect QwtDial::boundingRect() const +{ + const QRect cr = contentsRect(); + + const double dim = qMin( cr.width(), cr.height() ); + + QRect inner( 0, 0, dim, dim ); + inner.moveCenter( cr.center() ); + + return inner; +} + +/*! + \return rectangle inside the scale + \sa setLineWidth(), boundingRect(), innerRect() +*/ +QRect QwtDial::scaleInnerRect() const +{ + QRect rect = innerRect(); + + const QwtAbstractScaleDraw *sd = scaleDraw(); + if ( sd ) + { + int scaleDist = qCeil( sd->extent( font() ) ); + scaleDist++; // margin + + rect.adjust( scaleDist, scaleDist, -scaleDist, -scaleDist ); + } + + return rect; +} + +/*! + \brief Change the mode of the dial. + \param mode New mode + + In case of QwtDial::RotateNeedle the needle is rotating, in case of + QwtDial::RotateScale, the needle points to origin() + and the scale is rotating. + + The default mode is QwtDial::RotateNeedle. + + \sa mode(), setValue(), setOrigin() +*/ +void QwtDial::setMode( Mode mode ) +{ + if ( mode != d_data->mode ) + { + invalidateCache(); + + d_data->mode = mode; + sliderChange(); + } +} + +/*! + \return Mode of the dial. + \sa setMode(), origin(), setScaleArc(), value() +*/ +QwtDial::Mode QwtDial::mode() const +{ + return d_data->mode; +} + +/*! + Invalidate the internal caches used to speed up repainting + */ +void QwtDial::invalidateCache() +{ + d_data->pixmapCache = QPixmap(); +} + +/*! + Paint the dial + \param event Paint event +*/ +void QwtDial::paintEvent( QPaintEvent *event ) +{ + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QStyleOption opt; + opt.init(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); + + if ( d_data->mode == QwtDial::RotateScale ) + { + painter.save(); + painter.setRenderHint( QPainter::Antialiasing, true ); + + drawContents( &painter ); + + painter.restore(); + } + + const QRect r = contentsRect(); + if ( r.size() != d_data->pixmapCache.size() ) + { + d_data->pixmapCache = QwtPainter::backingStore( this, r.size() ); + d_data->pixmapCache.fill( Qt::transparent ); + + QPainter p( &d_data->pixmapCache ); + p.setRenderHint( QPainter::Antialiasing, true ); + p.translate( -r.topLeft() ); + + if ( d_data->mode != QwtDial::RotateScale ) + drawContents( &p ); + + if ( lineWidth() > 0 ) + drawFrame( &p ); + + if ( d_data->mode != QwtDial::RotateNeedle ) + drawNeedle( &p ); + } + + painter.drawPixmap( r.topLeft(), d_data->pixmapCache ); + + if ( d_data->mode == QwtDial::RotateNeedle ) + drawNeedle( &painter ); + + if ( hasFocus() ) + drawFocusIndicator( &painter ); +} + +/*! + Draw the focus indicator + \param painter Painter +*/ +void QwtDial::drawFocusIndicator( QPainter *painter ) const +{ + QwtPainter::drawFocusRect( painter, this, boundingRect() ); +} + +/*! + Draw the frame around the dial + + \param painter Painter + \sa lineWidth(), frameShadow() +*/ +void QwtDial::drawFrame( QPainter *painter ) +{ + QwtPainter::drawRoundFrame( painter, boundingRect(), + palette(), lineWidth(), d_data->frameShadow ); +} + +/*! + \brief Draw the contents inside the frame + + QPalette::Window is the background color outside of the frame. + QPalette::Base is the background color inside the frame. + QPalette::WindowText is the background color inside the scale. + + \param painter Painter + + \sa boundingRect(), innerRect(), + scaleInnerRect(), QWidget::setPalette() +*/ +void QwtDial::drawContents( QPainter *painter ) const +{ + if ( testAttribute( Qt::WA_NoSystemBackground ) || + palette().brush( QPalette::Base ) != + palette().brush( QPalette::Window ) ) + { + const QRectF br = boundingRect(); + + painter->save(); + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().brush( QPalette::Base ) ); + painter->drawEllipse( br ); + painter->restore(); + } + + const QRectF insideScaleRect = scaleInnerRect(); + if ( palette().brush( QPalette::WindowText ) != + palette().brush( QPalette::Base ) ) + { + painter->save(); + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().brush( QPalette::WindowText ) ); + painter->drawEllipse( insideScaleRect ); + painter->restore(); + } + + const QPointF center = insideScaleRect.center(); + const double radius = 0.5 * insideScaleRect.width(); + + painter->save(); + drawScale( painter, center, radius ); + painter->restore(); + + painter->save(); + drawScaleContents( painter, center, radius ); + painter->restore(); +} + +/*! + Draw the needle + + \param painter Painter + \param center Center of the dial + \param radius Length for the needle + \param direction Direction of the needle in degrees, counter clockwise + \param colorGroup ColorGroup +*/ +void QwtDial::drawNeedle( QPainter *painter, const QPointF ¢er, + double radius, double direction, QPalette::ColorGroup colorGroup ) const +{ + if ( d_data->needle ) + { + direction = 360.0 - direction; // counter clockwise + d_data->needle->draw( painter, center, radius, direction, colorGroup ); + } +} + +void QwtDial::drawNeedle( QPainter *painter ) const +{ + if ( !isValid() ) + return; + + QPalette::ColorGroup colorGroup; + if ( isEnabled() ) + colorGroup = hasFocus() ? QPalette::Active : QPalette::Inactive; + else + colorGroup = QPalette::Disabled; + + const QRectF sr = scaleInnerRect(); + + painter->save(); + painter->setRenderHint( QPainter::Antialiasing, true ); + drawNeedle( painter, sr.center(), 0.5 * sr.width(), + transform( value() ) + 270.0, colorGroup ); + painter->restore(); +} + +/*! + Draw the scale + + \param painter Painter + \param center Center of the dial + \param radius Radius of the scale +*/ +void QwtDial::drawScale( QPainter *painter, + const QPointF ¢er, double radius ) const +{ + QwtRoundScaleDraw *sd = const_cast( scaleDraw() ); + if ( sd == NULL ) + return; + + sd->setRadius( radius ); + sd->moveCenter( center ); + + QPalette pal = palette(); + + const QColor textColor = pal.color( QPalette::Text ); + pal.setColor( QPalette::WindowText, textColor ); // ticks, backbone + + painter->setFont( font() ); + painter->setPen( QPen( textColor, sd->penWidth() ) ); + + painter->setBrush( Qt::red ); + sd->draw( painter, pal ); +} + +/*! + Draw the contents inside the scale + + Paints nothing. + + \param painter Painter + \param center Center of the contents circle + \param radius Radius of the contents circle +*/ +void QwtDial::drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const +{ + Q_UNUSED(painter); + Q_UNUSED(center); + Q_UNUSED(radius); +} + +/*! + Set a needle for the dial + + \param needle Needle + + \warning The needle will be deleted, when a different needle is + set or in ~QwtDial() +*/ +void QwtDial::setNeedle( QwtDialNeedle *needle ) +{ + if ( needle != d_data->needle ) + { + if ( d_data->needle ) + delete d_data->needle; + + d_data->needle = needle; + update(); + } +} + +/*! + \return needle + \sa setNeedle() +*/ +const QwtDialNeedle *QwtDial::needle() const +{ + return d_data->needle; +} + +/*! + \return needle + \sa setNeedle() +*/ +QwtDialNeedle *QwtDial::needle() +{ + return d_data->needle; +} + +//! \return the scale draw +QwtRoundScaleDraw *QwtDial::scaleDraw() +{ + return static_cast( abstractScaleDraw() ); +} + +//! \return the scale draw +const QwtRoundScaleDraw *QwtDial::scaleDraw() const +{ + return static_cast( abstractScaleDraw() ); +} + +/*! + Set an individual scale draw + + The motivation for setting a scale draw is often + to overload QwtRoundScaleDraw::label() to return + individual tick labels. + + \param scaleDraw Scale draw + \warning The previous scale draw is deleted +*/ +void QwtDial::setScaleDraw( QwtRoundScaleDraw *scaleDraw ) +{ + setAbstractScaleDraw( scaleDraw ); + sliderChange(); +} + +/*! + Change the arc of the scale + + \param minArc Lower limit + \param maxArc Upper limit + + \sa minScaleArc(), maxScaleArc() +*/ +void QwtDial::setScaleArc( double minArc, double maxArc ) +{ + if ( minArc != 360.0 && minArc != -360.0 ) + minArc = ::fmod( minArc, 360.0 ); + if ( maxArc != 360.0 && maxArc != -360.0 ) + maxArc = ::fmod( maxArc, 360.0 ); + + double minScaleArc = qMin( minArc, maxArc ); + double maxScaleArc = qMax( minArc, maxArc ); + + if ( maxScaleArc - minScaleArc > 360.0 ) + maxScaleArc = minScaleArc + 360.0; + + if ( ( minScaleArc != d_data->minScaleArc ) || + ( maxScaleArc != d_data->maxScaleArc ) ) + { + d_data->minScaleArc = minScaleArc; + d_data->maxScaleArc = maxScaleArc; + + invalidateCache(); + sliderChange(); + } +} + +/*! + Set the lower limit for the scale arc + + \param min Lower limit of the scale arc + \sa setScaleArc(), setMaxScaleArc() + */ +void QwtDial::setMinScaleArc( double min ) +{ + setScaleArc( min, d_data->maxScaleArc ); +} + +/*! + \return Lower limit of the scale arc + \sa setScaleArc() +*/ +double QwtDial::minScaleArc() const +{ + return d_data->minScaleArc; +} + +/*! + Set the upper limit for the scale arc + + \param max Upper limit of the scale arc + \sa setScaleArc(), setMinScaleArc() + */ +void QwtDial::setMaxScaleArc( double max ) +{ + setScaleArc( d_data->minScaleArc, max ); +} + +/*! + \return Upper limit of the scale arc + \sa setScaleArc() +*/ +double QwtDial::maxScaleArc() const +{ + return d_data->maxScaleArc; +} + +/*! + \brief Change the origin + + The origin is the angle where scale and needle is relative to. + + \param origin New origin + \sa origin() +*/ +void QwtDial::setOrigin( double origin ) +{ + invalidateCache(); + + d_data->origin = origin; + sliderChange(); +} + +/*! + The origin is the angle where scale and needle is relative to. + + \return Origin of the dial + \sa setOrigin() +*/ +double QwtDial::origin() const +{ + return d_data->origin; +} + +/*! + \return Size hint + \sa minimumSizeHint() +*/ +QSize QwtDial::sizeHint() const +{ + int sh = 0; + if ( scaleDraw() ) + sh = qCeil( scaleDraw()->extent( font() ) ); + + const int d = 6 * sh + 2 * lineWidth(); + + QSize hint( d, d ); + if ( !isReadOnly() ) + hint = hint.expandedTo( QApplication::globalStrut() ); + + return hint; +} + +/*! + \return Minimum size hint + \sa sizeHint() +*/ +QSize QwtDial::minimumSizeHint() const +{ + int sh = 0; + if ( scaleDraw() ) + sh = qCeil( scaleDraw()->extent( font() ) ); + + const int d = 3 * sh + 2 * lineWidth(); + + return QSize( d, d ); +} + +/*! + \brief Determine what to do when the user presses a mouse button. + + \param pos Mouse position + + \retval True, when the inner circle contains pos + \sa scrolledTo() +*/ +bool QwtDial::isScrollPosition( const QPoint &pos ) const +{ + const QRegion region( innerRect(), QRegion::Ellipse ); + if ( region.contains( pos ) && ( pos != innerRect().center() ) ) + { + double angle = QLineF( rect().center(), pos ).angle(); + if ( d_data->mode == QwtDial::RotateScale ) + angle = 360.0 - angle; + + double valueAngle = + qwtNormalizeDegrees( 90.0 - transform( value() ) ); + + d_data->mouseOffset = qwtNormalizeDegrees( angle - valueAngle ); + d_data->arcOffset = scaleMap().p1(); + + return true; + } + + return false; +} + +/*! + \brief Determine the value for a new position of the + slider handle. + + \param pos Mouse position + + \return Value for the mouse position + \sa isScrollPosition() +*/ +double QwtDial::scrolledTo( const QPoint &pos ) const +{ + double angle = QLineF( rect().center(), pos ).angle(); + if ( d_data->mode == QwtDial::RotateScale ) + { + angle += scaleMap().p1() - d_data->arcOffset; + angle = 360.0 - angle; + } + + angle = qwtNormalizeDegrees( angle - d_data->mouseOffset ); + angle = qwtNormalizeDegrees( 90.0 - angle ); + + if ( scaleMap().pDist() >= 360.0 ) + { + if ( angle < scaleMap().p1() ) + angle += 360.0; + + if ( !wrapping() ) + { + double boundedAngle = angle; + + const double arc = angle - transform( value() ); + if ( qAbs( arc ) > 180.0 ) + { + boundedAngle = ( arc > 0 ) + ? scaleMap().p1() : scaleMap().p2(); + } + + d_data->mouseOffset += ( boundedAngle - angle ); + + angle = boundedAngle; + } + } + else + { + const double boundedAngle = + qwtBoundedAngle( scaleMap().p1(), angle, scaleMap().p2() ); + + if ( !wrapping() ) + d_data->mouseOffset += ( boundedAngle - angle ); + + angle = boundedAngle; + } + + return invTransform( angle ); +} + +/*! + Change Event handler + \param event Change event + + Invalidates internal paint caches if necessary +*/ +void QwtDial::changeEvent( QEvent *event ) +{ + switch( event->type() ) + { + case QEvent::EnabledChange: + case QEvent::FontChange: + case QEvent::StyleChange: + case QEvent::PaletteChange: + case QEvent::LanguageChange: + case QEvent::LocaleChange: + { + invalidateCache(); + break; + } + default: + break; + } + + QwtAbstractSlider::changeEvent( event ); +} + +/*! + Wheel Event handler + \param event Wheel event +*/ +void QwtDial::wheelEvent( QWheelEvent *event ) +{ + const QRegion region( innerRect(), QRegion::Ellipse ); + if ( region.contains( event->pos() ) ) + QwtAbstractSlider::wheelEvent( event ); +} + +void QwtDial::setAngleRange( double angle, double span ) +{ + QwtRoundScaleDraw *sd = const_cast( scaleDraw() ); + if ( sd ) + { + angle = qwtNormalizeDegrees( angle - 270.0 ); + sd->setAngleRange( angle, angle + span ); + } +} + +/*! + Invalidate the internal caches and call + QwtAbstractSlider::scaleChange() + */ +void QwtDial::scaleChange() +{ + invalidateCache(); + QwtAbstractSlider::scaleChange(); +} + +void QwtDial::sliderChange() +{ + setAngleRange( d_data->origin + d_data->minScaleArc, + d_data->maxScaleArc - d_data->minScaleArc ); + + if ( mode() == RotateScale ) + { + const double arc = transform( value() ) - scaleMap().p1(); + setAngleRange( d_data->origin - arc, + d_data->maxScaleArc - d_data->minScaleArc ); + } + + QwtAbstractSlider::sliderChange(); +} diff --git a/qwt/src/qwt_dial.h b/qwt/src/qwt_dial.h new file mode 100644 index 000000000..a409b4be6 --- /dev/null +++ b/qwt/src/qwt_dial.h @@ -0,0 +1,168 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_DIAL_H +#define QWT_DIAL_H 1 + +#include "qwt_global.h" +#include "qwt_abstract_slider.h" +#include "qwt_abstract_scale_draw.h" +#include +#include + +class QwtDialNeedle; +class QwtRoundScaleDraw; + +/*! + \brief QwtDial class provides a rounded range control. + + QwtDial is intended as base class for dial widgets like + speedometers, compass widgets, clocks ... + + \image html dials2.png + + A dial contains a scale and a needle indicating the current value + of the dial. Depending on Mode one of them is fixed and the + other is rotating. If not isReadOnly() the + dial can be rotated by dragging the mouse or using keyboard inputs + (see QwtAbstractSlider::keyPressEvent()). A dial might be wrapping, what means + a rotation below/above one limit continues on the other limit (f.e compass). + The scale might cover any arc of the dial, its values are related to + the origin() of the dial. + + Often dials have to be updated very often according to values from external + devices. For these high refresh rates QwtDial caches as much as possible. + For derived classes it might be necessary to clear these caches manually + according to attribute changes using invalidateCache(). + + \sa QwtCompass, QwtAnalogClock, QwtDialNeedle + \note The controls and dials examples shows different types of dials. + \note QDial is more similar to QwtKnob than to QwtDial +*/ + +class QWT_EXPORT QwtDial: public QwtAbstractSlider +{ + Q_OBJECT + + Q_ENUMS( Shadow Mode Direction ) + + Q_PROPERTY( int lineWidth READ lineWidth WRITE setLineWidth ) + Q_PROPERTY( Shadow frameShadow READ frameShadow WRITE setFrameShadow ) + Q_PROPERTY( Mode mode READ mode WRITE setMode ) + Q_PROPERTY( double origin READ origin WRITE setOrigin ) + Q_PROPERTY( double minScaleArc READ minScaleArc WRITE setMinScaleArc ) + Q_PROPERTY( double maxScaleArc READ maxScaleArc WRITE setMaxScaleArc ) + +public: + + /*! + \brief Frame shadow + + Unfortunately it is not possible to use QFrame::Shadow + as a property of a widget that is not derived from QFrame. + The following enum is made for the designer only. It is safe + to use QFrame::Shadow instead. + */ + enum Shadow + { + //! QFrame::Plain + Plain = QFrame::Plain, + + //! QFrame::Raised + Raised = QFrame::Raised, + + //! QFrame::Sunken + Sunken = QFrame::Sunken + }; + + //! Mode controlling whether the needle or the scale is rotating + enum Mode + { + //! The needle is rotating + RotateNeedle, + + //! The needle is fixed, the scales are rotating + RotateScale + }; + + explicit QwtDial( QWidget *parent = NULL ); + virtual ~QwtDial(); + + void setFrameShadow( Shadow ); + Shadow frameShadow() const; + + void setLineWidth( int ); + int lineWidth() const; + + void setMode( Mode ); + Mode mode() const; + + void setScaleArc( double min, double max ); + + void setMinScaleArc( double min ); + double minScaleArc() const; + + void setMaxScaleArc( double min ); + double maxScaleArc() const; + + virtual void setOrigin( double ); + double origin() const; + + void setNeedle( QwtDialNeedle * ); + const QwtDialNeedle *needle() const; + QwtDialNeedle *needle(); + + QRect boundingRect() const; + QRect innerRect() const; + + virtual QRect scaleInnerRect() const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void setScaleDraw( QwtRoundScaleDraw * ); + + QwtRoundScaleDraw *scaleDraw(); + const QwtRoundScaleDraw *scaleDraw() const; + +protected: + virtual void wheelEvent( QWheelEvent * ); + virtual void paintEvent( QPaintEvent * ); + virtual void changeEvent( QEvent * ); + + virtual void drawFrame( QPainter *p ); + virtual void drawContents( QPainter * ) const; + virtual void drawFocusIndicator( QPainter * ) const; + + void invalidateCache(); + + virtual void drawScale( QPainter *, + const QPointF ¢er, double radius ) const; + + virtual void drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const; + + virtual void drawNeedle( QPainter *, const QPointF &, + double radius, double direction, QPalette::ColorGroup ) const; + + virtual double scrolledTo( const QPoint & ) const; + virtual bool isScrollPosition( const QPoint & ) const; + + virtual void sliderChange(); + virtual void scaleChange(); + +private: + void setAngleRange( double angle, double span ); + void drawNeedle( QPainter * ) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_dial_needle.cpp b/qwt/src/qwt_dial_needle.cpp new file mode 100644 index 000000000..1b53a3d5b --- /dev/null +++ b/qwt/src/qwt_dial_needle.cpp @@ -0,0 +1,440 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_dial_needle.h" +#include "qwt_global.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include +#include + +#if QT_VERSION < 0x040601 +#define qFastSin(x) qSin(x) +#define qFastCos(x) qCos(x) +#endif + +static void qwtDrawStyle1Needle( QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + double length ) +{ + const double r[] = { 0.4, 0.3, 1, 0.8, 1, 0.3, 0.4 }; + const double a[] = { -45, -20, -15, 0, 15, 20, 45 }; + + QPainterPath path; + for ( int i = 0; i < 7; i++ ) + { + const double angle = a[i] / 180.0 * M_PI; + const double radius = r[i] * length; + + const double x = radius * qFastCos( angle ); + const double y = radius * qFastSin( angle ); + + path.lineTo( x, -y ); + } + + painter->setPen( Qt::NoPen ); + painter->setBrush( palette.brush( colorGroup, QPalette::Light ) ); + painter->drawPath( path ); +} + +static void qwtDrawStyle2Needle( QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, double length ) +{ + const double ratioX = 0.7; + const double ratioY = 0.3; + + QPainterPath path1; + path1.lineTo( ratioX * length, 0.0 ); + path1.lineTo( length, ratioY * length ); + + QPainterPath path2; + path2.lineTo( ratioX * length, 0.0 ); + path2.lineTo( length, -ratioY * length ); + + painter->setPen( Qt::NoPen ); + + painter->setBrush( palette.brush( colorGroup, QPalette::Light ) ); + painter->drawPath( path1 ); + + painter->setBrush( palette.brush( colorGroup, QPalette::Dark ) ); + painter->drawPath( path2 ); +} + +static void qwtDrawShadedPointer( QPainter *painter, + const QColor &lightColor, const QColor &darkColor, + double length, double width ) +{ + const double peak = qMax( length / 10.0, 5.0 ); + + const double knobWidth = width + 8; + QRectF knobRect( 0, 0, knobWidth, knobWidth ); + knobRect.moveCenter( QPointF(0, 0) ); + + QPainterPath path1; + path1.lineTo( 0.0, 0.5 * width ); + path1.lineTo( length - peak, 0.5 * width ); + path1.lineTo( length, 0.0 ); + path1.lineTo( 0.0, 0.0 ); + + QPainterPath arcPath1; + arcPath1.arcTo( knobRect, 0.0, -90.0 ); + + path1 = path1.united( arcPath1 ); + + QPainterPath path2; + path2.lineTo( 0.0, -0.5 * width ); + path2.lineTo( length - peak, -0.5 * width ); + path2.lineTo( length, 0.0 ); + path2.lineTo( 0.0, 0.0 ); + + QPainterPath arcPath2; + arcPath2.arcTo( knobRect, 0.0, 90.0 ); + + path2 = path2.united( arcPath2 ); + + painter->setPen( Qt::NoPen ); + + painter->setBrush( lightColor ); + painter->drawPath( path1 ); + + painter->setBrush( darkColor ); + painter->drawPath( path2 ); +} + +static void qwtDrawArrowNeedle( QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + double length, double width ) +{ + if ( width <= 0 ) + width = qMax( length * 0.06, 9.0 ); + + const double peak = qMax( 2.0, 0.4 * width ); + + QPainterPath path; + path.moveTo( 0.0, 0.5 * width ); + path.lineTo( length - peak, 0.3 * width ); + path.lineTo( length, 0.0 ); + path.lineTo( length - peak, -0.3 * width ); + path.lineTo( 0.0, -0.5 * width ); + + QRectF br = path.boundingRect(); + + QPalette pal( palette.color( QPalette::Mid ) ); + QColor c1 = pal.color( QPalette::Light ); + QColor c2 = pal.color( QPalette::Dark ); + + QLinearGradient gradient( br.topLeft(), br.bottomLeft() ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 0.5, c1 ); + gradient.setColorAt( 0.5001, c2 ); + gradient.setColorAt( 1.0, c2 ); + + QPen pen( gradient, 1 ); + pen.setJoinStyle( Qt::MiterJoin ); + + painter->setPen( pen ); + painter->setBrush( palette.brush( colorGroup, QPalette::Mid ) ); + + painter->drawPath( path ); +} + +static void qwtDrawTriangleNeedle( QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + double length ) +{ + const double width = qRound( length / 3.0 ); + + QPainterPath path[4]; + + path[0].lineTo( length, 0.0 ); + path[0].lineTo( 0.0, width / 2 ); + + path[1].lineTo( length, 0.0 ); + path[1].lineTo( 0.0, -width / 2 ); + + path[2].lineTo( -length, 0.0 ); + path[2].lineTo( 0.0, width / 2 ); + + path[3].lineTo( -length, 0.0 ); + path[3].lineTo( 0.0, -width / 2 ); + + + const int colorOffset = 10; + const QColor darkColor = palette.color( colorGroup, QPalette::Dark ); + const QColor lightColor = palette.color( colorGroup, QPalette::Light ); + + QColor color[4]; + color[0] = darkColor.light( 100 + colorOffset ); + color[1] = darkColor.dark( 100 + colorOffset ); + color[2] = lightColor.light( 100 + colorOffset ); + color[3] = lightColor.dark( 100 + colorOffset ); + + painter->setPen( Qt::NoPen ); + + for ( int i = 0; i < 4; i++ ) + { + painter->setBrush( color[i] ); + painter->drawPath( path[i] ); + } +} + +//! Constructor +QwtDialNeedle::QwtDialNeedle(): + d_palette( QApplication::palette() ) +{ +} + +//! Destructor +QwtDialNeedle::~QwtDialNeedle() +{ +} + +/*! + Sets the palette for the needle. + + \param palette New Palette +*/ +void QwtDialNeedle::setPalette( const QPalette &palette ) +{ + d_palette = palette; +} + +/*! + \return the palette of the needle. +*/ +const QPalette &QwtDialNeedle::palette() const +{ + return d_palette; +} + +/*! + Draw the needle + + \param painter Painter + \param center Center of the dial, start position for the needle + \param length Length of the needle + \param direction Direction of the needle, in degrees counter clockwise + \param colorGroup Color group, used for painting +*/ +void QwtDialNeedle::draw( QPainter *painter, + const QPointF ¢er, double length, double direction, + QPalette::ColorGroup colorGroup ) const +{ + painter->save(); + + painter->translate( center ); + painter->rotate( -direction ); + + drawNeedle( painter, length, colorGroup ); + + painter->restore(); +} + +//! Draw the knob +void QwtDialNeedle::drawKnob( QPainter *painter, + double width, const QBrush &brush, bool sunken ) const +{ + QPalette palette( brush.color() ); + + QColor c1 = palette.color( QPalette::Light ); + QColor c2 = palette.color( QPalette::Dark ); + + if ( sunken ) + qSwap( c1, c2 ); + + QRectF rect( 0.0, 0.0, width, width ); + rect.moveCenter( painter->combinedTransform().map( QPointF() ) ); + + QLinearGradient gradient( rect.topLeft(), rect.bottomRight() ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 0.3, c1 ); + gradient.setColorAt( 0.7, c2 ); + gradient.setColorAt( 1.0, c2 ); + + painter->save(); + + painter->resetTransform(); + + painter->setPen( QPen( gradient, 1 ) ); + painter->setBrush( brush ); + painter->drawEllipse( rect ); + + painter->restore(); +} + +/*! + Constructor + + \param style Style + \param hasKnob With/Without knob + \param mid Middle color + \param base Base color +*/ +QwtDialSimpleNeedle::QwtDialSimpleNeedle( Style style, bool hasKnob, + const QColor &mid, const QColor &base ): + d_style( style ), + d_hasKnob( hasKnob ), + d_width( -1 ) +{ + QPalette palette; + palette.setColor( QPalette::Mid, mid ); + palette.setColor( QPalette::Base, base ); + + setPalette( palette ); +} + +/*! + Set the width of the needle + \param width Width + \sa width() +*/ +void QwtDialSimpleNeedle::setWidth( double width ) +{ + d_width = width; +} + +/*! + \return the width of the needle + \sa setWidth() +*/ +double QwtDialSimpleNeedle::width() const +{ + return d_width; +} + +/*! + Draw the needle + + \param painter Painter + \param length Length of the needle + \param colorGroup Color group, used for painting +*/ +void QwtDialSimpleNeedle::drawNeedle( QPainter *painter, + double length, QPalette::ColorGroup colorGroup ) const +{ + double knobWidth = 0.0; + double width = d_width; + + if ( d_style == Arrow ) + { + if ( width <= 0.0 ) + width = qMax(length * 0.06, 6.0); + + qwtDrawArrowNeedle( painter, + palette(), colorGroup, length, width ); + + knobWidth = qMin( width * 2.0, 0.2 * length ); + } + else + { + if ( width <= 0.0 ) + width = 5.0; + + QPen pen ( palette().brush( colorGroup, QPalette::Mid ), width ); + pen.setCapStyle( Qt::FlatCap ); + + painter->setPen( pen ); + painter->drawLine( QPointF( 0.0, 0.0 ), QPointF( length, 0.0 ) ); + + knobWidth = qMax( width * 3.0, 5.0 ); + } + + if ( d_hasKnob && knobWidth > 0.0 ) + { + drawKnob( painter, knobWidth, + palette().brush( colorGroup, QPalette::Base ), false ); + } +} + +//! Constructor +QwtCompassMagnetNeedle::QwtCompassMagnetNeedle( Style style, + const QColor &light, const QColor &dark ): + d_style( style ) +{ + QPalette palette; + palette.setColor( QPalette::Light, light ); + palette.setColor( QPalette::Dark, dark ); + palette.setColor( QPalette::Base, Qt::gray ); + + setPalette( palette ); +} + +/*! + Draw the needle + + \param painter Painter + \param length Length of the needle + \param colorGroup Color group, used for painting +*/ +void QwtCompassMagnetNeedle::drawNeedle( QPainter *painter, + double length, QPalette::ColorGroup colorGroup ) const +{ + if ( d_style == ThinStyle ) + { + const double width = qMax( length / 6.0, 3.0 ); + + const int colorOffset = 10; + + const QColor light = palette().color( colorGroup, QPalette::Light ); + const QColor dark = palette().color( colorGroup, QPalette::Dark ); + + qwtDrawShadedPointer( painter, + dark.light( 100 + colorOffset ), + dark.dark( 100 + colorOffset ), + length, width ); + + painter->rotate( 180.0 ); + + qwtDrawShadedPointer( painter, + light.light( 100 + colorOffset ), + light.dark( 100 + colorOffset ), + length, width ); + + const QBrush baseBrush = palette().brush( colorGroup, QPalette::Base ); + drawKnob( painter, width, baseBrush, true ); + } + else + { + qwtDrawTriangleNeedle( painter, palette(), colorGroup, length ); + } +} + +/*! + Constructor + + \param style Arrow style + \param light Light color + \param dark Dark color +*/ +QwtCompassWindArrow::QwtCompassWindArrow( Style style, + const QColor &light, const QColor &dark ): + d_style( style ) +{ + QPalette palette; + palette.setColor( QPalette::Light, light ); + palette.setColor( QPalette::Dark, dark ); + + setPalette( palette ); +} + +/*! + Draw the needle + + \param painter Painter + \param length Length of the needle + \param colorGroup Color group, used for painting +*/ +void QwtCompassWindArrow::drawNeedle( QPainter *painter, + double length, QPalette::ColorGroup colorGroup ) const +{ + if ( d_style == Style1 ) + qwtDrawStyle1Needle( painter, palette(), colorGroup, length ); + else + qwtDrawStyle2Needle( painter, palette(), colorGroup, length ); +} diff --git a/qwt/src/qwt_dial_needle.h b/qwt/src/qwt_dial_needle.h new file mode 100644 index 000000000..d84384a0f --- /dev/null +++ b/qwt/src/qwt_dial_needle.h @@ -0,0 +1,187 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_DIAL_NEEDLE_H +#define QWT_DIAL_NEEDLE_H 1 + +#include "qwt_global.h" +#include + +class QPainter; +class QPoint; + +/*! + \brief Base class for needles that can be used in a QwtDial. + + QwtDialNeedle is a pointer that indicates a value by pointing + to a specific direction. + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtDialNeedle +{ +public: + QwtDialNeedle(); + virtual ~QwtDialNeedle(); + + virtual void setPalette( const QPalette & ); + const QPalette &palette() const; + + virtual void draw( QPainter *painter, const QPointF ¢er, + double length, double direction, + QPalette::ColorGroup = QPalette::Active ) const; + +protected: + /*! + \brief Draw the needle + + The origin of the needle is at position (0.0, 0.0 ) + pointing in direction 0.0 ( = east ). + + The painter is already initialized with translation and + rotation. + + \param painter Painter + \param length Length of the needle + \param colorGroup Color group, used for painting + + \sa setPalette(), palette() + */ + virtual void drawNeedle( QPainter *painter, + double length, QPalette::ColorGroup colorGroup ) const = 0; + + virtual void drawKnob( QPainter *, double width, + const QBrush &, bool sunken ) const; + +private: + QPalette d_palette; +}; + +/*! + \brief A needle for dial widgets + + The following colors are used: + + - QPalette::Mid\n + Pointer + - QPalette::Base\n + Knob + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtDialSimpleNeedle: public QwtDialNeedle +{ +public: + //! Style of the needle + enum Style + { + //! Arrow + Arrow, + + //! A straight line from the center + Ray + }; + + QwtDialSimpleNeedle( Style, bool hasKnob = true, + const QColor &mid = Qt::gray, const QColor &base = Qt::darkGray ); + + void setWidth( double width ); + double width() const; + +protected: + virtual void drawNeedle( QPainter *, double length, + QPalette::ColorGroup ) const; + +private: + Style d_style; + bool d_hasKnob; + double d_width; +}; + +/*! + \brief A magnet needle for compass widgets + + A magnet needle points to two opposite directions indicating + north and south. + + The following colors are used: + - QPalette::Light\n + Used for pointing south + - QPalette::Dark\n + Used for pointing north + - QPalette::Base\n + Knob (ThinStyle only) + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtCompassMagnetNeedle: public QwtDialNeedle +{ +public: + //! Style of the needle + enum Style + { + //! A needle with a triangular shape + TriangleStyle, + + //! A thin needle + ThinStyle + }; + + QwtCompassMagnetNeedle( Style = TriangleStyle, + const QColor &light = Qt::white, const QColor &dark = Qt::red ); + +protected: + virtual void drawNeedle( QPainter *, + double length, QPalette::ColorGroup ) const; + +private: + Style d_style; +}; + +/*! + \brief An indicator for the wind direction + + QwtCompassWindArrow shows the direction where the wind comes from. + + - QPalette::Light\n + Used for Style1, or the light half of Style2 + - QPalette::Dark\n + Used for the dark half of Style2 + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtCompassWindArrow: public QwtDialNeedle +{ +public: + //! Style of the arrow + enum Style + { + //! A needle pointing to the center + Style1, + + //! A needle pointing to the center + Style2 + }; + + QwtCompassWindArrow( Style, const QColor &light = Qt::white, + const QColor &dark = Qt::gray ); + +protected: + virtual void drawNeedle( QPainter *, + double length, QPalette::ColorGroup ) const; + +private: + Style d_style; +}; + +#endif diff --git a/qwt/src/qwt_dyngrid_layout.cpp b/qwt/src/qwt_dyngrid_layout.cpp new file mode 100644 index 000000000..9e0fa2851 --- /dev/null +++ b/qwt/src/qwt_dyngrid_layout.cpp @@ -0,0 +1,591 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_dyngrid_layout.h" +#include "qwt_math.h" +#include +#include + +class QwtDynGridLayout::PrivateData +{ +public: + PrivateData(): + isDirty( true ) + { + } + + void updateLayoutCache(); + + mutable QList itemList; + + uint maxColumns; + uint numRows; + uint numColumns; + + Qt::Orientations expanding; + + bool isDirty; + QVector itemSizeHints; +}; + +void QwtDynGridLayout::PrivateData::updateLayoutCache() +{ + itemSizeHints.resize( itemList.count() ); + + int index = 0; + + for ( QList::iterator it = itemList.begin(); + it != itemList.end(); ++it, index++ ) + { + itemSizeHints[ index ] = ( *it )->sizeHint(); + } + + isDirty = false; +} + +/*! + \param parent Parent widget + \param margin Margin + \param spacing Spacing +*/ + +QwtDynGridLayout::QwtDynGridLayout( QWidget *parent, + int margin, int spacing ): + QLayout( parent ) +{ + init(); + + setSpacing( spacing ); + setMargin( margin ); +} + +/*! + \param spacing Spacing +*/ + +QwtDynGridLayout::QwtDynGridLayout( int spacing ) +{ + init(); + setSpacing( spacing ); +} + +/*! + Initialize the layout with default values. +*/ +void QwtDynGridLayout::init() +{ + d_data = new QwtDynGridLayout::PrivateData; + d_data->maxColumns = d_data->numRows = d_data->numColumns = 0; + d_data->expanding = 0; +} + +//! Destructor + +QwtDynGridLayout::~QwtDynGridLayout() +{ + for ( int i = 0; i < d_data->itemList.size(); i++ ) + delete d_data->itemList[i]; + + delete d_data; +} + +//! Invalidate all internal caches +void QwtDynGridLayout::invalidate() +{ + d_data->isDirty = true; + QLayout::invalidate(); +} + +/*! + Limit the number of columns. + \param maxColumns upper limit, 0 means unlimited + \sa maxColumns() +*/ +void QwtDynGridLayout::setMaxColumns( uint maxColumns ) +{ + d_data->maxColumns = maxColumns; +} + +/*! + \brief Return the upper limit for the number of columns. + + 0 means unlimited, what is the default. + + \return Upper limit for the number of columns + \sa setMaxColumns() +*/ +uint QwtDynGridLayout::maxColumns() const +{ + return d_data->maxColumns; +} + +/*! + \brief Add an item to the next free position. + \param item Layout item + */ +void QwtDynGridLayout::addItem( QLayoutItem *item ) +{ + d_data->itemList.append( item ); + invalidate(); +} + +/*! + \return true if this layout is empty. +*/ +bool QwtDynGridLayout::isEmpty() const +{ + return d_data->itemList.isEmpty(); +} + +/*! + \return number of layout items +*/ +uint QwtDynGridLayout::itemCount() const +{ + return d_data->itemList.count(); +} + +/*! + Find the item at a specific index + + \param index Index + \return Item at a specific index + \sa takeAt() +*/ +QLayoutItem *QwtDynGridLayout::itemAt( int index ) const +{ + if ( index < 0 || index >= d_data->itemList.count() ) + return NULL; + + return d_data->itemList.at( index ); +} + +/*! + Find the item at a specific index and remove it from the layout + + \param index Index + \return Layout item, removed from the layout + \sa itemAt() +*/ +QLayoutItem *QwtDynGridLayout::takeAt( int index ) +{ + if ( index < 0 || index >= d_data->itemList.count() ) + return NULL; + + d_data->isDirty = true; + return d_data->itemList.takeAt( index ); +} + +//! \return Number of items in the layout +int QwtDynGridLayout::count() const +{ + return d_data->itemList.count(); +} + +/*! + Set whether this layout can make use of more space than sizeHint(). + A value of Qt::Vertical or Qt::Horizontal means that it wants to grow in only + one dimension, while Qt::Vertical | Qt::Horizontal means that it wants + to grow in both dimensions. The default value is 0. + + \param expanding Or'd orientations + \sa expandingDirections() +*/ +void QwtDynGridLayout::setExpandingDirections( Qt::Orientations expanding ) +{ + d_data->expanding = expanding; +} + +/*! + \brief Returns whether this layout can make use of more space than sizeHint(). + + A value of Qt::Vertical or Qt::Horizontal means that it wants to grow in only + one dimension, while Qt::Vertical | Qt::Horizontal means that it wants + to grow in both dimensions. + + \return Orientations, where the layout expands + \sa setExpandingDirections() +*/ +Qt::Orientations QwtDynGridLayout::expandingDirections() const +{ + return d_data->expanding; +} + +/*! + Reorganizes columns and rows and resizes managed items within + a rectangle. + + \param rect Layout geometry +*/ +void QwtDynGridLayout::setGeometry( const QRect &rect ) +{ + QLayout::setGeometry( rect ); + + if ( isEmpty() ) + return; + + d_data->numColumns = columnsForWidth( rect.width() ); + d_data->numRows = itemCount() / d_data->numColumns; + if ( itemCount() % d_data->numColumns ) + d_data->numRows++; + + QList itemGeometries = layoutItems( rect, d_data->numColumns ); + + int index = 0; + for ( QList::iterator it = d_data->itemList.begin(); + it != d_data->itemList.end(); ++it ) + { + ( *it )->setGeometry( itemGeometries[index] ); + index++; + } +} + +/*! + \brief Calculate the number of columns for a given width. + + The calculation tries to use as many columns as possible + ( limited by maxColumns() ) + + \param width Available width for all columns + \return Number of columns for a given width + + \sa maxColumns(), setMaxColumns() +*/ +uint QwtDynGridLayout::columnsForWidth( int width ) const +{ + if ( isEmpty() ) + return 0; + + uint maxColumns = itemCount(); + if ( d_data->maxColumns > 0 ) + maxColumns = qMin( d_data->maxColumns, maxColumns ); + + if ( maxRowWidth( maxColumns ) <= width ) + return maxColumns; + + for ( uint numColumns = 2; numColumns <= maxColumns; numColumns++ ) + { + const int rowWidth = maxRowWidth( numColumns ); + if ( rowWidth > width ) + return numColumns - 1; + } + + return 1; // At least 1 column +} + +/*! + Calculate the width of a layout for a given number of + columns. + + \param numColumns Given number of columns + \param itemWidth Array of the width hints for all items +*/ +int QwtDynGridLayout::maxRowWidth( int numColumns ) const +{ + int col; + + QVector colWidth( numColumns ); + for ( col = 0; col < numColumns; col++ ) + colWidth[col] = 0; + + if ( d_data->isDirty ) + d_data->updateLayoutCache(); + + for ( int index = 0; + index < d_data->itemSizeHints.count(); index++ ) + { + col = index % numColumns; + colWidth[col] = qMax( colWidth[col], + d_data->itemSizeHints[int( index )].width() ); + } + + int rowWidth = 2 * margin() + ( numColumns - 1 ) * spacing(); + for ( col = 0; col < numColumns; col++ ) + rowWidth += colWidth[col]; + + return rowWidth; +} + +/*! + \return the maximum width of all layout items +*/ +int QwtDynGridLayout::maxItemWidth() const +{ + if ( isEmpty() ) + return 0; + + if ( d_data->isDirty ) + d_data->updateLayoutCache(); + + int w = 0; + for ( int i = 0; i < d_data->itemSizeHints.count(); i++ ) + { + const int itemW = d_data->itemSizeHints[i].width(); + if ( itemW > w ) + w = itemW; + } + + return w; +} + +/*! + Calculate the geometries of the layout items for a layout + with numColumns columns and a given rectangle. + + \param rect Rect where to place the items + \param numColumns Number of columns + \return item geometries +*/ + +QList QwtDynGridLayout::layoutItems( const QRect &rect, + uint numColumns ) const +{ + QList itemGeometries; + if ( numColumns == 0 || isEmpty() ) + return itemGeometries; + + uint numRows = itemCount() / numColumns; + if ( numColumns % itemCount() ) + numRows++; + + if ( numRows == 0 ) + return itemGeometries; + + QVector rowHeight( numRows ); + QVector colWidth( numColumns ); + + layoutGrid( numColumns, rowHeight, colWidth ); + + bool expandH, expandV; + expandH = expandingDirections() & Qt::Horizontal; + expandV = expandingDirections() & Qt::Vertical; + + if ( expandH || expandV ) + stretchGrid( rect, numColumns, rowHeight, colWidth ); + + const int maxColumns = d_data->maxColumns; + d_data->maxColumns = numColumns; + const QRect alignedRect = alignmentRect( rect ); + d_data->maxColumns = maxColumns; + + const int xOffset = expandH ? 0 : alignedRect.x(); + const int yOffset = expandV ? 0 : alignedRect.y(); + + QVector colX( numColumns ); + QVector rowY( numRows ); + + const int xySpace = spacing(); + + rowY[0] = yOffset + margin(); + for ( uint r = 1; r < numRows; r++ ) + rowY[r] = rowY[r-1] + rowHeight[r-1] + xySpace; + + colX[0] = xOffset + margin(); + for ( uint c = 1; c < numColumns; c++ ) + colX[c] = colX[c-1] + colWidth[c-1] + xySpace; + + const int itemCount = d_data->itemList.size(); + for ( int i = 0; i < itemCount; i++ ) + { + const int row = i / numColumns; + const int col = i % numColumns; + + QRect itemGeometry( colX[col], rowY[row], + colWidth[col], rowHeight[row] ); + itemGeometries.append( itemGeometry ); + } + + return itemGeometries; +} + + +/*! + Calculate the dimensions for the columns and rows for a grid + of numColumns columns. + + \param numColumns Number of columns. + \param rowHeight Array where to fill in the calculated row heights. + \param colWidth Array where to fill in the calculated column widths. +*/ + +void QwtDynGridLayout::layoutGrid( uint numColumns, + QVector& rowHeight, QVector& colWidth ) const +{ + if ( numColumns <= 0 ) + return; + + if ( d_data->isDirty ) + d_data->updateLayoutCache(); + + for ( int index = 0; index < d_data->itemSizeHints.count(); index++ ) + { + const int row = index / numColumns; + const int col = index % numColumns; + + const QSize &size = d_data->itemSizeHints[int( index )]; + + rowHeight[row] = ( col == 0 ) + ? size.height() : qMax( rowHeight[row], size.height() ); + colWidth[col] = ( row == 0 ) + ? size.width() : qMax( colWidth[col], size.width() ); + } +} + +/*! + \return true: QwtDynGridLayout implements heightForWidth(). + \sa heightForWidth() +*/ +bool QwtDynGridLayout::hasHeightForWidth() const +{ + return true; +} + +/*! + \return The preferred height for this layout, given a width. + \sa hasHeightForWidth() +*/ +int QwtDynGridLayout::heightForWidth( int width ) const +{ + if ( isEmpty() ) + return 0; + + const uint numColumns = columnsForWidth( width ); + uint numRows = itemCount() / numColumns; + if ( itemCount() % numColumns ) + numRows++; + + QVector rowHeight( numRows ); + QVector colWidth( numColumns ); + + layoutGrid( numColumns, rowHeight, colWidth ); + + int h = 2 * margin() + ( numRows - 1 ) * spacing(); + for ( uint row = 0; row < numRows; row++ ) + h += rowHeight[row]; + + return h; +} + +/*! + Stretch columns in case of expanding() & QSizePolicy::Horizontal and + rows in case of expanding() & QSizePolicy::Vertical to fill the entire + rect. Rows and columns are stretched with the same factor. + + \param rect Bounding rectangle + \param numColumns Number of columns + \param rowHeight Array to be filled with the calculated row heights + \param colWidth Array to be filled with the calculated column widths + + \sa setExpanding(), expanding() +*/ +void QwtDynGridLayout::stretchGrid( const QRect &rect, + uint numColumns, QVector& rowHeight, QVector& colWidth ) const +{ + if ( numColumns == 0 || isEmpty() ) + return; + + bool expandH, expandV; + expandH = expandingDirections() & Qt::Horizontal; + expandV = expandingDirections() & Qt::Vertical; + + if ( expandH ) + { + int xDelta = rect.width() - 2 * margin() - ( numColumns - 1 ) * spacing(); + for ( uint col = 0; col < numColumns; col++ ) + xDelta -= colWidth[col]; + + if ( xDelta > 0 ) + { + for ( uint col = 0; col < numColumns; col++ ) + { + const int space = xDelta / ( numColumns - col ); + colWidth[col] += space; + xDelta -= space; + } + } + } + + if ( expandV ) + { + uint numRows = itemCount() / numColumns; + if ( itemCount() % numColumns ) + numRows++; + + int yDelta = rect.height() - 2 * margin() - ( numRows - 1 ) * spacing(); + for ( uint row = 0; row < numRows; row++ ) + yDelta -= rowHeight[row]; + + if ( yDelta > 0 ) + { + for ( uint row = 0; row < numRows; row++ ) + { + const int space = yDelta / ( numRows - row ); + rowHeight[row] += space; + yDelta -= space; + } + } + } +} + +/*! + Return the size hint. If maxColumns() > 0 it is the size for + a grid with maxColumns() columns, otherwise it is the size for + a grid with only one row. + + \return Size hint + \sa maxColumns(), setMaxColumns() +*/ +QSize QwtDynGridLayout::sizeHint() const +{ + if ( isEmpty() ) + return QSize(); + + uint numColumns = itemCount(); + if ( d_data->maxColumns > 0 ) + numColumns = qMin( d_data->maxColumns, numColumns ); + + uint numRows = itemCount() / numColumns; + if ( itemCount() % numColumns ) + numRows++; + + QVector rowHeight( numRows ); + QVector colWidth( numColumns ); + + layoutGrid( numColumns, rowHeight, colWidth ); + + int h = 2 * margin() + ( numRows - 1 ) * spacing(); + for ( uint row = 0; row < numRows; row++ ) + h += rowHeight[row]; + + int w = 2 * margin() + ( numColumns - 1 ) * spacing(); + for ( uint col = 0; col < numColumns; col++ ) + w += colWidth[col]; + + return QSize( w, h ); +} + +/*! + \return Number of rows of the current layout. + \sa numColumns() + \warning The number of rows might change whenever the geometry changes +*/ +uint QwtDynGridLayout::numRows() const +{ + return d_data->numRows; +} + +/*! + \return Number of columns of the current layout. + \sa numRows() + \warning The number of columns might change whenever the geometry changes +*/ +uint QwtDynGridLayout::numColumns() const +{ + return d_data->numColumns; +} diff --git a/qwt/src/qwt_dyngrid_layout.h b/qwt/src/qwt_dyngrid_layout.h new file mode 100644 index 000000000..dd2026612 --- /dev/null +++ b/qwt/src/qwt_dyngrid_layout.h @@ -0,0 +1,83 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_DYNGRID_LAYOUT_H +#define QWT_DYNGRID_LAYOUT_H + +#include "qwt_global.h" +#include +#include +#include + +/*! + \brief The QwtDynGridLayout class lays out widgets in a grid, + adjusting the number of columns and rows to the current size. + + QwtDynGridLayout takes the space it gets, divides it up into rows and + columns, and puts each of the widgets it manages into the correct cell(s). + It lays out as many number of columns as possible (limited by maxColumns()). +*/ + +class QWT_EXPORT QwtDynGridLayout : public QLayout +{ + Q_OBJECT +public: + explicit QwtDynGridLayout( QWidget *, int margin = 0, int space = -1 ); + explicit QwtDynGridLayout( int space = -1 ); + + virtual ~QwtDynGridLayout(); + + virtual void invalidate(); + + void setMaxColumns( uint maxCols ); + uint maxColumns() const; + + uint numRows () const; + uint numColumns () const; + + virtual void addItem( QLayoutItem * ); + + virtual QLayoutItem *itemAt( int index ) const; + virtual QLayoutItem *takeAt( int index ); + virtual int count() const; + + void setExpandingDirections( Qt::Orientations ); + virtual Qt::Orientations expandingDirections() const; + QList layoutItems( const QRect &, uint numCols ) const; + + virtual int maxItemWidth() const; + + virtual void setGeometry( const QRect &rect ); + + virtual bool hasHeightForWidth() const; + virtual int heightForWidth( int ) const; + + virtual QSize sizeHint() const; + + virtual bool isEmpty() const; + uint itemCount() const; + + virtual uint columnsForWidth( int width ) const; + +protected: + + void layoutGrid( uint numCols, + QVector& rowHeight, QVector& colWidth ) const; + void stretchGrid( const QRect &rect, uint numCols, + QVector& rowHeight, QVector& colWidth ) const; + +private: + void init(); + int maxRowWidth( int numCols ) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_event_pattern.cpp b/qwt/src/qwt_event_pattern.cpp new file mode 100644 index 000000000..463774362 --- /dev/null +++ b/qwt/src/qwt_event_pattern.cpp @@ -0,0 +1,265 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_event_pattern.h" +#include + +/*! + Constructor + + \sa MousePatternCode, KeyPatternCode +*/ + +QwtEventPattern::QwtEventPattern(): + d_mousePattern( MousePatternCount ), + d_keyPattern( KeyPatternCount ) +{ + initKeyPattern(); + initMousePattern( 3 ); +} + +//! Destructor +QwtEventPattern::~QwtEventPattern() +{ +} + +/*! + Set default mouse patterns, depending on the number of mouse buttons + + \param numButtons Number of mouse buttons ( <= 3 ) + \sa MousePatternCode +*/ +void QwtEventPattern::initMousePattern( int numButtons ) +{ + d_mousePattern.resize( MousePatternCount ); + + switch ( numButtons ) + { + case 1: + { + setMousePattern( MouseSelect1, Qt::LeftButton ); + setMousePattern( MouseSelect2, Qt::LeftButton, Qt::ControlModifier ); + setMousePattern( MouseSelect3, Qt::LeftButton, Qt::AltModifier ); + break; + } + case 2: + { + setMousePattern( MouseSelect1, Qt::LeftButton ); + setMousePattern( MouseSelect2, Qt::RightButton ); + setMousePattern( MouseSelect3, Qt::LeftButton, Qt::AltModifier ); + break; + } + default: + { + setMousePattern( MouseSelect1, Qt::LeftButton ); + setMousePattern( MouseSelect2, Qt::RightButton ); + setMousePattern( MouseSelect3, Qt::MidButton ); + } + } + + setMousePattern( MouseSelect4, d_mousePattern[MouseSelect1].button, + d_mousePattern[MouseSelect1].modifiers | Qt::ShiftModifier ); + + setMousePattern( MouseSelect5, d_mousePattern[MouseSelect2].button, + d_mousePattern[MouseSelect2].modifiers | Qt::ShiftModifier ); + + setMousePattern( MouseSelect6, d_mousePattern[MouseSelect3].button, + d_mousePattern[MouseSelect3].modifiers | Qt::ShiftModifier ); +} + +/*! + Set default mouse patterns. + + \sa KeyPatternCode +*/ +void QwtEventPattern::initKeyPattern() +{ + d_keyPattern.resize( KeyPatternCount ); + + setKeyPattern( KeySelect1, Qt::Key_Return ); + setKeyPattern( KeySelect2, Qt::Key_Space ); + setKeyPattern( KeyAbort, Qt::Key_Escape ); + + setKeyPattern( KeyLeft, Qt::Key_Left ); + setKeyPattern( KeyRight, Qt::Key_Right ); + setKeyPattern( KeyUp, Qt::Key_Up ); + setKeyPattern( KeyDown, Qt::Key_Down ); + + setKeyPattern( KeyRedo, Qt::Key_Plus ); + setKeyPattern( KeyUndo, Qt::Key_Minus ); + setKeyPattern( KeyHome, Qt::Key_Escape ); +} + +/*! + Change one mouse pattern + + \param pattern Index of the pattern + \param button Button + \param modifiers Keyboard modifiers + + \sa QMouseEvent +*/ +void QwtEventPattern::setMousePattern( MousePatternCode pattern, + Qt::MouseButton button, Qt::KeyboardModifiers modifiers ) +{ + if ( pattern >= 0 && pattern < MousePatternCount ) + { + d_mousePattern[ pattern ].button = button; + d_mousePattern[ pattern ].modifiers = modifiers; + } +} + +/*! + Change one key pattern + + \param pattern Index of the pattern + \param key Key + \param modifiers Keyboard modifiers + + \sa QKeyEvent +*/ +void QwtEventPattern::setKeyPattern( KeyPatternCode pattern, + int key, Qt::KeyboardModifiers modifiers ) +{ + if ( pattern >= 0 && pattern < KeyPatternCount ) + { + d_keyPattern[ pattern ].key = key; + d_keyPattern[ pattern ].modifiers = modifiers; + } +} + +//! Change the mouse event patterns +void QwtEventPattern::setMousePattern( const QVector &pattern ) +{ + d_mousePattern = pattern; +} + +//! Change the key event patterns +void QwtEventPattern::setKeyPattern( const QVector &pattern ) +{ + d_keyPattern = pattern; +} + +//! \return Mouse pattern +const QVector & +QwtEventPattern::mousePattern() const +{ + return d_mousePattern; +} + +//! \return Key pattern +const QVector & +QwtEventPattern::keyPattern() const +{ + return d_keyPattern; +} + +//! \return Mouse pattern +QVector &QwtEventPattern::mousePattern() +{ + return d_mousePattern; +} + +//! \return Key pattern +QVector &QwtEventPattern::keyPattern() +{ + return d_keyPattern; +} + +/*! + \brief Compare a mouse event with an event pattern. + + A mouse event matches the pattern when both have the same button + value and in the state value the same key flags(Qt::KeyButtonMask) + are set. + + \param code Index of the event pattern + \param event Mouse event + \return true if matches + + \sa keyMatch() +*/ +bool QwtEventPattern::mouseMatch( MousePatternCode code, + const QMouseEvent *event ) const +{ + if ( code >= 0 && code < MousePatternCount ) + return mouseMatch( d_mousePattern[ code ], event ); + + return false; +} + +/*! + \brief Compare a mouse event with an event pattern. + + A mouse event matches the pattern when both have the same button + value and in the state value the same key flags(Qt::KeyButtonMask) + are set. + + \param pattern Mouse event pattern + \param event Mouse event + \return true if matches + + \sa keyMatch() +*/ + +bool QwtEventPattern::mouseMatch( const MousePattern &pattern, + const QMouseEvent *event ) const +{ + if ( event == NULL ) + return false; + + const MousePattern mousePattern( event->button(), event->modifiers() ); + return mousePattern == pattern; +} + +/*! + \brief Compare a key event with an event pattern. + + A key event matches the pattern when both have the same key + value and in the state value the same key flags (Qt::KeyButtonMask) + are set. + + \param code Index of the event pattern + \param event Key event + \return true if matches + + \sa mouseMatch() +*/ +bool QwtEventPattern::keyMatch( KeyPatternCode code, + const QKeyEvent *event ) const +{ + if ( code >= 0 && code < KeyPatternCount ) + return keyMatch( d_keyPattern[ code ], event ); + + return false; +} + +/*! + \brief Compare a key event with an event pattern. + + A key event matches the pattern when both have the same key + value and in the state value the same key flags (Qt::KeyButtonMask) + are set. + + \param pattern Key event pattern + \param event Key event + \return true if matches + + \sa mouseMatch() +*/ + +bool QwtEventPattern::keyMatch( + const KeyPattern &pattern, const QKeyEvent *event ) const +{ + if ( event == NULL ) + return false; + + const KeyPattern keyPattern( event->key(), event->modifiers() ); + return keyPattern == pattern; +} diff --git a/qwt/src/qwt_event_pattern.h b/qwt/src/qwt_event_pattern.h new file mode 100644 index 000000000..7c5d1a37f --- /dev/null +++ b/qwt/src/qwt_event_pattern.h @@ -0,0 +1,240 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_EVENT_PATTERN +#define QWT_EVENT_PATTERN 1 + +#include "qwt_global.h" +#include +#include + +class QMouseEvent; +class QKeyEvent; + +/*! + \brief A collection of event patterns + + QwtEventPattern introduces an level of indirection for mouse and + keyboard inputs. Those are represented by symbolic names, so + the application code can be configured by individual mappings. + + \sa QwtPicker, QwtPickerMachine, QwtPlotZoomer +*/ +class QWT_EXPORT QwtEventPattern +{ +public: + /*! + \brief Symbolic mouse input codes + + QwtEventPattern implements 3 different settings for + mice with 1, 2, or 3 buttons that can be activated + using initMousePattern(). The default setting is for + 3 button mice. + + Individual settings can be configured using setMousePattern(). + + \sa initMousePattern(), setMousePattern(), setKeyPattern() + */ + enum MousePatternCode + { + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + - Qt::LeftButton + - Qt::LeftButton + */ + MouseSelect1, + + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + Qt::ControlModifier + - Qt::RightButton + - Qt::RightButton + */ + MouseSelect2, + + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + Qt::AltModifier + - Qt::LeftButton + Qt::AltModifier + - Qt::MidButton + */ + MouseSelect3, + + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + Qt::ShiftModifier + - Qt::LeftButton + Qt::ShiftModifier + - Qt::LeftButton + Qt::ShiftModifier + */ + MouseSelect4, + + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + Qt::ControlButton | Qt::ShiftModifier + - Qt::RightButton + Qt::ShiftModifier + - Qt::RightButton + Qt::ShiftModifier + */ + MouseSelect5, + + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + Qt::AltModifier + Qt::ShiftModifier + - Qt::LeftButton + Qt::AltModifier | Qt::ShiftModifier + - Qt::MidButton + Qt::ShiftModifier + */ + MouseSelect6, + + //! Number of mouse patterns + MousePatternCount + }; + + /*! + \brief Symbolic keyboard input codes + + Individual settings can be configured using setKeyPattern() + + \sa setKeyPattern(), setMousePattern() + */ + enum KeyPatternCode + { + //! Qt::Key_Return + KeySelect1, + + //! Qt::Key_Space + KeySelect2, + + //! Qt::Key_Escape + KeyAbort, + + //! Qt::Key_Left + KeyLeft, + + //! Qt::Key_Right + KeyRight, + + //! Qt::Key_Up + KeyUp, + + //! Qt::Key_Down + KeyDown, + + //! Qt::Key_Plus + KeyRedo, + + //! Qt::Key_Minus + KeyUndo, + + //! Qt::Key_Escape + KeyHome, + + //! Number of key patterns + KeyPatternCount + }; + + //! A pattern for mouse events + class MousePattern + { + public: + //! Constructor + MousePattern( Qt::MouseButton btn = Qt::NoButton, + Qt::KeyboardModifiers modifierCodes = Qt::NoModifier ): + button( btn ), + modifiers( modifierCodes ) + { + } + + //! Button + Qt::MouseButton button; + + //! Keyboard modifier + Qt::KeyboardModifiers modifiers; + }; + + //! A pattern for key events + class KeyPattern + { + public: + //! Constructor + KeyPattern( int keyCode = Qt::Key_unknown, + Qt::KeyboardModifiers modifierCodes = Qt::NoModifier ): + key( keyCode ), + modifiers( modifierCodes ) + { + } + + //! Key code + int key; + + //! Modifiers + Qt::KeyboardModifiers modifiers; + }; + + QwtEventPattern(); + virtual ~QwtEventPattern(); + + void initMousePattern( int numButtons ); + void initKeyPattern(); + + void setMousePattern( MousePatternCode, Qt::MouseButton button, + Qt::KeyboardModifiers = Qt::NoModifier ); + + void setKeyPattern( KeyPatternCode, int keyCode, + Qt::KeyboardModifiers modifierCodes = Qt::NoModifier ); + + void setMousePattern( const QVector & ); + void setKeyPattern( const QVector & ); + + const QVector &mousePattern() const; + const QVector &keyPattern() const; + + QVector &mousePattern(); + QVector &keyPattern(); + + bool mouseMatch( MousePatternCode, const QMouseEvent * ) const; + bool keyMatch( KeyPatternCode, const QKeyEvent * ) const; + +protected: + virtual bool mouseMatch( const MousePattern &, const QMouseEvent * ) const; + virtual bool keyMatch( const KeyPattern &, const QKeyEvent * ) const; + +private: + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4251) +#endif + QVector d_mousePattern; + QVector d_keyPattern; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +}; + +//! Compare operator +inline bool operator==( QwtEventPattern::MousePattern b1, + QwtEventPattern::MousePattern b2 ) +{ + return b1.button == b2.button && b1.modifiers == b2.modifiers; +} + +//! Compare operator +inline bool operator==( QwtEventPattern::KeyPattern b1, + QwtEventPattern::KeyPattern b2 ) +{ + return b1.key == b2.key && b1.modifiers == b2.modifiers; +} + +#endif diff --git a/qwt/src/qwt_global.h b/qwt/src/qwt_global.h new file mode 100644 index 000000000..3833077c5 --- /dev/null +++ b/qwt/src/qwt_global.h @@ -0,0 +1,41 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_GLOBAL_H +#define QWT_GLOBAL_H + +#include + +// QWT_VERSION is (major << 16) + (minor << 8) + patch. + +#define QWT_VERSION 0x060101 +#define QWT_VERSION_STR "6.1.1" + +#if defined(_MSC_VER) /* MSVC Compiler */ +/* template-class specialization 'identifier' is already instantiated */ +#pragma warning(disable: 4660) +/* inherits via dominance */ +#pragma warning(disable: 4250) +#endif // _MSC_VER + +#ifdef QWT_DLL + +#if defined(QWT_MAKEDLL) // create a Qwt DLL library +#define QWT_EXPORT Q_DECL_EXPORT +#else // use a Qwt DLL library +#define QWT_EXPORT Q_DECL_IMPORT +#endif + +#endif // QWT_DLL + +#ifndef QWT_EXPORT +#define QWT_EXPORT +#endif + +#endif diff --git a/qwt/src/qwt_graphic.cpp b/qwt/src/qwt_graphic.cpp new file mode 100644 index 000000000..d67bcea0e --- /dev/null +++ b/qwt/src/qwt_graphic.cpp @@ -0,0 +1,986 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_graphic.h" +#include "qwt_painter_command.h" +#include +#include +#include +#include +#include +#include +#include + +static bool qwtHasScalablePen( const QPainter *painter ) +{ + const QPen pen = painter->pen(); + + bool scalablePen = false; + + if ( pen.style() != Qt::NoPen && pen.brush().style() != Qt::NoBrush ) + { + scalablePen = !pen.isCosmetic(); + if ( !scalablePen && pen.widthF() == 0.0 ) + { + const QPainter::RenderHints hints = painter->renderHints(); + if ( hints.testFlag( QPainter::NonCosmeticDefaultPen ) ) + scalablePen = true; + } + } + + return scalablePen; +} + +static QRectF qwtStrokedPathRect( + const QPainter *painter, const QPainterPath &path ) +{ + QPainterPathStroker stroker; + stroker.setWidth( painter->pen().widthF() ); + stroker.setCapStyle( painter->pen().capStyle() ); + stroker.setJoinStyle( painter->pen().joinStyle() ); + stroker.setMiterLimit( painter->pen().miterLimit() ); + + QRectF rect; + if ( qwtHasScalablePen( painter ) ) + { + QPainterPath stroke = stroker.createStroke(path); + rect = painter->transform().map(stroke).boundingRect(); + } + else + { + QPainterPath mappedPath = painter->transform().map(path); + mappedPath = stroker.createStroke( mappedPath ); + + rect = mappedPath.boundingRect(); + } + + return rect; +} + +static inline void qwtExecCommand( + QPainter *painter, const QwtPainterCommand &cmd, + QwtGraphic::RenderHints renderHints, + const QTransform &transform ) +{ + switch( cmd.type() ) + { + case QwtPainterCommand::Path: + { + bool doMap = false; + + if ( renderHints.testFlag( QwtGraphic::RenderPensUnscaled ) + && painter->transform().isScaling() ) + { + bool isCosmetic = painter->pen().isCosmetic(); + if ( isCosmetic && painter->pen().widthF() == 0.0 ) + { + QPainter::RenderHints hints = painter->renderHints(); + if ( hints.testFlag( QPainter::NonCosmeticDefaultPen ) ) + isCosmetic = false; + } + + doMap = !isCosmetic; + } + + if ( doMap ) + { + const QTransform transform = painter->transform(); + + painter->resetTransform(); + painter->drawPath( transform.map( *cmd.path() ) ); + + painter->setTransform( transform ); + } + else + { + painter->drawPath( *cmd.path() ); + } + break; + } + case QwtPainterCommand::Pixmap: + { + const QwtPainterCommand::PixmapData *data = cmd.pixmapData(); + painter->drawPixmap( data->rect, data->pixmap, data->subRect ); + break; + } + case QwtPainterCommand::Image: + { + const QwtPainterCommand::ImageData *data = cmd.imageData(); + painter->drawImage( data->rect, data->image, + data->subRect, data->flags ); + break; + } + case QwtPainterCommand::State: + { + const QwtPainterCommand::StateData *data = cmd.stateData(); + + if ( data->flags & QPaintEngine::DirtyPen ) + painter->setPen( data->pen ); + + if ( data->flags & QPaintEngine::DirtyBrush ) + painter->setBrush( data->brush ); + + if ( data->flags & QPaintEngine::DirtyBrushOrigin ) + painter->setBrushOrigin( data->brushOrigin ); + + if ( data->flags & QPaintEngine::DirtyFont ) + painter->setFont( data->font ); + + if ( data->flags & QPaintEngine::DirtyBackground ) + { + painter->setBackgroundMode( data->backgroundMode ); + painter->setBackground( data->backgroundBrush ); + } + + if ( data->flags & QPaintEngine::DirtyTransform ) + { + painter->setTransform( data->transform * transform ); + } + + if ( data->flags & QPaintEngine::DirtyClipEnabled ) + painter->setClipping( data->isClipEnabled ); + + if ( data->flags & QPaintEngine::DirtyClipRegion) + { + painter->setClipRegion( data->clipRegion, + data->clipOperation ); + } + + if ( data->flags & QPaintEngine::DirtyClipPath ) + { + painter->setClipPath( data->clipPath, data->clipOperation ); + } + + if ( data->flags & QPaintEngine::DirtyHints) + { + const QPainter::RenderHints hints = data->renderHints; + + painter->setRenderHint( QPainter::Antialiasing, + hints.testFlag( QPainter::Antialiasing ) ); + + painter->setRenderHint( QPainter::TextAntialiasing, + hints.testFlag( QPainter::TextAntialiasing ) ); + + painter->setRenderHint( QPainter::SmoothPixmapTransform, + hints.testFlag( QPainter::SmoothPixmapTransform ) ); + + painter->setRenderHint( QPainter::HighQualityAntialiasing, + hints.testFlag( QPainter::HighQualityAntialiasing ) ); + + painter->setRenderHint( QPainter::NonCosmeticDefaultPen, + hints.testFlag( QPainter::NonCosmeticDefaultPen ) ); + } + + if ( data->flags & QPaintEngine::DirtyCompositionMode) + painter->setCompositionMode( data->compositionMode ); + + if ( data->flags & QPaintEngine::DirtyOpacity) + painter->setOpacity( data->opacity ); + + break; + } + default: + break; + } + +} + +class QwtGraphic::PathInfo +{ +public: + PathInfo(): + d_scalablePen( false ) + { + // QVector needs a default constructor + } + + PathInfo( const QRectF &pointRect, + const QRectF &boundingRect, bool scalablePen ): + d_pointRect( pointRect ), + d_boundingRect( boundingRect ), + d_scalablePen( scalablePen ) + { + } + + inline QRectF scaledBoundingRect( double sx, double sy, + bool scalePens ) const + { + if ( sx == 1.0 && sy == 1.0 ) + return d_boundingRect; + + QTransform transform; + transform.scale( sx, sy ); + + QRectF rect; + if ( scalePens && d_scalablePen ) + { + rect = transform.mapRect( d_boundingRect ); + } + else + { + rect = transform.mapRect( d_pointRect ); + + const double l = qAbs( d_pointRect.left() - d_boundingRect.left() ); + const double r = qAbs( d_pointRect.right() - d_boundingRect.right() ); + const double t = qAbs( d_pointRect.top() - d_boundingRect.top() ); + const double b = qAbs( d_pointRect.bottom() - d_boundingRect.bottom() ); + + rect.adjust( -l, -t, r, b ); + } + + return rect; + } + + inline double scaleFactorX( const QRectF& pathRect, + const QRectF &targetRect, bool scalePens ) const + { + if ( pathRect.width() <= 0.0 ) + return 0.0; + + const QPointF p0 = d_pointRect.center(); + + const double l = qAbs( pathRect.left() - p0.x() ); + const double r = qAbs( pathRect.right() - p0.x() ); + + const double w = 2.0 * qMin( l, r ) + * targetRect.width() / pathRect.width(); + + double sx; + if ( scalePens && d_scalablePen ) + { + sx = w / d_boundingRect.width(); + } + else + { + const double pw = qMax( + qAbs( d_boundingRect.left() - d_pointRect.left() ), + qAbs( d_boundingRect.right() - d_pointRect.right() ) ); + + sx = ( w - 2 * pw ) / d_pointRect.width(); + } + + return sx; + } + + inline double scaleFactorY( const QRectF& pathRect, + const QRectF &targetRect, bool scalePens ) const + { + if ( pathRect.height() <= 0.0 ) + return 0.0; + + const QPointF p0 = d_pointRect.center(); + + const double t = qAbs( pathRect.top() - p0.y() ); + const double b = qAbs( pathRect.bottom() - p0.y() ); + + const double h = 2.0 * qMin( t, b ) + * targetRect.height() / pathRect.height(); + + double sy; + if ( scalePens && d_scalablePen ) + { + sy = h / d_boundingRect.height(); + } + else + { + const double pw = + qMax( qAbs( d_boundingRect.top() - d_pointRect.top() ), + qAbs( d_boundingRect.bottom() - d_pointRect.bottom() ) ); + + sy = ( h - 2 * pw ) / d_pointRect.height(); + } + + return sy; + } + +private: + QRectF d_pointRect; + QRectF d_boundingRect; + bool d_scalablePen; +}; + +class QwtGraphic::PrivateData +{ +public: + PrivateData(): + boundingRect( 0.0, 0.0, -1.0, -1.0 ), + pointRect( 0.0, 0.0, -1.0, -1.0 ) + { + } + + QSizeF defaultSize; + QVector commands; + QVector pathInfos; + + QRectF boundingRect; + QRectF pointRect; + + QwtGraphic::RenderHints renderHints; +}; + +/*! + \brief Constructor + + Initializes a null graphic + \sa isNull() + */ +QwtGraphic::QwtGraphic(): + QwtNullPaintDevice() +{ + setMode( QwtNullPaintDevice::PathMode ); + d_data = new PrivateData; +} + +/*! + \brief Copy constructor + + \param other Source + \sa operator=() + */ +QwtGraphic::QwtGraphic( const QwtGraphic &other ): + QwtNullPaintDevice() +{ + setMode( other.mode() ); + d_data = new PrivateData( *other.d_data ); +} + +//! Destructor +QwtGraphic::~QwtGraphic() +{ + delete d_data; +} + +/*! + \brief Assignment operator + + \param other Source + \return A reference of this object + */ +QwtGraphic& QwtGraphic::operator=(const QwtGraphic &other) +{ + setMode( other.mode() ); + *d_data = *other.d_data; + + return *this; +} + +/*! + \brief Clear all stored commands + \sa isNull() + */ +void QwtGraphic::reset() +{ + d_data->commands.clear(); + d_data->pathInfos.clear(); + + d_data->boundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); + d_data->pointRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); + d_data->defaultSize = QSizeF(); + +} + +/*! + \return True, when no painter commands have been stored + \sa isEmpty(), commands() +*/ +bool QwtGraphic::isNull() const +{ + return d_data->commands.isEmpty(); +} + +/*! + \return True, when the bounding rectangle is empty + \sa boundingRect(), isNull() +*/ +bool QwtGraphic::isEmpty() const +{ + return d_data->boundingRect.isEmpty(); +} + +/*! + Toggle an render hint + + \param hint Render hint + \param on true/false + + \sa testRenderHint(), RenderHint +*/ +void QwtGraphic::setRenderHint( RenderHint hint, bool on ) +{ + if ( on ) + d_data->renderHints |= hint; + else + d_data->renderHints &= ~hint; +} + +/*! + Test a render hint + + \param hint Render hint + \return true/false + \sa setRenderHint(), RenderHint +*/ +bool QwtGraphic::testRenderHint( RenderHint hint ) const +{ + return d_data->renderHints.testFlag( hint ); +} + +/*! + The bounding rectangle is the controlPointRect() + extended by the areas needed for rendering the outlines + with unscaled pens. + + \return Bounding rectangle of the graphic + \sa controlPointRect(), scaledBoundingRect() + */ +QRectF QwtGraphic::boundingRect() const +{ + if ( d_data->boundingRect.width() < 0 ) + return QRectF(); + + return d_data->boundingRect; +} + +/*! + The control point rectangle is the bounding rectangle + of all control points of the paths and the target + rectangles of the images/pixmaps. + + \return Control point rectangle + \sa boundingRect(), scaledBoundingRect() + */ +QRectF QwtGraphic::controlPointRect() const +{ + if ( d_data->pointRect.width() < 0 ) + return QRectF(); + + return d_data->pointRect; +} + +/*! + \brief Calculate the target rectangle for scaling the graphic + + \param sx Horizontal scaling factor + \param sy Vertical scaling factor + + \note In case of paths that are painted with a cosmetic pen + ( see QPen::isCosmetic() ) the target rectangle is different to + multiplying the bounding rectangle. + + \return Scaled bounding rectangle + \sa boundingRect(), controlPointRect() + */ +QRectF QwtGraphic::scaledBoundingRect( double sx, double sy ) const +{ + if ( sx == 1.0 && sy == 1.0 ) + return d_data->boundingRect; + + QTransform transform; + transform.scale( sx, sy ); + + QRectF rect = transform.mapRect( d_data->pointRect ); + + for ( int i = 0; i < d_data->pathInfos.size(); i++ ) + { + rect |= d_data->pathInfos[i].scaledBoundingRect( sx, sy, + !d_data->renderHints.testFlag( RenderPensUnscaled ) ); + } + + return rect; +} + +//! \return Ceiled defaultSize() +QSize QwtGraphic::sizeMetrics() const +{ + const QSizeF sz = defaultSize(); + return QSize( qCeil( sz.width() ), qCeil( sz.height() ) ); +} + +/*! + \brief Set a default size + + The default size is used in all methods rendering the graphic, + where no size is explicitly specified. Assigning an empty size + means, that the default size will be calculated from the bounding + rectangle. + + The default setting is an empty size. + + \param size Default size + + \sa defaultSize(), boundingRect() + */ +void QwtGraphic::setDefaultSize( const QSizeF &size ) +{ + const double w = qMax( qreal( 0.0 ), size.width() ); + const double h = qMax( qreal( 0.0 ), size.height() ); + + d_data->defaultSize = QSizeF( w, h ); +} + +/*! + \brief Default size + + When a non empty size has been assigned by setDefaultSize() this + size will be returned. Otherwise the default size is the size + of the bounding rectangle. + + The default size is used in all methods rendering the graphic, + where no size is explicitly specified. + + \return Default size + \sa setDefaultSize(), boundingRect() + */ +QSizeF QwtGraphic::defaultSize() const +{ + if ( !d_data->defaultSize.isEmpty() ) + return d_data->defaultSize; + + return boundingRect().size(); +} + +/*! + \brief Replay all recorded painter commands + \param painter Qt painter + */ +void QwtGraphic::render( QPainter *painter ) const +{ + if ( isNull() ) + return; + + const int numCommands = d_data->commands.size(); + const QwtPainterCommand *commands = d_data->commands.constData(); + + const QTransform transform = painter->transform(); + + painter->save(); + + for ( int i = 0; i < numCommands; i++ ) + { + qwtExecCommand( painter, commands[i], + d_data->renderHints, transform ); + } + + painter->restore(); +} + +/*! + \brief Replay all recorded painter commands + + The graphic is scaled to fit into the rectangle + of the given size starting at ( 0, 0 ). + + \param painter Qt painter + \param size Size for the scaled graphic + \param aspectRatioMode Mode how to scale - See Qt::AspectRatioMode + */ +void QwtGraphic::render( QPainter *painter, const QSizeF &size, + Qt::AspectRatioMode aspectRatioMode ) const +{ + const QRectF r( 0.0, 0.0, size.width(), size.height() ); + render( painter, r, aspectRatioMode ); +} + +/*! + \brief Replay all recorded painter commands + + The graphic is scaled to fit into the given rectangle + + \param painter Qt painter + \param rect Rectangle for the scaled graphic + \param aspectRatioMode Mode how to scale - See Qt::AspectRatioMode + */ +void QwtGraphic::render( QPainter *painter, const QRectF &rect, + Qt::AspectRatioMode aspectRatioMode ) const +{ + if ( isEmpty() || rect.isEmpty() ) + return; + + double sx = 1.0; + double sy = 1.0; + + if ( d_data->pointRect.width() > 0.0 ) + sx = rect.width() / d_data->pointRect.width(); + + if ( d_data->pointRect.height() > 0.0 ) + sy = rect.height() / d_data->pointRect.height(); + + const bool scalePens = + !d_data->renderHints.testFlag( RenderPensUnscaled ); + + for ( int i = 0; i < d_data->pathInfos.size(); i++ ) + { + const PathInfo info = d_data->pathInfos[i]; + + const double ssx = info.scaleFactorX( + d_data->pointRect, rect, scalePens ); + + if ( ssx > 0.0 ) + sx = qMin( sx, ssx ); + + const double ssy = info.scaleFactorY( + d_data->pointRect, rect, scalePens ); + + if ( ssy > 0.0 ) + sy = qMin( sy, ssy ); + } + + if ( aspectRatioMode == Qt::KeepAspectRatio ) + { + const double s = qMin( sx, sy ); + sx = s; + sy = s; + } + else if ( aspectRatioMode == Qt::KeepAspectRatioByExpanding ) + { + const double s = qMax( sx, sy ); + sx = s; + sy = s; + } + + QTransform tr; + tr.translate( rect.center().x() - 0.5 * sx * d_data->pointRect.width(), + rect.center().y() - 0.5 * sy * d_data->pointRect.height() ); + tr.scale( sx, sy ); + tr.translate( -d_data->pointRect.x(), -d_data->pointRect.y() ); + + const QTransform transform = painter->transform(); + + painter->setTransform( tr, true ); + render( painter ); + + painter->setTransform( transform ); +} + +/*! + \brief Replay all recorded painter commands + + The graphic is scaled to the defaultSize() and aligned + to a position. + + \param painter Qt painter + \param pos Reference point, where to render + \param alignment Flags how to align the target rectangle + to pos. + */ +void QwtGraphic::render( QPainter *painter, + const QPointF &pos, Qt::Alignment alignment ) const +{ + QRectF r( pos, defaultSize() ); + + if ( alignment & Qt::AlignLeft ) + { + r.moveLeft( pos.x() ); + } + else if ( alignment & Qt::AlignHCenter ) + { + r.moveCenter( QPointF( pos.x(), r.center().y() ) ); + } + else if ( alignment & Qt::AlignRight ) + { + r.moveRight( pos.x() ); + } + + if ( alignment & Qt::AlignTop ) + { + r.moveTop( pos.y() ); + } + else if ( alignment & Qt::AlignVCenter ) + { + r.moveCenter( QPointF( r.center().x(), pos.y() ) ); + } + else if ( alignment & Qt::AlignBottom ) + { + r.moveBottom( pos.y() ); + } + + render( painter, r ); +} + +/*! + \brief Convert the graphic to a QPixmap + + All pixels of the pixmap get initialized by Qt::transparent + before the graphic is scaled and rendered on it. + + The size of the pixmap is the default size ( ceiled to integers ) + of the graphic. + + \return The graphic as pixmap in default size + \sa defaultSize(), toImage(), render() + */ +QPixmap QwtGraphic::toPixmap() const +{ + if ( isNull() ) + return QPixmap(); + + const QSizeF sz = defaultSize(); + + const int w = qCeil( sz.width() ); + const int h = qCeil( sz.height() ); + + QPixmap pixmap( w, h ); + pixmap.fill( Qt::transparent ); + + const QRectF r( 0.0, 0.0, sz.width(), sz.height() ); + + QPainter painter( &pixmap ); + render( &painter, r, Qt::KeepAspectRatio ); + painter.end(); + + return pixmap; +} + +/*! + \brief Convert the graphic to a QPixmap + + All pixels of the pixmap get initialized by Qt::transparent + before the graphic is scaled and rendered on it. + + \param size Size of the image + \param aspectRatioMode Aspect ratio how to scale the graphic + + \return The graphic as pixmap + \sa toImage(), render() + */ +QPixmap QwtGraphic::toPixmap( const QSize &size, + Qt::AspectRatioMode aspectRatioMode ) const +{ + QPixmap pixmap( size ); + pixmap.fill( Qt::transparent ); + + const QRect r( 0, 0, size.width(), size.height() ); + + QPainter painter( &pixmap ); + render( &painter, r, aspectRatioMode ); + painter.end(); + + return pixmap; +} + +/*! + \brief Convert the graphic to a QImage + + All pixels of the image get initialized by 0 ( transparent ) + before the graphic is scaled and rendered on it. + + The format of the image is QImage::Format_ARGB32_Premultiplied. + + \param size Size of the image + \param aspectRatioMode Aspect ratio how to scale the graphic + + \return The graphic as image + \sa toPixmap(), render() + */ +QImage QwtGraphic::toImage( const QSize &size, + Qt::AspectRatioMode aspectRatioMode ) const +{ + QImage image( size, QImage::Format_ARGB32_Premultiplied ); + image.fill( 0 ); + + const QRect r( 0, 0, size.width(), size.height() ); + + QPainter painter( &image ); + render( &painter, r, aspectRatioMode ); + painter.end(); + + return image; +} + +/*! + \brief Convert the graphic to a QImage + + All pixels of the image get initialized by 0 ( transparent ) + before the graphic is scaled and rendered on it. + + The format of the image is QImage::Format_ARGB32_Premultiplied. + + The size of the image is the default size ( ceiled to integers ) + of the graphic. + + \return The graphic as image in default size + \sa defaultSize(), toPixmap(), render() + */ +QImage QwtGraphic::toImage() const +{ + if ( isNull() ) + return QImage(); + + const QSizeF sz = defaultSize(); + + const int w = qCeil( sz.width() ); + const int h = qCeil( sz.height() ); + + QImage image( w, h, QImage::Format_ARGB32 ); + image.fill( 0 ); + + const QRect r( 0, 0, sz.width(), sz.height() ); + + QPainter painter( &image ); + render( &painter, r, Qt::KeepAspectRatio ); + painter.end(); + + return image; +} + +/*! + Store a path command in the command list + + \param path Painter path + \sa QPaintEngine::drawPath() +*/ +void QwtGraphic::drawPath( const QPainterPath &path ) +{ + const QPainter *painter = paintEngine()->painter(); + if ( painter == NULL ) + return; + + d_data->commands += QwtPainterCommand( path ); + + if ( !path.isEmpty() ) + { + const QPainterPath scaledPath = painter->transform().map( path ); + + QRectF pointRect = scaledPath.boundingRect(); + QRectF boundingRect = pointRect; + + if ( painter->pen().style() != Qt::NoPen + && painter->pen().brush().style() != Qt::NoBrush ) + { + boundingRect = qwtStrokedPathRect( painter, path ); + } + + updateControlPointRect( pointRect ); + updateBoundingRect( boundingRect ); + + d_data->pathInfos += PathInfo( pointRect, + boundingRect, qwtHasScalablePen( painter ) ); + } +} + +/*! + \brief Store a pixmap command in the command list + + \param rect target rectangle + \param pixmap Pixmap to be painted + \param subRect Reactangle of the pixmap to be painted + + \sa QPaintEngine::drawPixmap() +*/ +void QwtGraphic::drawPixmap( const QRectF &rect, + const QPixmap &pixmap, const QRectF &subRect ) +{ + const QPainter *painter = paintEngine()->painter(); + if ( painter == NULL ) + return; + + d_data->commands += QwtPainterCommand( rect, pixmap, subRect ); + + const QRectF r = painter->transform().mapRect( rect ); + updateControlPointRect( r ); + updateBoundingRect( r ); +} + +/*! + \brief Store a image command in the command list + + \param rect traget rectangle + \param image Image to be painted + \param subRect Reactangle of the pixmap to be painted + \param flags Image conversion flags + + \sa QPaintEngine::drawImage() + */ +void QwtGraphic::drawImage( const QRectF &rect, const QImage &image, + const QRectF &subRect, Qt::ImageConversionFlags flags) +{ + const QPainter *painter = paintEngine()->painter(); + if ( painter == NULL ) + return; + + d_data->commands += QwtPainterCommand( rect, image, subRect, flags ); + + const QRectF r = painter->transform().mapRect( rect ); + + updateControlPointRect( r ); + updateBoundingRect( r ); +} + +/*! + \brief Store a state command in the command list + + \param state State to be stored + \sa QPaintEngine::updateState() + */ +void QwtGraphic::updateState( const QPaintEngineState &state) +{ + d_data->commands += QwtPainterCommand( state ); +} + +void QwtGraphic::updateBoundingRect( const QRectF &rect ) +{ + QRectF br = rect; + + const QPainter *painter = paintEngine()->painter(); + if ( painter && painter->hasClipping() ) + { + QRectF cr = painter->clipRegion().boundingRect(); + cr = painter->transform().mapRect( br ); + + br &= cr; + } + + if ( d_data->boundingRect.width() < 0 ) + d_data->boundingRect = br; + else + d_data->boundingRect |= br; +} + +void QwtGraphic::updateControlPointRect( const QRectF &rect ) +{ + if ( d_data->pointRect.width() < 0.0 ) + d_data->pointRect = rect; + else + d_data->pointRect |= rect; +} + +/*! + \return List of recorded paint commands + \sa setCommands() + */ +const QVector< QwtPainterCommand > &QwtGraphic::commands() const +{ + return d_data->commands; +} + +/*! + \brief Append paint commands + + \param commands Paint commands + \sa commands() + */ +void QwtGraphic::setCommands( QVector< QwtPainterCommand > &commands ) +{ + reset(); + + const int numCommands = commands.size(); + if ( numCommands <= 0 ) + return; + + // to calculate a proper bounding rectangle we don't simply copy + // the commands. + + const QwtPainterCommand *cmds = commands.constData(); + + QPainter painter( this ); + for ( int i = 0; i < numCommands; i++ ) + qwtExecCommand( &painter, cmds[i], RenderHints(), QTransform() ); + + painter.end(); +} diff --git a/qwt/src/qwt_graphic.h b/qwt/src/qwt_graphic.h new file mode 100644 index 000000000..a1ce1e8a2 --- /dev/null +++ b/qwt/src/qwt_graphic.h @@ -0,0 +1,172 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_GRAPHIC_H +#define QWT_GRAPHIC_H + +#include "qwt_global.h" +#include "qwt_null_paintdevice.h" +#include +#include +#include + +class QwtPainterCommand; + +/*! + \brief A paint device for scalable graphics + + QwtGraphic is the representation of a graphic that is tailored for + scalability. Like QPicture it will be initialized by QPainter + operations and can be replayed later to any target paint device. + + While the usual image representations QImage and QPixmap are not + scalable Qt offers two paint devices, that might be candidates + for representing a vector graphic: + + - QPicture\n + Unfortunately QPicture had been forgotten, when Qt4 + introduced floating point based render engines. Its API + is still on integers, what make it unusable for proper scaling. + + - QSvgRenderer/QSvgGenerator\n + Unfortunately QSvgRenderer hides to much information about + its nodes in internal APIs, that are necessary for proper + layout calculations. Also it is derived from QObject and + can't be copied like QImage/QPixmap. + + QwtGraphic maps all scalable drawing primitives to a QPainterPath + and stores them together with the painter state changes + ( pen, brush, transformation ... ) in a list of QwtPaintCommands. + For being a complete QPaintDevice it also stores pixmaps or images, + what is somehow against the idea of the class, because these objects + can't be scaled without a loss in quality. + + The main issue about scaling a QwtGraphic object are the pens used for + drawing the outlines of the painter paths. While non cosmetic pens + ( QPen::isCosmetic() ) are scaled with the same ratio as the path, + cosmetic pens have a fixed width. A graphic might have paths with + different pens - cosmetic and non-cosmetic. + + QwtGraphic caches 2 different rectangles: + + - control point rectangle\n + The control point rectangle is the bounding rectangle of all + control point rectangles of the painter paths, or the target + rectangle of the pixmaps/images. + + - bounding rectangle\n + The bounding rectangle extends the control point rectangle by + what is needed for rendering the outline with an unscaled pen. + + Because the offset for drawing the outline depends on the shape + of the painter path ( the peak of a triangle is different than the flat side ) + scaling with a fixed aspect ratio always needs to be calculated from the + control point rectangle. + + \sa QwtPainterCommand + */ +class QWT_EXPORT QwtGraphic: public QwtNullPaintDevice +{ +public: + /*! + Hint how to render a graphic + \sa setRenderHint(), testRenderHint() + */ + enum RenderHint + { + /*! + When RenderPensUnscaled is set non cosmetic pens are + painted unscaled - like cosmetic pens. The difference to + using cosmetic pens is, when the graphic is rendered + to a document in a scalable vector format ( PDF, SVG ): + the width of non cosmetic pens will be scaled by the + document viewer. + */ + RenderPensUnscaled = 0x1 + }; + + /*! + \brief Render hints + + The default setting is to disable all hints + */ + typedef QFlags RenderHints; + + QwtGraphic(); + QwtGraphic( const QwtGraphic & ); + + virtual ~QwtGraphic(); + + QwtGraphic& operator=( const QwtGraphic & ); + + void reset(); + + bool isNull() const; + bool isEmpty() const; + + void render( QPainter * ) const; + + void render( QPainter *, const QSizeF &, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio ) const; + + void render( QPainter *, const QRectF &, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio ) const; + + void render( QPainter *, const QPointF &, + Qt::Alignment = Qt::AlignTop | Qt::AlignLeft ) const; + + QPixmap toPixmap() const; + QPixmap toPixmap( const QSize &, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio ) const; + + QImage toImage() const; + QImage toImage( const QSize &, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio ) const; + + QRectF scaledBoundingRect( double sx, double sy ) const; + + QRectF boundingRect() const; + QRectF controlPointRect() const; + + const QVector< QwtPainterCommand > &commands() const; + void setCommands( QVector< QwtPainterCommand > & ); + + void setDefaultSize( const QSizeF & ); + QSizeF defaultSize() const; + + void setRenderHint( RenderHint, bool on = true ); + bool testRenderHint( RenderHint ) const; + +protected: + virtual QSize sizeMetrics() const; + + virtual void drawPath( const QPainterPath & ); + + virtual void drawPixmap( const QRectF &, + const QPixmap &, const QRectF & ); + + virtual void drawImage( const QRectF &, + const QImage &, const QRectF &, Qt::ImageConversionFlags ); + + virtual void updateState( const QPaintEngineState &state ); + +private: + void updateBoundingRect( const QRectF & ); + void updateControlPointRect( const QRectF & ); + + class PathInfo; + + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtGraphic::RenderHints ) +Q_DECLARE_METATYPE( QwtGraphic ) + +#endif diff --git a/qwt/src/qwt_interval.cpp b/qwt/src/qwt_interval.cpp new file mode 100644 index 000000000..b7c6ee992 --- /dev/null +++ b/qwt/src/qwt_interval.cpp @@ -0,0 +1,354 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_interval.h" +#include "qwt_math.h" +#include + +/*! + \brief Normalize the limits of the interval + + If maxValue() < minValue() the limits will be inverted. + \return Normalized interval + + \sa isValid(), inverted() +*/ +QwtInterval QwtInterval::normalized() const +{ + if ( d_minValue > d_maxValue ) + { + return inverted(); + } + if ( d_minValue == d_maxValue && d_borderFlags == ExcludeMinimum ) + { + return inverted(); + } + + return *this; +} + +/*! + Invert the limits of the interval + \return Inverted interval + \sa normalized() +*/ +QwtInterval QwtInterval::inverted() const +{ + BorderFlags borderFlags = IncludeBorders; + if ( d_borderFlags & ExcludeMinimum ) + borderFlags |= ExcludeMaximum; + if ( d_borderFlags & ExcludeMaximum ) + borderFlags |= ExcludeMinimum; + + return QwtInterval( d_maxValue, d_minValue, borderFlags ); +} + +/*! + Test if a value is inside an interval + + \param value Value + \return true, if value >= minValue() && value <= maxValue() +*/ +bool QwtInterval::contains( double value ) const +{ + if ( !isValid() ) + return false; + + if ( value < d_minValue || value > d_maxValue ) + return false; + + if ( value == d_minValue && d_borderFlags & ExcludeMinimum ) + return false; + + if ( value == d_maxValue && d_borderFlags & ExcludeMaximum ) + return false; + + return true; +} + +//! Unite 2 intervals +QwtInterval QwtInterval::unite( const QwtInterval &other ) const +{ + /* + If one of the intervals is invalid return the other one. + If both are invalid return an invalid default interval + */ + if ( !isValid() ) + { + if ( !other.isValid() ) + return QwtInterval(); + else + return other; + } + if ( !other.isValid() ) + return *this; + + QwtInterval united; + BorderFlags flags = IncludeBorders; + + // minimum + if ( d_minValue < other.minValue() ) + { + united.setMinValue( d_minValue ); + flags &= d_borderFlags & ExcludeMinimum; + } + else if ( other.minValue() < d_minValue ) + { + united.setMinValue( other.minValue() ); + flags &= other.borderFlags() & ExcludeMinimum; + } + else // d_minValue == other.minValue() + { + united.setMinValue( d_minValue ); + flags &= ( d_borderFlags & other.borderFlags() ) & ExcludeMinimum; + } + + // maximum + if ( d_maxValue > other.maxValue() ) + { + united.setMaxValue( d_maxValue ); + flags &= d_borderFlags & ExcludeMaximum; + } + else if ( other.maxValue() > d_maxValue ) + { + united.setMaxValue( other.maxValue() ); + flags &= other.borderFlags() & ExcludeMaximum; + } + else // d_maxValue == other.maxValue() ) + { + united.setMaxValue( d_maxValue ); + flags &= d_borderFlags & other.borderFlags() & ExcludeMaximum; + } + + united.setBorderFlags( flags ); + return united; +} + +/*! + \brief Intersect 2 intervals + + \param other Interval to be intersect with + \return Intersection + */ +QwtInterval QwtInterval::intersect( const QwtInterval &other ) const +{ + if ( !other.isValid() || !isValid() ) + return QwtInterval(); + + QwtInterval i1 = *this; + QwtInterval i2 = other; + + // swap i1/i2, so that the minimum of i1 + // is smaller then the minimum of i2 + + if ( i1.minValue() > i2.minValue() ) + { + qSwap( i1, i2 ); + } + else if ( i1.minValue() == i2.minValue() ) + { + if ( i1.borderFlags() & ExcludeMinimum ) + qSwap( i1, i2 ); + } + + if ( i1.maxValue() < i2.minValue() ) + { + return QwtInterval(); + } + + if ( i1.maxValue() == i2.minValue() ) + { + if ( i1.borderFlags() & ExcludeMaximum || + i2.borderFlags() & ExcludeMinimum ) + { + return QwtInterval(); + } + } + + QwtInterval intersected; + BorderFlags flags = IncludeBorders; + + intersected.setMinValue( i2.minValue() ); + flags |= i2.borderFlags() & ExcludeMinimum; + + if ( i1.maxValue() < i2.maxValue() ) + { + intersected.setMaxValue( i1.maxValue() ); + flags |= i1.borderFlags() & ExcludeMaximum; + } + else if ( i2.maxValue() < i1.maxValue() ) + { + intersected.setMaxValue( i2.maxValue() ); + flags |= i2.borderFlags() & ExcludeMaximum; + } + else // i1.maxValue() == i2.maxValue() + { + intersected.setMaxValue( i1.maxValue() ); + flags |= i1.borderFlags() & i2.borderFlags() & ExcludeMaximum; + } + + intersected.setBorderFlags( flags ); + return intersected; +} + +/*! + \brief Unite this interval with the given interval. + + \param other Interval to be united with + \return This interval + */ +QwtInterval& QwtInterval::operator|=( const QwtInterval &other ) +{ + *this = *this | other; + return *this; +} + +/*! + \brief Intersect this interval with the given interval. + + \param other Interval to be intersected with + \return This interval + */ +QwtInterval& QwtInterval::operator&=( const QwtInterval &other ) +{ + *this = *this & other; + return *this; +} + +/*! + \brief Test if two intervals overlap + + \param other Interval + \return True, when the intervals are intersecting +*/ +bool QwtInterval::intersects( const QwtInterval &other ) const +{ + if ( !isValid() || !other.isValid() ) + return false; + + QwtInterval i1 = *this; + QwtInterval i2 = other; + + // swap i1/i2, so that the minimum of i1 + // is smaller then the minimum of i2 + + if ( i1.minValue() > i2.minValue() ) + { + qSwap( i1, i2 ); + } + else if ( i1.minValue() == i2.minValue() && + i1.borderFlags() & ExcludeMinimum ) + { + qSwap( i1, i2 ); + } + + if ( i1.maxValue() > i2.minValue() ) + { + return true; + } + if ( i1.maxValue() == i2.minValue() ) + { + return !( ( i1.borderFlags() & ExcludeMaximum ) || + ( i2.borderFlags() & ExcludeMinimum ) ); + } + return false; +} + +/*! + Adjust the limit that is closer to value, so that value becomes + the center of the interval. + + \param value Center + \return Interval with value as center +*/ +QwtInterval QwtInterval::symmetrize( double value ) const +{ + if ( !isValid() ) + return *this; + + const double delta = + qMax( qAbs( value - d_maxValue ), qAbs( value - d_minValue ) ); + + return QwtInterval( value - delta, value + delta ); +} + +/*! + Limit the interval, keeping the border modes + + \param lowerBound Lower limit + \param upperBound Upper limit + + \return Limited interval +*/ +QwtInterval QwtInterval::limited( double lowerBound, double upperBound ) const +{ + if ( !isValid() || lowerBound > upperBound ) + return QwtInterval(); + + double minValue = qMax( d_minValue, lowerBound ); + minValue = qMin( minValue, upperBound ); + + double maxValue = qMax( d_maxValue, lowerBound ); + maxValue = qMin( maxValue, upperBound ); + + return QwtInterval( minValue, maxValue, d_borderFlags ); +} + +/*! + \brief Extend the interval + + If value is below minValue(), value becomes the lower limit. + If value is above maxValue(), value becomes the upper limit. + + extend() has no effect for invalid intervals + + \param value Value + \return extended interval + + \sa isValid() +*/ +QwtInterval QwtInterval::extend( double value ) const +{ + if ( !isValid() ) + return *this; + + return QwtInterval( qMin( value, d_minValue ), + qMax( value, d_maxValue ), d_borderFlags ); +} + +/*! + Extend an interval + + \param value Value + \return Reference of the extended interval + + \sa extend() +*/ +QwtInterval& QwtInterval::operator|=( double value ) +{ + *this = *this | value; + return *this; +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<( QDebug debug, const QwtInterval &interval ) +{ + const int flags = interval.borderFlags(); + + debug.nospace() << "QwtInterval(" + << ( ( flags & QwtInterval::ExcludeMinimum ) ? "]" : "[" ) + << interval.minValue() << "," << interval.maxValue() + << ( ( flags & QwtInterval::ExcludeMaximum ) ? "[" : "]" ) + << ")"; + + return debug.space(); +} + +#endif diff --git a/qwt/src/qwt_interval.h b/qwt/src/qwt_interval.h new file mode 100644 index 000000000..68841e00b --- /dev/null +++ b/qwt/src/qwt_interval.h @@ -0,0 +1,320 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_INTERVAL_H +#define QWT_INTERVAL_H + +#include "qwt_global.h" +#include + +#ifndef QT_NO_DEBUG_STREAM +#include +#endif + +/*! + \brief A class representing an interval + + The interval is represented by 2 doubles, the lower and the upper limit. +*/ + +class QWT_EXPORT QwtInterval +{ +public: + /*! + Flag indicating if a border is included or excluded + \sa setBorderFlags(), borderFlags() + */ + enum BorderFlag + { + //! Min/Max values are inside the interval + IncludeBorders = 0x00, + + //! Min value is not included in the interval + ExcludeMinimum = 0x01, + + //! Max value is not included in the interval + ExcludeMaximum = 0x02, + + //! Min/Max values are not included in the interval + ExcludeBorders = ExcludeMinimum | ExcludeMaximum + }; + + //! Border flags + typedef QFlags BorderFlags; + + QwtInterval(); + QwtInterval( double minValue, double maxValue, + BorderFlags = IncludeBorders ); + + void setInterval( double minValue, double maxValue, + BorderFlags = IncludeBorders ); + + QwtInterval normalized() const; + QwtInterval inverted() const; + QwtInterval limited( double minValue, double maxValue ) const; + + bool operator==( const QwtInterval & ) const; + bool operator!=( const QwtInterval & ) const; + + void setBorderFlags( BorderFlags ); + BorderFlags borderFlags() const; + + double minValue() const; + double maxValue() const; + + double width() const; + + void setMinValue( double ); + void setMaxValue( double ); + + bool contains( double value ) const; + + bool intersects( const QwtInterval & ) const; + QwtInterval intersect( const QwtInterval & ) const; + QwtInterval unite( const QwtInterval & ) const; + + QwtInterval operator|( const QwtInterval & ) const; + QwtInterval operator&( const QwtInterval & ) const; + + QwtInterval &operator|=( const QwtInterval & ); + QwtInterval &operator&=( const QwtInterval & ); + + QwtInterval extend( double value ) const; + QwtInterval operator|( double ) const; + QwtInterval &operator|=( double ); + + bool isValid() const; + bool isNull() const; + void invalidate(); + + QwtInterval symmetrize( double value ) const; + +private: + double d_minValue; + double d_maxValue; + BorderFlags d_borderFlags; +}; + +Q_DECLARE_TYPEINFO(QwtInterval, Q_MOVABLE_TYPE); + +/*! + \brief Default Constructor + + Creates an invalid interval [0.0, -1.0] + \sa setInterval(), isValid() +*/ +inline QwtInterval::QwtInterval(): + d_minValue( 0.0 ), + d_maxValue( -1.0 ), + d_borderFlags( IncludeBorders ) +{ +} + +/*! + Constructor + + Build an interval with from min/max values + + \param minValue Minimum value + \param maxValue Maximum value + \param borderFlags Include/Exclude borders +*/ +inline QwtInterval::QwtInterval( + double minValue, double maxValue, BorderFlags borderFlags ): + d_minValue( minValue ), + d_maxValue( maxValue ), + d_borderFlags( borderFlags ) +{ +} + +/*! + Assign the limits of the interval + + \param minValue Minimum value + \param maxValue Maximum value + \param borderFlags Include/Exclude borders +*/ +inline void QwtInterval::setInterval( + double minValue, double maxValue, BorderFlags borderFlags ) +{ + d_minValue = minValue; + d_maxValue = maxValue; + d_borderFlags = borderFlags; +} + +/*! + Change the border flags + + \param borderFlags Or'd BorderMode flags + \sa borderFlags() +*/ +inline void QwtInterval::setBorderFlags( BorderFlags borderFlags ) +{ + d_borderFlags = borderFlags; +} + +/*! + \return Border flags + \sa setBorderFlags() +*/ +inline QwtInterval::BorderFlags QwtInterval::borderFlags() const +{ + return d_borderFlags; +} + +/*! + Assign the lower limit of the interval + + \param minValue Minimum value +*/ +inline void QwtInterval::setMinValue( double minValue ) +{ + d_minValue = minValue; +} + +/*! + Assign the upper limit of the interval + + \param maxValue Maximum value +*/ +inline void QwtInterval::setMaxValue( double maxValue ) +{ + d_maxValue = maxValue; +} + +//! \return Lower limit of the interval +inline double QwtInterval::minValue() const +{ + return d_minValue; +} + +//! \return Upper limit of the interval +inline double QwtInterval::maxValue() const +{ + return d_maxValue; +} + +/*! + A interval is valid when minValue() <= maxValue(). + In case of QwtInterval::ExcludeBorders it is true + when minValue() < maxValue() + + \return True, when the interval is valid +*/ +inline bool QwtInterval::isValid() const +{ + if ( ( d_borderFlags & ExcludeBorders ) == 0 ) + return d_minValue <= d_maxValue; + else + return d_minValue < d_maxValue; +} + +/*! + \brief Return the width of an interval + + The width of invalid intervals is 0.0, otherwise the result is + maxValue() - minValue(). + + \return Interval width + \sa isValid() +*/ +inline double QwtInterval::width() const +{ + return isValid() ? ( d_maxValue - d_minValue ) : 0.0; +} + +/*! + \brief Intersection of two intervals + + \param other Interval to intersect with + \return Intersection of this and other + + \sa intersect() +*/ +inline QwtInterval QwtInterval::operator&( + const QwtInterval &other ) const +{ + return intersect( other ); +} + +/*! + Union of two intervals + + \param other Interval to unite with + \return Union of this and other + + \sa unite() +*/ +inline QwtInterval QwtInterval::operator|( + const QwtInterval &other ) const +{ + return unite( other ); +} + +/*! + \brief Compare two intervals + + \param other Interval to compare with + \return True, when this and other are equal +*/ +inline bool QwtInterval::operator==( const QwtInterval &other ) const +{ + return ( d_minValue == other.d_minValue ) && + ( d_maxValue == other.d_maxValue ) && + ( d_borderFlags == other.d_borderFlags ); +} +/*! + \brief Compare two intervals + + \param other Interval to compare with + \return True, when this and other are not equal +*/ +inline bool QwtInterval::operator!=( const QwtInterval &other ) const +{ + return ( !( *this == other ) ); +} + +/*! + Extend an interval + + \param value Value + \return Extended interval + \sa extend() +*/ +inline QwtInterval QwtInterval::operator|( double value ) const +{ + return extend( value ); +} + +//! \return true, if isValid() && (minValue() >= maxValue()) +inline bool QwtInterval::isNull() const +{ + return isValid() && d_minValue >= d_maxValue; +} + +/*! + Invalidate the interval + + The limits are set to interval [0.0, -1.0] + \sa isValid() +*/ +inline void QwtInterval::invalidate() +{ + d_minValue = 0.0; + d_maxValue = -1.0; +} + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtInterval::BorderFlags ) +Q_DECLARE_METATYPE( QwtInterval ) + +#ifndef QT_NO_DEBUG_STREAM +QWT_EXPORT QDebug operator<<( QDebug, const QwtInterval & ); +#endif + +#endif diff --git a/qwt/src/qwt_interval_symbol.cpp b/qwt/src/qwt_interval_symbol.cpp new file mode 100644 index 000000000..83c842d98 --- /dev/null +++ b/qwt/src/qwt_interval_symbol.cpp @@ -0,0 +1,319 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_interval_symbol.h" +#include "qwt_painter.h" +#include "qwt_math.h" +#include + +#if QT_VERSION < 0x040601 +#define qAtan2(y, x) ::atan2(y, x) +#define qFastSin(x) qSin(x) +#define qFastCos(x) qCos(x) +#endif + +class QwtIntervalSymbol::PrivateData +{ +public: + PrivateData(): + style( QwtIntervalSymbol::NoSymbol ), + width( 6 ) + { + } + + bool operator==( const PrivateData &other ) const + { + return ( style == other.style ) + && ( width == other.width ) + && ( brush == other.brush ) + && ( pen == other.pen ); + } + + QwtIntervalSymbol::Style style; + int width; + + QPen pen; + QBrush brush; +}; + +/*! + Constructor + + \param style Style of the symbol + \sa setStyle(), style(), Style +*/ +QwtIntervalSymbol::QwtIntervalSymbol( Style style ) +{ + d_data = new PrivateData(); + d_data->style = style; +} + +//! Copy constructor +QwtIntervalSymbol::QwtIntervalSymbol( const QwtIntervalSymbol &other ) +{ + d_data = new PrivateData(); + *d_data = *other.d_data; +} + +//! Destructor +QwtIntervalSymbol::~QwtIntervalSymbol() +{ + delete d_data; +} + +//! \brief Assignment operator +QwtIntervalSymbol &QwtIntervalSymbol::operator=( + const QwtIntervalSymbol &other ) +{ + *d_data = *other.d_data; + return *this; +} + +//! \brief Compare two symbols +bool QwtIntervalSymbol::operator==( + const QwtIntervalSymbol &other ) const +{ + return *d_data == *other.d_data; +} + +//! \brief Compare two symbols +bool QwtIntervalSymbol::operator!=( + const QwtIntervalSymbol &other ) const +{ + return !( *d_data == *other.d_data ); +} + +/*! + Specify the symbol style + + \param style Style + \sa style(), Style +*/ +void QwtIntervalSymbol::setStyle( Style style ) +{ + d_data->style = style; +} + +/*! + \return Current symbol style + \sa setStyle() +*/ +QwtIntervalSymbol::Style QwtIntervalSymbol::style() const +{ + return d_data->style; +} + +/*! + Specify the width of the symbol + It is used depending on the style. + + \param width Width + \sa width(), setStyle() +*/ +void QwtIntervalSymbol::setWidth( int width ) +{ + d_data->width = width; +} + +/*! + \return Width of the symbol. + \sa setWidth(), setStyle() +*/ +int QwtIntervalSymbol::width() const +{ + return d_data->width; +} + +/*! + \brief Assign a brush + + The brush is used for the Box style. + + \param brush Brush + \sa brush() +*/ +void QwtIntervalSymbol::setBrush( const QBrush &brush ) +{ + d_data->brush = brush; +} + +/*! + \return Brush + \sa setBrush() +*/ +const QBrush& QwtIntervalSymbol::brush() const +{ + return d_data->brush; +} + +/*! + Build and assign a pen + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtIntervalSymbol::setPen( const QColor &color, + qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen + + \param pen Pen + \sa pen(), setBrush() +*/ +void QwtIntervalSymbol::setPen( const QPen &pen ) +{ + d_data->pen = pen; +} + +/*! + \return Pen + \sa setPen(), brush() +*/ +const QPen& QwtIntervalSymbol::pen() const +{ + return d_data->pen; +} + +/*! + Draw a symbol depending on its style + + \param painter Painter + \param orientation Orientation + \param from Start point of the interval in target device coordinates + \param to End point of the interval in target device coordinates + + \sa setStyle() +*/ +void QwtIntervalSymbol::draw( QPainter *painter, Qt::Orientation orientation, + const QPointF &from, const QPointF &to ) const +{ + const qreal pw = qMax( painter->pen().widthF(), qreal( 1.0 ) ); + + QPointF p1 = from; + QPointF p2 = to; + if ( QwtPainter::roundingAlignment( painter ) ) + { + p1 = p1.toPoint(); + p2 = p2.toPoint(); + } + + switch ( d_data->style ) + { + case QwtIntervalSymbol::Bar: + { + QwtPainter::drawLine( painter, p1, p2 ); + if ( d_data->width > pw ) + { + if ( ( orientation == Qt::Horizontal ) + && ( p1.y() == p2.y() ) ) + { + const double sw = d_data->width; + + const double y = p1.y() - sw / 2; + QwtPainter::drawLine( painter, + p1.x(), y, p1.x(), y + sw ); + QwtPainter::drawLine( painter, + p2.x(), y, p2.x(), y + sw ); + } + else if ( ( orientation == Qt::Vertical ) + && ( p1.x() == p2.x() ) ) + { + const double sw = d_data->width; + + const double x = p1.x() - sw / 2; + QwtPainter::drawLine( painter, + x, p1.y(), x + sw, p1.y() ); + QwtPainter::drawLine( painter, + x, p2.y(), x + sw, p2.y() ); + } + else + { + const double sw = d_data->width; + + const double dx = p2.x() - p1.x(); + const double dy = p2.y() - p1.y(); + const double angle = qAtan2( dy, dx ) + M_PI_2; + double dw2 = sw / 2.0; + + const double cx = qFastCos( angle ) * dw2; + const double sy = qFastSin( angle ) * dw2; + + QwtPainter::drawLine( painter, + p1.x() - cx, p1.y() - sy, + p1.x() + cx, p1.y() + sy ); + QwtPainter::drawLine( painter, + p2.x() - cx, p2.y() - sy, + p2.x() + cx, p2.y() + sy ); + } + } + break; + } + case QwtIntervalSymbol::Box: + { + if ( d_data->width <= pw ) + { + QwtPainter::drawLine( painter, p1, p2 ); + } + else + { + if ( ( orientation == Qt::Horizontal ) + && ( p1.y() == p2.y() ) ) + { + const double sw = d_data->width; + + const double y = p1.y() - d_data->width / 2; + QwtPainter::drawRect( painter, + p1.x(), y, p2.x() - p1.x(), sw ); + } + else if ( ( orientation == Qt::Vertical ) + && ( p1.x() == p2.x() ) ) + { + const double sw = d_data->width; + + const double x = p1.x() - d_data->width / 2; + QwtPainter::drawRect( painter, + x, p1.y(), sw, p2.y() - p1.y() ); + } + else + { + const double sw = d_data->width; + + const double dx = p2.x() - p1.x(); + const double dy = p2.y() - p1.y(); + const double angle = qAtan2( dy, dx ) + M_PI_2; + double dw2 = sw / 2.0; + + const double cx = qFastCos( angle ) * dw2; + const double sy = qFastSin( angle ) * dw2; + + QPolygonF polygon; + polygon += QPointF( p1.x() - cx, p1.y() - sy ); + polygon += QPointF( p1.x() + cx, p1.y() + sy ); + polygon += QPointF( p2.x() + cx, p2.y() + sy ); + polygon += QPointF( p2.x() - cx, p2.y() - sy ); + + QwtPainter::drawPolygon( painter, polygon ); + } + } + break; + } + default:; + } +} diff --git a/qwt/src/qwt_interval_symbol.h b/qwt/src/qwt_interval_symbol.h new file mode 100644 index 000000000..f32e1c41d --- /dev/null +++ b/qwt/src/qwt_interval_symbol.h @@ -0,0 +1,87 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_INTERVAL_SYMBOL_H +#define QWT_INTERVAL_SYMBOL_H + +#include "qwt_global.h" +#include +#include + +class QPainter; +class QRect; +class QPointF; + +/*! + \brief A drawing primitive for displaying an interval like an error bar + + \sa QwtPlotIntervalCurve +*/ +class QWT_EXPORT QwtIntervalSymbol +{ +public: + //! Symbol style + enum Style + { + //! No Style. The symbol cannot be drawn. + NoSymbol = -1, + + /*! + The symbol displays a line with caps at the beginning/end. + The size of the caps depends on the symbol width(). + */ + Bar, + + /*! + The symbol displays a plain rectangle using pen() and brush(). + The size of the rectangle depends on the translated interval and + the width(), + */ + Box, + + /*! + Styles >= UserSymbol are reserved for derived + classes of QwtIntervalSymbol that overload draw() with + additional application specific symbol types. + */ + UserSymbol = 1000 + }; + +public: + QwtIntervalSymbol( Style = NoSymbol ); + QwtIntervalSymbol( const QwtIntervalSymbol & ); + virtual ~QwtIntervalSymbol(); + + QwtIntervalSymbol &operator=( const QwtIntervalSymbol & ); + bool operator==( const QwtIntervalSymbol & ) const; + bool operator!=( const QwtIntervalSymbol & ) const; + + void setWidth( int ); + int width() const; + + void setBrush( const QBrush& b ); + const QBrush& brush() const; + + void setPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen & ); + const QPen& pen() const; + + void setStyle( Style ); + Style style() const; + + virtual void draw( QPainter *, Qt::Orientation, + const QPointF& from, const QPointF& to ) const; + +private: + + class PrivateData; + PrivateData* d_data; +}; + +#endif diff --git a/qwt/src/qwt_knob.cpp b/qwt/src/qwt_knob.cpp new file mode 100644 index 000000000..5860e42c4 --- /dev/null +++ b/qwt/src/qwt_knob.cpp @@ -0,0 +1,845 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_knob.h" +#include "qwt_round_scale_draw.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION < 0x040601 +#define qAtan2(y, x) ::atan2(y, x) +#define qFabs(x) ::fabs(x) +#define qFastCos(x) qCos(x) +#define qFastSin(x) qSin(x) +#endif + +static QSize qwtKnobSizeHint( const QwtKnob *knob, int min ) +{ + int knobWidth = knob->knobWidth(); + if ( knobWidth <= 0 ) + knobWidth = qMax( 3 * knob->markerSize(), min ); + + // Add the scale radial thickness to the knobWidth + const int extent = qCeil( knob->scaleDraw()->extent( knob->font() ) ); + const int d = 2 * ( extent + 4 ) + knobWidth; + + int left, right, top, bottom; + knob->getContentsMargins( &left, &top, &right, &bottom ); + + return QSize( d + left + right, d + top + bottom ); +} + +static inline double qwtToScaleAngle( double angle ) +{ + // the map is counter clockwise with the origin + // at 90° using angles from -180° -> 180° + + double a = 90.0 - angle; + if ( a <= -180.0 ) + a += 360.0; + else if ( a >= 180.0 ) + a -= 360.0; + + return a; +} + +static double qwtToDegrees( double value ) +{ + return qwtNormalizeDegrees( 90.0 - value ); +} + +class QwtKnob::PrivateData +{ +public: + PrivateData(): + knobStyle( QwtKnob::Raised ), + markerStyle( QwtKnob::Notch ), + borderWidth( 2 ), + borderDist( 4 ), + scaleDist( 4 ), + maxScaleTicks( 11 ), + knobWidth( 0 ), + alignment( Qt::AlignCenter ), + markerSize( 8 ), + totalAngle( 270.0 ), + mouseOffset( 0.0 ) + { + } + + QwtKnob::KnobStyle knobStyle; + QwtKnob::MarkerStyle markerStyle; + + int borderWidth; + int borderDist; + int scaleDist; + int maxScaleTicks; + int knobWidth; + Qt::Alignment alignment; + int markerSize; + + double totalAngle; + + double mouseOffset; +}; + +/*! + \brief Constructor + + Construct a knob with an angle of 270°. The style is + QwtKnob::Raised and the marker style is QwtKnob::Notch. + The width of the knob is set to 50 pixels. + + \param parent Parent widget + + \sa setTotalAngle() +*/ +QwtKnob::QwtKnob( QWidget* parent ): + QwtAbstractSlider( parent ) +{ + d_data = new PrivateData; + + setScaleDraw( new QwtRoundScaleDraw() ); + + setTotalAngle( 270.0 ); + + setScale( 0.0, 10.0 ); + setValue( 0.0 ); + + setSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding ); +} + +//! Destructor +QwtKnob::~QwtKnob() +{ + delete d_data; +} + +/*! + \brief Set the knob type + + \param knobStyle Knob type + \sa knobStyle(), setBorderWidth() +*/ +void QwtKnob::setKnobStyle( KnobStyle knobStyle ) +{ + if ( d_data->knobStyle != knobStyle ) + { + d_data->knobStyle = knobStyle; + update(); + } +} + +/*! + \return Marker type of the knob + \sa setKnobStyle(), setBorderWidth() +*/ +QwtKnob::KnobStyle QwtKnob::knobStyle() const +{ + return d_data->knobStyle; +} + +/*! + \brief Set the marker type of the knob + + \param markerStyle Marker type + \sa markerStyle(), setMarkerSize() +*/ +void QwtKnob::setMarkerStyle( MarkerStyle markerStyle ) +{ + if ( d_data->markerStyle != markerStyle ) + { + d_data->markerStyle = markerStyle; + update(); + } +} + +/*! + \return Marker type of the knob + \sa setMarkerStyle(), setMarkerSize() +*/ +QwtKnob::MarkerStyle QwtKnob::markerStyle() const +{ + return d_data->markerStyle; +} + +/*! + \brief Set the total angle by which the knob can be turned + \param angle Angle in degrees. + + The angle has to be between [10, 360] degrees. Angles above + 360 ( so that the knob can be turned several times around its axis ) + have to be set using setNumTurns(). + + The default angle is 270 degrees. + + \sa totalAngle(), setNumTurns() +*/ +void QwtKnob::setTotalAngle ( double angle ) +{ + angle = qBound( 10.0, angle, 360.0 ); + + if ( angle != d_data->totalAngle ) + { + d_data->totalAngle = angle; + + scaleDraw()->setAngleRange( -0.5 * d_data->totalAngle, + 0.5 * d_data->totalAngle ); + + updateGeometry(); + update(); + } +} + +/*! + \return the total angle + \sa setTotalAngle(), setNumTurns(), numTurns() + */ +double QwtKnob::totalAngle() const +{ + return d_data->totalAngle; +} + +/*! + \brief Set the number of turns + + When numTurns > 1 the knob can be turned several times around its axis + - otherwise the total angle is floored to 360°. + + \sa numTurns(), totalAngle(), setTotalAngle() +*/ + +void QwtKnob::setNumTurns( int numTurns ) +{ + numTurns = qMax( numTurns, 1 ); + + if ( numTurns == 1 && d_data->totalAngle <= 360.0 ) + return; + + const double angle = numTurns * 360.0; + if ( angle != d_data->totalAngle ) + { + d_data->totalAngle = angle; + + scaleDraw()->setAngleRange( -0.5 * d_data->totalAngle, + 0.5 * d_data->totalAngle ); + + updateGeometry(); + update(); + } +} + +/*! + \return Number of turns. + + When the total angle is below 360° numTurns() is ceiled to 1. + \sa setNumTurns(), setTotalAngle(), totalAngle() + */ +int QwtKnob::numTurns() const +{ + return qCeil( d_data->totalAngle / 360.0 ); +} + +/*! + Change the scale draw of the knob + + For changing the labels of the scales, it + is necessary to derive from QwtRoundScaleDraw and + overload QwtRoundScaleDraw::label(). + + \sa scaleDraw() +*/ +void QwtKnob::setScaleDraw( QwtRoundScaleDraw *scaleDraw ) +{ + setAbstractScaleDraw( scaleDraw ); + setTotalAngle( d_data->totalAngle ); +} + +/*! + \return the scale draw of the knob + \sa setScaleDraw() +*/ +const QwtRoundScaleDraw *QwtKnob::scaleDraw() const +{ + return static_cast( abstractScaleDraw() ); +} + +/*! + \return the scale draw of the knob + \sa setScaleDraw() +*/ +QwtRoundScaleDraw *QwtKnob::scaleDraw() +{ + return static_cast( abstractScaleDraw() ); +} + +/*! + Calculate the bounding rectangle of the knob without the scale + + \return Bounding rectangle of the knob + \sa knobWidth(), alignment(), QWidget::contentsRect() + */ +QRect QwtKnob::knobRect() const +{ + const QRect cr = contentsRect(); + + const int extent = qCeil( scaleDraw()->extent( font() ) ); + const int d = extent + d_data->scaleDist; + + int w = d_data->knobWidth; + if ( w <= 0 ) + { + const int dim = qMin( cr.width(), cr.height() ); + + w = dim - 2 * ( d ); + w = qMax( 0, w ); + } + + QRect r( 0, 0, w, w ); + + if ( d_data->alignment & Qt::AlignLeft ) + { + r.moveLeft( cr.left() + d ); + } + else if ( d_data->alignment & Qt::AlignRight ) + { + r.moveRight( cr.right() - d ); + } + else + { + r.moveCenter( QPoint( cr.center().x(), r.center().y() ) ); + } + + if ( d_data->alignment & Qt::AlignTop ) + { + r.moveTop( cr.top() + d ); + } + else if ( d_data->alignment & Qt::AlignBottom ) + { + r.moveBottom( cr.bottom() - d ); + } + else + { + r.moveCenter( QPoint( r.center().x(), cr.center().y() ) ); + } + + return r; +} + +/*! + \brief Determine what to do when the user presses a mouse button. + + \param pos Mouse position + + \retval True, when pos is inside the circle of the knob. + \sa scrolledTo() +*/ +bool QwtKnob::isScrollPosition( const QPoint &pos ) const +{ + const QRect kr = knobRect(); + + const QRegion region( kr, QRegion::Ellipse ); + if ( region.contains( pos ) && ( pos != kr.center() ) ) + { + const double angle = QLineF( kr.center(), pos ).angle(); + const double valueAngle = qwtToDegrees( transform( value() ) ); + + d_data->mouseOffset = qwtNormalizeDegrees( angle - valueAngle ); + + return true; + } + + return false; +} + +/*! + \brief Determine the value for a new position of the mouse + + \param pos Mouse position + + \return Value for the mouse position + \sa isScrollPosition() +*/ +double QwtKnob::scrolledTo( const QPoint &pos ) const +{ + double angle = QLineF( rect().center(), pos ).angle(); + angle = qwtNormalizeDegrees( angle - d_data->mouseOffset ); + + if ( scaleMap().pDist() > 360.0 ) + { + angle = qwtToDegrees( angle ); + + const double v = transform( value() ); + + int numTurns = qFloor( ( v - scaleMap().p1() ) / 360.0 ); + + double valueAngle = qwtNormalizeDegrees( v ); + if ( qAbs( valueAngle - angle ) > 180.0 ) + { + numTurns += ( angle > valueAngle ) ? -1 : 1; + } + + angle += scaleMap().p1() + numTurns * 360.0; + + if ( !wrapping() ) + { + const double boundedAngle = + qBound( scaleMap().p1(), angle, scaleMap().p2() ); + + d_data->mouseOffset += ( boundedAngle - angle ); + angle = boundedAngle; + } + } + else + { + angle = qwtToScaleAngle( angle ); + + const double boundedAngle = + qBound( scaleMap().p1(), angle, scaleMap().p2() ); + + if ( !wrapping() ) + d_data->mouseOffset += ( boundedAngle - angle ); + + angle = boundedAngle; + } + + return invTransform( angle ); +} + +/*! + Handle QEvent::StyleChange and QEvent::FontChange; + \param event Change event +*/ +void QwtKnob::changeEvent( QEvent *event ) +{ + switch( event->type() ) + { + case QEvent::StyleChange: + case QEvent::FontChange: + { + updateGeometry(); + update(); + break; + } + default: + break; + } +} + +/*! + Repaint the knob + \param event Paint event +*/ +void QwtKnob::paintEvent( QPaintEvent *event ) +{ + const QRectF knobRect = this->knobRect(); + + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QStyleOption opt; + opt.init(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); + + painter.setRenderHint( QPainter::Antialiasing, true ); + + if ( !knobRect.contains( event->region().boundingRect() ) ) + { + scaleDraw()->setRadius( 0.5 * knobRect.width() + d_data->scaleDist ); + scaleDraw()->moveCenter( knobRect.center() ); + + scaleDraw()->draw( &painter, palette() ); + } + + drawKnob( &painter, knobRect ); + + drawMarker( &painter, knobRect, + qwtNormalizeDegrees( transform( value() ) ) ); + + painter.setRenderHint( QPainter::Antialiasing, false ); + + if ( hasFocus() ) + drawFocusIndicator( &painter ); +} + +/*! + \brief Draw the knob + + \param painter painter + \param knobRect Bounding rectangle of the knob (without scale) +*/ +void QwtKnob::drawKnob( QPainter *painter, const QRectF &knobRect ) const +{ + double dim = qMin( knobRect.width(), knobRect.height() ); + dim -= d_data->borderWidth * 0.5; + + QRectF aRect( 0, 0, dim, dim ); + aRect.moveCenter( knobRect.center() ); + + QPen pen( Qt::NoPen ); + if ( d_data->borderWidth > 0 ) + { + QColor c1 = palette().color( QPalette::Light ); + QColor c2 = palette().color( QPalette::Dark ); + + QLinearGradient gradient( aRect.topLeft(), aRect.bottomRight() ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 0.3, c1 ); + gradient.setColorAt( 0.7, c2 ); + gradient.setColorAt( 1.0, c2 ); + + pen = QPen( gradient, d_data->borderWidth ); + } + + QBrush brush; + switch( d_data->knobStyle ) + { + case QwtKnob::Raised: + { + double off = 0.3 * knobRect.width(); + QRadialGradient gradient( knobRect.center(), + knobRect.width(), knobRect.topLeft() + QPointF( off, off ) ); + + gradient.setColorAt( 0.0, palette().color( QPalette::Midlight ) ); + gradient.setColorAt( 1.0, palette().color( QPalette::Button ) ); + + brush = QBrush( gradient ); + + break; + } + case QwtKnob::Styled: + { + QRadialGradient gradient(knobRect.center().x() - knobRect.width() / 3, + knobRect.center().y() - knobRect.height() / 2, + knobRect.width() * 1.3, + knobRect.center().x(), + knobRect.center().y() - knobRect.height() / 2); + + const QColor c = palette().color( QPalette::Button ); + gradient.setColorAt(0, c.lighter(110)); + gradient.setColorAt(qreal(0.5), c); + gradient.setColorAt(qreal(0.501), c.darker(102)); + gradient.setColorAt(1, c.darker(115)); + + brush = QBrush( gradient ); + + break; + } + case QwtKnob::Sunken: + { + QLinearGradient gradient( + knobRect.topLeft(), knobRect.bottomRight() ); + gradient.setColorAt( 0.0, palette().color( QPalette::Mid ) ); + gradient.setColorAt( 0.5, palette().color( QPalette::Button ) ); + gradient.setColorAt( 1.0, palette().color( QPalette::Midlight ) ); + brush = QBrush( gradient ); + + break; + } + case QwtKnob::Flat: + default: + brush = palette().brush( QPalette::Button ); + } + + painter->setPen( pen ); + painter->setBrush( brush ); + painter->drawEllipse( aRect ); +} + + +/*! + \brief Draw the marker at the knob's front + + \param painter Painter + \param rect Bounding rectangle of the knob without scale + \param angle Angle of the marker in degrees + ( clockwise, 0 at the 12 o'clock position ) +*/ +void QwtKnob::drawMarker( QPainter *painter, + const QRectF &rect, double angle ) const +{ + if ( d_data->markerStyle == NoMarker || !isValid() ) + return; + + const double radians = qwtRadians( angle ); + const double sinA = -qFastSin( radians ); + const double cosA = qFastCos( radians ); + + const double xm = rect.center().x(); + const double ym = rect.center().y(); + const double margin = 4.0; + + double radius = 0.5 * ( rect.width() - d_data->borderWidth ) - margin; + if ( radius < 1.0 ) + radius = 1.0; + + int markerSize = d_data->markerSize; + if ( markerSize <= 0 ) + markerSize = qRound( 0.4 * radius ); + + switch ( d_data->markerStyle ) + { + case Notch: + case Nub: + { + const double dotWidth = + qMin( double( markerSize ), radius); + + const double dotCenterDist = radius - 0.5 * dotWidth; + if ( dotCenterDist > 0.0 ) + { + const QPointF center( xm - sinA * dotCenterDist, + ym - cosA * dotCenterDist ); + + QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth ); + ellipse.moveCenter( center ); + + QColor c1 = palette().color( QPalette::Light ); + QColor c2 = palette().color( QPalette::Mid ); + + if ( d_data->markerStyle == Notch ) + qSwap( c1, c2 ); + + QLinearGradient gradient( + ellipse.topLeft(), ellipse.bottomRight() ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 1.0, c2 ); + + painter->setPen( Qt::NoPen ); + painter->setBrush( gradient ); + + painter->drawEllipse( ellipse ); + } + break; + } + case Dot: + { + const double dotWidth = + qMin( double( markerSize ), radius); + + const double dotCenterDist = radius - 0.5 * dotWidth; + if ( dotCenterDist > 0.0 ) + { + const QPointF center( xm - sinA * dotCenterDist, + ym - cosA * dotCenterDist ); + + QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth ); + ellipse.moveCenter( center ); + + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().color( QPalette::ButtonText ) ); + painter->drawEllipse( ellipse ); + } + + break; + } + case Tick: + { + const double rb = qMax( radius - markerSize, 1.0 ); + const double re = radius; + + const QLineF line( xm - sinA * rb, ym - cosA * rb, + xm - sinA * re, ym - cosA * re ); + + QPen pen( palette().color( QPalette::ButtonText ), 0 ); + pen.setCapStyle( Qt::FlatCap ); + painter->setPen( pen ); + painter->drawLine ( line ); + + break; + } + case Triangle: + { + const double rb = qMax( radius - markerSize, 1.0 ); + const double re = radius; + + painter->translate( rect.center() ); + painter->rotate( angle - 90.0 ); + + QPolygonF polygon; + polygon += QPointF( re, 0.0 ); + polygon += QPointF( rb, 0.5 * ( re - rb ) ); + polygon += QPointF( rb, -0.5 * ( re - rb ) ); + + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().color( QPalette::ButtonText ) ); + painter->drawPolygon( polygon ); + + painter->resetTransform(); + + break; + } + default: + break; + } +} + +/*! + Draw the focus indicator + \param painter Painter +*/ +void QwtKnob::drawFocusIndicator( QPainter *painter ) const +{ + const QRect cr = contentsRect(); + + int w = d_data->knobWidth; + if ( w <= 0 ) + { + w = qMin( cr.width(), cr.height() ); + } + else + { + const int extent = qCeil( scaleDraw()->extent( font() ) ); + w += 2 * ( extent + d_data->scaleDist ); + } + + QRect focusRect( 0, 0, w, w ); + focusRect.moveCenter( cr.center() ); + + QwtPainter::drawFocusRect( painter, this, focusRect ); +} + +/*! + \brief Set the alignment of the knob + + Similar to a QLabel::alignment() the flags decide how + to align the knob inside of contentsRect(). + + The default setting is Qt::AlignCenter + + \param alignment Or'd alignment flags + + \sa alignment(), setKnobWidth(), knobRect() + */ +void QwtKnob::setAlignment( Qt::Alignment alignment ) +{ + if ( d_data->alignment != alignment ) + { + d_data->alignment = alignment; + update(); + } +} + +/*! + \return Alignment of the knob inside of contentsRect() + \sa setAlignment(), knobWidth(), knobRect() + */ +Qt::Alignment QwtKnob::alignment() const +{ + return d_data->alignment; +} + +/*! + \brief Change the knob's width. + + Setting a fixed value for the diameter of the knob + is helpful for aligning several knobs in a row. + + \param width New width + + \sa knobWidth(), setAlignment() + \note Modifies the sizePolicy() +*/ +void QwtKnob::setKnobWidth( int width ) +{ + width = qMax( width, 0 ); + + if ( width != d_data->knobWidth ) + { + QSizePolicy::Policy policy; + if ( width > 0 ) + policy = QSizePolicy::Minimum; + else + policy = QSizePolicy::MinimumExpanding; + + setSizePolicy( policy, policy ); + + d_data->knobWidth = width; + + updateGeometry(); + update(); + } +} + +//! Return the width of the knob +int QwtKnob::knobWidth() const +{ + return d_data->knobWidth; +} + +/*! + \brief Set the knob's border width + \param borderWidth new border width +*/ +void QwtKnob::setBorderWidth( int borderWidth ) +{ + d_data->borderWidth = qMax( borderWidth, 0 ); + + updateGeometry(); + update(); + +} + +//! Return the border width +int QwtKnob::borderWidth() const +{ + return d_data->borderWidth; +} + +/*! + \brief Set the size of the marker + + When setting a size <= 0 the marker will + automatically scaled to 40% of the radius of the knob. + + \sa markerSize(), markerStyle() +*/ +void QwtKnob::setMarkerSize( int size ) +{ + if ( d_data->markerSize != size ) + { + d_data->markerSize = size; + update(); + } +} + +/*! + \return Marker size + \sa setMarkerSize() + */ +int QwtKnob::markerSize() const +{ + return d_data->markerSize; +} + +/*! + \return sizeHint() +*/ +QSize QwtKnob::sizeHint() const +{ + const QSize hint = qwtKnobSizeHint( this, 50 ); + return hint.expandedTo( QApplication::globalStrut() ); +} + +/*! + \return Minimum size hint + \sa sizeHint() +*/ +QSize QwtKnob::minimumSizeHint() const +{ + return qwtKnobSizeHint( this, 20 ); +} diff --git a/qwt/src/qwt_knob.h b/qwt/src/qwt_knob.h new file mode 100644 index 000000000..852374c02 --- /dev/null +++ b/qwt/src/qwt_knob.h @@ -0,0 +1,178 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_KNOB_H +#define QWT_KNOB_H + +#include "qwt_global.h" +#include "qwt_abstract_slider.h" + +class QwtRoundScaleDraw; + +/*! + \brief The Knob Widget + + The QwtKnob widget imitates look and behavior of a volume knob on a radio. + It looks similar to QDial - not to QwtDial. + + The value range of a knob might be divided into several turns. + + The layout of the knob depends on the knobWidth(). + + - width > 0 + The diameter of the knob is fixed and the knob is aligned + according to the alignment() flags inside of the contentsRect(). + + - width <= 0 + The knob is extended to the minimum of width/height of the contentsRect() + and aligned in the other direction according to alignment(). + + Setting a fixed knobWidth() is helpful to align several knobs with different + scale labels. + + \image html knob.png +*/ + +class QWT_EXPORT QwtKnob: public QwtAbstractSlider +{ + Q_OBJECT + + Q_ENUMS ( KnobStyle MarkerStyle ) + + Q_PROPERTY( KnobStyle knobStyle READ knobStyle WRITE setKnobStyle ) + Q_PROPERTY( int knobWidth READ knobWidth WRITE setKnobWidth ) + Q_PROPERTY( Qt::Alignment alignment READ alignment WRITE setAlignment ) + Q_PROPERTY( double totalAngle READ totalAngle WRITE setTotalAngle ) + Q_PROPERTY( int numTurns READ numTurns WRITE setNumTurns ) + Q_PROPERTY( MarkerStyle markerStyle READ markerStyle WRITE setMarkerStyle ) + Q_PROPERTY( int markerSize READ markerSize WRITE setMarkerSize ) + Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth ) + +public: + /*! + \brief Style of the knob surface + + Depending on the KnobStyle the surface of the knob is + filled from the brushes of the widget palette(). + + \sa setKnobStyle(), knobStyle() + */ + enum KnobStyle + { + //! Fill the knob with a brush from QPalette::Button. + Flat, + + //! Build a gradient from QPalette::Midlight and QPalette::Button + Raised, + + /*! + Build a gradient from QPalette::Midlight, QPalette::Button + and QPalette::Midlight + */ + Sunken, + + /*! + Build a radial gradient from QPalette::Button + like it is used for QDial in various Qt styles. + */ + Styled + }; + + /*! + \brief Marker type + + The marker indicates the current value on the knob + The default setting is a Notch marker. + + \sa setMarkerStyle(), setMarkerSize() + */ + enum MarkerStyle + { + //! Don't paint any marker + NoMarker = -1, + + //! Paint a single tick in QPalette::ButtonText color + Tick, + + //! Paint a triangle in QPalette::ButtonText color + Triangle, + + //! Paint a circle in QPalette::ButtonText color + Dot, + + /*! + Draw a raised ellipse with a gradient build from + QPalette::Light and QPalette::Mid + */ + Nub, + + /*! + Draw a sunken ellipse with a gradient build from + QPalette::Light and QPalette::Mid + */ + Notch + }; + + explicit QwtKnob( QWidget* parent = NULL ); + virtual ~QwtKnob(); + + void setAlignment( Qt::Alignment ); + Qt::Alignment alignment() const; + + void setKnobWidth( int ); + int knobWidth() const; + + void setNumTurns( int ); + int numTurns() const; + + void setTotalAngle ( double angle ); + double totalAngle() const; + + void setKnobStyle( KnobStyle ); + KnobStyle knobStyle() const; + + void setBorderWidth( int bw ); + int borderWidth() const; + + void setMarkerStyle( MarkerStyle ); + MarkerStyle markerStyle() const; + + void setMarkerSize( int ); + int markerSize() const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void setScaleDraw( QwtRoundScaleDraw * ); + + const QwtRoundScaleDraw *scaleDraw() const; + QwtRoundScaleDraw *scaleDraw(); + + QRect knobRect() const; + +protected: + virtual void paintEvent( QPaintEvent * ); + virtual void changeEvent( QEvent * ); + + virtual void drawKnob( QPainter *, const QRectF & ) const; + + virtual void drawFocusIndicator( QPainter * ) const; + + virtual void drawMarker( QPainter *, + const QRectF &, double arc ) const; + + virtual double scrolledTo( const QPoint & ) const; + virtual bool isScrollPosition( const QPoint & ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_legend.cpp b/qwt/src/qwt_legend.cpp new file mode 100644 index 000000000..01cef89f1 --- /dev/null +++ b/qwt/src/qwt_legend.cpp @@ -0,0 +1,801 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_legend.h" +#include "qwt_legend_label.h" +#include "qwt_dyngrid_layout.h" +#include "qwt_math.h" +#include "qwt_plot_item.h" +#include "qwt_painter.h" +#include +#include +#include +#include +#include +#include + +class QwtLegendMap +{ +public: + inline bool isEmpty() const { return d_entries.isEmpty(); } + + void insert( const QVariant &, const QList & ); + void remove( const QVariant & ); + + void removeWidget( const QWidget * ); + + QList legendWidgets( const QVariant & ) const; + QVariant itemInfo( const QWidget * ) const; + +private: + // we don't know anything about itemInfo and therefore don't have + // any key that can be used for a map or hashtab. + // But a simple linear list is o.k. here, as we will never have + // more than a few entries. + + class Entry + { + public: + QVariant itemInfo; + QList widgets; + }; + + QList< Entry > d_entries; +}; + +void QwtLegendMap::insert( const QVariant &itemInfo, + const QList &widgets ) +{ + for ( int i = 0; i < d_entries.size(); i++ ) + { + Entry &entry = d_entries[i]; + if ( entry.itemInfo == itemInfo ) + { + entry.widgets = widgets; + return; + } + } + + Entry newEntry; + newEntry.itemInfo = itemInfo; + newEntry.widgets = widgets; + + d_entries += newEntry; +} + +void QwtLegendMap::remove( const QVariant &itemInfo ) +{ + for ( int i = 0; i < d_entries.size(); i++ ) + { + Entry &entry = d_entries[i]; + if ( entry.itemInfo == itemInfo ) + { + d_entries.removeAt( i ); + return; + } + } +} + +void QwtLegendMap::removeWidget( const QWidget *widget ) +{ + QWidget *w = const_cast( widget ); + + for ( int i = 0; i < d_entries.size(); i++ ) + d_entries[ i ].widgets.removeAll( w ); +} + +QVariant QwtLegendMap::itemInfo( const QWidget *widget ) const +{ + if ( widget != NULL ) + { + QWidget *w = const_cast( widget ); + + for ( int i = 0; i < d_entries.size(); i++ ) + { + const Entry &entry = d_entries[i]; + if ( entry.widgets.indexOf( w ) >= 0 ) + return entry.itemInfo; + } + } + + return QVariant(); +} + +QList QwtLegendMap::legendWidgets( const QVariant &itemInfo ) const +{ + if ( itemInfo.isValid() ) + { + for ( int i = 0; i < d_entries.size(); i++ ) + { + const Entry &entry = d_entries[i]; + if ( entry.itemInfo == itemInfo ) + return entry.widgets; + } + } + + return QList(); +} + +class QwtLegend::PrivateData +{ +public: + PrivateData(): + itemMode( QwtLegendData::ReadOnly ), + view( NULL ) + { + } + + QwtLegendData::Mode itemMode; + QwtLegendMap itemMap; + + class LegendView; + LegendView *view; +}; + +class QwtLegend::PrivateData::LegendView: public QScrollArea +{ +public: + LegendView( QWidget *parent ): + QScrollArea( parent ) + { + contentsWidget = new QWidget( this ); + contentsWidget->setObjectName( "QwtLegendViewContents" ); + + setWidget( contentsWidget ); + setWidgetResizable( false ); + + viewport()->setObjectName( "QwtLegendViewport" ); + + // QScrollArea::setWidget internally sets autoFillBackground to true + // But we don't want a background. + contentsWidget->setAutoFillBackground( false ); + viewport()->setAutoFillBackground( false ); + } + + virtual bool event( QEvent *event ) + { + if ( event->type() == QEvent::PolishRequest ) + { + setFocusPolicy( Qt::NoFocus ); + } + + if ( event->type() == QEvent::Resize ) + { + // adjust the size to en/disable the scrollbars + // before QScrollArea adjusts the viewport size + + const QRect cr = contentsRect(); + + int w = cr.width(); + int h = contentsWidget->heightForWidth( cr.width() ); + if ( h > w ) + { + w -= verticalScrollBar()->sizeHint().width(); + h = contentsWidget->heightForWidth( w ); + } + + contentsWidget->resize( w, h ); + } + + return QScrollArea::event( event ); + } + + virtual bool viewportEvent( QEvent *event ) + { + bool ok = QScrollArea::viewportEvent( event ); + + if ( event->type() == QEvent::Resize ) + { + layoutContents(); + } + return ok; + } + + QSize viewportSize( int w, int h ) const + { + const int sbHeight = horizontalScrollBar()->sizeHint().height(); + const int sbWidth = verticalScrollBar()->sizeHint().width(); + + const int cw = contentsRect().width(); + const int ch = contentsRect().height(); + + int vw = cw; + int vh = ch; + + if ( w > vw ) + vh -= sbHeight; + + if ( h > vh ) + { + vw -= sbWidth; + if ( w > vw && vh == ch ) + vh -= sbHeight; + } + return QSize( vw, vh ); + } + + void layoutContents() + { + const QwtDynGridLayout *tl = qobject_cast( + contentsWidget->layout() ); + if ( tl == NULL ) + return; + + const QSize visibleSize = viewport()->contentsRect().size(); + + const int minW = int( tl->maxItemWidth() ) + 2 * tl->margin(); + + int w = qMax( visibleSize.width(), minW ); + int h = qMax( tl->heightForWidth( w ), visibleSize.height() ); + + const int vpWidth = viewportSize( w, h ).width(); + if ( w > vpWidth ) + { + w = qMax( vpWidth, minW ); + h = qMax( tl->heightForWidth( w ), visibleSize.height() ); + } + + contentsWidget->resize( w, h ); + } + + QWidget *contentsWidget; +}; + +/*! + Constructor + \param parent Parent widget +*/ +QwtLegend::QwtLegend( QWidget *parent ): + QwtAbstractLegend( parent ) +{ + setFrameStyle( NoFrame ); + + d_data = new QwtLegend::PrivateData; + + d_data->view = new QwtLegend::PrivateData::LegendView( this ); + d_data->view->setObjectName( "QwtLegendView" ); + d_data->view->setFrameStyle( NoFrame ); + + QwtDynGridLayout *gridLayout = new QwtDynGridLayout( + d_data->view->contentsWidget ); + gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop ); + + d_data->view->contentsWidget->installEventFilter( this ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->addWidget( d_data->view ); +} + +//! Destructor +QwtLegend::~QwtLegend() +{ + delete d_data; +} + +/*! + \brief Set the maximum number of entries in a row + + F.e when the maximum is set to 1 all items are aligned + vertically. 0 means unlimited + + \param numColums Maximum number of entries in a row + + \sa maxColumns(), QwtDynGridLayout::setMaxColumns() + */ +void QwtLegend::setMaxColumns( uint numColums ) +{ + QwtDynGridLayout *tl = qobject_cast( + d_data->view->contentsWidget->layout() ); + if ( tl ) + tl->setMaxColumns( numColums ); +} + +/*! + \return Maximum number of entries in a row + \sa setMaxColumns(), QwtDynGridLayout::maxColumns() + */ +uint QwtLegend::maxColumns() const +{ + uint maxCols = 0; + + const QwtDynGridLayout *tl = qobject_cast( + d_data->view->contentsWidget->layout() ); + if ( tl ) + maxCols = tl->maxColumns(); + + return maxCols; +} + +/*! + \brief Set the default mode for legend labels + + Legend labels will be constructed according to the + attributes in a QwtLegendData object. When it doesn't + contain a value for the QwtLegendData::ModeRole the + label will be initialized with the default mode of the legend. + + \param mode Default item mode + + \sa itemMode(), QwtLegendData::value(), QwtPlotItem::legendData() + \note Changing the mode doesn't have any effect on existing labels. + */ +void QwtLegend::setDefaultItemMode( QwtLegendData::Mode mode ) +{ + d_data->itemMode = mode; +} + +/*! + \return Default item mode + \sa setDefaultItemMode() +*/ +QwtLegendData::Mode QwtLegend::defaultItemMode() const +{ + return d_data->itemMode; +} + +/*! + The contents widget is the only child of the viewport of + the internal QScrollArea and the parent widget of all legend items. + + \return Container widget of the legend items +*/ +QWidget *QwtLegend::contentsWidget() +{ + return d_data->view->contentsWidget; +} + +/*! + \return Horizontal scrollbar + \sa verticalScrollBar() +*/ +QScrollBar *QwtLegend::horizontalScrollBar() const +{ + return d_data->view->horizontalScrollBar(); +} + +/*! + \return Vertical scrollbar + \sa horizontalScrollBar() +*/ +QScrollBar *QwtLegend::verticalScrollBar() const +{ + return d_data->view->verticalScrollBar(); +} + +/*! + The contents widget is the only child of the viewport of + the internal QScrollArea and the parent widget of all legend items. + + \return Container widget of the legend items + +*/ +const QWidget *QwtLegend::contentsWidget() const +{ + return d_data->view->contentsWidget; +} + +/*! + \brief Update the entries for an item + + \param itemInfo Info for an item + \param data List of legend entry attributes for the item + */ +void QwtLegend::updateLegend( const QVariant &itemInfo, + const QList &data ) +{ + QList widgetList = legendWidgets( itemInfo ); + + if ( widgetList.size() != data.size() ) + { + QLayout *contentsLayout = d_data->view->contentsWidget->layout(); + + while ( widgetList.size() > data.size() ) + { + QWidget *w = widgetList.takeLast(); + + contentsLayout->removeWidget( w ); + + // updates might be triggered by signals from the legend widget + // itself. So we better don't delete it here. + + w->hide(); + w->deleteLater(); + } + + for ( int i = widgetList.size(); i < data.size(); i++ ) + { + QWidget *widget = createWidget( data[i] ); + + if ( contentsLayout ) + contentsLayout->addWidget( widget ); + + widgetList += widget; + } + + if ( widgetList.isEmpty() ) + { + d_data->itemMap.remove( itemInfo ); + } + else + { + d_data->itemMap.insert( itemInfo, widgetList ); + } + + updateTabOrder(); + } + + for ( int i = 0; i < data.size(); i++ ) + updateWidget( widgetList[i], data[i] ); +} + +/*! + \brief Create a widget to be inserted into the legend + + The default implementation returns a QwtLegendLabel. + + \param data Attributes of the legend entry + \return Widget representing data on the legend + + \note updateWidget() will called soon after createWidget() + with the same attributes. + */ +QWidget *QwtLegend::createWidget( const QwtLegendData &data ) const +{ + Q_UNUSED( data ); + + QwtLegendLabel *label = new QwtLegendLabel(); + label->setItemMode( defaultItemMode() ); + + connect( label, SIGNAL( clicked() ), SLOT( itemClicked() ) ); + connect( label, SIGNAL( checked( bool ) ), SLOT( itemChecked( bool ) ) ); + + return label; +} + +/*! + \brief Update the widget + + \param widget Usually a QwtLegendLabel + \param data Attributes to be displayed + + \sa createWidget() + \note When widget is no QwtLegendLabel updateWidget() does nothing. + */ +void QwtLegend::updateWidget( QWidget *widget, const QwtLegendData &data ) +{ + QwtLegendLabel *label = qobject_cast( widget ); + if ( label ) + { + label->setData( data ); + if ( !data.value( QwtLegendData::ModeRole ).isValid() ) + { + // use the default mode, when there is no specific + // hint from the legend data + + label->setItemMode( defaultItemMode() ); + } + } +} + +void QwtLegend::updateTabOrder() +{ + QLayout *contentsLayout = d_data->view->contentsWidget->layout(); + if ( contentsLayout ) + { + // set tab focus chain + + QWidget *w = NULL; + + for ( int i = 0; i < contentsLayout->count(); i++ ) + { + QLayoutItem *item = contentsLayout->itemAt( i ); + if ( w && item->widget() ) + QWidget::setTabOrder( w, item->widget() ); + + w = item->widget(); + } + } +} + +//! Return a size hint. +QSize QwtLegend::sizeHint() const +{ + QSize hint = d_data->view->contentsWidget->sizeHint(); + hint += QSize( 2 * frameWidth(), 2 * frameWidth() ); + + return hint; +} + +/*! + \return The preferred height, for a width. + \param width Width +*/ +int QwtLegend::heightForWidth( int width ) const +{ + width -= 2 * frameWidth(); + + int h = d_data->view->contentsWidget->heightForWidth( width ); + if ( h >= 0 ) + h += 2 * frameWidth(); + + return h; +} + + +/*! + Handle QEvent::ChildRemoved andQEvent::LayoutRequest events + for the contentsWidget(). + + \param object Object to be filtered + \param event Event + + \return Forwarded to QwtAbstractLegend::eventFilter() +*/ +bool QwtLegend::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == d_data->view->contentsWidget ) + { + switch ( event->type() ) + { + case QEvent::ChildRemoved: + { + const QChildEvent *ce = + static_cast(event); + if ( ce->child()->isWidgetType() ) + { + QWidget *w = static_cast< QWidget * >( ce->child() ); + d_data->itemMap.removeWidget( w ); + } + break; + } + case QEvent::LayoutRequest: + { + d_data->view->layoutContents(); + + if ( parentWidget() && parentWidget()->layout() == NULL ) + { + /* + We want the parent widget ( usually QwtPlot ) to recalculate + its layout, when the contentsWidget has changed. But + because of the scroll view we have to forward the LayoutRequest + event manually. + + We don't use updateGeometry() because it doesn't post LayoutRequest + events when the legend is hidden. But we want the + parent widget notified, so it can show/hide the legend + depending on its items. + */ + QApplication::postEvent( parentWidget(), + new QEvent( QEvent::LayoutRequest ) ); + } + break; + } + default: + break; + } + } + + return QwtAbstractLegend::eventFilter( object, event ); +} + +/*! + Called internally when the legend has been clicked on. + Emits a clicked() signal. +*/ +void QwtLegend::itemClicked() +{ + QWidget *w = qobject_cast( sender() ); + if ( w ) + { + const QVariant itemInfo = d_data->itemMap.itemInfo( w ); + if ( itemInfo.isValid() ) + { + const QList widgetList = + d_data->itemMap.legendWidgets( itemInfo ); + + const int index = widgetList.indexOf( w ); + if ( index >= 0 ) + Q_EMIT clicked( itemInfo, index ); + } + } +} + +/*! + Called internally when the legend has been checked + Emits a checked() signal. +*/ +void QwtLegend::itemChecked( bool on ) +{ + QWidget *w = qobject_cast( sender() ); + if ( w ) + { + const QVariant itemInfo = d_data->itemMap.itemInfo( w ); + if ( itemInfo.isValid() ) + { + const QList widgetList = + d_data->itemMap.legendWidgets( itemInfo ); + + const int index = widgetList.indexOf( w ); + if ( index >= 0 ) + Q_EMIT checked( itemInfo, on, index ); + } + } +} + +/*! + Render the legend into a given rectangle. + + \param painter Painter + \param rect Bounding rectangle + \param fillBackground When true, fill rect with the widget background + + \sa renderLegend() is used by QwtPlotRenderer - not by QwtLegend itself +*/ +void QwtLegend::renderLegend( QPainter *painter, + const QRectF &rect, bool fillBackground ) const +{ + if ( d_data->itemMap.isEmpty() ) + return; + + if ( fillBackground ) + { + if ( autoFillBackground() || + testAttribute( Qt::WA_StyledBackground ) ) + { + QwtPainter::drawBackgound( painter, rect, this ); + } + } + + const QwtDynGridLayout *legendLayout = + qobject_cast( contentsWidget()->layout() ); + if ( legendLayout == NULL ) + return; + + int left, right, top, bottom; + getContentsMargins( &left, &top, &right, &bottom ); + + QRect layoutRect; + layoutRect.setLeft( qCeil( rect.left() ) + left ); + layoutRect.setTop( qCeil( rect.top() ) + top ); + layoutRect.setRight( qFloor( rect.right() ) - right ); + layoutRect.setBottom( qFloor( rect.bottom() ) - bottom ); + + uint numCols = legendLayout->columnsForWidth( layoutRect.width() ); + QList itemRects = + legendLayout->layoutItems( layoutRect, numCols ); + + int index = 0; + + for ( int i = 0; i < legendLayout->count(); i++ ) + { + QLayoutItem *item = legendLayout->itemAt( i ); + QWidget *w = item->widget(); + if ( w ) + { + painter->save(); + + painter->setClipRect( itemRects[index] ); + renderItem( painter, w, itemRects[index], fillBackground ); + + index++; + painter->restore(); + } + } +} + +/*! + Render a legend entry into a given rectangle. + + \param painter Painter + \param widget Widget representing a legend entry + \param rect Bounding rectangle + \param fillBackground When true, fill rect with the widget background + + \note When widget is not derived from QwtLegendLabel renderItem + does nothing beside the background +*/ +void QwtLegend::renderItem( QPainter *painter, + const QWidget *widget, const QRectF &rect, bool fillBackground ) const +{ + if ( fillBackground ) + { + if ( widget->autoFillBackground() || + widget->testAttribute( Qt::WA_StyledBackground ) ) + { + QwtPainter::drawBackgound( painter, rect, widget ); + } + } + + const QwtLegendLabel *label = qobject_cast( widget ); + if ( label ) + { + // icon + + const QwtGraphic &icon = label->data().icon(); + const QSizeF sz = icon.defaultSize(); + + const QRectF iconRect( rect.x() + label->margin(), + rect.center().y() - 0.5 * sz.height(), + sz.width(), sz.height() ); + + icon.render( painter, iconRect, Qt::KeepAspectRatio ); + + // title + + QRectF titleRect = rect; + titleRect.setX( iconRect.right() + 2 * label->spacing() ); + + painter->setFont( label->font() ); + painter->setPen( label->palette().color( QPalette::Text ) ); + const_cast< QwtLegendLabel *>( label )->drawText( painter, titleRect ); + } +} + +/*! + \return List of widgets associated to a item + \param itemInfo Info about an item + \sa legendWidget(), itemInfo(), QwtPlot::itemToInfo() + */ +QList QwtLegend::legendWidgets( const QVariant &itemInfo ) const +{ + return d_data->itemMap.legendWidgets( itemInfo ); +} + +/*! + \return First widget in the list of widgets associated to an item + \param itemInfo Info about an item + \sa itemInfo(), QwtPlot::itemToInfo() + \note Almost all types of items have only one widget +*/ +QWidget *QwtLegend::legendWidget( const QVariant &itemInfo ) const +{ + const QList list = d_data->itemMap.legendWidgets( itemInfo ); + if ( list.isEmpty() ) + return NULL; + + return list[0]; +} + +/*! + Find the item that is associated to a widget + + \param widget Widget on the legend + \return Associated item info + \sa legendWidget() + */ +QVariant QwtLegend::itemInfo( const QWidget *widget ) const +{ + return d_data->itemMap.itemInfo( widget ); +} + +//! \return True, when no item is inserted +bool QwtLegend::isEmpty() const +{ + return d_data->itemMap.isEmpty(); +} + +/*! + Return the extent, that is needed for the scrollbars + + \param orientation Orientation ( + \return The width of the vertical scrollbar for Qt::Horizontal and v.v. + */ +int QwtLegend::scrollExtent( Qt::Orientation orientation ) const +{ + int extent = 0; + + if ( orientation == Qt::Horizontal ) + extent = verticalScrollBar()->sizeHint().width(); + else + extent = horizontalScrollBar()->sizeHint().height(); + + return extent; +} + diff --git a/qwt/src/qwt_legend.h b/qwt/src/qwt_legend.h new file mode 100644 index 000000000..3d8fca67a --- /dev/null +++ b/qwt/src/qwt_legend.h @@ -0,0 +1,117 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_LEGEND_H +#define QWT_LEGEND_H + +#include "qwt_global.h" +#include "qwt_abstract_legend.h" +#include + +class QScrollBar; + +/*! + \brief The legend widget + + The QwtLegend widget is a tabular arrangement of legend items. Legend + items might be any type of widget, but in general they will be + a QwtLegendLabel. + + \sa QwtLegendLabel, QwtPlotItem, QwtPlot +*/ + +class QWT_EXPORT QwtLegend : public QwtAbstractLegend +{ + Q_OBJECT + +public: + explicit QwtLegend( QWidget *parent = NULL ); + virtual ~QwtLegend(); + + void setMaxColumns( uint numColums ); + uint maxColumns() const; + + void setDefaultItemMode( QwtLegendData::Mode ); + QwtLegendData::Mode defaultItemMode() const; + + QWidget *contentsWidget(); + const QWidget *contentsWidget() const; + + QWidget *legendWidget( const QVariant & ) const; + QList legendWidgets( const QVariant & ) const; + + QVariant itemInfo( const QWidget * ) const; + + virtual bool eventFilter( QObject *, QEvent * ); + + virtual QSize sizeHint() const; + virtual int heightForWidth( int w ) const; + + QScrollBar *horizontalScrollBar() const; + QScrollBar *verticalScrollBar() const; + + virtual void renderLegend( QPainter *, + const QRectF &, bool fillBackground ) const; + + virtual void renderItem( QPainter *, + const QWidget *, const QRectF &, bool fillBackground ) const; + + virtual bool isEmpty() const; + virtual int scrollExtent( Qt::Orientation ) const; + +Q_SIGNALS: + /*! + A signal which is emitted when the user has clicked on + a legend label, which is in QwtLegendData::Clickable mode. + + \param itemInfo Info for the item item of the + selected legend item + \param index Index of the legend label in the list of widgets + that are associated with the plot item + + \note clicks are disabled as default + \sa setDefaultItemMode(), defaultItemMode(), QwtPlot::itemToInfo() + */ + void clicked( const QVariant &itemInfo, int index ); + + /*! + A signal which is emitted when the user has clicked on + a legend label, which is in QwtLegendData::Checkable mode + + \param itemInfo Info for the item of the + selected legend label + \param index Index of the legend label in the list of widgets + that are associated with the plot item + \param on True when the legend label is checked + + \note clicks are disabled as default + \sa setDefaultItemMode(), defaultItemMode(), QwtPlot::itemToInfo() + */ + void checked( const QVariant &itemInfo, bool on, int index ); + +public Q_SLOTS: + virtual void updateLegend( const QVariant &, + const QList & ); + +protected Q_SLOTS: + void itemClicked(); + void itemChecked( bool ); + +protected: + virtual QWidget *createWidget( const QwtLegendData & ) const; + virtual void updateWidget( QWidget *widget, const QwtLegendData &data ); + +private: + void updateTabOrder(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_legend_data.cpp b/qwt/src/qwt_legend_data.cpp new file mode 100644 index 000000000..cf0cb2ce3 --- /dev/null +++ b/qwt/src/qwt_legend_data.cpp @@ -0,0 +1,129 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_legend_data.h" + +//! Constructor +QwtLegendData::QwtLegendData() +{ +} + +//! Destructor +QwtLegendData::~QwtLegendData() +{ +} + +/*! + Set the legend attributes + + QwtLegendData actually is a QMap with some + convenience interfaces + + \param map Values + \sa values() + */ +void QwtLegendData::setValues( const QMap &map ) +{ + d_map = map; +} + +/*! + \return Legend attributes + \sa setValues() + */ +const QMap &QwtLegendData::values() const +{ + return d_map; +} + +/*! + \param role Attribute role + \return True, when the internal map has an entry for role + */ +bool QwtLegendData::hasRole( int role ) const +{ + return d_map.contains( role ); +} + +/*! + Set an attribute value + + \param role Attribute role + \param data Attribute value + + \sa value() + */ +void QwtLegendData::setValue( int role, const QVariant &data ) +{ + d_map[role] = data; +} + +/*! + \param role Attribute role + \return Attribute value for a specific role + */ +QVariant QwtLegendData::value( int role ) const +{ + if ( !d_map.contains( role ) ) + return QVariant(); + + return d_map[role]; +} + +//! \return True, when the internal map is empty +bool QwtLegendData::isValid() const +{ + return !d_map.isEmpty(); +} + +//! \return Value of the TitleRole attribute +QwtText QwtLegendData::title() const +{ + QwtText text; + + const QVariant titleValue = value( QwtLegendData::TitleRole ); + if ( titleValue.canConvert() ) + { + text = qvariant_cast( titleValue ); + } + else if ( titleValue.canConvert() ) + { + text.setText( qvariant_cast( titleValue ) ); + } + + return text; +} + +//! \return Value of the IconRole attribute +QwtGraphic QwtLegendData::icon() const +{ + const QVariant iconValue = value( QwtLegendData::IconRole ); + + QwtGraphic graphic; + if ( iconValue.canConvert() ) + { + graphic = qvariant_cast( iconValue ); + } + + return graphic; +} + +//! \return Value of the ModeRole attribute +QwtLegendData::Mode QwtLegendData::mode() const +{ + const QVariant modeValue = value( QwtLegendData::ModeRole ); + if ( modeValue.canConvert() ) + { + const int mode = qvariant_cast( modeValue ); + return static_cast( mode ); + } + + return QwtLegendData::ReadOnly; +} + diff --git a/qwt/src/qwt_legend_data.h b/qwt/src/qwt_legend_data.h new file mode 100644 index 000000000..d83e13264 --- /dev/null +++ b/qwt/src/qwt_legend_data.h @@ -0,0 +1,87 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_LEGEND_DATA_H +#define QWT_LEGEND_DATA_H + +#include "qwt_global.h" +#include "qwt_text.h" +#include "qwt_graphic.h" +#include +#include +#include + +/*! + \brief Attributes of an entry on a legend + + QwtLegendData is an abstract container ( like QAbstractModel ) + to exchange attributes, that are only known between to + the plot item and the legend. + + By overloading QwtPlotItem::legendData() any other set of attributes + could be used, that can be handled by a modified ( or completely + different ) implementation of a legend. + + \sa QwtLegend, QwtPlotLegendItem + \note The stockchart example implements a legend as a tree + with checkable items + */ +class QWT_EXPORT QwtLegendData +{ +public: + //! Mode defining how a legend entry interacts + enum Mode + { + //! The legend item is not interactive, like a label + ReadOnly, + + //! The legend item is clickable, like a push button + Clickable, + + //! The legend item is checkable, like a checkable button + Checkable + }; + + //! Identifier how to interprete a QVariant + enum Role + { + // The value is a Mode + ModeRole, + + // The value is a title + TitleRole, + + // The value is an icon + IconRole, + + // Values < UserRole are reserved for internal use + UserRole = 32 + }; + + QwtLegendData(); + ~QwtLegendData(); + + void setValues( const QMap & ); + const QMap &values() const; + + void setValue( int role, const QVariant & ); + QVariant value( int role ) const; + + bool hasRole( int role ) const; + bool isValid() const; + + QwtGraphic icon() const; + QwtText title() const; + Mode mode() const; + +private: + QMap d_map; +}; + +#endif diff --git a/qwt/src/qwt_legend_label.cpp b/qwt/src/qwt_legend_label.cpp new file mode 100644 index 000000000..19a7eb957 --- /dev/null +++ b/qwt/src/qwt_legend_label.cpp @@ -0,0 +1,421 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_legend_label.h" +#include "qwt_legend_data.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_symbol.h" +#include "qwt_graphic.h" +#include +#include +#include +#include +#include +#include +#include + +static const int ButtonFrame = 2; +static const int Margin = 2; + +static QSize buttonShift( const QwtLegendLabel *w ) +{ + QStyleOption option; + option.init( w ); + + const int ph = w->style()->pixelMetric( + QStyle::PM_ButtonShiftHorizontal, &option, w ); + const int pv = w->style()->pixelMetric( + QStyle::PM_ButtonShiftVertical, &option, w ); + return QSize( ph, pv ); +} + +class QwtLegendLabel::PrivateData +{ +public: + PrivateData(): + itemMode( QwtLegendData::ReadOnly ), + isDown( false ), + spacing( Margin ) + { + } + + QwtLegendData::Mode itemMode; + QwtLegendData legendData; + bool isDown; + + QPixmap icon; + + int spacing; +}; + +/*! + Set the attributes of the legend label + + \param legendData Attributes of the label + \sa data() + */ +void QwtLegendLabel::setData( const QwtLegendData &legendData ) +{ + d_data->legendData = legendData; + + const bool doUpdate = updatesEnabled(); + setUpdatesEnabled( false ); + + setText( legendData.title() ); + setIcon( legendData.icon().toPixmap() ); + + if ( legendData.hasRole( QwtLegendData::ModeRole ) ) + setItemMode( legendData.mode() ); + + if ( doUpdate ) + { + setUpdatesEnabled( true ); + update(); + } +} + +/*! + \return Attributes of the label + \sa setData(), QwtPlotItem::legendData() + */ +const QwtLegendData &QwtLegendLabel::data() const +{ + return d_data->legendData; +} + +/*! + \param parent Parent widget +*/ +QwtLegendLabel::QwtLegendLabel( QWidget *parent ): + QwtTextLabel( parent ) +{ + d_data = new PrivateData; + setMargin( Margin ); + setIndent( Margin ); +} + +//! Destructor +QwtLegendLabel::~QwtLegendLabel() +{ + delete d_data; + d_data = NULL; +} + +/*! + Set the text to the legend item + + \param text Text label + \sa QwtTextLabel::text() +*/ +void QwtLegendLabel::setText( const QwtText &text ) +{ + const int flags = Qt::AlignLeft | Qt::AlignVCenter + | Qt::TextExpandTabs | Qt::TextWordWrap; + + QwtText txt = text; + txt.setRenderFlags( flags ); + + QwtTextLabel::setText( txt ); +} + +/*! + Set the item mode + The default is QwtLegendData::ReadOnly + + \param mode Item mode + \sa itemMode() +*/ +void QwtLegendLabel::setItemMode( QwtLegendData::Mode mode ) +{ + if ( mode != d_data->itemMode ) + { + d_data->itemMode = mode; + d_data->isDown = false; + + setFocusPolicy( ( mode != QwtLegendData::ReadOnly ) + ? Qt::TabFocus : Qt::NoFocus ); + setMargin( ButtonFrame + Margin ); + + updateGeometry(); + } +} + +/*! + \return Item mode + \sa setItemMode() +*/ +QwtLegendData::Mode QwtLegendLabel::itemMode() const +{ + return d_data->itemMode; +} + +/*! + Assign the icon + + \param icon Pixmap representing a plot item + + \sa icon(), QwtPlotItem::legendIcon() +*/ +void QwtLegendLabel::setIcon( const QPixmap &icon ) +{ + d_data->icon = icon; + + int indent = margin() + d_data->spacing; + if ( icon.width() > 0 ) + indent += icon.width() + d_data->spacing; + + setIndent( indent ); +} + +/*! + \return Pixmap representing a plot item + \sa setIcon() +*/ +QPixmap QwtLegendLabel::icon() const +{ + return d_data->icon; +} + +/*! + \brief Change the spacing between icon and text + + \param spacing Spacing + \sa spacing(), QwtTextLabel::margin() +*/ +void QwtLegendLabel::setSpacing( int spacing ) +{ + spacing = qMax( spacing, 0 ); + if ( spacing != d_data->spacing ) + { + d_data->spacing = spacing; + + int indent = margin() + d_data->spacing; + if ( d_data->icon.width() > 0 ) + indent += d_data->icon.width() + d_data->spacing; + + setIndent( indent ); + } +} + +/*! + \return Spacing between icon and text + \sa setSpacing(), QwtTextLabel::margin() +*/ +int QwtLegendLabel::spacing() const +{ + return d_data->spacing; +} + +/*! + Check/Uncheck a the item + + \param on check/uncheck + \sa setItemMode() +*/ +void QwtLegendLabel::setChecked( bool on ) +{ + if ( d_data->itemMode == QwtLegendData::Checkable ) + { + const bool isBlocked = signalsBlocked(); + blockSignals( true ); + + setDown( on ); + + blockSignals( isBlocked ); + } +} + +//! Return true, if the item is checked +bool QwtLegendLabel::isChecked() const +{ + return d_data->itemMode == QwtLegendData::Checkable && isDown(); +} + +//! Set the item being down +void QwtLegendLabel::setDown( bool down ) +{ + if ( down == d_data->isDown ) + return; + + d_data->isDown = down; + update(); + + if ( d_data->itemMode == QwtLegendData::Clickable ) + { + if ( d_data->isDown ) + Q_EMIT pressed(); + else + { + Q_EMIT released(); + Q_EMIT clicked(); + } + } + + if ( d_data->itemMode == QwtLegendData::Checkable ) + Q_EMIT checked( d_data->isDown ); +} + +//! Return true, if the item is down +bool QwtLegendLabel::isDown() const +{ + return d_data->isDown; +} + +//! Return a size hint +QSize QwtLegendLabel::sizeHint() const +{ + QSize sz = QwtTextLabel::sizeHint(); + sz.setHeight( qMax( sz.height(), d_data->icon.height() + 4 ) ); + + if ( d_data->itemMode != QwtLegendData::ReadOnly ) + { + sz += buttonShift( this ); + sz = sz.expandedTo( QApplication::globalStrut() ); + } + + return sz; +} + +//! Paint event +void QwtLegendLabel::paintEvent( QPaintEvent *e ) +{ + const QRect cr = contentsRect(); + + QPainter painter( this ); + painter.setClipRegion( e->region() ); + + if ( d_data->isDown ) + { + qDrawWinButton( &painter, 0, 0, width(), height(), + palette(), true ); + } + + painter.save(); + + if ( d_data->isDown ) + { + const QSize shiftSize = buttonShift( this ); + painter.translate( shiftSize.width(), shiftSize.height() ); + } + + painter.setClipRect( cr ); + + drawContents( &painter ); + + if ( !d_data->icon.isNull() ) + { + QRect iconRect = cr; + iconRect.setX( iconRect.x() + margin() ); + if ( d_data->itemMode != QwtLegendData::ReadOnly ) + iconRect.setX( iconRect.x() + ButtonFrame ); + + iconRect.setSize( d_data->icon.size() ); + iconRect.moveCenter( QPoint( iconRect.center().x(), cr.center().y() ) ); + + painter.drawPixmap( iconRect, d_data->icon ); + } + + painter.restore(); +} + +//! Handle mouse press events +void QwtLegendLabel::mousePressEvent( QMouseEvent *e ) +{ + if ( e->button() == Qt::LeftButton ) + { + switch ( d_data->itemMode ) + { + case QwtLegendData::Clickable: + { + setDown( true ); + return; + } + case QwtLegendData::Checkable: + { + setDown( !isDown() ); + return; + } + default:; + } + } + QwtTextLabel::mousePressEvent( e ); +} + +//! Handle mouse release events +void QwtLegendLabel::mouseReleaseEvent( QMouseEvent *e ) +{ + if ( e->button() == Qt::LeftButton ) + { + switch ( d_data->itemMode ) + { + case QwtLegendData::Clickable: + { + setDown( false ); + return; + } + case QwtLegendData::Checkable: + { + return; // do nothing, but accept + } + default:; + } + } + QwtTextLabel::mouseReleaseEvent( e ); +} + +//! Handle key press events +void QwtLegendLabel::keyPressEvent( QKeyEvent *e ) +{ + if ( e->key() == Qt::Key_Space ) + { + switch ( d_data->itemMode ) + { + case QwtLegendData::Clickable: + { + if ( !e->isAutoRepeat() ) + setDown( true ); + return; + } + case QwtLegendData::Checkable: + { + if ( !e->isAutoRepeat() ) + setDown( !isDown() ); + return; + } + default:; + } + } + + QwtTextLabel::keyPressEvent( e ); +} + +//! Handle key release events +void QwtLegendLabel::keyReleaseEvent( QKeyEvent *e ) +{ + if ( e->key() == Qt::Key_Space ) + { + switch ( d_data->itemMode ) + { + case QwtLegendData::Clickable: + { + if ( !e->isAutoRepeat() ) + setDown( false ); + return; + } + case QwtLegendData::Checkable: + { + return; // do nothing, but accept + } + default:; + } + } + + QwtTextLabel::keyReleaseEvent( e ); +} diff --git a/qwt/src/qwt_legend_label.h b/qwt/src/qwt_legend_label.h new file mode 100644 index 000000000..f0a1584af --- /dev/null +++ b/qwt/src/qwt_legend_label.h @@ -0,0 +1,80 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_LEGEND_LABEL_H +#define QWT_LEGEND_LABEL_H + +#include "qwt_global.h" +#include "qwt_legend_data.h" +#include "qwt_text.h" +#include "qwt_text_label.h" +#include + +class QwtLegendData; + +/*! + \brief A widget representing something on a QwtLegend. +*/ +class QWT_EXPORT QwtLegendLabel: public QwtTextLabel +{ + Q_OBJECT +public: + explicit QwtLegendLabel( QWidget *parent = 0 ); + virtual ~QwtLegendLabel(); + + void setData( const QwtLegendData & ); + const QwtLegendData &data() const; + + void setItemMode( QwtLegendData::Mode ); + QwtLegendData::Mode itemMode() const; + + void setSpacing( int spacing ); + int spacing() const; + + virtual void setText( const QwtText & ); + + void setIcon( const QPixmap & ); + QPixmap icon() const; + + virtual QSize sizeHint() const; + + bool isChecked() const; + +public Q_SLOTS: + void setChecked( bool on ); + +Q_SIGNALS: + //! Signal, when the legend item has been clicked + void clicked(); + + //! Signal, when the legend item has been pressed + void pressed(); + + //! Signal, when the legend item has been released + void released(); + + //! Signal, when the legend item has been toggled + void checked( bool ); + +protected: + void setDown( bool ); + bool isDown() const; + + virtual void paintEvent( QPaintEvent * ); + virtual void mousePressEvent( QMouseEvent * ); + virtual void mouseReleaseEvent( QMouseEvent * ); + virtual void keyPressEvent( QKeyEvent * ); + virtual void keyReleaseEvent( QKeyEvent * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_magnifier.cpp b/qwt/src/qwt_magnifier.cpp new file mode 100644 index 000000000..55e7bb5eb --- /dev/null +++ b/qwt/src/qwt_magnifier.cpp @@ -0,0 +1,492 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_magnifier.h" +#include "qwt_math.h" +#include +#include + +class QwtMagnifier::PrivateData +{ +public: + PrivateData(): + isEnabled( false ), + wheelFactor( 0.9 ), + wheelModifiers( Qt::NoModifier ), + mouseFactor( 0.95 ), + mouseButton( Qt::RightButton ), + mouseButtonModifiers( Qt::NoModifier ), + keyFactor( 0.9 ), + zoomInKey( Qt::Key_Plus ), + zoomInKeyModifiers( Qt::NoModifier ), + zoomOutKey( Qt::Key_Minus ), + zoomOutKeyModifiers( Qt::NoModifier ), + mousePressed( false ) + { + } + + bool isEnabled; + + double wheelFactor; + Qt::KeyboardModifiers wheelModifiers; + + double mouseFactor; + + Qt::MouseButton mouseButton; + Qt::KeyboardModifiers mouseButtonModifiers; + + double keyFactor; + + int zoomInKey; + Qt::KeyboardModifiers zoomInKeyModifiers; + + int zoomOutKey; + Qt::KeyboardModifiers zoomOutKeyModifiers; + + bool mousePressed; + bool hasMouseTracking; + QPoint mousePos; +}; + +/*! + Constructor + \param parent Widget to be magnified +*/ +QwtMagnifier::QwtMagnifier( QWidget *parent ): + QObject( parent ) +{ + d_data = new PrivateData(); + setEnabled( true ); +} + +//! Destructor +QwtMagnifier::~QwtMagnifier() +{ + delete d_data; +} + +/*! + \brief En/disable the magnifier + + When enabled is true an event filter is installed for + the observed widget, otherwise the event filter is removed. + + \param on true or false + \sa isEnabled(), eventFilter() +*/ +void QwtMagnifier::setEnabled( bool on ) +{ + if ( d_data->isEnabled != on ) + { + d_data->isEnabled = on; + + QObject *o = parent(); + if ( o ) + { + if ( d_data->isEnabled ) + o->installEventFilter( this ); + else + o->removeEventFilter( this ); + } + } +} + +/*! + \return true when enabled, false otherwise + \sa setEnabled(), eventFilter() +*/ +bool QwtMagnifier::isEnabled() const +{ + return d_data->isEnabled; +} + +/*! + \brief Change the wheel factor + + The wheel factor defines the ratio between the current range + on the parent widget and the zoomed range for each step of the wheel. + + Use values > 1 for magnification (i.e. 2.0) and values < 1 for + scaling down (i.e. 1/2.0 = 0.5). You can use this feature for + inverting the direction of the wheel. + + The default value is 0.9. + + \param factor Wheel factor + \sa wheelFactor(), setWheelButtonState(), + setMouseFactor(), setKeyFactor() +*/ +void QwtMagnifier::setWheelFactor( double factor ) +{ + d_data->wheelFactor = factor; +} + +/*! + \return Wheel factor + \sa setWheelFactor() +*/ +double QwtMagnifier::wheelFactor() const +{ + return d_data->wheelFactor; +} + +/*! + Assign keyboard modifiers for zooming in/out using the wheel. + The default modifiers are Qt::NoModifiers. + + \param modifiers Keyboard modifiers + \sa wheelModifiers() +*/ +void QwtMagnifier::setWheelModifiers( Qt::KeyboardModifiers modifiers ) +{ + d_data->wheelModifiers = modifiers; +} + +/*! + \return Wheel modifiers + \sa setWheelModifiers() +*/ +Qt::KeyboardModifiers QwtMagnifier::wheelModifiers() const +{ + return d_data->wheelModifiers; +} + +/*! + \brief Change the mouse factor + + The mouse factor defines the ratio between the current range + on the parent widget and the zoomed range for each vertical mouse movement. + The default value is 0.95. + + \param factor Wheel factor + \sa mouseFactor(), setMouseButton(), setWheelFactor(), setKeyFactor() +*/ +void QwtMagnifier::setMouseFactor( double factor ) +{ + d_data->mouseFactor = factor; +} + +/*! + \return Mouse factor + \sa setMouseFactor() +*/ +double QwtMagnifier::mouseFactor() const +{ + return d_data->mouseFactor; +} + +/*! + Assign the mouse button, that is used for zooming in/out. + The default value is Qt::RightButton. + + \param button Button + \param modifiers Keyboard modifiers + + \sa getMouseButton() +*/ +void QwtMagnifier::setMouseButton( + Qt::MouseButton button, Qt::KeyboardModifiers modifiers ) +{ + d_data->mouseButton = button; + d_data->mouseButtonModifiers = modifiers; +} + +//! \sa setMouseButton() +void QwtMagnifier::getMouseButton( + Qt::MouseButton &button, Qt::KeyboardModifiers &modifiers ) const +{ + button = d_data->mouseButton; + modifiers = d_data->mouseButtonModifiers; +} + +/*! + \brief Change the key factor + + The key factor defines the ratio between the current range + on the parent widget and the zoomed range for each key press of + the zoom in/out keys. The default value is 0.9. + + \param factor Key factor + \sa keyFactor(), setZoomInKey(), setZoomOutKey(), + setWheelFactor, setMouseFactor() +*/ +void QwtMagnifier::setKeyFactor( double factor ) +{ + d_data->keyFactor = factor; +} + +/*! + \return Key factor + \sa setKeyFactor() +*/ +double QwtMagnifier::keyFactor() const +{ + return d_data->keyFactor; +} + +/*! + Assign the key, that is used for zooming in. + The default combination is Qt::Key_Plus + Qt::NoModifier. + + \param key + \param modifiers + \sa getZoomInKey(), setZoomOutKey() +*/ +void QwtMagnifier::setZoomInKey( int key, + Qt::KeyboardModifiers modifiers ) +{ + d_data->zoomInKey = key; + d_data->zoomInKeyModifiers = modifiers; +} + +/*! + \brief Retrieve the settings of the zoom in key + + \param key Key code, see Qt::Key + \param modifiers Keyboard modifiers + + \sa setZoomInKey() +*/ +void QwtMagnifier::getZoomInKey( int &key, + Qt::KeyboardModifiers &modifiers ) const +{ + key = d_data->zoomInKey; + modifiers = d_data->zoomInKeyModifiers; +} + +/*! + Assign the key, that is used for zooming out. + The default combination is Qt::Key_Minus + Qt::NoModifier. + + \param key + \param modifiers + \sa getZoomOutKey(), setZoomOutKey() +*/ +void QwtMagnifier::setZoomOutKey( int key, + Qt::KeyboardModifiers modifiers ) +{ + d_data->zoomOutKey = key; + d_data->zoomOutKeyModifiers = modifiers; +} + +/*! + \brief Retrieve the settings of the zoom out key + + \param key Key code, see Qt::Key + \param modifiers Keyboard modifiers + + \sa setZoomOutKey() +*/ +void QwtMagnifier::getZoomOutKey( int &key, + Qt::KeyboardModifiers &modifiers ) const +{ + key = d_data->zoomOutKey; + modifiers = d_data->zoomOutKeyModifiers; +} + +/*! + \brief Event filter + + When isEnabled() is true, the mouse events of the + observed widget are filtered. + + \param object Object to be filtered + \param event Event + + \return Forwarded to QObject::eventFilter() + + \sa widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyPressEvent() + widgetKeyReleaseEvent() +*/ +bool QwtMagnifier::eventFilter( QObject *object, QEvent *event ) +{ + if ( object && object == parent() ) + { + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + widgetMousePressEvent( static_cast( event ) ); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent( static_cast( event ) ); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent( static_cast( event ) ); + break; + } + case QEvent::Wheel: + { + widgetWheelEvent( static_cast( event ) ); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent( static_cast( event ) ); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent( static_cast( event ) ); + break; + } + default:; + } + } + return QObject::eventFilter( object, event ); +} + +/*! + Handle a mouse press event for the observed widget. + + \param mouseEvent Mouse event + \sa eventFilter(), widgetMouseReleaseEvent(), widgetMouseMoveEvent() +*/ +void QwtMagnifier::widgetMousePressEvent( QMouseEvent *mouseEvent ) +{ + if ( parentWidget() == NULL ) + return; + + if ( ( mouseEvent->button() != d_data->mouseButton ) || + ( mouseEvent->modifiers() != d_data->mouseButtonModifiers ) ) + { + return; + } + + d_data->hasMouseTracking = parentWidget()->hasMouseTracking(); + + parentWidget()->setMouseTracking( true ); + d_data->mousePos = mouseEvent->pos(); + d_data->mousePressed = true; +} + +/*! + Handle a mouse release event for the observed widget. + + \param mouseEvent Mouse event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseMoveEvent(), +*/ +void QwtMagnifier::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) +{ + Q_UNUSED( mouseEvent ); + + if ( d_data->mousePressed && parentWidget() ) + { + d_data->mousePressed = false; + parentWidget()->setMouseTracking( d_data->hasMouseTracking ); + } +} + +/*! + Handle a mouse move event for the observed widget. + + \param mouseEvent Mouse event + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), +*/ +void QwtMagnifier::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) +{ + if ( !d_data->mousePressed ) + return; + + const int dy = mouseEvent->pos().y() - d_data->mousePos.y(); + if ( dy != 0 ) + { + double f = d_data->mouseFactor; + if ( dy < 0 ) + f = 1 / f; + + rescale( f ); + } + + d_data->mousePos = mouseEvent->pos(); +} + +/*! + Handle a wheel event for the observed widget. + + \param wheelEvent Wheel event + \sa eventFilter() +*/ +void QwtMagnifier::widgetWheelEvent( QWheelEvent *wheelEvent ) +{ + if ( wheelEvent->modifiers() != d_data->wheelModifiers ) + { + return; + } + + if ( d_data->wheelFactor != 0.0 ) + { + /* + A positive delta indicates that the wheel was + rotated forwards away from the user; a negative + value indicates that the wheel was rotated + backwards toward the user. + Most mouse types work in steps of 15 degrees, + in which case the delta value is a multiple + of 120 (== 15 * 8). + */ + double f = qPow( d_data->wheelFactor, + qAbs( wheelEvent->delta() / 120.0 ) ); + + if ( wheelEvent->delta() > 0 ) + f = 1 / f; + + rescale( f ); + } +} + +/*! + Handle a key press event for the observed widget. + + \param keyEvent Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtMagnifier::widgetKeyPressEvent( QKeyEvent *keyEvent ) +{ + if ( keyEvent->key() == d_data->zoomInKey && + keyEvent->modifiers() == d_data->zoomInKeyModifiers ) + { + rescale( d_data->keyFactor ); + } + else if ( keyEvent->key() == d_data->zoomOutKey && + keyEvent->modifiers() == d_data->zoomOutKeyModifiers ) + { + rescale( 1.0 / d_data->keyFactor ); + } +} + +/*! + Handle a key release event for the observed widget. + + \param keyEvent Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtMagnifier::widgetKeyReleaseEvent( QKeyEvent *keyEvent ) +{ + Q_UNUSED( keyEvent ); +} + +//! \return Parent widget, where the rescaling happens +QWidget *QwtMagnifier::parentWidget() +{ + return qobject_cast( parent() ); +} + +//! \return Parent widget, where the rescaling happens +const QWidget *QwtMagnifier::parentWidget() const +{ + return qobject_cast( parent() ); +} + diff --git a/qwt/src/qwt_magnifier.h b/qwt/src/qwt_magnifier.h new file mode 100644 index 000000000..48e8ed8c4 --- /dev/null +++ b/qwt/src/qwt_magnifier.h @@ -0,0 +1,86 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_MAGNIFIER_H +#define QWT_MAGNIFIER_H 1 + +#include "qwt_global.h" +#include + +class QWidget; +class QMouseEvent; +class QWheelEvent; +class QKeyEvent; + +/*! + \brief QwtMagnifier provides zooming, by magnifying in steps. + + Using QwtMagnifier a plot can be zoomed in/out in steps using + keys, the mouse wheel or moving a mouse button in vertical direction. +*/ +class QWT_EXPORT QwtMagnifier: public QObject +{ + Q_OBJECT + +public: + explicit QwtMagnifier( QWidget * ); + virtual ~QwtMagnifier(); + + QWidget *parentWidget(); + const QWidget *parentWidget() const; + + void setEnabled( bool ); + bool isEnabled() const; + + // mouse + void setMouseFactor( double ); + double mouseFactor() const; + + void setMouseButton( Qt::MouseButton, Qt::KeyboardModifiers = Qt::NoModifier ); + void getMouseButton( Qt::MouseButton &, Qt::KeyboardModifiers & ) const; + + // mouse wheel + void setWheelFactor( double ); + double wheelFactor() const; + + void setWheelModifiers( Qt::KeyboardModifiers ); + Qt::KeyboardModifiers wheelModifiers() const; + + // keyboard + void setKeyFactor( double ); + double keyFactor() const; + + void setZoomInKey( int key, Qt::KeyboardModifiers = Qt::NoModifier ); + void getZoomInKey( int &key, Qt::KeyboardModifiers & ) const; + + void setZoomOutKey( int key, Qt::KeyboardModifiers = Qt::NoModifier ); + void getZoomOutKey( int &key, Qt::KeyboardModifiers & ) const; + + virtual bool eventFilter( QObject *, QEvent * ); + +protected: + /*! + Rescale the parent widget + \param factor Scale factor + */ + virtual void rescale( double factor ) = 0; + + virtual void widgetMousePressEvent( QMouseEvent * ); + virtual void widgetMouseReleaseEvent( QMouseEvent * ); + virtual void widgetMouseMoveEvent( QMouseEvent * ); + virtual void widgetWheelEvent( QWheelEvent * ); + virtual void widgetKeyPressEvent( QKeyEvent * ); + virtual void widgetKeyReleaseEvent( QKeyEvent * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_math.cpp b/qwt/src/qwt_math.cpp new file mode 100644 index 000000000..9e898c105 --- /dev/null +++ b/qwt/src/qwt_math.cpp @@ -0,0 +1,74 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_math.h" + +/*! + \brief Find the smallest value in an array + \param array Pointer to an array + \param size Array size +*/ +double qwtGetMin( const double *array, int size ) +{ + if ( size <= 0 ) + return 0.0; + + double rv = array[0]; + for ( int i = 1; i < size; i++ ) + rv = qMin( rv, array[i] ); + + return rv; +} + + +/*! + \brief Find the largest value in an array + \param array Pointer to an array + \param size Array size +*/ +double qwtGetMax( const double *array, int size ) +{ + if ( size <= 0 ) + return 0.0; + + double rv = array[0]; + for ( int i = 1; i < size; i++ ) + rv = qMax( rv, array[i] ); + + return rv; +} + +/*! + \brief Normalize an angle to be int the range [0.0, 2 * PI[ + \param radians Angle in radians + \return Normalized angle in radians +*/ +double qwtNormalizeRadians( double radians ) +{ + double a = ::fmod( radians, 2.0 * M_PI ); + if ( a < 0.0 ) + a += 2.0 * M_PI; + + return a; + +} + +/*! + \brief Normalize an angle to be int the range [0.0, 360.0[ + \param radians Angle in degrees + \return Normalized angle in degrees +*/ +double qwtNormalizeDegrees( double degrees ) +{ + double a = ::fmod( degrees, 360.0 ); + if ( a < 0.0 ) + a += 360.0; + + return a; +} diff --git a/qwt/src/qwt_math.h b/qwt/src/qwt_math.h new file mode 100644 index 000000000..23ad20560 --- /dev/null +++ b/qwt/src/qwt_math.h @@ -0,0 +1,149 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_MATH_H +#define QWT_MATH_H + +#include "qwt_global.h" + +#if defined(_MSC_VER) +/* + Microsoft says: + + Define _USE_MATH_DEFINES before including math.h to expose these macro + definitions for common math constants. These are placed under an #ifdef + since these commonly-defined names are not part of the C/C++ standards. +*/ +#define _USE_MATH_DEFINES 1 +#endif + +#include +#include "qwt_global.h" + +#ifndef M_PI_2 +// For Qt <= 4.8.4 M_PI_2 is not known by MinGW-w64 +// when compiling with -std=c++11 +#define M_PI_2 (1.57079632679489661923) +#endif + +#ifndef LOG_MIN +//! Minimum value for logarithmic scales +#define LOG_MIN 1.0e-100 +#endif + +#ifndef LOG_MAX +//! Maximum value for logarithmic scales +#define LOG_MAX 1.0e100 +#endif + +QWT_EXPORT double qwtGetMin( const double *array, int size ); +QWT_EXPORT double qwtGetMax( const double *array, int size ); + +QWT_EXPORT double qwtNormalizeRadians( double radians ); +QWT_EXPORT double qwtNormalizeDegrees( double degrees ); + +/*! + \brief Compare 2 values, relative to an interval + + Values are "equal", when : + \f$\cdot value2 - value1 <= abs(intervalSize * 10e^{-6})\f$ + + \param value1 First value to compare + \param value2 Second value to compare + \param intervalSize interval size + + \return 0: if equal, -1: if value2 > value1, 1: if value1 > value2 +*/ +inline int qwtFuzzyCompare( double value1, double value2, double intervalSize ) +{ + const double eps = qAbs( 1.0e-6 * intervalSize ); + + if ( value2 - value1 > eps ) + return -1; + + if ( value1 - value2 > eps ) + return 1; + + return 0; +} + + +inline bool qwtFuzzyGreaterOrEqual( double d1, double d2 ) +{ + return ( d1 >= d2 ) || qFuzzyCompare( d1, d2 ); +} + +inline bool qwtFuzzyLessOrEqual( double d1, double d2 ) +{ + return ( d1 <= d2 ) || qFuzzyCompare( d1, d2 ); +} + +//! Return the sign +inline int qwtSign( double x ) +{ + if ( x > 0.0 ) + return 1; + else if ( x < 0.0 ) + return ( -1 ); + else + return 0; +} + +//! Return the square of a number +inline double qwtSqr( double x ) +{ + return x * x; +} + +//! Approximation of arc tangent ( error below 0,005 radians ) +inline double qwtFastAtan( double x ) +{ + if ( x < -1.0 ) + return -M_PI_2 - x / ( x * x + 0.28 ); + + if ( x > 1.0 ) + return M_PI_2 - x / ( x * x + 0.28 ); + + return x / ( 1.0 + x * x * 0.28 ); +} + +//! Approximation of arc tangent ( error below 0,005 radians ) +inline double qwtFastAtan2( double y, double x ) +{ + if ( x > 0 ) + return qwtFastAtan( y / x ); + + if ( x < 0 ) + { + const double d = qwtFastAtan( y / x ); + return ( y >= 0 ) ? d + M_PI : d - M_PI; + } + + if ( y < 0.0 ) + return -M_PI_2; + + if ( y > 0.0 ) + return M_PI_2; + + return 0.0; +} + +// Translate degrees into radians +inline double qwtRadians( double degrees ) +{ + return degrees * M_PI / 180.0; +} + +// Translate radians into degrees +inline double qwtDegrees( double degrees ) +{ + return degrees * 180.0 / M_PI; +} + +#endif diff --git a/qwt/src/qwt_matrix_raster_data.cpp b/qwt/src/qwt_matrix_raster_data.cpp new file mode 100644 index 000000000..69355adb3 --- /dev/null +++ b/qwt/src/qwt_matrix_raster_data.cpp @@ -0,0 +1,298 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_matrix_raster_data.h" +#include +#include + +class QwtMatrixRasterData::PrivateData +{ +public: + PrivateData(): + resampleMode(QwtMatrixRasterData::NearestNeighbour), + numColumns(0) + { + } + + inline double value(int row, int col) const + { + return values.data()[ row * numColumns + col ]; + } + + QwtMatrixRasterData::ResampleMode resampleMode; + + QVector values; + int numColumns; + int numRows; + + double dx; + double dy; +}; + +//! Constructor +QwtMatrixRasterData::QwtMatrixRasterData() +{ + d_data = new PrivateData(); + update(); +} + +//! Destructor +QwtMatrixRasterData::~QwtMatrixRasterData() +{ + delete d_data; +} + +/*! + \brief Set the resampling algorithm + + \param mode Resampling mode + \sa resampleMode(), value() +*/ +void QwtMatrixRasterData::setResampleMode( ResampleMode mode ) +{ + d_data->resampleMode = mode; +} + +/*! + \return resampling algorithm + \sa setResampleMode(), value() +*/ +QwtMatrixRasterData::ResampleMode QwtMatrixRasterData::resampleMode() const +{ + return d_data->resampleMode; +} + +/*! + \brief Assign the bounding interval for an axis + + Setting the bounding intervals for the X/Y axis is mandatory + to define the positions for the values of the value matrix. + The interval in Z direction defines the possible range for + the values in the matrix, what is f.e used by QwtPlotSpectrogram + to map values to colors. The Z-interval might be the bounding + interval of the values in the matrix, but usually it isn't. + ( f.e a interval of 0.0-100.0 for values in percentage ) + + \param axis X, Y or Z axis + \param interval Interval + + \sa QwtRasterData::interval(), setValueMatrix() +*/ +void QwtMatrixRasterData::setInterval( + Qt::Axis axis, const QwtInterval &interval ) +{ + QwtRasterData::setInterval( axis, interval ); + update(); +} + +/*! + \brief Assign a value matrix + + The positions of the values are calculated by dividing + the bounding rectangle of the X/Y intervals into equidistant + rectangles ( pixels ). Each value corresponds to the center of + a pixel. + + \param values Vector of values + \param numColumns Number of columns + + \sa valueMatrix(), numColumns(), numRows(), setInterval()() +*/ +void QwtMatrixRasterData::setValueMatrix( + const QVector &values, int numColumns ) +{ + d_data->values = values; + d_data->numColumns = qMax( numColumns, 0 ); + update(); +} + +/*! + \return Value matrix + \sa setValueMatrix(), numColumns(), numRows(), setInterval() +*/ +const QVector QwtMatrixRasterData::valueMatrix() const +{ + return d_data->values; +} + +/*! + \brief Change a single value in the matrix + + \param row Row index + \param col Column index + \param value New value + + \sa value(), setValueMatrix() +*/ +void QwtMatrixRasterData::setValue( int row, int col, double value ) +{ + if ( row >= 0 && row < d_data->numRows && + col >= 0 && col < d_data->numColumns ) + { + const int index = row * d_data->numColumns + col; + d_data->values.data()[ index ] = value; + } +} + +/*! + \return Number of columns of the value matrix + \sa valueMatrix(), numRows(), setValueMatrix() +*/ +int QwtMatrixRasterData::numColumns() const +{ + return d_data->numColumns; +} + +/*! + \return Number of rows of the value matrix + \sa valueMatrix(), numColumns(), setValueMatrix() +*/ +int QwtMatrixRasterData::numRows() const +{ + return d_data->numRows; +} + +/*! + \brief Calculate the pixel hint + + pixelHint() returns the geometry of a pixel, that can be used + to calculate the resolution and alignment of the plot item, that is + representing the data. + + - NearestNeighbour\n + pixelHint() returns the surrounding pixel of the top left value + in the matrix. + + - BilinearInterpolation\n + Returns an empty rectangle recommending + to render in target device ( f.e. screen ) resolution. + + \param area Requested area, ignored + \return Calculated hint + + \sa ResampleMode, setMatrix(), setInterval() +*/ +QRectF QwtMatrixRasterData::pixelHint( const QRectF &area ) const +{ + Q_UNUSED( area ) + + QRectF rect; + if ( d_data->resampleMode == NearestNeighbour ) + { + const QwtInterval intervalX = interval( Qt::XAxis ); + const QwtInterval intervalY = interval( Qt::YAxis ); + if ( intervalX.isValid() && intervalY.isValid() ) + { + rect = QRectF( intervalX.minValue(), intervalY.minValue(), + d_data->dx, d_data->dy ); + } + } + + return rect; +} + +/*! + \return the value at a raster position + + \param x X value in plot coordinates + \param y Y value in plot coordinates + + \sa ResampleMode +*/ +double QwtMatrixRasterData::value( double x, double y ) const +{ + const QwtInterval xInterval = interval( Qt::XAxis ); + const QwtInterval yInterval = interval( Qt::YAxis ); + + if ( !( xInterval.contains(x) && yInterval.contains(y) ) ) + return qQNaN(); + + double value; + + switch( d_data->resampleMode ) + { + case BilinearInterpolation: + { + int col1 = qRound( (x - xInterval.minValue() ) / d_data->dx ) - 1; + int row1 = qRound( (y - yInterval.minValue() ) / d_data->dy ) - 1; + int col2 = col1 + 1; + int row2 = row1 + 1; + + if ( col1 < 0 ) + col1 = col2; + else if ( col2 >= static_cast( d_data->numColumns ) ) + col2 = col1; + + if ( row1 < 0 ) + row1 = row2; + else if ( row2 >= static_cast( d_data->numRows ) ) + row2 = row1; + + const double v11 = d_data->value( row1, col1 ); + const double v21 = d_data->value( row1, col2 ); + const double v12 = d_data->value( row2, col1 ); + const double v22 = d_data->value( row2, col2 ); + + const double x2 = xInterval.minValue() + + ( col2 + 0.5 ) * d_data->dx; + const double y2 = yInterval.minValue() + + ( row2 + 0.5 ) * d_data->dy; + + const double rx = ( x2 - x ) / d_data->dx; + const double ry = ( y2 - y ) / d_data->dy; + + const double vr1 = rx * v11 + ( 1.0 - rx ) * v21; + const double vr2 = rx * v12 + ( 1.0 - rx ) * v22; + + value = ry * vr1 + ( 1.0 - ry ) * vr2; + + break; + } + case NearestNeighbour: + default: + { + int row = int( (y - yInterval.minValue() ) / d_data->dy ); + int col = int( (x - xInterval.minValue() ) / d_data->dx ); + + // In case of intervals, where the maximum is included + // we get out of bound for row/col, when the value for the + // maximum is requested. Instead we return the value + // from the last row/col + + if ( row >= d_data->numRows ) + row = d_data->numRows - 1; + + if ( col >= d_data->numColumns ) + col = d_data->numColumns - 1; + + value = d_data->value( row, col ); + } + } + + return value; +} + +void QwtMatrixRasterData::update() +{ + d_data->numRows = 0; + d_data->dx = 0.0; + d_data->dy = 0.0; + + if ( d_data->numColumns > 0 ) + { + d_data->numRows = d_data->values.size() / d_data->numColumns; + + const QwtInterval xInterval = interval( Qt::XAxis ); + const QwtInterval yInterval = interval( Qt::YAxis ); + if ( xInterval.isValid() ) + d_data->dx = xInterval.width() / d_data->numColumns; + if ( yInterval.isValid() ) + d_data->dy = yInterval.width() / d_data->numRows; + } +} diff --git a/qwt/src/qwt_matrix_raster_data.h b/qwt/src/qwt_matrix_raster_data.h new file mode 100644 index 000000000..0b107c9fd --- /dev/null +++ b/qwt/src/qwt_matrix_raster_data.h @@ -0,0 +1,74 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_MATRIX_RASTER_DATA_H +#define QWT_MATRIX_RASTER_DATA_H 1 + +#include "qwt_global.h" +#include "qwt_raster_data.h" +#include + +/*! + \brief A class representing a matrix of values as raster data + + QwtMatrixRasterData implements an interface for a matrix of + equidistant values, that can be used by a QwtPlotRasterItem. + It implements a couple of resampling algorithms, to provide + values for positions, that or not on the value matrix. +*/ +class QWT_EXPORT QwtMatrixRasterData: public QwtRasterData +{ +public: + /*! + \brief Resampling algorithm + The default setting is NearestNeighbour; + */ + enum ResampleMode + { + /*! + Return the value from the matrix, that is nearest to the + the requested position. + */ + NearestNeighbour, + + /*! + Interpolate the value from the distances and values of the + 4 surrounding values in the matrix, + */ + BilinearInterpolation + }; + + QwtMatrixRasterData(); + virtual ~QwtMatrixRasterData(); + + void setResampleMode(ResampleMode mode); + ResampleMode resampleMode() const; + + virtual void setInterval( Qt::Axis, const QwtInterval & ); + + void setValueMatrix( const QVector &values, int numColumns ); + const QVector valueMatrix() const; + + void setValue( int row, int col, double value ); + + int numColumns() const; + int numRows() const; + + virtual QRectF pixelHint( const QRectF & ) const; + + virtual double value( double x, double y ) const; + +private: + void update(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_null_paintdevice.cpp b/qwt/src/qwt_null_paintdevice.cpp new file mode 100644 index 000000000..db1611da2 --- /dev/null +++ b/qwt/src/qwt_null_paintdevice.cpp @@ -0,0 +1,593 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_null_paintdevice.h" +#include +#include + +class QwtNullPaintDevice::PrivateData +{ +public: + PrivateData(): + mode( QwtNullPaintDevice::NormalMode ) + { + } + + QwtNullPaintDevice::Mode mode; +}; + +class QwtNullPaintDevice::PaintEngine: public QPaintEngine +{ +public: + PaintEngine(); + + virtual bool begin( QPaintDevice * ); + virtual bool end(); + + virtual Type type () const; + virtual void updateState(const QPaintEngineState &); + + virtual void drawRects(const QRect *, int ); + virtual void drawRects(const QRectF *, int ); + + virtual void drawLines(const QLine *, int ); + virtual void drawLines(const QLineF *, int ); + + virtual void drawEllipse(const QRectF &); + virtual void drawEllipse(const QRect &); + + virtual void drawPath(const QPainterPath &); + + virtual void drawPoints(const QPointF *, int ); + virtual void drawPoints(const QPoint *, int ); + + virtual void drawPolygon(const QPointF *, int , PolygonDrawMode ); + virtual void drawPolygon(const QPoint *, int , PolygonDrawMode ); + + virtual void drawPixmap(const QRectF &, + const QPixmap &, const QRectF &); + + virtual void drawTextItem(const QPointF &, const QTextItem &); + + virtual void drawTiledPixmap(const QRectF &, + const QPixmap &, const QPointF &s); + + virtual void drawImage(const QRectF &, + const QImage &, const QRectF &, Qt::ImageConversionFlags ); + +private: + QwtNullPaintDevice *nullDevice(); +}; + +QwtNullPaintDevice::PaintEngine::PaintEngine(): + QPaintEngine( QPaintEngine::AllFeatures ) +{ +} + +bool QwtNullPaintDevice::PaintEngine::begin( QPaintDevice * ) +{ + setActive( true ); + return true; +} + +bool QwtNullPaintDevice::PaintEngine::end() +{ + setActive( false ); + return true; +} + +QPaintEngine::Type QwtNullPaintDevice::PaintEngine::type() const +{ + return QPaintEngine::User; +} + +void QwtNullPaintDevice::PaintEngine::drawRects( + const QRect *rects, int rectCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawRects( rects, rectCount ); + return; + } + + device->drawRects( rects, rectCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawRects( + const QRectF *rects, int rectCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawRects( rects, rectCount ); + return; + } + + device->drawRects( rects, rectCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawLines( + const QLine *lines, int lineCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawLines( lines, lineCount ); + return; + } + + device->drawLines( lines, lineCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawLines( + const QLineF *lines, int lineCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawLines( lines, lineCount ); + return; + } + + device->drawLines( lines, lineCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawEllipse( + const QRectF &rect) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawEllipse( rect ); + return; + } + + device->drawEllipse( rect ); +} + +void QwtNullPaintDevice::PaintEngine::drawEllipse( + const QRect &rect) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawEllipse( rect ); + return; + } + + device->drawEllipse( rect ); +} + + +void QwtNullPaintDevice::PaintEngine::drawPath( + const QPainterPath &path) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + device->drawPath( path ); +} + +void QwtNullPaintDevice::PaintEngine::drawPoints( + const QPointF *points, int pointCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawPoints( points, pointCount ); + return; + } + + device->drawPoints( points, pointCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawPoints( + const QPoint *points, int pointCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawPoints( points, pointCount ); + return; + } + + device->drawPoints( points, pointCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawPolygon( + const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() == QwtNullPaintDevice::PathMode ) + { + QPainterPath path; + + if ( pointCount > 0 ) + { + path.moveTo( points[0] ); + for ( int i = 1; i < pointCount; i++ ) + path.lineTo( points[i] ); + + if ( mode != PolylineMode ) + path.closeSubpath(); + } + + device->drawPath( path ); + return; + } + + device->drawPolygon( points, pointCount, mode ); +} + +void QwtNullPaintDevice::PaintEngine::drawPolygon( + const QPoint *points, int pointCount, PolygonDrawMode mode) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() == QwtNullPaintDevice::PathMode ) + { + QPainterPath path; + + if ( pointCount > 0 ) + { + path.moveTo( points[0] ); + for ( int i = 1; i < pointCount; i++ ) + path.lineTo( points[i] ); + + if ( mode != PolylineMode ) + path.closeSubpath(); + } + + device->drawPath( path ); + return; + } + + device->drawPolygon( points, pointCount, mode ); +} + +void QwtNullPaintDevice::PaintEngine::drawPixmap( + const QRectF &rect, const QPixmap &pm, const QRectF &subRect ) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + device->drawPixmap( rect, pm, subRect ); +} + +void QwtNullPaintDevice::PaintEngine::drawTextItem( + const QPointF &pos, const QTextItem &textItem) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawTextItem( pos, textItem ); + return; + } + + device->drawTextItem( pos, textItem ); +} + +void QwtNullPaintDevice::PaintEngine::drawTiledPixmap( + const QRectF &rect, const QPixmap &pixmap, + const QPointF &subRect) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawTiledPixmap( rect, pixmap, subRect ); + return; + } + + device->drawTiledPixmap( rect, pixmap, subRect ); +} + +void QwtNullPaintDevice::PaintEngine::drawImage( + const QRectF &rect, const QImage &image, + const QRectF &subRect, Qt::ImageConversionFlags flags) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + device->drawImage( rect, image, subRect, flags ); +} + +void QwtNullPaintDevice::PaintEngine::updateState( + const QPaintEngineState &state) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + device->updateState( state ); +} + +inline QwtNullPaintDevice *QwtNullPaintDevice::PaintEngine::nullDevice() +{ + if ( !isActive() ) + return NULL; + + return static_cast( paintDevice() ); +} + +//! Constructor +QwtNullPaintDevice::QwtNullPaintDevice(): + d_engine( NULL ) +{ + d_data = new PrivateData; +} + +//! Destructor +QwtNullPaintDevice::~QwtNullPaintDevice() +{ + delete d_engine; + delete d_data; +} + +/*! + Set the render mode + + \param mode New mode + \sa mode() + */ +void QwtNullPaintDevice::setMode( Mode mode ) +{ + d_data->mode = mode; +} + +/*! + \return Render mode + \sa setMode() +*/ +QwtNullPaintDevice::Mode QwtNullPaintDevice::mode() const +{ + return d_data->mode; +} + +//! See QPaintDevice::paintEngine() +QPaintEngine *QwtNullPaintDevice::paintEngine() const +{ + if ( d_engine == NULL ) + { + QwtNullPaintDevice *that = + const_cast< QwtNullPaintDevice * >( this ); + + that->d_engine = new PaintEngine(); + } + + return d_engine; +} + +/*! + See QPaintDevice::metric() + + \param deviceMetric Type of metric + \return Metric information for the given paint device metric. + + \sa sizeMetrics() +*/ +int QwtNullPaintDevice::metric( PaintDeviceMetric deviceMetric ) const +{ + int value; + + switch ( deviceMetric ) + { + case PdmWidth: + { + value = sizeMetrics().width(); + break; + } + case PdmHeight: + { + value = sizeMetrics().height(); + break; + } + case PdmNumColors: + { + value = 0xffffffff; + break; + } + case PdmDepth: + { + value = 32; + break; + } + case PdmPhysicalDpiX: + case PdmPhysicalDpiY: + case PdmDpiY: + case PdmDpiX: + { + value = 72; + break; + } + case PdmWidthMM: + { + value = qRound( metric( PdmWidth ) * 25.4 / metric( PdmDpiX ) ); + break; + } + case PdmHeightMM: + { + value = qRound( metric( PdmHeight ) * 25.4 / metric( PdmDpiY ) ); + break; + } + default: + value = 0; + } + return value; + +} + +//! See QPaintEngine::drawRects() +void QwtNullPaintDevice::drawRects( + const QRect *rects, int rectCount) +{ + Q_UNUSED(rects); + Q_UNUSED(rectCount); +} + +//! See QPaintEngine::drawRects() +void QwtNullPaintDevice::drawRects( + const QRectF *rects, int rectCount) +{ + Q_UNUSED(rects); + Q_UNUSED(rectCount); +} + +//! See QPaintEngine::drawLines() +void QwtNullPaintDevice::drawLines( + const QLine *lines, int lineCount) +{ + Q_UNUSED(lines); + Q_UNUSED(lineCount); +} + +//! See QPaintEngine::drawLines() +void QwtNullPaintDevice::drawLines( + const QLineF *lines, int lineCount) +{ + Q_UNUSED(lines); + Q_UNUSED(lineCount); +} + +//! See QPaintEngine::drawEllipse() +void QwtNullPaintDevice::drawEllipse( const QRectF &rect ) +{ + Q_UNUSED(rect); +} + +//! See QPaintEngine::drawEllipse() +void QwtNullPaintDevice::drawEllipse( const QRect &rect ) +{ + Q_UNUSED(rect); +} + +//! See QPaintEngine::drawPath() +void QwtNullPaintDevice::drawPath( const QPainterPath &path ) +{ + Q_UNUSED(path); +} + +//! See QPaintEngine::drawPoints() +void QwtNullPaintDevice::drawPoints( + const QPointF *points, int pointCount) +{ + Q_UNUSED(points); + Q_UNUSED(pointCount); +} + +//! See QPaintEngine::drawPoints() +void QwtNullPaintDevice::drawPoints( + const QPoint *points, int pointCount) +{ + Q_UNUSED(points); + Q_UNUSED(pointCount); +} + +//! See QPaintEngine::drawPolygon() +void QwtNullPaintDevice::drawPolygon( + const QPointF *points, int pointCount, + QPaintEngine::PolygonDrawMode mode) +{ + Q_UNUSED(points); + Q_UNUSED(pointCount); + Q_UNUSED(mode); +} + +//! See QPaintEngine::drawPolygon() +void QwtNullPaintDevice::drawPolygon( + const QPoint *points, int pointCount, + QPaintEngine::PolygonDrawMode mode) +{ + Q_UNUSED(points); + Q_UNUSED(pointCount); + Q_UNUSED(mode); +} + +//! See QPaintEngine::drawPixmap() +void QwtNullPaintDevice::drawPixmap( const QRectF &rect, + const QPixmap &pm, const QRectF &subRect ) +{ + Q_UNUSED(rect); + Q_UNUSED(pm); + Q_UNUSED(subRect); +} + +//! See QPaintEngine::drawTextItem() +void QwtNullPaintDevice::drawTextItem( + const QPointF &pos, const QTextItem &textItem) +{ + Q_UNUSED(pos); + Q_UNUSED(textItem); +} + +//! See QPaintEngine::drawTiledPixmap() +void QwtNullPaintDevice::drawTiledPixmap( + const QRectF &rect, const QPixmap &pixmap, + const QPointF &subRect) +{ + Q_UNUSED(rect); + Q_UNUSED(pixmap); + Q_UNUSED(subRect); +} + +//! See QPaintEngine::drawImage() +void QwtNullPaintDevice::drawImage( + const QRectF &rect, const QImage &image, + const QRectF &subRect, Qt::ImageConversionFlags flags) +{ + Q_UNUSED(rect); + Q_UNUSED(image); + Q_UNUSED(subRect); + Q_UNUSED(flags); +} + +//! See QPaintEngine::updateState() +void QwtNullPaintDevice::updateState( + const QPaintEngineState &state ) +{ + Q_UNUSED(state); +} diff --git a/qwt/src/qwt_null_paintdevice.h b/qwt/src/qwt_null_paintdevice.h new file mode 100644 index 000000000..d7f03beea --- /dev/null +++ b/qwt/src/qwt_null_paintdevice.h @@ -0,0 +1,126 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_NULL_PAINT_DEVICE_H +#define QWT_NULL_PAINT_DEVICE_H 1 + +#include "qwt_global.h" +#include +#include + +/*! + \brief A null paint device doing nothing + + Sometimes important layout/rendering geometries are not + available or changeable from the public Qt class interface. + ( f.e hidden in the style implementation ). + + QwtNullPaintDevice can be used to manipulate or filter out + this information by analyzing the stream of paint primitives. + + F.e. QwtNullPaintDevice is used by QwtPlotCanvas to identify + styled backgrounds with rounded corners. +*/ + +class QWT_EXPORT QwtNullPaintDevice: public QPaintDevice +{ +public: + /*! + \brief Render mode + + \sa setMode(), mode() + */ + enum Mode + { + /*! + All vector graphic primitives are painted by + the corresponding draw methods + */ + NormalMode, + + /*! + Vector graphic primitives ( beside polygons ) are mapped to a QPainterPath + and are painted by drawPath. In PathMode mode + only a few draw methods are called: + + - drawPath() + - drawPixmap() + - drawImage() + - drawPolygon() + */ + PolygonPathMode, + + /*! + Vector graphic primitives are mapped to a QPainterPath + and are painted by drawPath. In PathMode mode + only a few draw methods are called: + + - drawPath() + - drawPixmap() + - drawImage() + */ + PathMode + }; + + QwtNullPaintDevice(); + virtual ~QwtNullPaintDevice(); + + void setMode( Mode ); + Mode mode() const; + + virtual QPaintEngine *paintEngine() const; + + virtual int metric( PaintDeviceMetric metric ) const; + + virtual void drawRects(const QRect *, int ); + virtual void drawRects(const QRectF *, int ); + + virtual void drawLines(const QLine *, int ); + virtual void drawLines(const QLineF *, int ); + + virtual void drawEllipse(const QRectF &); + virtual void drawEllipse(const QRect &); + + virtual void drawPath(const QPainterPath &); + + virtual void drawPoints(const QPointF *, int ); + virtual void drawPoints(const QPoint *, int ); + + virtual void drawPolygon( + const QPointF *, int , QPaintEngine::PolygonDrawMode ); + + virtual void drawPolygon( + const QPoint *, int , QPaintEngine::PolygonDrawMode ); + + virtual void drawPixmap(const QRectF &, + const QPixmap &, const QRectF &); + + virtual void drawTextItem(const QPointF &, const QTextItem &); + + virtual void drawTiledPixmap(const QRectF &, + const QPixmap &, const QPointF &s); + + virtual void drawImage(const QRectF &, + const QImage &, const QRectF &, Qt::ImageConversionFlags ); + + virtual void updateState( const QPaintEngineState &state ); + +protected: + //! \return Size needed to implement metric() + virtual QSize sizeMetrics() const = 0; + +private: + class PaintEngine; + PaintEngine *d_engine; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_painter.cpp b/qwt/src/qwt_painter.cpp new file mode 100644 index 000000000..0bbf258c5 --- /dev/null +++ b/qwt/src/qwt_painter.cpp @@ -0,0 +1,1266 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_painter.h" +#include "qwt_math.h" +#include "qwt_clipper.h" +#include "qwt_color_map.h" +#include "qwt_scale_map.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= 0x050000 +#include +#endif + +#if QT_VERSION < 0x050000 + +#ifdef Q_WS_X11 +#include +#endif + +#endif + +bool QwtPainter::d_polylineSplitting = true; +bool QwtPainter::d_roundingAlignment = true; + +static inline bool qwtIsClippingNeeded( + const QPainter *painter, QRectF &clipRect ) +{ + bool doClipping = false; + const QPaintEngine *pe = painter->paintEngine(); + if ( pe && pe->type() == QPaintEngine::SVG ) + { + // The SVG paint engine ignores any clipping, + + if ( painter->hasClipping() ) + { + doClipping = true; + clipRect = painter->clipRegion().boundingRect(); + } + } + + return doClipping; +} + +template +static inline void qwtDrawPolyline( QPainter *painter, + const T *points, int pointCount, bool polylineSplitting ) +{ + bool doSplit = false; + if ( polylineSplitting ) + { + const QPaintEngine *pe = painter->paintEngine(); + if ( pe && pe->type() == QPaintEngine::Raster ) + { + /* + The raster paint engine seems to use some algo with O(n*n). + ( Qt 4.3 is better than Qt 4.2, but remains unacceptable) + To work around this problem, we have to split the polygon into + smaller pieces. + */ + doSplit = true; + } + } + + if ( doSplit ) + { + const int splitSize = 20; + for ( int i = 0; i < pointCount; i += splitSize ) + { + const int n = qMin( splitSize + 1, pointCount - i ); + painter->drawPolyline( points + i, n ); + } + } + else + painter->drawPolyline( points, pointCount ); +} + +static inline void qwtUnscaleFont( QPainter *painter ) +{ + if ( painter->font().pixelSize() >= 0 ) + return; + + static QSize screenResolution; + if ( !screenResolution.isValid() ) + { + QDesktopWidget *desktop = QApplication::desktop(); + if ( desktop ) + { + screenResolution.setWidth( desktop->logicalDpiX() ); + screenResolution.setHeight( desktop->logicalDpiY() ); + } + } + + const QPaintDevice *pd = painter->device(); + if ( pd->logicalDpiX() != screenResolution.width() || + pd->logicalDpiY() != screenResolution.height() ) + { + QFont pixelFont( painter->font(), QApplication::desktop() ); + pixelFont.setPixelSize( QFontInfo( pixelFont ).pixelSize() ); + + painter->setFont( pixelFont ); + } +} + +/*! + Check is the application is running with the X11 graphics system + that has some special capabilities that can be used for incremental + painting to a widget. + + \return True, when the graphics system is X11 +*/ +bool QwtPainter::isX11GraphicsSystem() +{ + static int onX11 = -1; + if ( onX11 < 0 ) + { + QPixmap pm( 1, 1 ); + QPainter painter( &pm ); + + onX11 = ( painter.paintEngine()->type() == QPaintEngine::X11 ) ? 1 : 0; + } + + return onX11 == 1; +} + +/*! + Check if the painter is using a paint engine, that aligns + coordinates to integers. Today these are all paint engines + beside QPaintEngine::Pdf and QPaintEngine::SVG. + + If we have an integer based paint engine it is also + checked if the painter has a transformation matrix, + that rotates or scales. + + \param painter Painter + \return true, when the painter is aligning + + \sa setRoundingAlignment() +*/ +bool QwtPainter::isAligning( QPainter *painter ) +{ + if ( painter && painter->isActive() ) + { + switch ( painter->paintEngine()->type() ) + { + case QPaintEngine::Pdf: + case QPaintEngine::SVG: + return false; + + default:; + } + + const QTransform tr = painter->transform(); + if ( tr.isRotating() || tr.isScaling() ) + { + // we might have to check translations too + return false; + } + } + + return true; +} + +/*! + Enable whether coordinates should be rounded, before they are painted + to a paint engine that floors to integer values. For other paint engines + this ( PDF, SVG ), this flag has no effect. + QwtPainter stores this flag only, the rounding itself is done in + the painting code ( f.e the plot items ). + + The default setting is true. + + \sa roundingAlignment(), isAligning() +*/ +void QwtPainter::setRoundingAlignment( bool enable ) +{ + d_roundingAlignment = enable; +} + +/*! + \brief En/Disable line splitting for the raster paint engine + + In some Qt versions the raster paint engine paints polylines of many points + much faster when they are split in smaller chunks: f.e all supported Qt versions + >= Qt 5.0 when drawing an antialiased polyline with a pen width >=2. + + The default setting is true. + + \sa polylineSplitting() +*/ +void QwtPainter::setPolylineSplitting( bool enable ) +{ + d_polylineSplitting = enable; +} + +//! Wrapper for QPainter::drawPath() +void QwtPainter::drawPath( QPainter *painter, const QPainterPath &path ) +{ + painter->drawPath( path ); +} + +//! Wrapper for QPainter::drawRect() +void QwtPainter::drawRect( QPainter *painter, double x, double y, double w, double h ) +{ + drawRect( painter, QRectF( x, y, w, h ) ); +} + +//! Wrapper for QPainter::drawRect() +void QwtPainter::drawRect( QPainter *painter, const QRectF &rect ) +{ + const QRectF r = rect; + + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + if ( !clipRect.intersects( r ) ) + return; + + if ( !clipRect.contains( r ) ) + { + fillRect( painter, r & clipRect, painter->brush() ); + + painter->save(); + painter->setBrush( Qt::NoBrush ); + drawPolyline( painter, QPolygonF( r ) ); + painter->restore(); + + return; + } + } + + painter->drawRect( r ); +} + +//! Wrapper for QPainter::fillRect() +void QwtPainter::fillRect( QPainter *painter, + const QRectF &rect, const QBrush &brush ) +{ + if ( !rect.isValid() ) + return; + + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + /* + Performance of Qt4 is horrible for a non trivial brush. Without + clipping expect minutes or hours for repainting large rectangles + (might result from zooming) + */ + + if ( deviceClipping ) + clipRect &= painter->window(); + else + clipRect = painter->window(); + + if ( painter->hasClipping() ) + clipRect &= painter->clipRegion().boundingRect(); + + QRectF r = rect; + if ( deviceClipping ) + r = r.intersected( clipRect ); + + if ( r.isValid() ) + painter->fillRect( r, brush ); +} + +//! Wrapper for QPainter::drawPie() +void QwtPainter::drawPie( QPainter *painter, const QRectF &rect, + int a, int alen ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + if ( deviceClipping && !clipRect.contains( rect ) ) + return; + + painter->drawPie( rect, a, alen ); +} + +//! Wrapper for QPainter::drawEllipse() +void QwtPainter::drawEllipse( QPainter *painter, const QRectF &rect ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping && !clipRect.contains( rect ) ) + return; + + painter->drawEllipse( rect ); +} + +//! Wrapper for QPainter::drawText() +void QwtPainter::drawText( QPainter *painter, double x, double y, + const QString &text ) +{ + drawText( painter, QPointF( x, y ), text ); +} + +//! Wrapper for QPainter::drawText() +void QwtPainter::drawText( QPainter *painter, const QPointF &pos, + const QString &text ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping && !clipRect.contains( pos ) ) + return; + + + painter->save(); + qwtUnscaleFont( painter ); + painter->drawText( pos, text ); + painter->restore(); +} + +//! Wrapper for QPainter::drawText() +void QwtPainter::drawText( QPainter *painter, + double x, double y, double w, double h, + int flags, const QString &text ) +{ + drawText( painter, QRectF( x, y, w, h ), flags, text ); +} + +//! Wrapper for QPainter::drawText() +void QwtPainter::drawText( QPainter *painter, const QRectF &rect, + int flags, const QString &text ) +{ + painter->save(); + qwtUnscaleFont( painter ); + painter->drawText( rect, flags, text ); + painter->restore(); +} + +#ifndef QT_NO_RICHTEXT + +/*! + Draw a text document into a rectangle + + \param painter Painter + \param rect Traget rectangle + \param flags Alignments/Text flags, see QPainter::drawText() + \param text Text document +*/ +void QwtPainter::drawSimpleRichText( QPainter *painter, const QRectF &rect, + int flags, const QTextDocument &text ) +{ + QTextDocument *txt = text.clone(); + + painter->save(); + + painter->setFont( txt->defaultFont() ); + qwtUnscaleFont( painter ); + + txt->setDefaultFont( painter->font() ); + txt->setPageSize( QSizeF( rect.width(), QWIDGETSIZE_MAX ) ); + + QAbstractTextDocumentLayout* layout = txt->documentLayout(); + + const double height = layout->documentSize().height(); + double y = rect.y(); + if ( flags & Qt::AlignBottom ) + y += ( rect.height() - height ); + else if ( flags & Qt::AlignVCenter ) + y += ( rect.height() - height ) / 2; + + QAbstractTextDocumentLayout::PaintContext context; + context.palette.setColor( QPalette::Text, painter->pen().color() ); + + painter->translate( rect.x(), y ); + layout->draw( painter, context ); + + painter->restore(); + delete txt; +} + +#endif // !QT_NO_RICHTEXT + + +//! Wrapper for QPainter::drawLine() +void QwtPainter::drawLine( QPainter *painter, + const QPointF &p1, const QPointF &p2 ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping && + !( clipRect.contains( p1 ) && clipRect.contains( p2 ) ) ) + { + QPolygonF polygon; + polygon += p1; + polygon += p2; + drawPolyline( painter, polygon ); + return; + } + + painter->drawLine( p1, p2 ); +} + +//! Wrapper for QPainter::drawPolygon() +void QwtPainter::drawPolygon( QPainter *painter, const QPolygonF &polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + QPolygonF cpa = polygon; + if ( deviceClipping ) + cpa = QwtClipper::clipPolygonF( clipRect, polygon ); + + painter->drawPolygon( cpa ); +} + +//! Wrapper for QPainter::drawPolyline() +void QwtPainter::drawPolyline( QPainter *painter, const QPolygonF &polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + QPolygonF cpa = polygon; + if ( deviceClipping ) + cpa = QwtClipper::clipPolygonF( clipRect, cpa ); + + qwtDrawPolyline( painter, + cpa.constData(), cpa.size(), d_polylineSplitting ); +} + +//! Wrapper for QPainter::drawPolyline() +void QwtPainter::drawPolyline( QPainter *painter, + const QPointF *points, int pointCount ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + QPolygonF polygon( pointCount ); + ::memcpy( polygon.data(), points, pointCount * sizeof( QPointF ) ); + + polygon = QwtClipper::clipPolygonF( clipRect, polygon ); + qwtDrawPolyline( painter, + polygon.constData(), polygon.size(), d_polylineSplitting ); + } + else + { + qwtDrawPolyline( painter, points, pointCount, d_polylineSplitting ); + } +} + +//! Wrapper for QPainter::drawPolygon() +void QwtPainter::drawPolygon( QPainter *painter, const QPolygon &polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + QPolygon cpa = polygon; + if ( deviceClipping ) + cpa = QwtClipper::clipPolygon( clipRect, polygon ); + + painter->drawPolygon( cpa ); +} + +//! Wrapper for QPainter::drawPolyline() +void QwtPainter::drawPolyline( QPainter *painter, const QPolygon &polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + QPolygon cpa = polygon; + if ( deviceClipping ) + cpa = QwtClipper::clipPolygon( clipRect, cpa ); + + qwtDrawPolyline( painter, + cpa.constData(), cpa.size(), d_polylineSplitting ); +} + +//! Wrapper for QPainter::drawPolyline() +void QwtPainter::drawPolyline( QPainter *painter, + const QPoint *points, int pointCount ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + QPolygon polygon( pointCount ); + ::memcpy( polygon.data(), points, pointCount * sizeof( QPoint ) ); + + polygon = QwtClipper::clipPolygon( clipRect, polygon ); + qwtDrawPolyline( painter, + polygon.constData(), polygon.size(), d_polylineSplitting ); + } + else + qwtDrawPolyline( painter, points, pointCount, d_polylineSplitting ); +} + +//! Wrapper for QPainter::drawPoint() +void QwtPainter::drawPoint( QPainter *painter, const QPointF &pos ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping && !clipRect.contains( pos ) ) + return; + + painter->drawPoint( pos ); +} + +//! Wrapper for QPainter::drawPoint() +void QwtPainter::drawPoint( QPainter *painter, const QPoint &pos ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + const int minX = qCeil( clipRect.left() ); + const int maxX = qFloor( clipRect.right() ); + const int minY = qCeil( clipRect.top() ); + const int maxY = qFloor( clipRect.bottom() ); + + if ( pos.x() < minX || pos.x() > maxX + || pos.y() < minY || pos.y() > maxY ) + { + return; + } + } + + painter->drawPoint( pos ); +} + +//! Wrapper for QPainter::drawPoints() +void QwtPainter::drawPoints( QPainter *painter, + const QPoint *points, int pointCount ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + const int minX = qCeil( clipRect.left() ); + const int maxX = qFloor( clipRect.right() ); + const int minY = qCeil( clipRect.top() ); + const int maxY = qFloor( clipRect.bottom() ); + + const QRect r( minX, minY, maxX - minX, maxY - minY ); + + QPolygon clippedPolygon( pointCount ); + QPoint *clippedData = clippedPolygon.data(); + + int numClippedPoints = 0; + for ( int i = 0; i < pointCount; i++ ) + { + if ( r.contains( points[i] ) ) + clippedData[ numClippedPoints++ ] = points[i]; + } + painter->drawPoints( clippedData, numClippedPoints ); + } + else + { + painter->drawPoints( points, pointCount ); + } +} + +//! Wrapper for QPainter::drawPoints() +void QwtPainter::drawPoints( QPainter *painter, + const QPointF *points, int pointCount ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + QPolygonF clippedPolygon( pointCount ); + QPointF *clippedData = clippedPolygon.data(); + + int numClippedPoints = 0; + for ( int i = 0; i < pointCount; i++ ) + { + if ( clipRect.contains( points[i] ) ) + clippedData[ numClippedPoints++ ] = points[i]; + } + painter->drawPoints( clippedData, numClippedPoints ); + } + else + { + painter->drawPoints( points, pointCount ); + } +} + +//! Wrapper for QPainter::drawImage() +void QwtPainter::drawImage( QPainter *painter, + const QRectF &rect, const QImage &image ) +{ + const QRect alignedRect = rect.toAlignedRect(); + + if ( alignedRect != rect ) + { + const QRectF clipRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + + painter->save(); + painter->setClipRect( clipRect, Qt::IntersectClip ); + painter->drawImage( alignedRect, image ); + painter->restore(); + } + else + { + painter->drawImage( alignedRect, image ); + } +} + +//! Wrapper for QPainter::drawPixmap() +void QwtPainter::drawPixmap( QPainter *painter, + const QRectF &rect, const QPixmap &pixmap ) +{ + const QRect alignedRect = rect.toAlignedRect(); + + if ( alignedRect != rect ) + { + const QRectF clipRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + + painter->save(); + painter->setClipRect( clipRect, Qt::IntersectClip ); + painter->drawPixmap( alignedRect, pixmap ); + painter->restore(); + } + else + { + painter->drawPixmap( alignedRect, pixmap ); + } +} + +//! Draw a focus rectangle on a widget using its style. +void QwtPainter::drawFocusRect( QPainter *painter, const QWidget *widget ) +{ + drawFocusRect( painter, widget, widget->rect() ); +} + +//! Draw a focus rectangle on a widget using its style. +void QwtPainter::drawFocusRect( QPainter *painter, const QWidget *widget, + const QRect &rect ) +{ + QStyleOptionFocusRect opt; + opt.init( widget ); + opt.rect = rect; + opt.state |= QStyle::State_HasFocus; + + widget->style()->drawPrimitive( QStyle::PE_FrameFocusRect, + &opt, painter, widget ); +} + +/*! + Draw a round frame + + \param painter Painter + \param rect Frame rectangle + \param palette QPalette::WindowText is used for plain borders + QPalette::Dark and QPalette::Light for raised + or sunken borders + \param lineWidth Line width + \param frameStyle bitwise OR´ed value of QFrame::Shape and QFrame::Shadow +*/ +void QwtPainter::drawRoundFrame( QPainter *painter, + const QRectF &rect, const QPalette &palette, + int lineWidth, int frameStyle ) +{ + enum Style + { + Plain, + Sunken, + Raised + }; + + Style style = Plain; + if ( (frameStyle & QFrame::Sunken) == QFrame::Sunken ) + style = Sunken; + else if ( (frameStyle & QFrame::Raised) == QFrame::Raised ) + style = Raised; + + const double lw2 = 0.5 * lineWidth; + QRectF r = rect.adjusted( lw2, lw2, -lw2, -lw2 ); + + QBrush brush; + + if ( style != Plain ) + { + QColor c1 = palette.color( QPalette::Light ); + QColor c2 = palette.color( QPalette::Dark ); + + if ( style == Sunken ) + qSwap( c1, c2 ); + + QLinearGradient gradient( r.topLeft(), r.bottomRight() ); + gradient.setColorAt( 0.0, c1 ); +#if 0 + gradient.setColorAt( 0.3, c1 ); + gradient.setColorAt( 0.7, c2 ); +#endif + gradient.setColorAt( 1.0, c2 ); + + brush = QBrush( gradient ); + } + else // Plain + { + brush = palette.brush( QPalette::WindowText ); + } + + painter->save(); + + painter->setPen( QPen( brush, lineWidth ) ); + painter->setBrush( Qt::NoBrush ); + + painter->drawEllipse( r ); + + painter->restore(); +} + +/*! + Draw a rectangular frame + + \param painter Painter + \param rect Frame rectangle + \param palette Palette + \param foregroundRole Foreground role used for QFrame::Plain + \param frameWidth Frame width + \param midLineWidth Used for QFrame::Box + \param frameStyle bitwise OR´ed value of QFrame::Shape and QFrame::Shadow +*/ +void QwtPainter::drawFrame( QPainter *painter, const QRectF &rect, + const QPalette &palette, QPalette::ColorRole foregroundRole, + int frameWidth, int midLineWidth, int frameStyle ) +{ + if ( frameWidth <= 0 || rect.isEmpty() ) + return; + + const int shadow = frameStyle & QFrame::Shadow_Mask; + + painter->save(); + + if ( shadow == QFrame::Plain ) + { + const QRectF outerRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + const QRectF innerRect = outerRect.adjusted( + frameWidth, frameWidth, -frameWidth, -frameWidth ); + + QPainterPath path; + path.addRect( outerRect ); + path.addRect( innerRect ); + + painter->setPen( Qt::NoPen ); + painter->setBrush( palette.color( foregroundRole ) ); + + painter->drawPath( path ); + } + else + { + const int shape = frameStyle & QFrame::Shape_Mask; + + if ( shape == QFrame::Box ) + { + const QRectF outerRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + const QRectF midRect1 = outerRect.adjusted( + frameWidth, frameWidth, -frameWidth, -frameWidth ); + const QRectF midRect2 = midRect1.adjusted( + midLineWidth, midLineWidth, -midLineWidth, -midLineWidth ); + + const QRectF innerRect = midRect2.adjusted( + frameWidth, frameWidth, -frameWidth, -frameWidth ); + + QPainterPath path1; + path1.moveTo( outerRect.bottomLeft() ); + path1.lineTo( outerRect.topLeft() ); + path1.lineTo( outerRect.topRight() ); + path1.lineTo( midRect1.topRight() ); + path1.lineTo( midRect1.topLeft() ); + path1.lineTo( midRect1.bottomLeft() ); + + QPainterPath path2; + path2.moveTo( outerRect.bottomLeft() ); + path2.lineTo( outerRect.bottomRight() ); + path2.lineTo( outerRect.topRight() ); + path2.lineTo( midRect1.topRight() ); + path2.lineTo( midRect1.bottomRight() ); + path2.lineTo( midRect1.bottomLeft() ); + + QPainterPath path3; + path3.moveTo( midRect2.bottomLeft() ); + path3.lineTo( midRect2.topLeft() ); + path3.lineTo( midRect2.topRight() ); + path3.lineTo( innerRect.topRight() ); + path3.lineTo( innerRect.topLeft() ); + path3.lineTo( innerRect.bottomLeft() ); + + QPainterPath path4; + path4.moveTo( midRect2.bottomLeft() ); + path4.lineTo( midRect2.bottomRight() ); + path4.lineTo( midRect2.topRight() ); + path4.lineTo( innerRect.topRight() ); + path4.lineTo( innerRect.bottomRight() ); + path4.lineTo( innerRect.bottomLeft() ); + + QPainterPath path5; + path5.addRect( midRect1 ); + path5.addRect( midRect2 ); + + painter->setPen( Qt::NoPen ); + + QBrush brush1 = palette.dark().color(); + QBrush brush2 = palette.light().color(); + + if ( shadow == QFrame::Raised ) + qSwap( brush1, brush2 ); + + painter->setBrush( brush1 ); + painter->drawPath( path1 ); + painter->drawPath( path4 ); + + painter->setBrush( brush2 ); + painter->drawPath( path2 ); + painter->drawPath( path3 ); + + painter->setBrush( palette.mid() ); + painter->drawPath( path5 ); + } +#if 0 + // qDrawWinPanel doesn't result in something nice + // on a scalable document like PDF. Better draw a + // Panel. + + else if ( shape == QFrame::WinPanel ) + { + painter->setRenderHint( QPainter::NonCosmeticDefaultPen, true ); + qDrawWinPanel ( painter, rect.toRect(), palette, + frameStyle & QFrame::Sunken ); + } + else if ( shape == QFrame::StyledPanel ) + { + } +#endif + else + { + const QRectF outerRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + const QRectF innerRect = outerRect.adjusted( + frameWidth - 1.0, frameWidth - 1.0, + -( frameWidth - 1.0 ), -( frameWidth - 1.0 ) ); + + QPainterPath path1; + path1.moveTo( outerRect.bottomLeft() ); + path1.lineTo( outerRect.topLeft() ); + path1.lineTo( outerRect.topRight() ); + path1.lineTo( innerRect.topRight() ); + path1.lineTo( innerRect.topLeft() ); + path1.lineTo( innerRect.bottomLeft() ); + + + QPainterPath path2; + path2.moveTo( outerRect.bottomLeft() ); + path2.lineTo( outerRect.bottomRight() ); + path2.lineTo( outerRect.topRight() ); + path2.lineTo( innerRect.topRight() ); + path2.lineTo( innerRect.bottomRight() ); + path2.lineTo( innerRect.bottomLeft() ); + + painter->setPen( Qt::NoPen ); + + QBrush brush1 = palette.dark().color(); + QBrush brush2 = palette.light().color(); + + if ( shadow == QFrame::Raised ) + qSwap( brush1, brush2 ); + + painter->setBrush( brush1 ); + painter->drawPath( path1 ); + + painter->setBrush( brush2 ); + painter->drawPath( path2 ); + } + + } + + painter->restore(); +} + +/*! + Draw a rectangular frame with rounded borders + + \param painter Painter + \param rect Frame rectangle + \param xRadius x-radius of the ellipses defining the corners + \param yRadius y-radius of the ellipses defining the corners + \param palette QPalette::WindowText is used for plain borders + QPalette::Dark and QPalette::Light for raised + or sunken borders + \param lineWidth Line width + \param frameStyle bitwise OR´ed value of QFrame::Shape and QFrame::Shadow +*/ + +void QwtPainter::drawRoundedFrame( QPainter *painter, + const QRectF &rect, double xRadius, double yRadius, + const QPalette &palette, int lineWidth, int frameStyle ) +{ + painter->save(); + painter->setRenderHint( QPainter::Antialiasing, true ); + painter->setBrush( Qt::NoBrush ); + + double lw2 = lineWidth * 0.5; + QRectF r = rect.adjusted( lw2, lw2, -lw2, -lw2 ); + + QPainterPath path; + path.addRoundedRect( r, xRadius, yRadius ); + + enum Style + { + Plain, + Sunken, + Raised + }; + + Style style = Plain; + if ( (frameStyle & QFrame::Sunken) == QFrame::Sunken ) + style = Sunken; + else if ( (frameStyle & QFrame::Raised) == QFrame::Raised ) + style = Raised; + + if ( style != Plain && path.elementCount() == 17 ) + { + // move + 4 * ( cubicTo + lineTo ) + QPainterPath pathList[8]; + + for ( int i = 0; i < 4; i++ ) + { + const int j = i * 4 + 1; + + pathList[ 2 * i ].moveTo( + path.elementAt(j - 1).x, path.elementAt( j - 1 ).y + ); + + pathList[ 2 * i ].cubicTo( + path.elementAt(j + 0).x, path.elementAt(j + 0).y, + path.elementAt(j + 1).x, path.elementAt(j + 1).y, + path.elementAt(j + 2).x, path.elementAt(j + 2).y ); + + pathList[ 2 * i + 1 ].moveTo( + path.elementAt(j + 2).x, path.elementAt(j + 2).y + ); + pathList[ 2 * i + 1 ].lineTo( + path.elementAt(j + 3).x, path.elementAt(j + 3).y + ); + } + + QColor c1( palette.color( QPalette::Dark ) ); + QColor c2( palette.color( QPalette::Light ) ); + + if ( style == Raised ) + qSwap( c1, c2 ); + + for ( int i = 0; i < 4; i++ ) + { + QRectF r = pathList[2 * i].controlPointRect(); + + QPen arcPen; + arcPen.setCapStyle( Qt::FlatCap ); + arcPen.setWidth( lineWidth ); + + QPen linePen; + linePen.setCapStyle( Qt::FlatCap ); + linePen.setWidth( lineWidth ); + + switch( i ) + { + case 0: + { + arcPen.setColor( c1 ); + linePen.setColor( c1 ); + break; + } + case 1: + { + QLinearGradient gradient; + gradient.setStart( r.topLeft() ); + gradient.setFinalStop( r.bottomRight() ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 1.0, c2 ); + + arcPen.setBrush( gradient ); + linePen.setColor( c2 ); + break; + } + case 2: + { + arcPen.setColor( c2 ); + linePen.setColor( c2 ); + break; + } + case 3: + { + QLinearGradient gradient; + + gradient.setStart( r.bottomRight() ); + gradient.setFinalStop( r.topLeft() ); + gradient.setColorAt( 0.0, c2 ); + gradient.setColorAt( 1.0, c1 ); + + arcPen.setBrush( gradient ); + linePen.setColor( c1 ); + break; + } + } + + + painter->setPen( arcPen ); + painter->drawPath( pathList[ 2 * i] ); + + painter->setPen( linePen ); + painter->drawPath( pathList[ 2 * i + 1] ); + } + } + else + { + QPen pen( palette.color( QPalette::WindowText ), lineWidth ); + painter->setPen( pen ); + painter->drawPath( path ); + } + + painter->restore(); +} + +/*! + Draw a color bar into a rectangle + + \param painter Painter + \param colorMap Color map + \param interval Value range + \param scaleMap Scale map + \param orientation Orientation + \param rect Traget rectangle +*/ +void QwtPainter::drawColorBar( QPainter *painter, + const QwtColorMap &colorMap, const QwtInterval &interval, + const QwtScaleMap &scaleMap, Qt::Orientation orientation, + const QRectF &rect ) +{ + QVector colorTable; + if ( colorMap.format() == QwtColorMap::Indexed ) + colorTable = colorMap.colorTable( interval ); + + QColor c; + + const QRect devRect = rect.toAlignedRect(); + + /* + We paint to a pixmap first to have something scalable for printing + ( f.e. in a Pdf document ) + */ + + QPixmap pixmap( devRect.size() ); + QPainter pmPainter( &pixmap ); + pmPainter.translate( -devRect.x(), -devRect.y() ); + + if ( orientation == Qt::Horizontal ) + { + QwtScaleMap sMap = scaleMap; + sMap.setPaintInterval( rect.left(), rect.right() ); + + for ( int x = devRect.left(); x <= devRect.right(); x++ ) + { + const double value = sMap.invTransform( x ); + + if ( colorMap.format() == QwtColorMap::RGB ) + c.setRgba( colorMap.rgb( interval, value ) ); + else + c = colorTable[colorMap.colorIndex( interval, value )]; + + pmPainter.setPen( c ); + pmPainter.drawLine( x, devRect.top(), x, devRect.bottom() ); + } + } + else // Vertical + { + QwtScaleMap sMap = scaleMap; + sMap.setPaintInterval( rect.bottom(), rect.top() ); + + for ( int y = devRect.top(); y <= devRect.bottom(); y++ ) + { + const double value = sMap.invTransform( y ); + + if ( colorMap.format() == QwtColorMap::RGB ) + c.setRgb( colorMap.rgb( interval, value ) ); + else + c = colorTable[colorMap.colorIndex( interval, value )]; + + pmPainter.setPen( c ); + pmPainter.drawLine( devRect.left(), y, devRect.right(), y ); + } + } + pmPainter.end(); + + drawPixmap( painter, rect, pixmap ); +} + +static inline void qwtFillRect( const QWidget *widget, QPainter *painter, + const QRect &rect, const QBrush &brush) +{ + if ( brush.style() == Qt::TexturePattern ) + { + painter->save(); + + painter->setClipRect( rect ); + painter->drawTiledPixmap(rect, brush.texture(), rect.topLeft()); + + painter->restore(); + } + else if ( brush.gradient() ) + { + painter->save(); + + painter->setClipRect( rect ); + painter->fillRect(0, 0, widget->width(), + widget->height(), brush); + + painter->restore(); + } + else + { + painter->fillRect(rect, brush); + } +} + +/*! + Fill a pixmap with the content of a widget + + In Qt >= 5.0 QPixmap::fill() is a nop, in Qt 4.x it is buggy + for backgrounds with gradients. Thus fillPixmap() offers + an alternative implementation. + + \param widget Widget + \param pixmap Pixmap to be filled + \param offset Offset + + \sa QPixmap::fill() + */ +void QwtPainter::fillPixmap( const QWidget *widget, + QPixmap &pixmap, const QPoint &offset ) +{ + const QRect rect( offset, pixmap.size() ); + + QPainter painter( &pixmap ); + painter.translate( -offset ); + + const QBrush autoFillBrush = + widget->palette().brush( widget->backgroundRole() ); + + if ( !( widget->autoFillBackground() && autoFillBrush.isOpaque() ) ) + { + const QBrush bg = widget->palette().brush( QPalette::Window ); + qwtFillRect( widget, &painter, rect, bg); + } + + if ( widget->autoFillBackground() ) + qwtFillRect( widget, &painter, rect, autoFillBrush); + + if ( widget->testAttribute(Qt::WA_StyledBackground) ) + { + painter.setClipRegion( rect ); + + QStyleOption opt; + opt.initFrom( widget ); + widget->style()->drawPrimitive( QStyle::PE_Widget, + &opt, &painter, widget ); + } +} + +/*! + Fill rect with the background of a widget + + \param painter Painter + \param rect Rectangle to be filled + \param widget Widget + + \sa QStyle::PE_Widget, QWidget::backgroundRole() + */ +void QwtPainter::drawBackgound( QPainter *painter, + const QRectF &rect, const QWidget *widget ) +{ + if ( widget->testAttribute( Qt::WA_StyledBackground ) ) + { + QStyleOption opt; + opt.initFrom( widget ); + opt.rect = rect.toAlignedRect(); + + widget->style()->drawPrimitive( + QStyle::PE_Widget, &opt, painter, widget); + } + else + { + const QBrush brush = + widget->palette().brush( widget->backgroundRole() ); + + painter->fillRect( rect, brush ); + } +} + +/*! + \return A pixmap that can be used as backing store + + \param widget Widget, for which the backinstore is intended + \param size Size of the pixmap + */ +QPixmap QwtPainter::backingStore( QWidget *widget, const QSize &size ) +{ + QPixmap pm; + +#define QWT_HIGH_DPI 1 + +#if QT_VERSION >= 0x050000 && QWT_HIGH_DPI + qreal pixelRatio = 1.0; + + if ( widget && widget->windowHandle() ) + { + pixelRatio = widget->windowHandle()->devicePixelRatio(); + } + else + { + if ( qApp ) + pixelRatio = qApp->devicePixelRatio(); + } + + pm = QPixmap( size * pixelRatio ); + pm.setDevicePixelRatio( pixelRatio ); +#else + Q_UNUSED( widget ) + pm = QPixmap( size ); +#endif + +#if QT_VERSION < 0x050000 +#ifdef Q_WS_X11 + if ( widget && isX11GraphicsSystem() ) + { + if ( pm.x11Info().screen() != widget->x11Info().screen() ) + pm.x11SetScreen( widget->x11Info().screen() ); + } +#endif +#endif + + return pm; +} + diff --git a/qwt/src/qwt_painter.h b/qwt/src/qwt_painter.h new file mode 100644 index 000000000..9609b6935 --- /dev/null +++ b/qwt/src/qwt_painter.h @@ -0,0 +1,188 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PAINTER_H +#define QWT_PAINTER_H + +#include "qwt_global.h" + +#include +#include +#include +#include +#include + +class QPainter; +class QBrush; +class QColor; +class QWidget; +class QPolygonF; +class QRectF; +class QImage; +class QPixmap; +class QwtScaleMap; +class QwtColorMap; +class QwtInterval; + +class QTextDocument; +class QPainterPath; + +/*! + \brief A collection of QPainter workarounds +*/ +class QWT_EXPORT QwtPainter +{ +public: + static void setPolylineSplitting( bool ); + static bool polylineSplitting(); + + static void setRoundingAlignment( bool ); + static bool roundingAlignment(); + static bool roundingAlignment(QPainter *); + + static void drawText( QPainter *, double x, double y, const QString & ); + static void drawText( QPainter *, const QPointF &, const QString & ); + static void drawText( QPainter *, double x, double y, double w, double h, + int flags, const QString & ); + static void drawText( QPainter *, const QRectF &, + int flags, const QString & ); + +#ifndef QT_NO_RICHTEXT + static void drawSimpleRichText( QPainter *, const QRectF &, + int flags, const QTextDocument & ); +#endif + + static void drawRect( QPainter *, double x, double y, double w, double h ); + static void drawRect( QPainter *, const QRectF &rect ); + static void fillRect( QPainter *, const QRectF &, const QBrush & ); + + static void drawEllipse( QPainter *, const QRectF & ); + static void drawPie( QPainter *, const QRectF & r, int a, int alen ); + + static void drawLine( QPainter *, double x1, double y1, double x2, double y2 ); + static void drawLine( QPainter *, const QPointF &p1, const QPointF &p2 ); + static void drawLine( QPainter *, const QLineF & ); + + static void drawPolygon( QPainter *, const QPolygonF & ); + static void drawPolyline( QPainter *, const QPolygonF & ); + static void drawPolyline( QPainter *, const QPointF *, int pointCount ); + + static void drawPolygon( QPainter *, const QPolygon & ); + static void drawPolyline( QPainter *, const QPolygon & ); + static void drawPolyline( QPainter *, const QPoint *, int pointCount ); + + static void drawPoint( QPainter *, const QPoint & ); + static void drawPoints( QPainter *, const QPolygon & ); + static void drawPoints( QPainter *, const QPoint *, int pointCount ); + + static void drawPoint( QPainter *, double x, double y ); + static void drawPoint( QPainter *, const QPointF & ); + static void drawPoints( QPainter *, const QPolygonF & ); + static void drawPoints( QPainter *, const QPointF *, int pointCount ); + + static void drawPath( QPainter *, const QPainterPath & ); + static void drawImage( QPainter *, const QRectF &, const QImage & ); + static void drawPixmap( QPainter *, const QRectF &, const QPixmap & ); + + static void drawRoundFrame( QPainter *, + const QRectF &, const QPalette &, int lineWidth, int frameStyle ); + + static void drawRoundedFrame( QPainter *, + const QRectF &, double xRadius, double yRadius, + const QPalette &, int lineWidth, int frameStyle ); + + static void drawFrame( QPainter *, const QRectF &rect, + const QPalette &palette, QPalette::ColorRole foregroundRole, + int lineWidth, int midLineWidth, int frameStyle ); + + static void drawFocusRect( QPainter *, const QWidget * ); + static void drawFocusRect( QPainter *, const QWidget *, const QRect & ); + + static void drawColorBar( QPainter *painter, + const QwtColorMap &, const QwtInterval &, + const QwtScaleMap &, Qt::Orientation, const QRectF & ); + + static bool isAligning( QPainter *painter ); + static bool isX11GraphicsSystem(); + + static void fillPixmap( const QWidget *, + QPixmap &, const QPoint &offset = QPoint() ); + + static void drawBackgound( QPainter *painter, + const QRectF &rect, const QWidget *widget ); + + static QPixmap backingStore( QWidget *, const QSize & ); + +private: + static bool d_polylineSplitting; + static bool d_roundingAlignment; +}; + +//! Wrapper for QPainter::drawPoint() +inline void QwtPainter::drawPoint( QPainter *painter, double x, double y ) +{ + QwtPainter::drawPoint( painter, QPointF( x, y ) ); +} + +//! Wrapper for QPainter::drawPoints() +inline void QwtPainter::drawPoints( QPainter *painter, const QPolygon &polygon ) +{ + drawPoints( painter, polygon.data(), polygon.size() ); +} + +//! Wrapper for QPainter::drawPoints() +inline void QwtPainter::drawPoints( QPainter *painter, const QPolygonF &polygon ) +{ + drawPoints( painter, polygon.data(), polygon.size() ); +} + +//! Wrapper for QPainter::drawLine() +inline void QwtPainter::drawLine( QPainter *painter, + double x1, double y1, double x2, double y2 ) +{ + QwtPainter::drawLine( painter, QPointF( x1, y1 ), QPointF( x2, y2 ) ); +} + +//! Wrapper for QPainter::drawLine() +inline void QwtPainter::drawLine( QPainter *painter, const QLineF &line ) +{ + QwtPainter::drawLine( painter, line.p1(), line.p2() ); +} + +/*! + \return True, when line splitting for the raster paint engine is enabled. + \sa setPolylineSplitting() +*/ +inline bool QwtPainter::polylineSplitting() +{ + return d_polylineSplitting; +} + +/*! + Check whether coordinates should be rounded, before they are painted + to a paint engine that rounds to integer values. For other paint engines + ( PDF, SVG ), this flag has no effect. + + \return True, when rounding is enabled + \sa setRoundingAlignment(), isAligning() +*/ +inline bool QwtPainter::roundingAlignment() +{ + return d_roundingAlignment; +} + +/*! + \return roundingAlignment() && isAligning(painter); + \param painter Painter +*/ +inline bool QwtPainter::roundingAlignment(QPainter *painter) +{ + return d_roundingAlignment && isAligning(painter); +} +#endif diff --git a/qwt/src/qwt_painter_command.cpp b/qwt/src/qwt_painter_command.cpp new file mode 100644 index 000000000..f6affae37 --- /dev/null +++ b/qwt/src/qwt_painter_command.cpp @@ -0,0 +1,237 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_painter_command.h" + +//! Construct an invalid command +QwtPainterCommand::QwtPainterCommand(): + d_type( Invalid ) +{ +} + +//! Copy constructor +QwtPainterCommand::QwtPainterCommand( const QPainterPath &path ): + d_type( Path ) +{ + d_path = new QPainterPath( path ); +} + +/*! + Constructor for Pixmap paint operation + + \param rect Target rectangle + \param pixmap Pixmap + \param subRect Rectangle inside the pixmap + + \sa QPainter::drawPixmap() + */ +QwtPainterCommand::QwtPainterCommand( const QRectF &rect, + const QPixmap &pixmap, const QRectF& subRect ): + d_type( Pixmap ) +{ + d_pixmapData = new PixmapData(); + d_pixmapData->rect = rect; + d_pixmapData->pixmap = pixmap; + d_pixmapData->subRect = subRect; +} + +/*! + Constructor for Image paint operation + + \param rect Target rectangle + \param image Image + \param subRect Rectangle inside the image + \param flags Conversion flags + + \sa QPainter::drawImage() + */ +QwtPainterCommand::QwtPainterCommand( const QRectF &rect, + const QImage &image, const QRectF& subRect, + Qt::ImageConversionFlags flags ): + d_type( Image ) +{ + d_imageData = new ImageData(); + d_imageData->rect = rect; + d_imageData->image = image; + d_imageData->subRect = subRect; + d_imageData->flags = flags; +} + +/*! + Constructor for State paint operation + \param state Paint engine state + */ +QwtPainterCommand::QwtPainterCommand( const QPaintEngineState &state ): + d_type( State ) +{ + d_stateData = new StateData(); + + d_stateData->flags = state.state(); + + if ( d_stateData->flags & QPaintEngine::DirtyPen ) + d_stateData->pen = state.pen(); + + if ( d_stateData->flags & QPaintEngine::DirtyBrush ) + d_stateData->brush = state.brush(); + + if ( d_stateData->flags & QPaintEngine::DirtyBrushOrigin ) + d_stateData->brushOrigin = state.brushOrigin(); + + if ( d_stateData->flags & QPaintEngine::DirtyFont ) + d_stateData->font = state.font(); + + if ( d_stateData->flags & QPaintEngine::DirtyBackground ) + { + d_stateData->backgroundMode = state.backgroundMode(); + d_stateData->backgroundBrush = state.backgroundBrush(); + } + + if ( d_stateData->flags & QPaintEngine::DirtyTransform ) + d_stateData->transform = state.transform(); + + if ( d_stateData->flags & QPaintEngine::DirtyClipEnabled ) + d_stateData->isClipEnabled = state.isClipEnabled(); + + if ( d_stateData->flags & QPaintEngine::DirtyClipRegion ) + { + d_stateData->clipRegion = state.clipRegion(); + d_stateData->clipOperation = state.clipOperation(); + } + + if ( d_stateData->flags & QPaintEngine::DirtyClipPath ) + { + d_stateData->clipPath = state.clipPath(); + d_stateData->clipOperation = state.clipOperation(); + } + + if ( d_stateData->flags & QPaintEngine::DirtyHints ) + d_stateData->renderHints = state.renderHints(); + + if ( d_stateData->flags & QPaintEngine::DirtyCompositionMode ) + d_stateData->compositionMode = state.compositionMode(); + + if ( d_stateData->flags & QPaintEngine::DirtyOpacity ) + d_stateData->opacity = state.opacity(); +} + +/*! + Copy constructor + \param other Command to be copied + + */ +QwtPainterCommand::QwtPainterCommand(const QwtPainterCommand &other) +{ + copy( other ); +} + +//! Destructor +QwtPainterCommand::~QwtPainterCommand() +{ + reset(); +} + +/*! + Assignment operator + + \param other Command to be copied + \return Modified command + */ +QwtPainterCommand &QwtPainterCommand::operator=(const QwtPainterCommand &other) +{ + reset(); + copy( other ); + + return *this; +} + +void QwtPainterCommand::copy( const QwtPainterCommand &other ) +{ + d_type = other.d_type; + + switch( other.d_type ) + { + case Path: + { + d_path = new QPainterPath( *other.d_path ); + break; + } + case Pixmap: + { + d_pixmapData = new PixmapData( *other.d_pixmapData ); + break; + } + case Image: + { + d_imageData = new ImageData( *other.d_imageData ); + break; + } + case State: + { + d_stateData = new StateData( *other.d_stateData ); + break; + } + default: + break; + } +} + +void QwtPainterCommand::reset() +{ + switch( d_type ) + { + case Path: + { + delete d_path; + break; + } + case Pixmap: + { + delete d_pixmapData; + break; + } + case Image: + { + delete d_imageData; + break; + } + case State: + { + delete d_stateData; + break; + } + default: + break; + } + + d_type = Invalid; +} + +//! \return Painter path to be painted +QPainterPath *QwtPainterCommand::path() +{ + return d_path; +} + +//! \return Attributes how to paint a QPixmap +QwtPainterCommand::PixmapData* QwtPainterCommand::pixmapData() +{ + return d_pixmapData; +} + +//! \return Attributes how to paint a QImage +QwtPainterCommand::ImageData* QwtPainterCommand::imageData() +{ + return d_imageData; +} + +//! \return Attributes of a state change +QwtPainterCommand::StateData* QwtPainterCommand::stateData() +{ + return d_stateData; +} diff --git a/qwt/src/qwt_painter_command.h b/qwt/src/qwt_painter_command.h new file mode 100644 index 000000000..2da597a7f --- /dev/null +++ b/qwt/src/qwt_painter_command.h @@ -0,0 +1,173 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PAINTER_COMMAND_H +#define QWT_PAINTER_COMMAND_H + +#include "qwt_global.h" +#include +#include +#include +#include + +class QPainterPath; + +/*! + QwtPainterCommand represents the attributes of a paint operation + how it is used between QPainter and QPaintDevice + + It is used by QwtGraphic to record and replay paint operations + + \sa QwtGraphic::commands() + */ + +class QWT_EXPORT QwtPainterCommand +{ +public: + //! Type of the paint command + enum Type + { + //! Invalid command + Invalid = -1, + + //! Draw a QPainterPath + Path, + + //! Draw a QPixmap + Pixmap, + + //! Draw a QImage + Image, + + //! QPainter state change + State + }; + + //! Attributes how to paint a QPixmap + struct PixmapData + { + QRectF rect; + QPixmap pixmap; + QRectF subRect; + }; + + //! Attributes how to paint a QImage + struct ImageData + { + QRectF rect; + QImage image; + QRectF subRect; + Qt::ImageConversionFlags flags; + }; + + //! Attributes of a state change + struct StateData + { + QPaintEngine::DirtyFlags flags; + + QPen pen; + QBrush brush; + QPointF brushOrigin; + QBrush backgroundBrush; + Qt::BGMode backgroundMode; + QFont font; + QMatrix matrix; + QTransform transform; + + Qt::ClipOperation clipOperation; + QRegion clipRegion; + QPainterPath clipPath; + bool isClipEnabled; + + QPainter::RenderHints renderHints; + QPainter::CompositionMode compositionMode; + qreal opacity; + }; + + QwtPainterCommand(); + QwtPainterCommand(const QwtPainterCommand &); + + QwtPainterCommand( const QPainterPath & ); + + QwtPainterCommand( const QRectF &rect, + const QPixmap &, const QRectF& subRect ); + + QwtPainterCommand( const QRectF &rect, + const QImage &, const QRectF& subRect, + Qt::ImageConversionFlags ); + + QwtPainterCommand( const QPaintEngineState & ); + + ~QwtPainterCommand(); + + QwtPainterCommand &operator=(const QwtPainterCommand & ); + + Type type() const; + + QPainterPath *path(); + const QPainterPath *path() const; + + PixmapData* pixmapData(); + const PixmapData* pixmapData() const; + + ImageData* imageData(); + const ImageData* imageData() const; + + StateData* stateData(); + const StateData* stateData() const; + +private: + void copy( const QwtPainterCommand & ); + void reset(); + + Type d_type; + + union + { + QPainterPath *d_path; + PixmapData *d_pixmapData; + ImageData *d_imageData; + StateData *d_stateData; + }; +}; + +//! \return Type of the command +inline QwtPainterCommand::Type QwtPainterCommand::type() const +{ + return d_type; +} + +//! \return Painter path to be painted +inline const QPainterPath *QwtPainterCommand::path() const +{ + return d_path; +} + +//! \return Attributes how to paint a QPixmap +inline const QwtPainterCommand::PixmapData* +QwtPainterCommand::pixmapData() const +{ + return d_pixmapData; +} + +//! \return Attributes how to paint a QImage +inline const QwtPainterCommand::ImageData * +QwtPainterCommand::imageData() const +{ + return d_imageData; +} + +//! \return Attributes of a state change +inline const QwtPainterCommand::StateData * +QwtPainterCommand::stateData() const +{ + return d_stateData; +} + +#endif diff --git a/qwt/src/qwt_panner.cpp b/qwt/src/qwt_panner.cpp new file mode 100644 index 000000000..18497a916 --- /dev/null +++ b/qwt/src/qwt_panner.cpp @@ -0,0 +1,538 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_panner.h" +#include "qwt_picker.h" +#include "qwt_painter.h" +#include +#include +#include +#include +#include + +static QVector qwtActivePickers( QWidget *w ) +{ + QVector pickers; + + QObjectList children = w->children(); + for ( int i = 0; i < children.size(); i++ ) + { + QwtPicker *picker = qobject_cast( children[i] ); + if ( picker && picker->isEnabled() ) + pickers += picker; + } + + return pickers; +} + +class QwtPanner::PrivateData +{ +public: + PrivateData(): + button( Qt::LeftButton ), + buttonModifiers( Qt::NoModifier ), + abortKey( Qt::Key_Escape ), + abortKeyModifiers( Qt::NoModifier ), +#ifndef QT_NO_CURSOR + cursor( NULL ), + restoreCursor( NULL ), + hasCursor( false ), +#endif + isEnabled( false ) + { + orientations = Qt::Vertical | Qt::Horizontal; + } + + ~PrivateData() + { +#ifndef QT_NO_CURSOR + delete cursor; + delete restoreCursor; +#endif + } + + Qt::MouseButton button; + Qt::KeyboardModifiers buttonModifiers; + + int abortKey; + Qt::KeyboardModifiers abortKeyModifiers; + + QPoint initialPos; + QPoint pos; + + QPixmap pixmap; + QBitmap contentsMask; + +#ifndef QT_NO_CURSOR + QCursor *cursor; + QCursor *restoreCursor; + bool hasCursor; +#endif + bool isEnabled; + Qt::Orientations orientations; +}; + +/*! + Creates an panner that is enabled for the left mouse button. + + \param parent Parent widget to be panned +*/ +QwtPanner::QwtPanner( QWidget *parent ): + QWidget( parent ) +{ + d_data = new PrivateData(); + + setAttribute( Qt::WA_TransparentForMouseEvents ); + setAttribute( Qt::WA_NoSystemBackground ); + setFocusPolicy( Qt::NoFocus ); + hide(); + + setEnabled( true ); +} + +//! Destructor +QwtPanner::~QwtPanner() +{ + delete d_data; +} + +/*! + Change the mouse button and modifiers used for panning + The defaults are Qt::LeftButton and Qt::NoModifier +*/ +void QwtPanner::setMouseButton( Qt::MouseButton button, + Qt::KeyboardModifiers modifiers ) +{ + d_data->button = button; + d_data->buttonModifiers = modifiers; +} + +//! Get mouse button and modifiers used for panning +void QwtPanner::getMouseButton( Qt::MouseButton &button, + Qt::KeyboardModifiers &modifiers ) const +{ + button = d_data->button; + modifiers = d_data->buttonModifiers; +} + +/*! + Change the abort key + The defaults are Qt::Key_Escape and Qt::NoModifiers + + \param key Key ( See Qt::Keycode ) + \param modifiers Keyboard modifiers +*/ +void QwtPanner::setAbortKey( int key, + Qt::KeyboardModifiers modifiers ) +{ + d_data->abortKey = key; + d_data->abortKeyModifiers = modifiers; +} + +//! Get the abort key and modifiers +void QwtPanner::getAbortKey( int &key, + Qt::KeyboardModifiers &modifiers ) const +{ + key = d_data->abortKey; + modifiers = d_data->abortKeyModifiers; +} + +/*! + Change the cursor, that is active while panning + The default is the cursor of the parent widget. + + \param cursor New cursor + + \sa setCursor() +*/ +#ifndef QT_NO_CURSOR +void QwtPanner::setCursor( const QCursor &cursor ) +{ + d_data->cursor = new QCursor( cursor ); +} +#endif + +/*! + \return Cursor that is active while panning + \sa setCursor() +*/ +#ifndef QT_NO_CURSOR +const QCursor QwtPanner::cursor() const +{ + if ( d_data->cursor ) + return *d_data->cursor; + + if ( parentWidget() ) + return parentWidget()->cursor(); + + return QCursor(); +} +#endif + +/*! + \brief En/disable the panner + + When enabled is true an event filter is installed for + the observed widget, otherwise the event filter is removed. + + \param on true or false + \sa isEnabled(), eventFilter() +*/ +void QwtPanner::setEnabled( bool on ) +{ + if ( d_data->isEnabled != on ) + { + d_data->isEnabled = on; + + QWidget *w = parentWidget(); + if ( w ) + { + if ( d_data->isEnabled ) + { + w->installEventFilter( this ); + } + else + { + w->removeEventFilter( this ); + hide(); + } + } + } +} + +/*! + Set the orientations, where panning is enabled + The default value is in both directions: Qt::Horizontal | Qt::Vertical + + /param o Orientation +*/ +void QwtPanner::setOrientations( Qt::Orientations o ) +{ + d_data->orientations = o; +} + +//! Return the orientation, where paning is enabled +Qt::Orientations QwtPanner::orientations() const +{ + return d_data->orientations; +} + +/*! + \return True if an orientation is enabled + \sa orientations(), setOrientations() +*/ +bool QwtPanner::isOrientationEnabled( Qt::Orientation o ) const +{ + return d_data->orientations & o; +} + +/*! + \return true when enabled, false otherwise + \sa setEnabled, eventFilter() +*/ +bool QwtPanner::isEnabled() const +{ + return d_data->isEnabled; +} + +/*! + \brief Paint event + + Repaint the grabbed pixmap on its current position and + fill the empty spaces by the background of the parent widget. + + \param pe Paint event +*/ +void QwtPanner::paintEvent( QPaintEvent *pe ) +{ + int dx = d_data->pos.x() - d_data->initialPos.x(); + int dy = d_data->pos.y() - d_data->initialPos.y(); + + QRect r( 0, 0, d_data->pixmap.width(), d_data->pixmap.height() ); + r.moveCenter( QPoint( r.center().x() + dx, r.center().y() + dy ) ); + + QPixmap pm( size() ); + QwtPainter::fillPixmap( parentWidget(), pm ); + + QPainter painter( &pm ); + + if ( !d_data->contentsMask.isNull() ) + { + QPixmap masked = d_data->pixmap; + masked.setMask( d_data->contentsMask ); + painter.drawPixmap( r, masked ); + } + else + { + painter.drawPixmap( r, d_data->pixmap ); + } + + painter.end(); + + if ( !d_data->contentsMask.isNull() ) + pm.setMask( d_data->contentsMask ); + + painter.begin( this ); + painter.setClipRegion( pe->region() ); + painter.drawPixmap( 0, 0, pm ); +} + +/*! + \brief Calculate a mask for the contents of the panned widget + + Sometimes only parts of the contents of a widget should be + panned. F.e. for a widget with a styled background with rounded borders + only the area inside of the border should be panned. + + \return An empty bitmap, indicating no mask +*/ +QBitmap QwtPanner::contentsMask() const +{ + return QBitmap(); +} + +/*! + Grab the widget into a pixmap. + \return Grabbed pixmap +*/ +QPixmap QwtPanner::grab() const +{ +#if QT_VERSION >= 0x050000 + return parentWidget()->grab( parentWidget()->rect() ); +#else + return QPixmap::grabWidget( parentWidget() ); +#endif +} + +/*! + \brief Event filter + + When isEnabled() is true mouse events of the + observed widget are filtered. + + \param object Object to be filtered + \param event Event + + \return Always false, beside for paint events for the + parent widget. + + \sa widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent() +*/ +bool QwtPanner::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == NULL || object != parentWidget() ) + return false; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + widgetMousePressEvent( static_cast( event ) ); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent( static_cast( event ) ); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent( static_cast( event ) ); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent( static_cast( event ) ); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent( static_cast( event ) ); + break; + } + case QEvent::Paint: + { + if ( isVisible() ) + return true; + break; + } + default:; + } + + return false; +} + +/*! + Handle a mouse press event for the observed widget. + + \param mouseEvent Mouse event + \sa eventFilter(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent(), +*/ +void QwtPanner::widgetMousePressEvent( QMouseEvent *mouseEvent ) +{ + if ( ( mouseEvent->button() != d_data->button ) + || ( mouseEvent->modifiers() != d_data->buttonModifiers ) ) + { + return; + } + + QWidget *w = parentWidget(); + if ( w == NULL ) + return; + +#ifndef QT_NO_CURSOR + showCursor( true ); +#endif + + d_data->initialPos = d_data->pos = mouseEvent->pos(); + + setGeometry( parentWidget()->rect() ); + + // We don't want to grab the picker ! + QVector pickers = qwtActivePickers( parentWidget() ); + for ( int i = 0; i < pickers.size(); i++ ) + pickers[i]->setEnabled( false ); + + d_data->pixmap = grab(); + d_data->contentsMask = contentsMask(); + + for ( int i = 0; i < pickers.size(); i++ ) + pickers[i]->setEnabled( true ); + + show(); +} + +/*! + Handle a mouse move event for the observed widget. + + \param mouseEvent Mouse event + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent() +*/ +void QwtPanner::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) +{ + if ( !isVisible() ) + return; + + QPoint pos = mouseEvent->pos(); + if ( !isOrientationEnabled( Qt::Horizontal ) ) + pos.setX( d_data->initialPos.x() ); + if ( !isOrientationEnabled( Qt::Vertical ) ) + pos.setY( d_data->initialPos.y() ); + + if ( pos != d_data->pos && rect().contains( pos ) ) + { + d_data->pos = pos; + update(); + + Q_EMIT moved( d_data->pos.x() - d_data->initialPos.x(), + d_data->pos.y() - d_data->initialPos.y() ); + } +} + +/*! + Handle a mouse release event for the observed widget. + + \param mouseEvent Mouse event + \sa eventFilter(), widgetMousePressEvent(), + widgetMouseMoveEvent(), +*/ +void QwtPanner::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) +{ + if ( isVisible() ) + { + hide(); +#ifndef QT_NO_CURSOR + showCursor( false ); +#endif + + QPoint pos = mouseEvent->pos(); + if ( !isOrientationEnabled( Qt::Horizontal ) ) + pos.setX( d_data->initialPos.x() ); + if ( !isOrientationEnabled( Qt::Vertical ) ) + pos.setY( d_data->initialPos.y() ); + + d_data->pixmap = QPixmap(); + d_data->contentsMask = QBitmap(); + d_data->pos = pos; + + if ( d_data->pos != d_data->initialPos ) + { + Q_EMIT panned( d_data->pos.x() - d_data->initialPos.x(), + d_data->pos.y() - d_data->initialPos.y() ); + } + } +} + +/*! + Handle a key press event for the observed widget. + + \param keyEvent Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtPanner::widgetKeyPressEvent( QKeyEvent *keyEvent ) +{ + if ( ( keyEvent->key() == d_data->abortKey ) + && ( keyEvent->modifiers() == d_data->abortKeyModifiers ) ) + { + hide(); + +#ifndef QT_NO_CURSOR + showCursor( false ); +#endif + d_data->pixmap = QPixmap(); + } +} + +/*! + Handle a key release event for the observed widget. + + \param keyEvent Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtPanner::widgetKeyReleaseEvent( QKeyEvent *keyEvent ) +{ + Q_UNUSED( keyEvent ); +} + +#ifndef QT_NO_CURSOR +void QwtPanner::showCursor( bool on ) +{ + if ( on == d_data->hasCursor ) + return; + + QWidget *w = parentWidget(); + if ( w == NULL || d_data->cursor == NULL ) + return; + + d_data->hasCursor = on; + + if ( on ) + { + if ( w->testAttribute( Qt::WA_SetCursor ) ) + { + delete d_data->restoreCursor; + d_data->restoreCursor = new QCursor( w->cursor() ); + } + w->setCursor( *d_data->cursor ); + } + else + { + if ( d_data->restoreCursor ) + { + w->setCursor( *d_data->restoreCursor ); + delete d_data->restoreCursor; + d_data->restoreCursor = NULL; + } + else + w->unsetCursor(); + } +} +#endif diff --git a/qwt/src/qwt_panner.h b/qwt/src/qwt_panner.h new file mode 100644 index 000000000..a0c6873ab --- /dev/null +++ b/qwt/src/qwt_panner.h @@ -0,0 +1,103 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PANNER_H +#define QWT_PANNER_H 1 + +#include "qwt_global.h" +#include +#include + +class QCursor; + +/*! + \brief QwtPanner provides panning of a widget + + QwtPanner grabs the contents of a widget, that can be dragged + in all directions. The offset between the start and the end position + is emitted by the panned signal. + + QwtPanner grabs the content of the widget into a pixmap and moves + the pixmap around, without initiating any repaint events for the widget. + Areas, that are not part of content are not painted while panning. + This makes panning fast enough for widgets, where + repaints are too slow for mouse movements. + + For widgets, where repaints are very fast it might be better to + implement panning manually by mapping mouse events into paint events. +*/ +class QWT_EXPORT QwtPanner: public QWidget +{ + Q_OBJECT + +public: + QwtPanner( QWidget* parent ); + virtual ~QwtPanner(); + + void setEnabled( bool ); + bool isEnabled() const; + + void setMouseButton( Qt::MouseButton, + Qt::KeyboardModifiers = Qt::NoModifier ); + void getMouseButton( Qt::MouseButton &button, + Qt::KeyboardModifiers & ) const; + + void setAbortKey( int key, Qt::KeyboardModifiers = Qt::NoModifier ); + void getAbortKey( int &key, Qt::KeyboardModifiers & ) const; + + void setCursor( const QCursor & ); + const QCursor cursor() const; + + void setOrientations( Qt::Orientations ); + Qt::Orientations orientations() const; + + bool isOrientationEnabled( Qt::Orientation ) const; + + virtual bool eventFilter( QObject *, QEvent * ); + +Q_SIGNALS: + /*! + Signal emitted, when panning is done + + \param dx Offset in horizontal direction + \param dy Offset in vertical direction + */ + void panned( int dx, int dy ); + + /*! + Signal emitted, while the widget moved, but panning + is not finished. + + \param dx Offset in horizontal direction + \param dy Offset in vertical direction + */ + void moved( int dx, int dy ); + +protected: + virtual void widgetMousePressEvent( QMouseEvent * ); + virtual void widgetMouseReleaseEvent( QMouseEvent * ); + virtual void widgetMouseMoveEvent( QMouseEvent * ); + virtual void widgetKeyPressEvent( QKeyEvent * ); + virtual void widgetKeyReleaseEvent( QKeyEvent * ); + + virtual void paintEvent( QPaintEvent * ); + + virtual QBitmap contentsMask() const; + virtual QPixmap grab() const; + +private: +#ifndef QT_NO_CURSOR + void showCursor( bool ); +#endif + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_picker.cpp b/qwt/src/qwt_picker.cpp new file mode 100644 index 000000000..335ddf0cd --- /dev/null +++ b/qwt/src/qwt_picker.cpp @@ -0,0 +1,1577 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_picker.h" +#include "qwt_picker_machine.h" +#include "qwt_painter.h" +#include "qwt_math.h" +#include "qwt_widget_overlay.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline QRegion qwtMaskRegion( const QRect &r, int penWidth ) +{ + const int pw = qMax( penWidth, 1 ); + const int pw2 = penWidth / 2; + + int x1 = r.left() - pw2; + int x2 = r.right() + 1 + pw2 + ( pw % 2 ); + + int y1 = r.top() - pw2; + int y2 = r.bottom() + 1 + pw2 + ( pw % 2 ); + + QRegion region; + + region += QRect( x1, y1, x2 - x1, pw ); + region += QRect( x1, y1, pw, y2 - y1 ); + region += QRect( x1, y2 - pw, x2 - x1, pw ); + region += QRect( x2 - pw, y1, pw, y2 - y1 ); + + return region; +} + +static inline QRegion qwtMaskRegion( const QLine &l, int penWidth ) +{ + const int pw = qMax( penWidth, 1 ); + const int pw2 = penWidth / 2; + + QRegion region; + + if ( l.x1() == l.x2() ) + { + region += QRect( l.x1() - pw2, l.y1(), + pw, l.y2() ).normalized(); + } + else if ( l.y1() == l.y2() ) + { + region += QRect( l.x1(), l.y1() - pw2, + l.x2(), pw ).normalized(); + } + + return region; +} + +class QwtPickerRubberband: public QwtWidgetOverlay +{ +public: + QwtPickerRubberband( QwtPicker *, QWidget * ); + +protected: + virtual void drawOverlay( QPainter * ) const; + virtual QRegion maskHint() const; + + QwtPicker *d_picker; +}; + +class QwtPickerTracker: public QwtWidgetOverlay +{ +public: + QwtPickerTracker( QwtPicker *, QWidget * ); + +protected: + virtual void drawOverlay( QPainter * ) const; + virtual QRegion maskHint() const; + + QwtPicker *d_picker; +}; + + +class QwtPicker::PrivateData +{ +public: + PrivateData(): + enabled( false ), + stateMachine( NULL ), + resizeMode( QwtPicker::Stretch ), + rubberBand( QwtPicker::NoRubberBand ), + trackerMode( QwtPicker::AlwaysOff ), + isActive( false ), + trackerPosition( -1, -1 ), + mouseTracking( false ), + openGL( false ) + { + } + + bool enabled; + + QwtPickerMachine *stateMachine; + + QwtPicker::ResizeMode resizeMode; + + QwtPicker::RubberBand rubberBand; + QPen rubberBandPen; + + QwtPicker::DisplayMode trackerMode; + QPen trackerPen; + QFont trackerFont; + + QPolygon pickedPoints; + bool isActive; + QPoint trackerPosition; + + bool mouseTracking; // used to save previous value + + QPointer< QwtPickerRubberband > rubberBandOverlay; + QPointer< QwtPickerTracker> trackerOverlay; + + bool openGL; +}; + +QwtPickerRubberband::QwtPickerRubberband( + QwtPicker *picker, QWidget *parent ): + QwtWidgetOverlay( parent ), + d_picker( picker ) +{ + setMaskMode( QwtWidgetOverlay::MaskHint ); +} + +QRegion QwtPickerRubberband::maskHint() const +{ + return d_picker->rubberBandMask(); +} + +void QwtPickerRubberband::drawOverlay( QPainter *painter ) const +{ + painter->setPen( d_picker->rubberBandPen() ); + d_picker->drawRubberBand( painter ); +} + +QwtPickerTracker::QwtPickerTracker( + QwtPicker *picker, QWidget *parent ): + QwtWidgetOverlay( parent ), + d_picker( picker ) +{ + setMaskMode( QwtWidgetOverlay::MaskHint ); +} + +QRegion QwtPickerTracker::maskHint() const +{ + return d_picker->trackerRect( font() ); +} + +void QwtPickerTracker::drawOverlay( QPainter *painter ) const +{ + painter->setPen( d_picker->trackerPen() ); + d_picker->drawTracker( painter ); +} + +/*! + Constructor + + Creates an picker that is enabled, but without a state machine. + rubber band and tracker are disabled. + + \param parent Parent widget, that will be observed + */ + +QwtPicker::QwtPicker( QWidget *parent ): + QObject( parent ) +{ + init( parent, NoRubberBand, AlwaysOff ); +} + +/*! + Constructor + + \param rubberBand Rubber band style + \param trackerMode Tracker mode + \param parent Parent widget, that will be observed + */ +QwtPicker::QwtPicker( RubberBand rubberBand, + DisplayMode trackerMode, QWidget *parent ): + QObject( parent ) +{ + init( parent, rubberBand, trackerMode ); +} + +//! Destructor +QwtPicker::~QwtPicker() +{ + setMouseTracking( false ); + + delete d_data->stateMachine; + delete d_data->rubberBandOverlay; + delete d_data->trackerOverlay; + + delete d_data; +} + +//! Initialize the picker - used by the constructors +void QwtPicker::init( QWidget *parent, + RubberBand rubberBand, DisplayMode trackerMode ) +{ + d_data = new PrivateData; + + d_data->rubberBand = rubberBand; + + if ( parent ) + { + if ( parent->focusPolicy() == Qt::NoFocus ) + parent->setFocusPolicy( Qt::WheelFocus ); + + d_data->openGL = parent->inherits( "QGLWidget" ); + d_data->trackerFont = parent->font(); + d_data->mouseTracking = parent->hasMouseTracking(); + + setEnabled( true ); + } + + setTrackerMode( trackerMode ); +} + +/*! + Set a state machine and delete the previous one + + \param stateMachine State machine + \sa stateMachine() +*/ +void QwtPicker::setStateMachine( QwtPickerMachine *stateMachine ) +{ + if ( d_data->stateMachine != stateMachine ) + { + reset(); + + delete d_data->stateMachine; + d_data->stateMachine = stateMachine; + + if ( d_data->stateMachine ) + d_data->stateMachine->reset(); + } +} + +/*! + \return Assigned state machine + \sa setStateMachine() +*/ +QwtPickerMachine *QwtPicker::stateMachine() +{ + return d_data->stateMachine; +} + +/*! + \return Assigned state machine + \sa setStateMachine() +*/ +const QwtPickerMachine *QwtPicker::stateMachine() const +{ + return d_data->stateMachine; +} + +//! Return the parent widget, where the selection happens +QWidget *QwtPicker::parentWidget() +{ + QObject *obj = parent(); + if ( obj && obj->isWidgetType() ) + return static_cast( obj ); + + return NULL; +} + +//! Return the parent widget, where the selection happens +const QWidget *QwtPicker::parentWidget() const +{ + QObject *obj = parent(); + if ( obj && obj->isWidgetType() ) + return static_cast< const QWidget *>( obj ); + + return NULL; +} + +/*! + Set the rubber band style + + \param rubberBand Rubber band style + The default value is NoRubberBand. + + \sa rubberBand(), RubberBand, setRubberBandPen() +*/ +void QwtPicker::setRubberBand( RubberBand rubberBand ) +{ + d_data->rubberBand = rubberBand; +} + +/*! + \return Rubber band style + \sa setRubberBand(), RubberBand, rubberBandPen() +*/ +QwtPicker::RubberBand QwtPicker::rubberBand() const +{ + return d_data->rubberBand; +} + +/*! + \brief Set the display mode of the tracker. + + A tracker displays information about current position of + the cursor as a string. The display mode controls + if the tracker has to be displayed whenever the observed + widget has focus and cursor (AlwaysOn), never (AlwaysOff), or + only when the selection is active (ActiveOnly). + + \param mode Tracker display mode + + \warning In case of AlwaysOn, mouseTracking will be enabled + for the observed widget. + \sa trackerMode(), DisplayMode +*/ + +void QwtPicker::setTrackerMode( DisplayMode mode ) +{ + if ( d_data->trackerMode != mode ) + { + d_data->trackerMode = mode; + setMouseTracking( d_data->trackerMode == AlwaysOn ); + } +} + +/*! + \return Tracker display mode + \sa setTrackerMode(), DisplayMode +*/ +QwtPicker::DisplayMode QwtPicker::trackerMode() const +{ + return d_data->trackerMode; +} + +/*! + \brief Set the resize mode. + + The resize mode controls what to do with the selected points of an active + selection when the observed widget is resized. + + Stretch means the points are scaled according to the new + size, KeepSize means the points remain unchanged. + + The default mode is Stretch. + + \param mode Resize mode + \sa resizeMode(), ResizeMode +*/ +void QwtPicker::setResizeMode( ResizeMode mode ) +{ + d_data->resizeMode = mode; +} + +/*! + \return Resize mode + \sa setResizeMode(), ResizeMode +*/ + +QwtPicker::ResizeMode QwtPicker::resizeMode() const +{ + return d_data->resizeMode; +} + +/*! + \brief En/disable the picker + + When enabled is true an event filter is installed for + the observed widget, otherwise the event filter is removed. + + \param enabled true or false + \sa isEnabled(), eventFilter() +*/ +void QwtPicker::setEnabled( bool enabled ) +{ + if ( d_data->enabled != enabled ) + { + d_data->enabled = enabled; + + QWidget *w = parentWidget(); + if ( w ) + { + if ( enabled ) + w->installEventFilter( this ); + else + w->removeEventFilter( this ); + } + + updateDisplay(); + } +} + +/*! + \return true when enabled, false otherwise + \sa setEnabled(), eventFilter() +*/ + +bool QwtPicker::isEnabled() const +{ + return d_data->enabled; +} + +/*! + Set the font for the tracker + + \param font Tracker font + \sa trackerFont(), setTrackerMode(), setTrackerPen() +*/ +void QwtPicker::setTrackerFont( const QFont &font ) +{ + if ( font != d_data->trackerFont ) + { + d_data->trackerFont = font; + updateDisplay(); + } +} + +/*! + \return Tracker font + \sa setTrackerFont(), trackerMode(), trackerPen() +*/ + +QFont QwtPicker::trackerFont() const +{ + return d_data->trackerFont; +} + +/*! + Set the pen for the tracker + + \param pen Tracker pen + \sa trackerPen(), setTrackerMode(), setTrackerFont() +*/ +void QwtPicker::setTrackerPen( const QPen &pen ) +{ + if ( pen != d_data->trackerPen ) + { + d_data->trackerPen = pen; + updateDisplay(); + } +} + +/*! + \return Tracker pen + \sa setTrackerPen(), trackerMode(), trackerFont() +*/ +QPen QwtPicker::trackerPen() const +{ + return d_data->trackerPen; +} + +/*! + Set the pen for the rubberband + + \param pen Rubber band pen + \sa rubberBandPen(), setRubberBand() +*/ +void QwtPicker::setRubberBandPen( const QPen &pen ) +{ + if ( pen != d_data->rubberBandPen ) + { + d_data->rubberBandPen = pen; + updateDisplay(); + } +} + +/*! + \return Rubber band pen + \sa setRubberBandPen(), rubberBand() +*/ +QPen QwtPicker::rubberBandPen() const +{ + return d_data->rubberBandPen; +} + +/*! + \brief Return the label for a position + + In case of HLineRubberBand the label is the value of the + y position, in case of VLineRubberBand the value of the x position. + Otherwise the label contains x and y position separated by a ',' . + + The format for the string conversion is "%d". + + \param pos Position + \return Converted position as string +*/ + +QwtText QwtPicker::trackerText( const QPoint &pos ) const +{ + QString label; + + switch ( rubberBand() ) + { + case HLineRubberBand: + label.sprintf( "%d", pos.y() ); + break; + case VLineRubberBand: + label.sprintf( "%d", pos.x() ); + break; + default: + label.sprintf( "%d, %d", pos.x(), pos.y() ); + } + return label; +} + +/*! + Calculate the mask for the rubber band overlay + + \return Region for the mask + \sa QWidget::setMask() + */ +QRegion QwtPicker::rubberBandMask() const +{ + QRegion mask; + + if ( !isActive() || rubberBand() == NoRubberBand || + rubberBandPen().style() == Qt::NoPen ) + { + return mask; + } + + const QPolygon pa = adjustedPoints( d_data->pickedPoints ); + + QwtPickerMachine::SelectionType selectionType = + QwtPickerMachine::NoSelection; + + if ( d_data->stateMachine ) + selectionType = d_data->stateMachine->selectionType(); + + switch ( selectionType ) + { + case QwtPickerMachine::NoSelection: + case QwtPickerMachine::PointSelection: + { + if ( pa.count() < 1 ) + return mask; + + const QPoint pos = pa[0]; + const int pw = rubberBandPen().width(); + + const QRect pRect = pickArea().boundingRect().toRect(); + switch ( rubberBand() ) + { + case VLineRubberBand: + { + mask += qwtMaskRegion( QLine( pos.x(), pRect.top(), + pos.x(), pRect.bottom() ), pw ); + break; + } + case HLineRubberBand: + { + mask += qwtMaskRegion( QLine( pRect.left(), pos.y(), + pRect.right(), pos.y() ), pw ); + break; + } + case CrossRubberBand: + { + mask += qwtMaskRegion( QLine( pos.x(), pRect.top(), + pos.x(), pRect.bottom() ), pw ); + mask += qwtMaskRegion( QLine( pRect.left(), pos.y(), + pRect.right(), pos.y() ), pw ); + break; + } + default: + break; + } + break; + } + case QwtPickerMachine::RectSelection: + { + if ( pa.count() < 2 ) + return mask; + + const int pw = rubberBandPen().width(); + + switch ( rubberBand() ) + { + case RectRubberBand: + { + const QRect r = QRect( pa.first(), pa.last() ); + mask = qwtMaskRegion( r.normalized(), pw ); + break; + } + case EllipseRubberBand: + { + const QRect r = QRect( pa.first(), pa.last() ); + mask += r.adjusted( -pw, -pw, pw, pw ); + break; + } + default: + break; + } + break; + } + case QwtPickerMachine::PolygonSelection: + { + const int pw = rubberBandPen().width(); + if ( pw <= 1 ) + { + // because of the join style we better + // return a mask for a pen width <= 1 only + + const int off = 2 * pw; + const QRect r = pa.boundingRect(); + mask += r.adjusted( -off, -off, off, off ); + } + break; + } + default: + break; + } + + return mask; +} + +/*! + Draw a rubber band, depending on rubberBand() + + \param painter Painter, initialized with a clip region + + \sa rubberBand(), RubberBand +*/ + +void QwtPicker::drawRubberBand( QPainter *painter ) const +{ + if ( !isActive() || rubberBand() == NoRubberBand || + rubberBandPen().style() == Qt::NoPen ) + { + return; + } + + const QPolygon pa = adjustedPoints( d_data->pickedPoints ); + + QwtPickerMachine::SelectionType selectionType = + QwtPickerMachine::NoSelection; + + if ( d_data->stateMachine ) + selectionType = d_data->stateMachine->selectionType(); + + switch ( selectionType ) + { + case QwtPickerMachine::NoSelection: + case QwtPickerMachine::PointSelection: + { + if ( pa.count() < 1 ) + return; + + const QPoint pos = pa[0]; + + const QRect pRect = pickArea().boundingRect().toRect(); + switch ( rubberBand() ) + { + case VLineRubberBand: + { + QwtPainter::drawLine( painter, pos.x(), + pRect.top(), pos.x(), pRect.bottom() ); + break; + } + case HLineRubberBand: + { + QwtPainter::drawLine( painter, pRect.left(), + pos.y(), pRect.right(), pos.y() ); + break; + } + case CrossRubberBand: + { + QwtPainter::drawLine( painter, pos.x(), + pRect.top(), pos.x(), pRect.bottom() ); + QwtPainter::drawLine( painter, pRect.left(), + pos.y(), pRect.right(), pos.y() ); + break; + } + default: + break; + } + break; + } + case QwtPickerMachine::RectSelection: + { + if ( pa.count() < 2 ) + return; + + const QRect rect = QRect( pa.first(), pa.last() ).normalized(); + switch ( rubberBand() ) + { + case EllipseRubberBand: + { + QwtPainter::drawEllipse( painter, rect ); + break; + } + case RectRubberBand: + { + QwtPainter::drawRect( painter, rect ); + break; + } + default: + break; + } + break; + } + case QwtPickerMachine::PolygonSelection: + { + if ( rubberBand() == PolygonRubberBand ) + painter->drawPolyline( pa ); + break; + } + default: + break; + } +} + +/*! + Draw the tracker + + \param painter Painter + \sa trackerRect(), trackerText() +*/ + +void QwtPicker::drawTracker( QPainter *painter ) const +{ + const QRect textRect = trackerRect( painter->font() ); + if ( !textRect.isEmpty() ) + { + const QwtText label = trackerText( d_data->trackerPosition ); + if ( !label.isEmpty() ) + label.draw( painter, textRect ); + } +} + +/*! + \brief Map the pickedPoints() into a selection() + + adjustedPoints() maps the points, that have been collected on + the parentWidget() into a selection(). The default implementation + simply returns the points unmodified. + + The reason, why a selection() differs from the picked points + depends on the application requirements. F.e. : + + - A rectangular selection might need to have a specific aspect ratio only.\n + - A selection could accept non intersecting polygons only.\n + - ...\n + + The example below is for a rectangular selection, where the first + point is the center of the selected rectangle. + \par Example + \verbatim QPolygon MyPicker::adjustedPoints(const QPolygon &points) const +{ + QPolygon adjusted; + if ( points.size() == 2 ) + { + const int width = qAbs(points[1].x() - points[0].x()); + const int height = qAbs(points[1].y() - points[0].y()); + + QRect rect(0, 0, 2 * width, 2 * height); + rect.moveCenter(points[0]); + + adjusted += rect.topLeft(); + adjusted += rect.bottomRight(); + } + return adjusted; +}\endverbatim\n + + \param points Selected points + \return Selected points unmodified +*/ +QPolygon QwtPicker::adjustedPoints( const QPolygon &points ) const +{ + return points; +} + +/*! + \return Selected points + \sa pickedPoints(), adjustedPoints() +*/ +QPolygon QwtPicker::selection() const +{ + return adjustedPoints( d_data->pickedPoints ); +} + +//! \return Current position of the tracker +QPoint QwtPicker::trackerPosition() const +{ + return d_data->trackerPosition; +} + +/*! + Calculate the bounding rectangle for the tracker text + from the current position of the tracker + + \param font Font of the tracker text + \return Bounding rectangle of the tracker text + + \sa trackerPosition() +*/ +QRect QwtPicker::trackerRect( const QFont &font ) const +{ + if ( trackerMode() == AlwaysOff || + ( trackerMode() == ActiveOnly && !isActive() ) ) + { + return QRect(); + } + + if ( d_data->trackerPosition.x() < 0 || d_data->trackerPosition.y() < 0 ) + return QRect(); + + QwtText text = trackerText( d_data->trackerPosition ); + if ( text.isEmpty() ) + return QRect(); + + const QSizeF textSize = text.textSize( font ); + QRect textRect( 0, 0, qCeil( textSize.width() ), qCeil( textSize.height() ) ); + + const QPoint &pos = d_data->trackerPosition; + + int alignment = 0; + if ( isActive() && d_data->pickedPoints.count() > 1 + && rubberBand() != NoRubberBand ) + { + const QPoint last = + d_data->pickedPoints[int( d_data->pickedPoints.count() ) - 2]; + + alignment |= ( pos.x() >= last.x() ) ? Qt::AlignRight : Qt::AlignLeft; + alignment |= ( pos.y() > last.y() ) ? Qt::AlignBottom : Qt::AlignTop; + } + else + alignment = Qt::AlignTop | Qt::AlignRight; + + const int margin = 5; + + int x = pos.x(); + if ( alignment & Qt::AlignLeft ) + x -= textRect.width() + margin; + else if ( alignment & Qt::AlignRight ) + x += margin; + + int y = pos.y(); + if ( alignment & Qt::AlignBottom ) + y += margin; + else if ( alignment & Qt::AlignTop ) + y -= textRect.height() + margin; + + textRect.moveTopLeft( QPoint( x, y ) ); + + const QRect pickRect = pickArea().boundingRect().toRect(); + + int right = qMin( textRect.right(), pickRect.right() - margin ); + int bottom = qMin( textRect.bottom(), pickRect.bottom() - margin ); + textRect.moveBottomRight( QPoint( right, bottom ) ); + + int left = qMax( textRect.left(), pickRect.left() + margin ); + int top = qMax( textRect.top(), pickRect.top() + margin ); + textRect.moveTopLeft( QPoint( left, top ) ); + + return textRect; +} + +/*! + \brief Event filter + + When isEnabled() is true all events of the observed widget are filtered. + Mouse and keyboard events are translated into widgetMouse- and widgetKey- + and widgetWheel-events. Paint and Resize events are handled to keep + rubber band and tracker up to date. + + \param object Object to be filtered + \param event Event + + \return Always false. + + \sa widgetEnterEvent(), widgetLeaveEvent(), + widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent(), + QObject::installEventFilter(), QObject::event() +*/ +bool QwtPicker::eventFilter( QObject *object, QEvent *event ) +{ + if ( object && object == parentWidget() ) + { + switch ( event->type() ) + { + case QEvent::Resize: + { + const QResizeEvent *re = static_cast( event ); + if ( d_data->resizeMode == Stretch ) + stretchSelection( re->oldSize(), re->size() ); + + break; + } + case QEvent::Enter: + { + widgetEnterEvent( event ); + break; + } + case QEvent::Leave: + { + widgetLeaveEvent( event ); + break; + } + case QEvent::MouseButtonPress: + { + widgetMousePressEvent( static_cast( event ) ); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent( static_cast( event ) ); + break; + } + case QEvent::MouseButtonDblClick: + { + widgetMouseDoubleClickEvent( static_cast( event ) ); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent( static_cast( event ) ); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent( static_cast( event ) ); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent( static_cast( event ) ); + break; + } + case QEvent::Wheel: + { + widgetWheelEvent( static_cast( event ) ); + break; + } + default: + break; + } + } + return false; +} + +/*! + Handle a mouse press event for the observed widget. + + \param mouseEvent Mouse event + + \sa eventFilter(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMousePressEvent( QMouseEvent *mouseEvent ) +{ + transition( mouseEvent ); +} + +/*! + Handle a mouse move event for the observed widget. + + \param mouseEvent Mouse event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) +{ + if ( pickArea().contains( mouseEvent->pos() ) ) + d_data->trackerPosition = mouseEvent->pos(); + else + d_data->trackerPosition = QPoint( -1, -1 ); + + if ( !isActive() ) + updateDisplay(); + + transition( mouseEvent ); +} + +/*! + Handle a enter event for the observed widget. + + \param event Qt event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetEnterEvent( QEvent *event ) +{ + transition( event ); +} + +/*! + Handle a leave event for the observed widget. + + \param event Qt event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetLeaveEvent( QEvent *event ) +{ + transition( event ); + + d_data->trackerPosition = QPoint( -1, -1 ); + if ( !isActive() ) + updateDisplay(); +} + +/*! + Handle a mouse release event for the observed widget. + + \param mouseEvent Mouse event + + \sa eventFilter(), widgetMousePressEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) +{ + transition( mouseEvent ); +} + +/*! + Handle mouse double click event for the observed widget. + + \param mouseEvent Mouse event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMouseDoubleClickEvent( QMouseEvent *mouseEvent ) +{ + transition( mouseEvent ); +} + + +/*! + Handle a wheel event for the observed widget. + + Move the last point of the selection in case of isActive() == true + + \param wheelEvent Wheel event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetWheelEvent( QWheelEvent *wheelEvent ) +{ + if ( pickArea().contains( wheelEvent->pos() ) ) + d_data->trackerPosition = wheelEvent->pos(); + else + d_data->trackerPosition = QPoint( -1, -1 ); + + updateDisplay(); + + transition( wheelEvent ); +} + +/*! + Handle a key press event for the observed widget. + + Selections can be completely done by the keyboard. The arrow keys + move the cursor, the abort key aborts a selection. All other keys + are handled by the current state machine. + + \param keyEvent Key event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyReleaseEvent(), stateMachine(), + QwtEventPattern::KeyPatternCode +*/ +void QwtPicker::widgetKeyPressEvent( QKeyEvent *keyEvent ) +{ + int dx = 0; + int dy = 0; + + int offset = 1; + if ( keyEvent->isAutoRepeat() ) + offset = 5; + + if ( keyMatch( KeyLeft, keyEvent ) ) + dx = -offset; + else if ( keyMatch( KeyRight, keyEvent ) ) + dx = offset; + else if ( keyMatch( KeyUp, keyEvent ) ) + dy = -offset; + else if ( keyMatch( KeyDown, keyEvent ) ) + dy = offset; + else if ( keyMatch( KeyAbort, keyEvent ) ) + { + reset(); + } + else + transition( keyEvent ); + + if ( dx != 0 || dy != 0 ) + { + const QRect rect = pickArea().boundingRect().toRect(); + const QPoint pos = parentWidget()->mapFromGlobal( QCursor::pos() ); + + int x = pos.x() + dx; + x = qMax( rect.left(), x ); + x = qMin( rect.right(), x ); + + int y = pos.y() + dy; + y = qMax( rect.top(), y ); + y = qMin( rect.bottom(), y ); + + QCursor::setPos( parentWidget()->mapToGlobal( QPoint( x, y ) ) ); + } +} + +/*! + Handle a key release event for the observed widget. + + Passes the event to the state machine. + + \param keyEvent Key event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), stateMachine() +*/ +void QwtPicker::widgetKeyReleaseEvent( QKeyEvent *keyEvent ) +{ + transition( keyEvent ); +} + +/*! + Passes an event to the state machine and executes the resulting + commands. Append and Move commands use the current position + of the cursor ( QCursor::pos() ). + + \param event Event +*/ +void QwtPicker::transition( const QEvent *event ) +{ + if ( !d_data->stateMachine ) + return; + + const QList commandList = + d_data->stateMachine->transition( *this, event ); + + QPoint pos; + switch ( event->type() ) + { + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + { + const QMouseEvent *me = + static_cast< const QMouseEvent * >( event ); + pos = me->pos(); + break; + } + default: + pos = parentWidget()->mapFromGlobal( QCursor::pos() ); + } + + for ( int i = 0; i < commandList.count(); i++ ) + { + switch ( commandList[i] ) + { + case QwtPickerMachine::Begin: + { + begin(); + break; + } + case QwtPickerMachine::Append: + { + append( pos ); + break; + } + case QwtPickerMachine::Move: + { + move( pos ); + break; + } + case QwtPickerMachine::Remove: + { + remove(); + break; + } + case QwtPickerMachine::End: + { + end(); + break; + } + } + } +} + +/*! + Open a selection setting the state to active + + \sa isActive(), end(), append(), move() +*/ +void QwtPicker::begin() +{ + if ( d_data->isActive ) + return; + + d_data->pickedPoints.resize( 0 ); + d_data->isActive = true; + Q_EMIT activated( true ); + + if ( trackerMode() != AlwaysOff ) + { + if ( d_data->trackerPosition.x() < 0 || d_data->trackerPosition.y() < 0 ) + { + QWidget *w = parentWidget(); + if ( w ) + d_data->trackerPosition = w->mapFromGlobal( QCursor::pos() ); + } + } + + updateDisplay(); + setMouseTracking( true ); +} + +/*! + \brief Close a selection setting the state to inactive. + + The selection is validated and maybe fixed by accept(). + + \param ok If true, complete the selection and emit a selected signal + otherwise discard the selection. + \return true if the selection is accepted, false otherwise + \sa isActive(), begin(), append(), move(), selected(), accept() +*/ +bool QwtPicker::end( bool ok ) +{ + if ( d_data->isActive ) + { + setMouseTracking( false ); + + d_data->isActive = false; + Q_EMIT activated( false ); + + if ( trackerMode() == ActiveOnly ) + d_data->trackerPosition = QPoint( -1, -1 ); + + if ( ok ) + ok = accept( d_data->pickedPoints ); + + if ( ok ) + Q_EMIT selected( d_data->pickedPoints ); + else + d_data->pickedPoints.resize( 0 ); + + updateDisplay(); + } + else + ok = false; + + return ok; +} + +/*! + Reset the state machine and terminate ( end(false) ) the selection +*/ +void QwtPicker::reset() +{ + if ( d_data->stateMachine ) + d_data->stateMachine->reset(); + + if ( isActive() ) + end( false ); +} + +/*! + Append a point to the selection and update rubber band and tracker. + The appended() signal is emitted. + + \param pos Additional point + + \sa isActive(), begin(), end(), move(), appended() +*/ +void QwtPicker::append( const QPoint &pos ) +{ + if ( d_data->isActive ) + { + const int idx = d_data->pickedPoints.count(); + d_data->pickedPoints.resize( idx + 1 ); + d_data->pickedPoints[idx] = pos; + + updateDisplay(); + Q_EMIT appended( pos ); + } +} + +/*! + Move the last point of the selection + The moved() signal is emitted. + + \param pos New position + \sa isActive(), begin(), end(), append() +*/ +void QwtPicker::move( const QPoint &pos ) +{ + if ( d_data->isActive ) + { + const int idx = d_data->pickedPoints.count() - 1; + if ( idx >= 0 ) + { + if ( d_data->pickedPoints[idx] != pos ) + { + d_data->pickedPoints[idx] = pos; + + updateDisplay(); + Q_EMIT moved( pos ); + } + } + } +} + +/*! + Remove the last point of the selection + The removed() signal is emitted. + + \sa isActive(), begin(), end(), append(), move() +*/ +void QwtPicker::remove() +{ + if ( d_data->isActive ) + { + const int idx = d_data->pickedPoints.count() - 1; + if ( idx > 0 ) + { + const int idx = d_data->pickedPoints.count(); + + const QPoint pos = d_data->pickedPoints[idx - 1]; + d_data->pickedPoints.resize( idx - 1 ); + + updateDisplay(); + Q_EMIT removed( pos ); + } + } +} + +/*! + \brief Validate and fix up the selection + + Accepts all selections unmodified + + \param selection Selection to validate and fix up + \return true, when accepted, false otherwise +*/ +bool QwtPicker::accept( QPolygon &selection ) const +{ + Q_UNUSED( selection ); + return true; +} + +/*! + A picker is active between begin() and end(). + \return true if the selection is active. +*/ +bool QwtPicker::isActive() const +{ + return d_data->isActive; +} + +/*! + Return the points, that have been collected so far. The selection() + is calculated from the pickedPoints() in adjustedPoints(). + \return Picked points +*/ +const QPolygon &QwtPicker::pickedPoints() const +{ + return d_data->pickedPoints; +} + +/*! + Scale the selection by the ratios of oldSize and newSize + The changed() signal is emitted. + + \param oldSize Previous size + \param newSize Current size + + \sa ResizeMode, setResizeMode(), resizeMode() +*/ +void QwtPicker::stretchSelection( const QSize &oldSize, const QSize &newSize ) +{ + if ( oldSize.isEmpty() ) + { + // avoid division by zero. But scaling for small sizes also + // doesn't make much sense, because of rounding losses. TODO ... + return; + } + + const double xRatio = + double( newSize.width() ) / double( oldSize.width() ); + const double yRatio = + double( newSize.height() ) / double( oldSize.height() ); + + for ( int i = 0; i < int( d_data->pickedPoints.count() ); i++ ) + { + QPoint &p = d_data->pickedPoints[i]; + p.setX( qRound( p.x() * xRatio ) ); + p.setY( qRound( p.y() * yRatio ) ); + + Q_EMIT changed( d_data->pickedPoints ); + } +} + +/*! + Set mouse tracking for the observed widget. + + In case of enable is true, the previous value + is saved, that is restored when enable is false. + + \warning Even when enable is false, mouse tracking might be restored + to true. When mouseTracking for the observed widget + has been changed directly by QWidget::setMouseTracking + while mouse tracking has been set to true, this value can't + be restored. +*/ + +void QwtPicker::setMouseTracking( bool enable ) +{ + QWidget *widget = parentWidget(); + if ( !widget ) + return; + + if ( enable ) + { + d_data->mouseTracking = widget->hasMouseTracking(); + widget->setMouseTracking( true ); + } + else + { + widget->setMouseTracking( d_data->mouseTracking ); + } +} + +/*! + Find the area of the observed widget, where selection might happen. + + \return parentWidget()->contentsRect() +*/ +QPainterPath QwtPicker::pickArea() const +{ + QPainterPath path; + + const QWidget *widget = parentWidget(); + if ( widget ) + path.addRect( widget->contentsRect() ); + + return path; +} + +//! Update the state of rubber band and tracker label +void QwtPicker::updateDisplay() +{ + QWidget *w = parentWidget(); + + bool showRubberband = false; + bool showTracker = false; + + if ( w && w->isVisible() && d_data->enabled ) + { + if ( rubberBand() != NoRubberBand && isActive() && + rubberBandPen().style() != Qt::NoPen ) + { + showRubberband = true; + } + + if ( trackerMode() == AlwaysOn || + ( trackerMode() == ActiveOnly && isActive() ) ) + { + if ( trackerPen() != Qt::NoPen + && !trackerRect( QFont() ).isEmpty() ) + { + showTracker = true; + } + } + } + + QPointer< QwtPickerRubberband > &rw = d_data->rubberBandOverlay; + if ( showRubberband ) + { + if ( rw.isNull() ) + { + rw = new QwtPickerRubberband( this, w ); + rw->setObjectName( "PickerRubberBand" ); + rw->resize( w->size() ); + } + + if ( d_data->rubberBand <= RectRubberBand ) + rw->setMaskMode( QwtWidgetOverlay::MaskHint ); + else + rw->setMaskMode( QwtWidgetOverlay::AlphaMask ); + + rw->updateOverlay(); + } + else + { + if ( d_data->openGL ) + { + // Qt 4.8 crashes for a delete + if ( !rw.isNull() ) + { + rw->hide(); + rw->deleteLater(); + rw = NULL; + } + } + else + { + delete rw; + } + } + + QPointer< QwtPickerTracker > &tw = d_data->trackerOverlay; + if ( showTracker ) + { + if ( tw.isNull() ) + { + tw = new QwtPickerTracker( this, w ); + tw->setObjectName( "PickerTracker" ); + tw->resize( w->size() ); + } + tw->setFont( d_data->trackerFont ); + tw->updateOverlay(); + } + else + { + if ( d_data->openGL ) + { + // Qt 4.8 crashes for a delete + if ( !tw.isNull() ) + { + tw->hide(); + tw->deleteLater(); + tw = NULL; + } + } + else + { + delete tw; + } + } +} + +//! \return Overlay displaying the rubber band +const QwtWidgetOverlay *QwtPicker::rubberBandOverlay() const +{ + return d_data->rubberBandOverlay; +} + +//! \return Overlay displaying the tracker text +const QwtWidgetOverlay *QwtPicker::trackerOverlay() const +{ + return d_data->trackerOverlay; +} + diff --git a/qwt/src/qwt_picker.h b/qwt/src/qwt_picker.h new file mode 100644 index 000000000..87d6805e9 --- /dev/null +++ b/qwt/src/qwt_picker.h @@ -0,0 +1,329 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PICKER +#define QWT_PICKER 1 + +#include "qwt_global.h" +#include "qwt_text.h" +#include "qwt_event_pattern.h" +#include +#include +#include +#include +#include + +class QWidget; +class QMouseEvent; +class QWheelEvent; +class QKeyEvent; +class QwtPickerMachine; +class QwtWidgetOverlay; + +/*! + \brief QwtPicker provides selections on a widget + + QwtPicker filters all enter, leave, mouse and keyboard events of a widget + and translates them into an array of selected points. + + The way how the points are collected depends on type of state machine + that is connected to the picker. Qwt offers a couple of predefined + state machines for selecting: + + - Nothing\n + QwtPickerTrackerMachine + - Single points\n + QwtPickerClickPointMachine, QwtPickerDragPointMachine + - Rectangles\n + QwtPickerClickRectMachine, QwtPickerDragRectMachine + - Polygons\n + QwtPickerPolygonMachine + + While these state machines cover the most common ways to collect points + it is also possible to implement individual machines as well. + + QwtPicker translates the picked points into a selection using the + adjustedPoints() method. adjustedPoints() is intended to be reimplemented + to fix up the selection according to application specific requirements. + (F.e. when an application accepts rectangles of a fixed aspect ratio only.) + + Optionally QwtPicker support the process of collecting points by a + rubber band and tracker displaying a text for the current mouse + position. + + \par Example + \verbatim #include +#include + +QwtPicker *picker = new QwtPicker(widget); +picker->setStateMachine(new QwtPickerDragRectMachine); +picker->setTrackerMode(QwtPicker::ActiveOnly); +picker->setRubberBand(QwtPicker::RectRubberBand); \endverbatim\n + + The state machine triggers the following commands: + + - begin()\n + Activate/Initialize the selection. + - append()\n + Add a new point + - move() \n + Change the position of the last point. + - remove()\n + Remove the last point. + - end()\n + Terminate the selection and call accept to validate the picked points. + + The picker is active (isActive()), between begin() and end(). + In active state the rubber band is displayed, and the tracker is visible + in case of trackerMode is ActiveOnly or AlwaysOn. + + The cursor can be moved using the arrow keys. All selections can be aborted + using the abort key. (QwtEventPattern::KeyPatternCode) + + \warning In case of QWidget::NoFocus the focus policy of the observed + widget is set to QWidget::WheelFocus and mouse tracking + will be manipulated while the picker is active, + or if trackerMode() is AlwayOn. +*/ + +class QWT_EXPORT QwtPicker: public QObject, public QwtEventPattern +{ + Q_OBJECT + + Q_ENUMS( RubberBand DisplayMode ResizeMode ) + + Q_PROPERTY( bool isEnabled READ isEnabled WRITE setEnabled ) + Q_PROPERTY( ResizeMode resizeMode READ resizeMode WRITE setResizeMode ) + + Q_PROPERTY( DisplayMode trackerMode READ trackerMode WRITE setTrackerMode ) + Q_PROPERTY( QPen trackerPen READ trackerPen WRITE setTrackerPen ) + Q_PROPERTY( QFont trackerFont READ trackerFont WRITE setTrackerFont ) + + Q_PROPERTY( RubberBand rubberBand READ rubberBand WRITE setRubberBand ) + Q_PROPERTY( QPen rubberBandPen READ rubberBandPen WRITE setRubberBandPen ) + +public: + /*! + Rubber band style + + The default value is QwtPicker::NoRubberBand. + \sa setRubberBand(), rubberBand() + */ + + enum RubberBand + { + //! No rubberband. + NoRubberBand = 0, + + //! A horizontal line ( only for QwtPickerMachine::PointSelection ) + HLineRubberBand, + + //! A vertical line ( only for QwtPickerMachine::PointSelection ) + VLineRubberBand, + + //! A crosshair ( only for QwtPickerMachine::PointSelection ) + CrossRubberBand, + + //! A rectangle ( only for QwtPickerMachine::RectSelection ) + RectRubberBand, + + //! An ellipse ( only for QwtPickerMachine::RectSelection ) + EllipseRubberBand, + + //! A polygon ( only for QwtPickerMachine::PolygonSelection ) + PolygonRubberBand, + + /*! + Values >= UserRubberBand can be used to define additional + rubber bands. + */ + UserRubberBand = 100 + }; + + /*! + \brief Display mode + \sa setTrackerMode(), trackerMode(), isActive() + */ + enum DisplayMode + { + //! Display never + AlwaysOff, + + //! Display always + AlwaysOn, + + //! Display only when the selection is active + ActiveOnly + }; + + /*! + Controls what to do with the selected points of an active + selection when the observed widget is resized. + + The default value is QwtPicker::Stretch. + \sa setResizeMode() + */ + + enum ResizeMode + { + //! All points are scaled according to the new size, + Stretch, + + //! All points remain unchanged. + KeepSize + }; + + explicit QwtPicker( QWidget *parent ); + explicit QwtPicker( RubberBand rubberBand, + DisplayMode trackerMode, QWidget * ); + + virtual ~QwtPicker(); + + void setStateMachine( QwtPickerMachine * ); + const QwtPickerMachine *stateMachine() const; + QwtPickerMachine *stateMachine(); + + void setRubberBand( RubberBand ); + RubberBand rubberBand() const; + + void setTrackerMode( DisplayMode ); + DisplayMode trackerMode() const; + + void setResizeMode( ResizeMode ); + ResizeMode resizeMode() const; + + void setRubberBandPen( const QPen & ); + QPen rubberBandPen() const; + + void setTrackerPen( const QPen & ); + QPen trackerPen() const; + + void setTrackerFont( const QFont & ); + QFont trackerFont() const; + + bool isEnabled() const; + bool isActive() const; + + virtual bool eventFilter( QObject *, QEvent * ); + + QWidget *parentWidget(); + const QWidget *parentWidget() const; + + virtual QPainterPath pickArea() const; + + virtual void drawRubberBand( QPainter * ) const; + virtual void drawTracker( QPainter * ) const; + + virtual QRegion rubberBandMask() const; + + virtual QwtText trackerText( const QPoint &pos ) const; + QPoint trackerPosition() const; + virtual QRect trackerRect( const QFont & ) const; + + QPolygon selection() const; + +public Q_SLOTS: + void setEnabled( bool ); + +Q_SIGNALS: + /*! + A signal indicating, when the picker has been activated. + Together with setEnabled() it can be used to implement + selections with more than one picker. + + \param on True, when the picker has been activated + */ + void activated( bool on ); + + /*! + A signal emitting the selected points, + at the end of a selection. + + \param polygon Selected points + */ + void selected( const QPolygon &polygon ); + + /*! + A signal emitted when a point has been appended to the selection + + \param pos Position of the appended point. + \sa append(). moved() + */ + void appended( const QPoint &pos ); + + /*! + A signal emitted whenever the last appended point of the + selection has been moved. + + \param pos Position of the moved last point of the selection. + \sa move(), appended() + */ + void moved( const QPoint &pos ); + + /*! + A signal emitted whenever the last appended point of the + selection has been removed. + + \param pos Position of the point, that has been removed + \sa remove(), appended() + */ + void removed( const QPoint &pos ); + /*! + A signal emitted when the active selection has been changed. + This might happen when the observed widget is resized. + + \param selection Changed selection + \sa stretchSelection() + */ + void changed( const QPolygon &selection ); + +protected: + virtual QPolygon adjustedPoints( const QPolygon & ) const; + + virtual void transition( const QEvent * ); + + virtual void begin(); + virtual void append( const QPoint & ); + virtual void move( const QPoint & ); + virtual void remove(); + virtual bool end( bool ok = true ); + + virtual bool accept( QPolygon & ) const; + virtual void reset(); + + virtual void widgetMousePressEvent( QMouseEvent * ); + virtual void widgetMouseReleaseEvent( QMouseEvent * ); + virtual void widgetMouseDoubleClickEvent( QMouseEvent * ); + virtual void widgetMouseMoveEvent( QMouseEvent * ); + virtual void widgetWheelEvent( QWheelEvent * ); + virtual void widgetKeyPressEvent( QKeyEvent * ); + virtual void widgetKeyReleaseEvent( QKeyEvent * ); + virtual void widgetEnterEvent( QEvent * ); + virtual void widgetLeaveEvent( QEvent * ); + + virtual void stretchSelection( const QSize &oldSize, + const QSize &newSize ); + + virtual void updateDisplay(); + + const QwtWidgetOverlay *rubberBandOverlay() const; + const QwtWidgetOverlay *trackerOverlay() const; + + const QPolygon &pickedPoints() const; + +private: + void init( QWidget *, RubberBand rubberBand, DisplayMode trackerMode ); + + void setMouseTracking( bool ); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_picker_machine.cpp b/qwt/src/qwt_picker_machine.cpp new file mode 100644 index 000000000..299624ed8 --- /dev/null +++ b/qwt/src/qwt_picker_machine.cpp @@ -0,0 +1,526 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_picker_machine.h" +#include "qwt_event_pattern.h" +#include + +//! Constructor +QwtPickerMachine::QwtPickerMachine( SelectionType type ): + d_selectionType( type ), + d_state( 0 ) +{ +} + +//! Destructor +QwtPickerMachine::~QwtPickerMachine() +{ +} + +//! Return the selection type +QwtPickerMachine::SelectionType QwtPickerMachine::selectionType() const +{ + return d_selectionType; +} + +//! Return the current state +int QwtPickerMachine::state() const +{ + return d_state; +} + +//! Change the current state +void QwtPickerMachine::setState( int state ) +{ + d_state = state; +} + +//! Set the current state to 0. +void QwtPickerMachine::reset() +{ + setState( 0 ); +} + +//! Constructor +QwtPickerTrackerMachine::QwtPickerTrackerMachine(): + QwtPickerMachine( NoSelection ) +{ +} + +//! Transition +QList QwtPickerTrackerMachine::transition( + const QwtEventPattern &, const QEvent *e ) +{ + QList cmdList; + + switch ( e->type() ) + { + case QEvent::Enter: + case QEvent::MouseMove: + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += Move; + } + break; + } + case QEvent::Leave: + { + cmdList += Remove; + cmdList += End; + setState( 0 ); + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerClickPointMachine::QwtPickerClickPointMachine(): + QwtPickerMachine( PointSelection ) +{ +} + +//! Transition +QList QwtPickerClickPointMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + cmdList += Begin; + cmdList += Append; + cmdList += End; + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast ( event ) ) ) + { + cmdList += Begin; + cmdList += Append; + cmdList += End; + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerDragPointMachine::QwtPickerDragPointMachine(): + QwtPickerMachine( PointSelection ) +{ +} + +//! Transition +QList QwtPickerDragPointMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + setState( 1 ); + } + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::MouseButtonRelease: + { + if ( state() != 0 ) + { + cmdList += End; + setState( 0 ); + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += End; + setState( 0 ); + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerClickRectMachine::QwtPickerClickRectMachine(): + QwtPickerMachine( RectSelection ) +{ +} + +//! Transition +QList QwtPickerClickRectMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + switch ( state() ) + { + case 0: + { + cmdList += Begin; + cmdList += Append; + setState( 1 ); + break; + } + case 1: + { + // Uh, strange we missed the MouseButtonRelease + break; + } + default: + { + cmdList += End; + setState( 0 ); + } + } + } + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::MouseButtonRelease: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + if ( state() == 1 ) + { + cmdList += Append; + setState( 2 ); + } + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast ( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + setState( 1 ); + } + else + { + if ( state() == 1 ) + { + cmdList += Append; + setState( 2 ); + } + else if ( state() == 2 ) + { + cmdList += End; + setState( 0 ); + } + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerDragRectMachine::QwtPickerDragRectMachine(): + QwtPickerMachine( RectSelection ) +{ +} + +//! Transition +QList QwtPickerDragRectMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 2 ); + } + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::MouseButtonRelease: + { + if ( state() == 2 ) + { + cmdList += End; + setState( 0 ); + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast ( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 2 ); + } + else + { + cmdList += End; + setState( 0 ); + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerPolygonMachine::QwtPickerPolygonMachine(): + QwtPickerMachine( PolygonSelection ) +{ +} + +//! Transition +QList QwtPickerPolygonMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += Append; + } + } + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect2, + static_cast( event ) ) ) + { + if ( state() == 1 ) + { + cmdList += End; + setState( 0 ); + } + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast ( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += Append; + } + } + else if ( eventPattern.keyMatch( QwtEventPattern::KeySelect2, + static_cast ( event ) ) ) + { + if ( state() == 1 ) + { + cmdList += End; + setState( 0 ); + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerDragLineMachine::QwtPickerDragLineMachine(): + QwtPickerMachine( PolygonSelection ) +{ +} + +//! Transition +QList QwtPickerDragLineMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast ( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += End; + setState( 0 ); + } + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + + break; + } + case QEvent::MouseButtonRelease: + { + if ( state() != 0 ) + { + cmdList += End; + setState( 0 ); + } + } + default: + break; + } + + return cmdList; +} diff --git a/qwt/src/qwt_picker_machine.h b/qwt/src/qwt_picker_machine.h new file mode 100644 index 000000000..6164b93b5 --- /dev/null +++ b/qwt/src/qwt_picker_machine.h @@ -0,0 +1,214 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PICKER_MACHINE +#define QWT_PICKER_MACHINE 1 + +#include "qwt_global.h" +#include + +class QEvent; +class QwtEventPattern; + +/*! + \brief A state machine for QwtPicker selections + + QwtPickerMachine accepts key and mouse events and translates them + into selection commands. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ + +class QWT_EXPORT QwtPickerMachine +{ +public: + /*! + Type of a selection. + \sa selectionType() + */ + enum SelectionType + { + //! The state machine not usable for any type of selection. + NoSelection = -1, + + //! The state machine is for selecting a single point. + PointSelection, + + //! The state machine is for selecting a rectangle (2 points). + RectSelection, + + //! The state machine is for selecting a polygon (many points). + PolygonSelection + }; + + //! Commands - the output of a state machine + enum Command + { + Begin, + Append, + Move, + Remove, + End + }; + + QwtPickerMachine( SelectionType ); + virtual ~QwtPickerMachine(); + + //! Transition + virtual QList transition( + const QwtEventPattern &, const QEvent * ) = 0; + void reset(); + + int state() const; + void setState( int ); + + SelectionType selectionType() const; + +private: + const SelectionType d_selectionType; + int d_state; +}; + +/*! + \brief A state machine for indicating mouse movements + + QwtPickerTrackerMachine supports displaying information + corresponding to mouse movements, but is not intended for + selecting anything. Begin/End are related to Enter/Leave events. +*/ +class QWT_EXPORT QwtPickerTrackerMachine: public QwtPickerMachine +{ +public: + QwtPickerTrackerMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for point selections + + Pressing QwtEventPattern::MouseSelect1 or + QwtEventPattern::KeySelect1 selects a point. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ +class QWT_EXPORT QwtPickerClickPointMachine: public QwtPickerMachine +{ +public: + QwtPickerClickPointMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for point selections + + Pressing QwtEventPattern::MouseSelect1 or QwtEventPattern::KeySelect1 + starts the selection, releasing QwtEventPattern::MouseSelect1 or + a second press of QwtEventPattern::KeySelect1 terminates it. +*/ +class QWT_EXPORT QwtPickerDragPointMachine: public QwtPickerMachine +{ +public: + QwtPickerDragPointMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for rectangle selections + + Pressing QwtEventPattern::MouseSelect1 starts + the selection, releasing it selects the first point. Pressing it + again selects the second point and terminates the selection. + Pressing QwtEventPattern::KeySelect1 also starts the + selection, a second press selects the first point. A third one selects + the second point and terminates the selection. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ + +class QWT_EXPORT QwtPickerClickRectMachine: public QwtPickerMachine +{ +public: + QwtPickerClickRectMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for rectangle selections + + Pressing QwtEventPattern::MouseSelect1 selects + the first point, releasing it the second point. + Pressing QwtEventPattern::KeySelect1 also selects the + first point, a second press selects the second point and terminates + the selection. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ + +class QWT_EXPORT QwtPickerDragRectMachine: public QwtPickerMachine +{ +public: + QwtPickerDragRectMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for line selections + + Pressing QwtEventPattern::MouseSelect1 selects + the first point, releasing it the second point. + Pressing QwtEventPattern::KeySelect1 also selects the + first point, a second press selects the second point and terminates + the selection. + + A common use case of QwtPickerDragLineMachine are pickers for + distance measurements. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ + +class QWT_EXPORT QwtPickerDragLineMachine: public QwtPickerMachine +{ +public: + QwtPickerDragLineMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for polygon selections + + Pressing QwtEventPattern::MouseSelect1 or QwtEventPattern::KeySelect1 + starts the selection and selects the first point, or appends a point. + Pressing QwtEventPattern::MouseSelect2 or QwtEventPattern::KeySelect2 + appends the last point and terminates the selection. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ + +class QWT_EXPORT QwtPickerPolygonMachine: public QwtPickerMachine +{ +public: + QwtPickerPolygonMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +#endif diff --git a/qwt/src/qwt_pixel_matrix.cpp b/qwt/src/qwt_pixel_matrix.cpp new file mode 100644 index 000000000..627992c7a --- /dev/null +++ b/qwt/src/qwt_pixel_matrix.cpp @@ -0,0 +1,51 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2003 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_pixel_matrix.h" + +/*! + \brief Constructor + + \param rect Bounding rectangle for the matrix +*/ +QwtPixelMatrix::QwtPixelMatrix( const QRect& rect ): + QBitArray( qMax( rect.width() * rect.height(), 0 ) ), + d_rect( rect ) +{ +} + +//! Destructor +QwtPixelMatrix::~QwtPixelMatrix() +{ +} + +/*! + Set the bounding rectangle of the matrix + + \param rect Bounding rectangle + + \note All bits are cleared + */ +void QwtPixelMatrix::setRect( const QRect& rect ) +{ + if ( rect != d_rect ) + { + d_rect = rect; + const int sz = qMax( rect.width() * rect.height(), 0 ); + resize( sz ); + } + + fill( false ); +} + +//! \return Bounding rectangle +QRect QwtPixelMatrix::rect() const +{ + return d_rect; +} diff --git a/qwt/src/qwt_pixel_matrix.h b/qwt/src/qwt_pixel_matrix.h new file mode 100644 index 000000000..0fe9daf1c --- /dev/null +++ b/qwt/src/qwt_pixel_matrix.h @@ -0,0 +1,98 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2003 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PIXEL_MATRIX_H +#define QWT_PIXEL_MATRIX_H + +#include "qwt_global.h" +#include +#include + +/*! + \brief A bit field corresponding to the pixels of a rectangle + + QwtPixelMatrix is intended to filter out duplicates in an + unsorted array of points. +*/ +class QWT_EXPORT QwtPixelMatrix: public QBitArray +{ +public: + QwtPixelMatrix( const QRect& rect ); + ~QwtPixelMatrix(); + + void setRect( const QRect& rect ); + QRect rect() const; + + bool testPixel( int x, int y ) const; + bool testAndSetPixel( int x, int y, bool on ); + + int index( int x, int y ) const; + +private: + QRect d_rect; +}; + +/*! + \brief Test if a pixel has been set + + \param x X-coordinate + \param y Y-coordinate + + \return true, when pos is outside of rect(), or when the pixel + has already been set. + */ +inline bool QwtPixelMatrix::testPixel( int x, int y ) const +{ + const int idx = index( x, y ); + return ( idx >= 0 ) ? testBit( idx ) : true; +} + +/*! + \brief Set a pixel and test if a pixel has been set before + + \param x X-coordinate + \param y Y-coordinate + \param on Set/Clear the pixel + + \return true, when pos is outside of rect(), or when the pixel + was set before. + */ +inline bool QwtPixelMatrix::testAndSetPixel( int x, int y, bool on ) +{ + const int idx = index( x, y ); + if ( idx < 0 ) + return true; + + const bool onBefore = testBit( idx ); + setBit( idx, on ); + + return onBefore; +} + +/*! + \brief Calculate the index in the bit field corresponding to a position + + \param x X-coordinate + \param y Y-coordinate + \return Index, when rect() contains pos - otherwise -1. + */ +inline int QwtPixelMatrix::index( int x, int y ) const +{ + const int dx = x - d_rect.x(); + if ( dx < 0 || dx >= d_rect.width() ) + return -1; + + const int dy = y - d_rect.y(); + if ( dy < 0 || dy >= d_rect.height() ) + return -1; + + return dy * d_rect.width() + dx; +} + +#endif diff --git a/qwt/src/qwt_plot.cpp b/qwt/src/qwt_plot.cpp new file mode 100644 index 000000000..eb4c7e6b4 --- /dev/null +++ b/qwt/src/qwt_plot.cpp @@ -0,0 +1,1180 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot.h" +#include "qwt_plot_dict.h" +#include "qwt_plot_layout.h" +#include "qwt_scale_widget.h" +#include "qwt_scale_engine.h" +#include "qwt_text_label.h" +#include "qwt_legend.h" +#include "qwt_legend_data.h" +#include "qwt_plot_canvas.h" +#include "qwt_scale_map_table.h" +#include +#include +#include +#include +#include +#include + +static inline void qwtEnableLegendItems( QwtPlot *plot, bool on ) +{ + if ( on ) + { + QObject::connect( + plot, SIGNAL( legendDataChanged( + const QVariant &, const QList & ) ), + plot, SLOT( updateLegendItems( + const QVariant &, const QList & ) ) ); + } + else + { + QObject::disconnect( + plot, SIGNAL( legendDataChanged( + const QVariant &, const QList & ) ), + plot, SLOT( updateLegendItems( + const QVariant &, const QList & ) ) ); + } +} + +static void qwtSetTabOrder( + QWidget *first, QWidget *second, bool withChildren ) +{ + QList tabChain; + tabChain += first; + tabChain += second; + + if ( withChildren ) + { + QList children = second->findChildren(); + + QWidget *w = second->nextInFocusChain(); + while ( children.contains( w ) ) + { + children.removeAll( w ); + + tabChain += w; + w = w->nextInFocusChain(); + } + } + + for ( int i = 0; i < tabChain.size() - 1; i++ ) + { + QWidget *from = tabChain[i]; + QWidget *to = tabChain[i+1]; + + const Qt::FocusPolicy policy1 = from->focusPolicy(); + const Qt::FocusPolicy policy2 = to->focusPolicy(); + + QWidget *proxy1 = from->focusProxy(); + QWidget *proxy2 = to->focusProxy(); + + from->setFocusPolicy( Qt::TabFocus ); + from->setFocusProxy( NULL); + + to->setFocusPolicy( Qt::TabFocus ); + to->setFocusProxy( NULL); + + QWidget::setTabOrder( from, to ); + + from->setFocusPolicy( policy1 ); + from->setFocusProxy( proxy1); + + to->setFocusPolicy( policy2 ); + to->setFocusProxy( proxy2 ); + } +} + +QwtScaleMapTable qwtScaleMapTable( const QwtPlot *plot ) +{ + QwtScaleMapTable table; + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < plot->axesCount( axisPos ); i++ ) + table.maps[axisPos] += plot->canvasMap( QwtAxisId( axisPos, i ) ); + } + + return table; +} + +class QwtPlot::PrivateData +{ +public: + QPointer titleLabel; + QPointer footerLabel; + QPointer canvas; + QPointer legend; + QwtPlotLayout *layout; + + bool autoReplot; +}; + +/*! + \brief Constructor + \param parent Parent widget + */ +QwtPlot::QwtPlot( QWidget *parent ): + QFrame( parent ) +{ + initPlot( QwtText() ); +} + +/*! + \brief Constructor + \param title Title text + \param parent Parent widget + */ +QwtPlot::QwtPlot( const QwtText &title, QWidget *parent ): + QFrame( parent ) +{ + initPlot( title ); +} + +//! Destructor +QwtPlot::~QwtPlot() +{ + detachItems( QwtPlotItem::Rtti_PlotItem, autoDelete() ); + + delete d_data->layout; + deleteScaleData(); + delete d_data; +} + +/*! + \brief Initializes a QwtPlot instance + \param title Title text + */ +void QwtPlot::initPlot( const QwtText &title ) +{ + d_data = new PrivateData; + + d_data->layout = new QwtPlotLayout; + d_data->autoReplot = false; + + // title + d_data->titleLabel = new QwtTextLabel( this ); + d_data->titleLabel->setObjectName( "QwtPlotTitle" ); + d_data->titleLabel->setFont( QFont( fontInfo().family(), 14, QFont::Bold ) ); + + QwtText text( title ); + text.setRenderFlags( Qt::AlignCenter | Qt::TextWordWrap ); + d_data->titleLabel->setText( text ); + + // footer + d_data->footerLabel = new QwtTextLabel( this ); + d_data->footerLabel->setObjectName( "QwtPlotFooter" ); + + QwtText footer; + footer.setRenderFlags( Qt::AlignCenter | Qt::TextWordWrap ); + d_data->footerLabel->setText( footer ); + + // legend + d_data->legend = NULL; + + // scales + initScaleData(); + + // canvas + d_data->canvas = new QwtPlotCanvas( this ); + d_data->canvas->setObjectName( "QwtPlotCanvas" ); + d_data->canvas->installEventFilter( this ); + + setSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding ); + + resize( 200, 200 ); + + QList focusChain; + focusChain << this << d_data->titleLabel; + + for ( int i = 0; i < axesCount( QwtAxis::xTop ); i++ ) + focusChain << axisWidget( QwtAxisId( QwtAxis::xTop, i ) ); + + for ( int i = 0; i < axesCount( QwtAxis::yLeft ); i++ ) + focusChain << axisWidget( QwtAxisId( QwtAxis::yLeft, i ) ); + + focusChain << d_data->canvas; + + for ( int i = 0; i < axesCount( QwtAxis::yRight ); i++ ) + focusChain << axisWidget( QwtAxisId( QwtAxis::yRight, i ) ); + + for ( int i = 0; i < axesCount( QwtAxis::xBottom ); i++ ) + focusChain << axisWidget( QwtAxisId( QwtAxis::xBottom, i ) ); + + focusChain << d_data->footerLabel; + + for ( int i = 0; i < focusChain.size() - 1; i++ ) + qwtSetTabOrder( focusChain[i], focusChain[i+1], false ); + + qwtEnableLegendItems( this, true ); +} + +/*! + \brief Set the drawing canvas of the plot widget + + QwtPlot invokes methods of the canvas as meta methods ( see QMetaObject ). + In opposite to using conventional C++ techniques like virtual methods + they allow to use canvas implementations that are derived from + QWidget or QGLWidget. + + The following meta methods could be implemented: + + - replot() + When the canvas doesn't offer a replot method, QwtPlot calls + update() instead. + + - borderPath() + The border path is necessary to clip the content of the canvas + When the canvas doesn't have any special border ( f.e rounded corners ) + it is o.k. not to implement this method. + + The default canvas is a QwtPlotCanvas + + \param canvas Canvas Widget + \sa canvas() + */ +void QwtPlot::setCanvas( QWidget *canvas ) +{ + if ( canvas == d_data->canvas ) + return; + + delete d_data->canvas; + d_data->canvas = canvas; + + if ( canvas ) + { + canvas->setParent( this ); + canvas->installEventFilter( this ); + + if ( isVisible() ) + canvas->show(); + } +} + +/*! + \brief Adds handling of layout requests + \param event Event + + \return See QFrame::event() +*/ +bool QwtPlot::event( QEvent *event ) +{ + bool ok = QFrame::event( event ); + switch ( event->type() ) + { + case QEvent::LayoutRequest: + updateLayout(); + break; + case QEvent::PolishRequest: + replot(); + break; + default:; + } + return ok; +} + +/*! + \brief Event filter + + The plot handles the following events for the canvas: + + - QEvent::Resize + The canvas margins might depend on its size + + - QEvent::ContentsRectChange + The layout needs to be recalculated + + \param object Object to be filtered + \param event Event + + \return See QFrame::eventFilter() + + \sa updateCanvasMargins(), updateLayout() +*/ +bool QwtPlot::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == d_data->canvas ) + { + if ( event->type() == QEvent::Resize ) + { + updateCanvasMargins(); + } + else if ( event->type() == QEvent::ContentsRectChange ) + { + updateLayout(); + } + } + + return QFrame::eventFilter( object, event ); +} + +//! Replots the plot if autoReplot() is \c true. +void QwtPlot::autoRefresh() +{ + if ( d_data->autoReplot ) + replot(); +} + +/*! + \brief Set or reset the autoReplot option + + If the autoReplot option is set, the plot will be + updated implicitly by manipulating member functions. + Since this may be time-consuming, it is recommended + to leave this option switched off and call replot() + explicitly if necessary. + + The autoReplot option is set to false by default, which + means that the user has to call replot() in order to make + changes visible. + \param tf \c true or \c false. Defaults to \c true. + \sa replot() +*/ +void QwtPlot::setAutoReplot( bool tf ) +{ + d_data->autoReplot = tf; +} + +/*! + \return true if the autoReplot option is set. + \sa setAutoReplot() +*/ +bool QwtPlot::autoReplot() const +{ + return d_data->autoReplot; +} + +/*! + Change the plot's title + \param title New title +*/ +void QwtPlot::setTitle( const QString &title ) +{ + if ( title != d_data->titleLabel->text().text() ) + { + d_data->titleLabel->setText( title ); + updateLayout(); + } +} + +/*! + Change the plot's title + \param title New title +*/ +void QwtPlot::setTitle( const QwtText &title ) +{ + if ( title != d_data->titleLabel->text() ) + { + d_data->titleLabel->setText( title ); + updateLayout(); + } +} + +//! \return Title of the plot +QwtText QwtPlot::title() const +{ + return d_data->titleLabel->text(); +} + +//! \return Title label widget. +QwtTextLabel *QwtPlot::titleLabel() +{ + return d_data->titleLabel; +} + +//! \return Title label widget. +const QwtTextLabel *QwtPlot::titleLabel() const +{ + return d_data->titleLabel; +} + +/*! + Change the text the footer + \param text New text of the footer +*/ +void QwtPlot::setFooter( const QString &text ) +{ + if ( text != d_data->footerLabel->text().text() ) + { + d_data->footerLabel->setText( text ); + updateLayout(); + } +} + +/*! + Change the text the footer + \param text New text of the footer +*/ +void QwtPlot::setFooter( const QwtText &text ) +{ + if ( text != d_data->footerLabel->text() ) + { + d_data->footerLabel->setText( text ); + updateLayout(); + } +} + +//! \return Text of the footer +QwtText QwtPlot::footer() const +{ + return d_data->footerLabel->text(); +} + +//! \return Footer label widget. +QwtTextLabel *QwtPlot::footerLabel() +{ + return d_data->footerLabel; +} + +//! \return Footer label widget. +const QwtTextLabel *QwtPlot::footerLabel() const +{ + return d_data->footerLabel; +} + +/*! + \brief Assign a new plot layout + + \param layout Layout() + \sa plotLayout() + */ +void QwtPlot::setPlotLayout( QwtPlotLayout *layout ) +{ + if ( layout != d_data->layout ) + { + delete d_data->layout; + layout = d_data->layout; + + updateLayout(); + } +} + +//! \return the plot's layout +QwtPlotLayout *QwtPlot::plotLayout() +{ + return d_data->layout; +} + +//! \return the plot's layout +const QwtPlotLayout *QwtPlot::plotLayout() const +{ + return d_data->layout; +} + +/*! + \return the plot's legend + \sa insertLegend() +*/ +QwtAbstractLegend *QwtPlot::legend() +{ + return d_data->legend; +} + +/*! + \return the plot's legend + \sa insertLegend() +*/ +const QwtAbstractLegend *QwtPlot::legend() const +{ + return d_data->legend; +} + + +/*! + \return the plot's canvas +*/ +QWidget *QwtPlot::canvas() +{ + return d_data->canvas; +} + +/*! + \return the plot's canvas +*/ +const QWidget *QwtPlot::canvas() const +{ + return d_data->canvas; +} + +/*! + \return Size hint for the plot widget + \sa minimumSizeHint() +*/ +QSize QwtPlot::sizeHint() const +{ + int dw = 0; + int dh = 0; + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < axesCount( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + if ( isAxisVisible( axisId ) ) + { + const int niceDist = 40; + const QwtScaleWidget *scaleWidget = axisWidget( axisId ); + const QwtScaleDiv &scaleDiv = scaleWidget->scaleDraw()->scaleDiv(); + const int majCnt = scaleDiv.ticks( QwtScaleDiv::MajorTick ).count(); + + const QSize hint = scaleWidget->minimumSizeHint(); + + if ( QwtAxis::isYAxis( axisPos ) ) + { + const int hDiff = ( majCnt - 1 ) * niceDist - hint.height(); + dh = qMax( dh, hDiff ); + } + else + { + const int wDiff = ( majCnt - 1 ) * niceDist - hint.width(); + dw = qMax( dw, wDiff ); + } + } + } + } + + return minimumSizeHint() + QSize( dw, dh ); +} + +/*! + \brief Return a minimum size hint +*/ +QSize QwtPlot::minimumSizeHint() const +{ + QSize hint = d_data->layout->minimumSizeHint( this ); + hint += QSize( 2 * frameWidth(), 2 * frameWidth() ); + + return hint; +} + +/*! + Resize and update internal layout + \param e Resize event +*/ +void QwtPlot::resizeEvent( QResizeEvent *e ) +{ + QFrame::resizeEvent( e ); + updateLayout(); +} + +/*! + \brief Redraw the plot + + If the autoReplot option is not set (which is the default) + or if any curves are attached to raw data, the plot has to + be refreshed explicitly in order to make changes visible. + + \sa updateAxes(), setAutoReplot() +*/ +void QwtPlot::replot() +{ + bool doAutoReplot = autoReplot(); + setAutoReplot( false ); + + updateAxes(); + + /* + Maybe the layout needs to be updated, because of changed + axes labels. We need to process them here before painting + to avoid that scales and canvas get out of sync. + */ + QApplication::sendPostedEvents( this, QEvent::LayoutRequest ); + + if ( d_data->canvas ) + { + const bool ok = QMetaObject::invokeMethod( + d_data->canvas, "replot", Qt::DirectConnection ); + if ( !ok ) + { + // fallback, when canvas has no a replot method + d_data->canvas->update( d_data->canvas->contentsRect() ); + } + } + + setAutoReplot( doAutoReplot ); +} + +/*! + \brief Adjust plot content to its current size. + \sa resizeEvent() +*/ +void QwtPlot::updateLayout() +{ + d_data->layout->update( this, contentsRect() ); + + const QRect titleRect = d_data->layout->titleRect().toRect(); + const QRect footerRect = d_data->layout->footerRect().toRect(); + const QRect legendRect = d_data->layout->legendRect().toRect(); + const QRect canvasRect = d_data->layout->canvasRect().toRect(); + + // resize and show the visible widgets + + if ( !d_data->titleLabel->text().isEmpty() ) + { + d_data->titleLabel->setGeometry( titleRect ); + if ( !d_data->titleLabel->isVisibleTo( this ) ) + d_data->titleLabel->show(); + } + else + d_data->titleLabel->hide(); + + if ( !d_data->footerLabel->text().isEmpty() ) + { + d_data->footerLabel->setGeometry( footerRect ); + if ( !d_data->footerLabel->isVisibleTo( this ) ) + d_data->footerLabel->show(); + } + else + d_data->footerLabel->hide(); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < axesCount( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + QwtScaleWidget *scaleWidget = axisWidget( axisId ); + if ( isAxisVisible( axisId ) ) + { + const QRect scaleRect = d_data->layout->scaleRect( axisId ).toRect(); + scaleWidget->setGeometry( scaleRect ); + + if ( !scaleWidget->isVisibleTo( this ) ) + scaleWidget->show(); + } + else + { + scaleWidget->hide(); + } + } + } + + if ( d_data->legend ) + { + if ( d_data->legend->isEmpty() ) + { + d_data->legend->hide(); + } + else + { + d_data->legend->setGeometry( legendRect ); + d_data->legend->show(); + } + } + + d_data->canvas->setGeometry( canvasRect ); +} + +/*! + \brief Calculate the canvas margins + + \param maps QwtAxis::NumPositions maps, mapping between plot and paint device coordinates + \param canvasRect Bounding rectangle where to paint + \param left Return parameter for the left margin + \param top Return parameter for the top margin + \param right Return parameter for the right margin + \param bottom Return parameter for the bottom margin + + Plot items might indicate, that they need some extra space + at the borders of the canvas by the QwtPlotItem::Margins flag. + + updateCanvasMargins(), QwtPlotItem::getCanvasMarginHint() + */ +void QwtPlot::getCanvasMarginsHint( + const QwtScaleMapTable& mapsTable, const QRectF &canvasRect, + double &left, double &top, double &right, double &bottom) const +{ + left = top = right = bottom = -1.0; + + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + const QwtPlotItem *item = *it; + if ( item->testItemAttribute( QwtPlotItem::Margins ) && + mapsTable.isValid( item->xAxis() ) && + mapsTable.isValid( item->yAxis() ) ) + { + double m[ QwtAxis::PosCount ]; + item->getCanvasMarginHint( + mapsTable.map( item->xAxis() ), + mapsTable.map( item->yAxis() ), + canvasRect, m[QwtAxis::yLeft], m[QwtAxis::xTop], + m[QwtAxis::yRight], m[QwtAxis::xBottom] ); + + left = qMax( left, m[QwtAxis::yLeft] ); + top = qMax( top, m[QwtAxis::xTop] ); + right = qMax( right, m[QwtAxis::yRight] ); + bottom = qMax( bottom, m[QwtAxis::xBottom] ); + } + } +} + +/*! + \brief Update the canvas margins + + Plot items might indicate, that they need some extra space + at the borders of the canvas by the QwtPlotItem::Margins flag. + + getCanvasMarginsHint(), QwtPlotItem::getCanvasMarginHint() + */ +void QwtPlot::updateCanvasMargins() +{ + double margins[QwtAxis::PosCount]; + getCanvasMarginsHint( qwtScaleMapTable( this ), canvas()->contentsRect(), + margins[QwtAxis::yLeft], margins[QwtAxis::xTop], + margins[QwtAxis::yRight], margins[QwtAxis::xBottom] ); + + bool doUpdate = false; + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + if ( margins[axisPos] >= 0.0 ) + { + const int m = qCeil( margins[axisPos] ); + plotLayout()->setCanvasMargin( m, axisPos); + doUpdate = true; + } + } + + if ( doUpdate ) + updateLayout(); +} + +/*! + Redraw the canvas. + \param painter Painter used for drawing + + \warning drawCanvas calls drawItems what is also used + for printing. Applications that like to add individual + plot items better overload drawItems() + \sa drawItems() +*/ +void QwtPlot::drawCanvas( QPainter *painter ) +{ + const QwtScaleMapTable table = qwtScaleMapTable( this ); + drawItems( painter, d_data->canvas->contentsRect(), table ); +} + +/*! + Redraw the canvas items. + + \param painter Painter used for drawing + \param canvasRect Bounding rectangle where to paint + \param maps QwtAxis::NumPositions maps, mapping between plot and paint device coordinates + + \note Usually canvasRect is contentsRect() of the plot canvas. + Due to a bug in Qt this rectangle might be wrong for certain + frame styles ( f.e QFrame::Box ) and it might be necessary to + fix the margins manually using QWidget::setContentsMargins() +*/ + +void QwtPlot::drawItems( QPainter *painter, + const QRectF &canvasRect, const QwtScaleMapTable &mapTable ) const +{ + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPlotItem *item = *it; + if ( item && item->isVisible() ) + { + if ( mapTable.isValid( item->xAxis() ) && + mapTable.isValid( item->yAxis() ) ) + { + painter->save(); + + painter->setRenderHint( QPainter::Antialiasing, + item->testRenderHint( QwtPlotItem::RenderAntialiased ) ); + painter->setRenderHint( QPainter::HighQualityAntialiasing, + item->testRenderHint( QwtPlotItem::RenderAntialiased ) ); + + item->draw( painter, mapTable.map( item->xAxis() ), + mapTable.map( item->yAxis() ), canvasRect ); + + painter->restore(); + } + } + } +} + +/*! + \param axisPos Axis + \return Map for the axis on the canvas. With this map pixel coordinates can + translated to plot coordinates and vice versa. + \sa QwtScaleMap, transform(), invTransform() + +*/ +QwtScaleMap QwtPlot::canvasMap( QwtAxisId axisId ) const +{ + QwtScaleMap map; + if ( !d_data->canvas ) + return map; + + map.setTransformation( axisScaleEngine( axisId )->transformation() ); + + const QwtScaleDiv &sd = axisScaleDiv( axisId ); + map.setScaleInterval( sd.lowerBound(), sd.upperBound() ); + + if ( isAxisVisible( axisId ) ) + { + const QwtScaleWidget *s = axisWidget( axisId ); + if ( QwtAxis::isYAxis( axisId.pos ) ) + { + double y = s->y() + s->startBorderDist() - d_data->canvas->y(); + double h = s->height() - s->startBorderDist() - s->endBorderDist(); + map.setPaintInterval( y + h, y ); + } + else + { + double x = s->x() + s->startBorderDist() - d_data->canvas->x(); + double w = s->width() - s->startBorderDist() - s->endBorderDist(); + map.setPaintInterval( x, x + w ); + } + } + else + { + int margin = 0; + if ( !plotLayout()->alignCanvasToScale( axisId.pos ) ) + margin = plotLayout()->canvasMargin( axisId.pos ); + + const QRect &canvasRect = d_data->canvas->contentsRect(); + if ( QwtAxis::isYAxis( axisId.pos ) ) + { + map.setPaintInterval( canvasRect.bottom() - margin, + canvasRect.top() + margin ); + } + else + { + map.setPaintInterval( canvasRect.left() + margin, + canvasRect.right() - margin ); + } + } + return map; +} + +/*! + \brief Change the background of the plotting area + + Sets brush to QPalette::Window of all color groups of + the palette of the canvas. Using canvas()->setPalette() + is a more powerful way to set these colors. + + \param brush New background brush + \sa canvasBackground() +*/ +void QwtPlot::setCanvasBackground( const QBrush &brush ) +{ + QPalette pal = d_data->canvas->palette(); + pal.setBrush( QPalette::Window, brush ); + + canvas()->setPalette( pal ); +} + +/*! + Nothing else than: canvas()->palette().brush( + QPalette::Normal, QPalette::Window); + + \return Background brush of the plotting area. + \sa setCanvasBackground() +*/ +QBrush QwtPlot::canvasBackground() const +{ + return canvas()->palette().brush( + QPalette::Normal, QPalette::Window ); +} + +/*! + \brief Insert a legend + + If the position legend is \c QwtPlot::LeftLegend or \c QwtPlot::RightLegend + the legend will be organized in one column from top to down. + Otherwise the legend items will be placed in a table + with a best fit number of columns from left to right. + + insertLegend() will set the plot widget as parent for the legend. + The legend will be deleted in the destructor of the plot or when + another legend is inserted. + + Legends, that are not inserted into the layout of the plot widget + need to connect to the legendDataChanged() signal. Calling updateLegend() + initiates this signal for an initial update. When the application code + wants to implement its own layout this also needs to be done for + rendering plots to a document ( see QwtPlotRenderer ). + + \param legend Legend + \param pos The legend's position. For top/left position the number + of columns will be limited to 1, otherwise it will be set to + unlimited. + + \param ratio Ratio between legend and the bounding rectangle + of title, canvas and axes. The legend will be shrunk + if it would need more space than the given ratio. + The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 + it will be reset to the default ratio. + The default vertical/horizontal ratio is 0.33/0.5. + + \sa legend(), QwtPlotLayout::legendPosition(), + QwtPlotLayout::setLegendPosition() +*/ +void QwtPlot::insertLegend( QwtAbstractLegend *legend, + QwtPlot::LegendPosition pos, double ratio ) +{ + d_data->layout->setLegendPosition( pos, ratio ); + + if ( legend != d_data->legend ) + { + if ( d_data->legend && d_data->legend->parent() == this ) + delete d_data->legend; + + d_data->legend = legend; + + if ( d_data->legend ) + { + connect( this, + SIGNAL( legendDataChanged( + const QVariant &, const QList & ) ), + d_data->legend, + SLOT( updateLegend( + const QVariant &, const QList & ) ) + ); + + if ( d_data->legend->parent() != this ) + d_data->legend->setParent( this ); + + qwtEnableLegendItems( this, false ); + updateLegend(); + qwtEnableLegendItems( this, true ); + + QwtLegend *lgd = qobject_cast( legend ); + if ( lgd ) + { + switch ( d_data->layout->legendPosition() ) + { + case LeftLegend: + case RightLegend: + { + if ( lgd->maxColumns() == 0 ) + lgd->setMaxColumns( 1 ); // 1 column: align vertical + break; + } + case TopLegend: + case BottomLegend: + { + lgd->setMaxColumns( 0 ); // unlimited + break; + } + default: + break; + } + } + + QWidget *previousInChain = NULL; + switch ( d_data->layout->legendPosition() ) + { + case LeftLegend: + { + previousInChain = axisWidget( + QwtAxisId( QwtAxis::xTop, axesCount( QwtAxis::xTop ) - 1 ) ); + break; + } + case TopLegend: + { + previousInChain = this; + break; + } + case RightLegend: + { + previousInChain = axisWidget( + QwtAxisId( QwtAxis::yRight, axesCount( QwtAxis::yRight ) - 1 ) ); + break; + } + case BottomLegend: + { + previousInChain = footerLabel(); + break; + } + } + + if ( previousInChain ) + qwtSetTabOrder( previousInChain, legend, true ); + } + } + + updateLayout(); +} + +/*! + Emit legendDataChanged() for all plot item + + \sa QwtPlotItem::legendData(), legendDataChanged() + */ +void QwtPlot::updateLegend() +{ + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + updateLegend( *it ); + } +} + +/*! + Emit legendDataChanged() for a plot item + + \param plotItem Plot item + \sa QwtPlotItem::legendData(), legendDataChanged() + */ +void QwtPlot::updateLegend( const QwtPlotItem *plotItem ) +{ + if ( plotItem == NULL ) + return; + + QList legendData; + + if ( plotItem->testItemAttribute( QwtPlotItem::Legend ) ) + legendData = plotItem->legendData(); + + const QVariant itemInfo = itemToInfo( const_cast< QwtPlotItem *>( plotItem) ); + Q_EMIT legendDataChanged( itemInfo, legendData ); +} + +/*! + \brief Update all plot items interested in legend attributes + + Call QwtPlotItem::updateLegend(), when the QwtPlotItem::LegendInterest + flag is set. + + \param itemInfo Info about the plot item + \param legendData Entries to be displayed for the plot item ( usually 1 ) + + \sa QwtPlotItem::LegendInterest, + QwtPlotLegendItem, QwtPlotItem::updateLegend() + */ +void QwtPlot::updateLegendItems( const QVariant &itemInfo, + const QList &legendData ) +{ + QwtPlotItem *plotItem = infoToItem( itemInfo ); + if ( plotItem ) + { + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPlotItem *item = *it; + if ( item->testItemInterest( QwtPlotItem::LegendInterest ) ) + item->updateLegend( plotItem, legendData ); + } + } +} + +/*! + \brief Attach/Detach a plot item + + \param plotItem Plot item + \param on When true attach the item, otherwise detach it + */ +void QwtPlot::attachItem( QwtPlotItem *plotItem, bool on ) +{ + if ( plotItem->testItemInterest( QwtPlotItem::LegendInterest ) ) + { + // plotItem is some sort of legend + + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPlotItem *item = *it; + + QList legendData; + if ( on && item->testItemAttribute( QwtPlotItem::Legend ) ) + { + legendData = item->legendData(); + plotItem->updateLegend( item, legendData ); + } + } + } + + if ( on ) + insertItem( plotItem ); + else + removeItem( plotItem ); + + Q_EMIT itemAttached( plotItem, on ); + + if ( plotItem->testItemAttribute( QwtPlotItem::Legend ) ) + { + // the item wants to be represented on the legend + + if ( on ) + { + updateLegend( plotItem ); + } + else + { + const QVariant itemInfo = itemToInfo( plotItem ); + Q_EMIT legendDataChanged( itemInfo, QList() ); + } + } + + if ( autoReplot() ) + update(); +} + +/*! + \brief Build an information, that can be used to identify + a plot item on the legend. + + The default implementation simply wraps the plot item + into a QVariant object. When overloading itemToInfo() + usually infoToItem() needs to reimplemeted too. + +\code + QVariant itemInfo; + qVariantSetValue( itemInfo, plotItem ); +\endcode + + \param plotItem Plot item + \return Plot item embedded in a QVariant + \sa infoToItem() + */ +QVariant QwtPlot::itemToInfo( QwtPlotItem *plotItem ) const +{ + QVariant itemInfo; + qVariantSetValue( itemInfo, plotItem ); + + return itemInfo; +} + +/*! + \brief Identify the plot item according to an item info object, + that has bee generated from itemToInfo(). + + The default implementation simply tries to unwrap a QwtPlotItem + pointer: + +\code + if ( itemInfo.canConvert() ) + return qvariant_cast( itemInfo ); +\endcode + \param itemInfo Plot item + \return A plot item, when successful, otherwise a NULL pointer. + \sa itemToInfo() +*/ +QwtPlotItem *QwtPlot::infoToItem( const QVariant &itemInfo ) const +{ + if ( itemInfo.canConvert() ) + return qvariant_cast( itemInfo ); + + return NULL; +} + + diff --git a/qwt/src/qwt_plot.h b/qwt/src/qwt_plot.h new file mode 100644 index 000000000..b8ff76788 --- /dev/null +++ b/qwt/src/qwt_plot.h @@ -0,0 +1,325 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_H +#define QWT_PLOT_H + +#include "qwt_global.h" +#include "qwt_axis_id.h" +#include "qwt_text.h" +#include "qwt_plot_dict.h" +#include "qwt_scale_map.h" +#include "qwt_interval.h" +#include +#include +#include + +class QwtPlotLayout; +class QwtAbstractLegend; +class QwtScaleWidget; +class QwtScaleEngine; +class QwtScaleDiv; +class QwtScaleDraw; +class QwtTextLabel; +class QwtScaleMapTable; + +#define QWT_COMPAT 1 // flag to disable compatibilities - will be removed later +#define QWT_DUMMY_ID 0 // dummy id to help for migrating the code - will be removed later + +/*! + \brief A 2-D plotting widget + + QwtPlot is a widget for plotting two-dimensional graphs. + An unlimited number of plot items can be displayed on + its canvas. Plot items might be curves (QwtPlotCurve), markers + (QwtPlotMarker), the grid (QwtPlotGrid), or anything else derived + from QwtPlotItem. + A plot can have up to four axes, with each plot item attached to an x- and + a y axis. The scales at the axes can be explicitly set (QwtScaleDiv), or + are calculated from the plot items, using algorithms (QwtScaleEngine) which + can be configured separately for each axis. + + The simpleplot example is a good starting point to see how to set up a + plot widget. + + \image html plot.png + + \par Example + The following example shows (schematically) the most simple + way to use QwtPlot. By default, only the left and bottom axes are + visible and their scales are computed automatically. + \verbatim +#include +#include + +QwtPlot *myPlot = new QwtPlot("Two Curves", parent); + +// add curves +QwtPlotCurve *curve1 = new QwtPlotCurve("Curve 1"); +QwtPlotCurve *curve2 = new QwtPlotCurve("Curve 2"); + +// connect or copy the data to the curves +curve1->setData(...); +curve2->setData(...); + +curve1->attach(myPlot); +curve2->attach(myPlot); + +// finally, refresh the plot +myPlot->replot(); +\endverbatim +*/ + +class QWT_EXPORT QwtPlot: public QFrame, public QwtPlotDict +{ + Q_OBJECT + + Q_PROPERTY( QBrush canvasBackground + READ canvasBackground WRITE setCanvasBackground ) + Q_PROPERTY( bool autoReplot READ autoReplot WRITE setAutoReplot ) + +#if 0 + // This property is intended to configure the plot + // widget from a special dialog in the deigner plugin. + // Disabled until such a dialog has been implemented. + + Q_PROPERTY( QString propertiesDocument + READ grabProperties WRITE applyProperties ) +#endif + +public: + /*! + Position of the legend, relative to the canvas. + + \sa insertLegend() + */ + enum LegendPosition + { + //! The legend will be left from the QwtAxis::yLeft axis. + LeftLegend, + + //! The legend will be right from the QwtAxis::yRight axis. + RightLegend, + + //! The legend will be below the footer + BottomLegend, + + //! The legend will be above the title + TopLegend + }; + + explicit QwtPlot( QWidget * = NULL ); + explicit QwtPlot( const QwtText &title, QWidget * = NULL ); + + virtual ~QwtPlot(); + + void applyProperties( const QString & ); + QString grabProperties() const; + + void setAutoReplot( bool on = true ); + bool autoReplot() const; + + // Layout + + void setPlotLayout( QwtPlotLayout * ); + + QwtPlotLayout *plotLayout(); + const QwtPlotLayout *plotLayout() const; + + // Title + + void setTitle( const QString & ); + void setTitle( const QwtText &t ); + QwtText title() const; + + QwtTextLabel *titleLabel(); + const QwtTextLabel *titleLabel() const; + + // Footer + + void setFooter( const QString & ); + void setFooter( const QwtText &t ); + QwtText footer() const; + + QwtTextLabel *footerLabel(); + const QwtTextLabel *footerLabel() const; + + // Canvas + + void setCanvas( QWidget * ); + + QWidget *canvas(); + const QWidget *canvas() const; + + void setCanvasBackground( const QBrush & ); + QBrush canvasBackground() const; + + virtual QwtScaleMap canvasMap( QwtAxisId ) const; + + double invTransform( QwtAxisId, double value ) const; + double transform( QwtAxisId, double value ) const; + + // Axes + + void setAxesCount( int axisPos, int count ); + + int axesCount( int axisPos, bool onlyVisible = false ) const; + bool isAxisValid( QwtAxisId ) const; + + QwtScaleEngine *axisScaleEngine( QwtAxisId ); + const QwtScaleEngine *axisScaleEngine( QwtAxisId ) const; + + void setAxisScaleEngine( QwtAxisId, QwtScaleEngine * ); + + void setAxisAutoScale( QwtAxisId, bool on = true ); + bool axisAutoScale( QwtAxisId ) const; + + void setAxisVisible( QwtAxisId, bool on = true ); + bool isAxisVisible( QwtAxisId ) const; + + void setAxisFont( QwtAxisId, const QFont & ); + QFont axisFont( QwtAxisId ) const; + + void setAxisScale( QwtAxisId, double min, double max, double step = 0 ); + void setAxisScaleDiv( QwtAxisId, const QwtScaleDiv & ); + void setAxisScaleDraw( QwtAxisId, QwtScaleDraw * ); + + double axisStepSize( QwtAxisId ) const; + QwtInterval axisInterval( QwtAxisId ) const; + + const QwtScaleDiv &axisScaleDiv( QwtAxisId ) const; + + const QwtScaleDraw *axisScaleDraw( QwtAxisId ) const; + QwtScaleDraw *axisScaleDraw( QwtAxisId ); + + const QwtScaleWidget *axisWidget( QwtAxisId ) const; + QwtScaleWidget *axisWidget( QwtAxisId ); + + void setAxisLabelAlignment( QwtAxisId, Qt::Alignment ); + void setAxisLabelRotation( QwtAxisId, double rotation ); + + void setAxisTitle( QwtAxisId, const QString & ); + void setAxisTitle( QwtAxisId, const QwtText & ); + QwtText axisTitle( QwtAxisId ) const; + + void setAxisMaxMinor( QwtAxisId, int maxMinor ); + int axisMaxMinor( QwtAxisId ) const; + + void setAxisMaxMajor( QwtAxisId, int maxMajor ); + int axisMaxMajor( QwtAxisId ) const; + + // Legend + + void insertLegend( QwtAbstractLegend *, + LegendPosition = QwtPlot::RightLegend, double ratio = -1.0 ); + + QwtAbstractLegend *legend(); + const QwtAbstractLegend *legend() const; + + void updateLegend(); + void updateLegend( const QwtPlotItem * ); + + // Misc + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + virtual void updateLayout(); + virtual void drawCanvas( QPainter * ); + + void updateAxes(); + void updateCanvasMargins(); + + virtual void getCanvasMarginsHint( + const QwtScaleMapTable &, const QRectF &canvasRect, + double &left, double &top, double &right, double &bottom) const; + + virtual bool event( QEvent * ); + virtual bool eventFilter( QObject *, QEvent * ); + + virtual void drawItems( QPainter *, const QRectF &, + const QwtScaleMapTable & ) const; + + virtual QVariant itemToInfo( QwtPlotItem * ) const; + virtual QwtPlotItem *infoToItem( const QVariant & ) const; + +#if QWT_COMPAT + enum Axis + { + yLeft = QwtAxis::yLeft, + yRight = QwtAxis::yRight, + xBottom = QwtAxis::xBottom, + xTop = QwtAxis::xTop, + axisCnt = QwtAxis::PosCount + }; + + + void enableAxis( int axisId, bool on = true ) + { + setAxisVisible( axisId, on ); + } + + bool axisEnabled( int axisId ) const + { + return isAxisVisible( axisId ); + } +#endif + +Q_SIGNALS: + /*! + A signal indicating, that an item has been attached/detached + + \param plotItem Plot item + \param on Attached/Detached + */ + void itemAttached( QwtPlotItem *plotItem, bool on ); + + /*! + A signal with the attributes how to update + the legend entries for a plot item. + + \param itemInfo Info about a plot item, build from itemToInfo() + \param data Attributes of the entries ( usually <= 1 ) for + the plot item. + + \sa itemToInfo(), infoToItem(), QwtAbstractLegend::updateLegend() + */ + void legendDataChanged( const QVariant &itemInfo, + const QList &data ); + +public Q_SLOTS: + virtual void replot(); + void autoRefresh(); + +protected: + + virtual void resizeEvent( QResizeEvent *e ); + +private Q_SLOTS: + void updateLegendItems( const QVariant &itemInfo, + const QList &data ); + +private: + friend class QwtPlotItem; + void attachItem( QwtPlotItem *, bool ); + + void initScaleData(); + void deleteScaleData(); + void updateScaleDiv(); + + void initPlot( const QwtText &title ); + + class ScaleData; + ScaleData *d_scaleData; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_abstract_barchart.cpp b/qwt/src/qwt_plot_abstract_barchart.cpp new file mode 100644 index 000000000..9a72fdd5b --- /dev/null +++ b/qwt/src/qwt_plot_abstract_barchart.cpp @@ -0,0 +1,367 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_abstract_barchart.h" +#include "qwt_scale_map.h" + +static inline double qwtTransformWidth( + const QwtScaleMap &map, double value, double width ) +{ + const double w2 = 0.5 * width; + + const double v1 = map.transform( value - w2 ); + const double v2 = map.transform( value + w2 ); + + return qAbs( v2 - v1 ); +} + +class QwtPlotAbstractBarChart::PrivateData +{ +public: + PrivateData(): + layoutPolicy( QwtPlotAbstractBarChart::AutoAdjustSamples ), + layoutHint( 0.5 ), + spacing( 10 ), + margin( 5 ), + baseline( 0.0 ) + { + } + + QwtPlotAbstractBarChart::LayoutPolicy layoutPolicy; + double layoutHint; + int spacing; + int margin; + double baseline; +}; + +/*! + Constructor + \param title Title of the chart +*/ +QwtPlotAbstractBarChart::QwtPlotAbstractBarChart( const QwtText &title ): + QwtPlotSeriesItem( title ) +{ + d_data = new PrivateData; + + setItemAttribute( QwtPlotItem::Legend, true ); + setItemAttribute( QwtPlotItem::AutoScale, true ); + setItemAttribute( QwtPlotItem::Margins, true ); + setZ( 19.0 ); +} + +//! Destructor +QwtPlotAbstractBarChart::~QwtPlotAbstractBarChart() +{ + delete d_data; +} + +/*! + The combination of layoutPolicy() and layoutHint() define how the width + of the bars is calculated + + \param policy Layout policy + + \sa layoutPolicy(), layoutHint() + */ +void QwtPlotAbstractBarChart::setLayoutPolicy( LayoutPolicy policy ) +{ + if ( policy != d_data->layoutPolicy ) + { + d_data->layoutPolicy = policy; + itemChanged(); + } +} + +/*! + The combination of layoutPolicy() and layoutHint() define how the width + of the bars is calculated + + \return Layout policy of the chart item + \sa setLayoutPolicy(), layoutHint() + */ +QwtPlotAbstractBarChart::LayoutPolicy QwtPlotAbstractBarChart::layoutPolicy() const +{ + return d_data->layoutPolicy; +} + +/*! + The combination of layoutPolicy() and layoutHint() define how the width + of the bars is calculated + + \param hint Layout hint + + \sa LayoutPolicy, layoutPolicy(), layoutHint() + */ +void QwtPlotAbstractBarChart::setLayoutHint( double hint ) +{ + hint = qMax( 0.0, hint ); + if ( hint != d_data->layoutHint ) + { + d_data->layoutHint = hint; + itemChanged(); + } +} + +/*! + The combination of layoutPolicy() and layoutHint() define how the width + of the bars is calculated + + \return Layout policy of the chart item + \sa LayoutPolicy, setLayoutHint(), layoutPolicy() +*/ +double QwtPlotAbstractBarChart::layoutHint() const +{ + return d_data->layoutHint; +} + +/*! + \brief Set the spacing + + The spacing is the distance between 2 samples ( bars for QwtPlotBarChart or + a group of bars for QwtPlotMultiBarChart ) in paint device coordinates. + + \sa spacing() + */ +void QwtPlotAbstractBarChart::setSpacing( int spacing ) +{ + spacing = qMax( spacing, 0 ); + if ( spacing != d_data->spacing ) + { + d_data->spacing = spacing; + itemChanged(); + } +} + +/*! + \return Spacing between 2 samples ( bars or groups of bars ) + \sa setSpacing(), margin() + */ +int QwtPlotAbstractBarChart::spacing() const +{ + return d_data->spacing; +} +/*! + \brief Set the margin + + The margin is the distance between the outmost bars and the contentsRect() + of the canvas. The default setting is 5 pixels. + + \param margin Margin + + \sa spacing(), margin() + */ +void QwtPlotAbstractBarChart::setMargin( int margin ) +{ + margin = qMax( margin, 0 ); + if ( margin != d_data->margin ) + { + d_data->margin = margin; + itemChanged(); + } +} + +/*! + \return Margin between the outmost bars and the contentsRect() + of the canvas. + + \sa setMargin(), spacing() + */ +int QwtPlotAbstractBarChart::margin() const +{ + return d_data->margin; +} + +/*! + \brief Set the baseline + + The baseline is the origin for the chart. Each bar is + painted from the baseline in the direction of the sample + value. In case of a horizontal orientation() the baseline + is interpreted as x - otherwise as y - value. + + The default value for the baseline is 0. + + \param value Value for the baseline + + \sa baseline(), QwtPlotSeriesItem::orientation() +*/ +void QwtPlotAbstractBarChart::setBaseline( double value ) +{ + if ( value != d_data->baseline ) + { + d_data->baseline = value; + itemChanged(); + } +} + +/*! + \return Value for the origin of the bar chart + \sa setBaseline(), QwtPlotSeriesItem::orientation() + */ +double QwtPlotAbstractBarChart::baseline() const +{ + return d_data->baseline; +} + +/*! + Calculate the width for a sample in paint device coordinates + + \param map Scale map for the corresponding scale + \param canvasSize Size of the canvas in paint device coordinates + \param boundingSize Bounding size of the chart in plot coordinates + ( used in AutoAdjustSamples mode ) + \param value Value of the sample + + \return Sample width + \sa layoutPolicy(), layoutHint() +*/ +double QwtPlotAbstractBarChart::sampleWidth( const QwtScaleMap &map, + double canvasSize, double boundingSize, double value ) const +{ + double width; + + switch( d_data->layoutPolicy ) + { + case ScaleSamplesToAxes: + { + width = qwtTransformWidth( map, value, d_data->layoutHint ); + break; + } + case ScaleSampleToCanvas: + { + width = canvasSize * d_data->layoutHint; + break; + } + case FixedSampleSize: + { + width = d_data->layoutHint; + break; + } + case AutoAdjustSamples: + default: + { + const size_t numSamples = dataSize(); + + double w = 1.0; + if ( numSamples > 1 ) + { + w = qAbs( boundingSize / ( numSamples - 1 ) ); + } + + width = qwtTransformWidth( map, value, w ); + width -= d_data->spacing; + } + } + + return width; +} + +/*! + \brief Calculate a hint for the canvas margin + + Bar charts need to reserve some space for displaying the bars + for the first and the last sample. The hint is calculated + from the layoutHint() depending on the layoutPolicy(). + + The margins are in target device coordinates ( pixels on screen ) + + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas in painter coordinates + \param left Returns the left margin + \param top Returns the top margin + \param right Returns the right margin + \param bottom Returns the bottom margin + + \return Margin + + \sa layoutPolicy(), layoutHint(), QwtPlotItem::Margins + QwtPlot::getCanvasMarginsHint(), QwtPlot::updateCanvasMargins() + */ +void QwtPlotAbstractBarChart::getCanvasMarginHint( const QwtScaleMap &xMap, + const QwtScaleMap &yMap, const QRectF &canvasRect, + double &left, double &top, double &right, double &bottom ) const +{ + double hint = -1.0; + + switch( layoutPolicy() ) + { + case ScaleSampleToCanvas: + { + if ( orientation() == Qt::Vertical ) + hint = 0.5 * canvasRect.width() * d_data->layoutHint; + else + hint = 0.5 * canvasRect.height() * d_data->layoutHint; + + break; + } + case FixedSampleSize: + { + hint = 0.5 * d_data->layoutHint; + break; + } + case AutoAdjustSamples: + case ScaleSamplesToAxes: + default: + { + const size_t numSamples = dataSize(); + if ( numSamples <= 0 ) + break; + + // doesn't work for nonlinear scales + + const QRectF br = dataRect(); + double spacing = 0.0; + double sampleWidthS = 1.0; + + if ( layoutPolicy() == ScaleSamplesToAxes ) + { + sampleWidthS = qMax( d_data->layoutHint, 0.0 ); + } + else + { + spacing = d_data->spacing; + + if ( numSamples > 1 ) + { + sampleWidthS = qAbs( br.width() / ( numSamples - 1 ) ); + } + } + + double ds, w; + if ( orientation() == Qt::Vertical ) + { + ds = qAbs( xMap.sDist() ); + w = canvasRect.width(); + } + else + { + ds = qAbs( yMap.sDist() ); + w = canvasRect.height(); + } + + const double sampleWidthP = ( w - spacing * ds ) + * sampleWidthS / ( ds + sampleWidthS ); + + hint = 0.5 * sampleWidthP; + hint += qMax( d_data->margin, 0 ); + } + } + + if ( orientation() == Qt::Vertical ) + { + left = right = hint; + top = bottom = -1.0; // no hint + } + else + { + left = right = -1.0; // no hint + top = bottom = hint; + } +} diff --git a/qwt/src/qwt_plot_abstract_barchart.h b/qwt/src/qwt_plot_abstract_barchart.h new file mode 100644 index 000000000..78b98a531 --- /dev/null +++ b/qwt/src/qwt_plot_abstract_barchart.h @@ -0,0 +1,97 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_ABSTRACT_BAR_CHART_H +#define QWT_PLOT_ABSTRACT_BAR_CHART_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" +#include "qwt_series_data.h" + +/*! + \brief Abstract base class for bar chart items + + In opposite to almost all other plot items bar charts can't be + displayed inside of their bounding rectangle and need a special + API how to calculate the width of the bars and how they affect + the layout of the attached plot. + */ +class QWT_EXPORT QwtPlotAbstractBarChart: public QwtPlotSeriesItem +{ +public: + /*! + \brief Mode how to calculate the bar width + + setLayoutPolicy(), setLayoutHint(), barWidthHint() + */ + enum LayoutPolicy + { + /*! + The sample width is calculated by dividing the bounding rectangle + by the number of samples. + + \sa boundingRectangle() + \note The layoutHint() is ignored + */ + AutoAdjustSamples, + + /*! + layoutHint() defines an interval in axis coordinates + */ + ScaleSamplesToAxes, + + /*! + The bar width is calculated by multiplying layoutHint() + with the height or width of the canvas. + + \sa boundingRectangle() + */ + ScaleSampleToCanvas, + + /*! + layoutHint() defines a fixed width in paint device coordinates. + */ + FixedSampleSize + }; + + explicit QwtPlotAbstractBarChart( const QwtText &title ); + virtual ~QwtPlotAbstractBarChart(); + + void setLayoutPolicy( LayoutPolicy ); + LayoutPolicy layoutPolicy() const; + + void setLayoutHint( double ); + double layoutHint() const; + + void setSpacing( int ); + int spacing() const; + + void setMargin( int ); + int margin() const; + + void setBaseline( double ); + double baseline() const; + + virtual void getCanvasMarginHint( + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, + double &left, double &top, double &right, double &bottom) const; + + +protected: + double sampleWidth( const QwtScaleMap &map, + double canvasSize, double dataSize, + double value ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_axis.cpp b/qwt/src/qwt_plot_axis.cpp new file mode 100644 index 000000000..43c52f122 --- /dev/null +++ b/qwt/src/qwt_plot_axis.cpp @@ -0,0 +1,829 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot.h" +#include "qwt_math.h" +#include "qwt_scale_widget.h" +#include "qwt_scale_div.h" +#include "qwt_scale_engine.h" + +class QwtPlotAxisData +{ +public: + QwtPlotAxisData(): + isVisible( true ), + doAutoScale( true ), + minValue( 0.0 ), + maxValue( 1000.0 ), + stepSize( 0.0 ), + maxMajor( 8 ), + maxMinor( 5 ), + isValid( false ), + scaleEngine( new QwtLinearScaleEngine() ), + scaleWidget( NULL ) + { + } + + void initWidget( QwtScaleDraw::Alignment align, const QString& name, QwtPlot* plot ) + { + scaleWidget = new QwtScaleWidget( align, plot ); + scaleWidget->setObjectName( name ); + +#if 1 + // better find the font sizes from the application font + const QFont fscl( plot->fontInfo().family(), 10 ); + const QFont fttl( plot->fontInfo().family(), 12, QFont::Bold ); +#endif + + scaleWidget->setTransformation( scaleEngine->transformation() ); + + scaleWidget->setFont( fscl ); + scaleWidget->setMargin( 2 ); + + QwtText text = scaleWidget->title(); + text.setFont( fttl ); + scaleWidget->setTitle( text ); + } + + bool isVisible; + bool doAutoScale; + + double minValue; + double maxValue; + double stepSize; + + int maxMajor; + int maxMinor; + + bool isValid; + + QwtScaleDiv scaleDiv; + QwtScaleEngine *scaleEngine; + QwtScaleWidget *scaleWidget; +}; + +class QwtPlot::ScaleData +{ +public: + ScaleData( QwtPlot *plot ) + { + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + setAxesCount( plot, axisPos, 1 ); + } + + ~ScaleData() + { + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < d[axisPos].axisData.size(); i++ ) + { + delete d[axisPos].axisData[i].scaleEngine; + } + } + } + + void setAxesCount( QwtPlot *plot, int axisPos, int count ) + { + QVector< QwtPlotAxisData > &axisData = d[axisPos].axisData; + + for ( int i = count; i < axisData.size(); i++ ) + { + delete axisData[i].scaleEngine; + delete axisData[i].scaleWidget; + } + + const int numAxis = axisData.size(); + axisData.resize( count ); + + for ( int i = numAxis; i < count; i++ ) + { + QString name; + QwtScaleDraw::Alignment align; + + switch( axisPos ) + { + case QwtAxis::yLeft: + align = QwtScaleDraw::LeftScale; + name = "QwtPlotAxisYLeft"; + break; + case QwtAxis::yRight: + align = QwtScaleDraw::RightScale; + name = "QwtPlotAxisYRight"; + break; + case QwtAxis::xBottom: + align = QwtScaleDraw::BottomScale; + name = "QwtPlotAxisXBottom"; + break; + case QwtAxis::xTop: + align = QwtScaleDraw::TopScale; + name = "QwtPlotAxisXTop"; + break; + } + + if ( i > 0 ) + name += QString().setNum( i ); + + + axisData[ i ].initWidget( align, name, plot ); + } + } + + inline int axesCount( int pos ) const + { + if ( !QwtAxis::isValid( pos ) ) + return -1; + + return d[pos].axisData.count(); + } + + inline QwtPlotAxisData &axisData( const QwtAxisId &axisId ) + { + return d[ axisId.pos ].axisData[ axisId.id ]; + } + + inline const QwtPlotAxisData &axisData( const QwtAxisId &axisId ) const + { + return d[ axisId.pos ].axisData[ axisId.id ]; + } + + struct + { + QVector< QwtPlotAxisData > axisData; + + } d[ QwtAxis::PosCount ]; +}; + +//! Initialize axes +void QwtPlot::initScaleData() +{ + d_scaleData = new ScaleData( this ); + + d_scaleData->axisData( QwtAxis::yRight ).isVisible = false; + d_scaleData->axisData( QwtAxis::xTop ).isVisible = false; +} + +void QwtPlot::deleteScaleData() +{ + delete d_scaleData; + d_scaleData = NULL; +} + +void QwtPlot::setAxesCount( int axisPos, int count ) +{ + count = qMax( count, 1 ); // we need at least one axis + + if ( count != axesCount( axisPos ) ) + { + d_scaleData->setAxesCount( this, axisPos, count ); + autoRefresh(); + } +} + +int QwtPlot::axesCount( int axisPos, bool onlyVisible ) const +{ + int count = 0; + + if ( onlyVisible ) + { + for ( int i = 0; i < d_scaleData->axesCount( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + if ( d_scaleData->axisData( axisId ).isVisible ) + count++; + } + } + else + { + count = d_scaleData->axesCount( axisPos ); + } + + return count; +} + +/*! + \return \c true if the specified axis exists, otherwise \c false + \param axisPos axis index + */ +bool QwtPlot::isAxisValid( QwtAxisId axisId ) const +{ + return d_scaleData->axesCount( axisId.pos ) > axisId.id; +} + +/*! + \return Scale widget of the specified axis, or NULL if axisPos is invalid. + \param axisPos Axis position +*/ +const QwtScaleWidget *QwtPlot::axisWidget( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).scaleWidget; + + return NULL; +} + +/*! + \return Scale widget of the specified axis, or NULL if axisPos is invalid. + \param axisPos Axis position +*/ +QwtScaleWidget *QwtPlot::axisWidget( QwtAxisId axisId ) +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).scaleWidget; + + return NULL; +} + +/*! + Change the scale engine for an axis + + \param axisPos Axis position + \param scaleEngine Scale engine + + \sa axisScaleEngine() +*/ +void QwtPlot::setAxisScaleEngine( QwtAxisId axisId, QwtScaleEngine *scaleEngine ) +{ + if ( isAxisValid( axisId ) && scaleEngine != NULL ) + { + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + + delete d.scaleEngine; + d.scaleEngine = scaleEngine; + + d.scaleWidget->setTransformation( scaleEngine->transformation() ); + + d.isValid = false; + + autoRefresh(); + } +} + +/*! + \param axisPos Axis position + \return Scale engine for a specific axis +*/ +QwtScaleEngine *QwtPlot::axisScaleEngine( QwtAxisId axisId ) +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).scaleEngine; + else + return NULL; +} + +/*! + \param axisPos Axis position + \return Scale engine for a specific axis +*/ +const QwtScaleEngine *QwtPlot::axisScaleEngine( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).scaleEngine; + else + return NULL; +} +/*! + \return \c True, if autoscaling is enabled + \param axisPos Axis position +*/ +bool QwtPlot::axisAutoScale( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).doAutoScale; + else + return false; +} + +/*! + \return \c True, if a specified axis is visible + \param axisPos Axis position +*/ +bool QwtPlot::isAxisVisible( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).isVisible; + else + return false; +} + +/*! + \return The font of the scale labels for a specified axis + \param axisPos Axis position +*/ +QFont QwtPlot::axisFont( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return axisWidget( axisId )->font(); + else + return QFont(); + +} + +/*! + \return The maximum number of major ticks for a specified axis + \param axisPos Axis position + \sa setAxisMaxMajor(), QwtScaleEngine::divideScale() +*/ +int QwtPlot::axisMaxMajor( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).maxMajor; + else + return 0; +} + +/*! + \return the maximum number of minor ticks for a specified axis + \param axisPos Axis position + \sa setAxisMaxMinor(), QwtScaleEngine::divideScale() +*/ +int QwtPlot::axisMaxMinor( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).maxMinor; + else + return 0; +} + +/*! + \brief Return the scale division of a specified axis + + axisScaleDiv(axisPos).lowerBound(), axisScaleDiv(axisPos).upperBound() + are the current limits of the axis scale. + + \param axisPos Axis position + \return Scale division + + \sa QwtScaleDiv, setAxisScaleDiv(), QwtScaleEngine::divideScale() +*/ +const QwtScaleDiv &QwtPlot::axisScaleDiv( QwtAxisId axisId ) const +{ + return d_scaleData->axisData( axisId ).scaleDiv; +} + +/*! + \brief Return the scale draw of a specified axis + + \param axisPos Axis position + \return Specified scaleDraw for axis, or NULL if axis is invalid. +*/ +const QwtScaleDraw *QwtPlot::axisScaleDraw( QwtAxisId axisId ) const +{ + if ( !isAxisValid( axisId ) ) + return NULL; + + return axisWidget( axisId )->scaleDraw(); +} + +/*! + \brief Return the scale draw of a specified axis + + \param axisPos Axis position + \return Specified scaleDraw for axis, or NULL if axis is invalid. +*/ +QwtScaleDraw *QwtPlot::axisScaleDraw( QwtAxisId axisId ) +{ + if ( !isAxisValid( axisId ) ) + return NULL; + + return axisWidget( axisId )->scaleDraw(); +} + +/*! + \brief Return the step size parameter that has been set in setAxisScale. + + This doesn't need to be the step size of the current scale. + + \param axisPos Axis position + \return step size parameter value + + \sa setAxisScale(), QwtScaleEngine::divideScale() +*/ +double QwtPlot::axisStepSize( QwtAxisId axisId ) const +{ + if ( !isAxisValid( axisId ) ) + return 0; + + return d_scaleData->axisData( axisId ).stepSize; +} + +/*! + \brief Return the current interval of the specified axis + + This is only a convenience function for axisScaleDiv( axisPos )->interval(); + + \param axisPos Axis position + \return Scale interval + + \sa QwtScaleDiv, axisScaleDiv() +*/ +QwtInterval QwtPlot::axisInterval( QwtAxisId axisId ) const +{ + if ( !isAxisValid( axisId ) ) + return QwtInterval(); + + return d_scaleData->axisData( axisId ).scaleDiv.interval(); +} + +/*! + \return Title of a specified axis + \param axisPos Axis position +*/ +QwtText QwtPlot::axisTitle( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return axisWidget( axisId )->title(); + else + return QwtText(); +} + +/*! + \brief Enable or disable a specified axis + + Even when an axis is not visible curves, markers and can be attached + to it, and transformation of screen coordinates into values works as normal. + + Only QwtAxis::xBottom and QwtAxis::yLeft are visible by default. + + \param axisPos Axis position + \param on \c true (visisble) or \c false (hidden) +*/ +void QwtPlot::setAxisVisible( QwtAxisId axisId, bool on ) +{ + if ( isAxisValid( axisId ) && on != d_scaleData->axisData( axisId ).isVisible ) + { + d_scaleData->axisData( axisId ).isVisible = on; + updateLayout(); + } +} + +/*! + Transform the x or y coordinate of a position in the + drawing region into a value. + + \param axisPos Axis position + \param pos position + + \return Position as axis coordinate + + \warning The position can be an x or a y coordinate, + depending on the specified axis. +*/ +double QwtPlot::invTransform( QwtAxisId axisId, double pos ) const +{ + if ( isAxisValid( axisId ) ) + return( canvasMap( axisId ).invTransform( pos ) ); + else + return 0.0; +} + + +/*! + \brief Transform a value into a coordinate in the plotting region + + \param axisPos Axis position + \param value value + \return X or Y coordinate in the plotting region corresponding + to the value. +*/ +double QwtPlot::transform( QwtAxisId axisId, double value ) const +{ + if ( isAxisValid( axisId ) ) + return( canvasMap( axisId ).transform( value ) ); + else + return 0.0; +} + +/*! + \brief Change the font of an axis + + \param axisPos Axis position + \param font Font + \warning This function changes the font of the tick labels, + not of the axis title. +*/ +void QwtPlot::setAxisFont( QwtAxisId axisId, const QFont &font ) +{ + if ( isAxisValid( axisId ) ) + axisWidget( axisId )->setFont( font ); +} + +/*! + \brief Enable autoscaling for a specified axis + + This member function is used to switch back to autoscaling mode + after a fixed scale has been set. Autoscaling is enabled by default. + + \param axisPos Axis position + \param on On/Off + \sa setAxisScale(), setAxisScaleDiv(), updateAxes() + + \note The autoscaling flag has no effect until updateAxes() is executed + ( called by replot() ). +*/ +void QwtPlot::setAxisAutoScale( QwtAxisId axisId, bool on ) +{ + if ( isAxisValid( axisId ) && ( d_scaleData->axisData( axisId ).doAutoScale != on ) ) + { + d_scaleData->axisData( axisId ).doAutoScale = on; + autoRefresh(); + } +} + +/*! + \brief Disable autoscaling and specify a fixed scale for a selected axis. + + In updateAxes() the scale engine calculates a scale division from the + specified parameters, that will be assigned to the scale widget. So + updates of the scale widget usually happen delayed with the next replot. + + \param axisPos Axis position + \param min Minimum of the scale + \param max Maximum of the scale + \param stepSize Major step size. If step == 0, the step size is + calculated automatically using the maxMajor setting. + + \sa setAxisMaxMajor(), setAxisAutoScale(), axisStepSize(), QwtScaleEngine::divideScale() +*/ +void QwtPlot::setAxisScale( QwtAxisId axisId, double min, double max, double stepSize ) +{ + if ( isAxisValid( axisId ) ) + { + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + + d.doAutoScale = false; + d.isValid = false; + + d.minValue = min; + d.maxValue = max; + d.stepSize = stepSize; + + autoRefresh(); + } +} + +/*! + \brief Disable autoscaling and specify a fixed scale for a selected axis. + + The scale division will be stored locally only until the next call + of updateAxes(). So updates of the scale widget usually happen delayed with + the next replot. + + \param axisPos Axis position + \param scaleDiv Scale division + + \sa setAxisScale(), setAxisAutoScale() +*/ +void QwtPlot::setAxisScaleDiv( QwtAxisId axisId, const QwtScaleDiv &scaleDiv ) +{ + if ( isAxisValid( axisId ) ) + { + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + + d.doAutoScale = false; + d.scaleDiv = scaleDiv; + d.isValid = true; + + autoRefresh(); + } +} + +/*! + \brief Set a scale draw + + \param axisPos Axis position + \param scaleDraw Object responsible for drawing scales. + + By passing scaleDraw it is possible to extend QwtScaleDraw + functionality and let it take place in QwtPlot. Please note + that scaleDraw has to be created with new and will be deleted + by the corresponding QwtScale member ( like a child object ). + + \sa QwtScaleDraw, QwtScaleWidget + \warning The attributes of scaleDraw will be overwritten by those of the + previous QwtScaleDraw. +*/ + +void QwtPlot::setAxisScaleDraw( QwtAxisId axisId, QwtScaleDraw *scaleDraw ) +{ + if ( isAxisValid( axisId ) ) + { + axisWidget( axisId )->setScaleDraw( scaleDraw ); + autoRefresh(); + } +} + +/*! + Change the alignment of the tick labels + + \param axisPos Axis position + \param alignment Or'd Qt::AlignmentFlags see + + \sa QwtScaleDraw::setLabelAlignment() +*/ +void QwtPlot::setAxisLabelAlignment( QwtAxisId axisId, Qt::Alignment alignment ) +{ + if ( isAxisValid( axisId ) ) + axisWidget( axisId )->setLabelAlignment( alignment ); +} + +/*! + Rotate all tick labels + + \param axisPos Axis position + \param rotation Angle in degrees. When changing the label rotation, + the label alignment might be adjusted too. + + \sa QwtScaleDraw::setLabelRotation(), setAxisLabelAlignment() +*/ +void QwtPlot::setAxisLabelRotation( QwtAxisId axisId, double rotation ) +{ + if ( isAxisValid( axisId ) ) + axisWidget( axisId )->setLabelRotation( rotation ); +} + +/*! + Set the maximum number of minor scale intervals for a specified axis + + \param axisPos Axis position + \param maxMinor Maximum number of minor steps + + \sa axisMaxMinor() +*/ +void QwtPlot::setAxisMaxMinor( QwtAxisId axisId, int maxMinor ) +{ + if ( isAxisValid( axisId ) ) + { + maxMinor = qBound( 0, maxMinor, 100 ); + + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + if ( maxMinor != d.maxMinor ) + { + d.maxMinor = maxMinor; + d.isValid = false; + autoRefresh(); + } + } +} + +/*! + Set the maximum number of major scale intervals for a specified axis + + \param axisPos Axis position + \param maxMajor Maximum number of major steps + + \sa axisMaxMajor() +*/ +void QwtPlot::setAxisMaxMajor( QwtAxisId axisId, int maxMajor ) +{ + if ( isAxisValid( axisId ) ) + { + maxMajor = qBound( 1, maxMajor, 10000 ); + + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + if ( maxMajor != d.maxMajor ) + { + d.maxMajor = maxMajor; + d.isValid = false; + autoRefresh(); + } + } +} + +/*! + \brief Change the title of a specified axis + + \param axisPos Axis position + \param title axis title +*/ +void QwtPlot::setAxisTitle( QwtAxisId axisId, const QString &title ) +{ + if ( isAxisValid( axisId ) ) + axisWidget( axisId )->setTitle( title ); +} + +/*! + \brief Change the title of a specified axis + + \param axisPos Axis position + \param title Axis title +*/ +void QwtPlot::setAxisTitle( QwtAxisId axisId, const QwtText &title ) +{ + if ( isAxisValid( axisId ) ) + axisWidget( axisId )->setTitle( title ); +} + +/*! + \brief Rebuild the axes scales + + In case of autoscaling the boundaries of a scale are calculated + from the bounding rectangles of all plot items, having the + QwtPlotItem::AutoScale flag enabled ( QwtScaleEngine::autoScale() ). + Then a scale division is calculated ( QwtScaleEngine::didvideScale() ) + and assigned to scale widget. + + When the scale boundaries have been assigned with setAxisScale() a + scale division is calculated ( QwtScaleEngine::didvideScale() ) + for this interval and assigned to the scale widget. + + When the scale has been set explicitly by setAxisScaleDiv() the + locally stored scale division gets assigned to the scale widget. + + The scale widget indicates modifications by emitting a + QwtScaleWidget::scaleDivChanged() signal. + + updateAxes() is usually called by replot(). + + \sa setAxisAutoScale(), setAxisScale(), setAxisScaleDiv(), replot() + QwtPlotItem::boundingRect() + */ +void QwtPlot::updateAxes() +{ + // Find bounding interval of the item data + // for all axes, where autoscaling is enabled + + QwtInterval intv[QwtAxis::PosCount]; + + const QwtPlotItemList& itmList = itemList(); + + QwtPlotItemIterator it; + for ( it = itmList.begin(); it != itmList.end(); ++it ) + { + const QwtPlotItem *item = *it; + + if ( !item->testItemAttribute( QwtPlotItem::AutoScale ) ) + continue; + + if ( !item->isVisible() ) + continue; + + if ( axisAutoScale( item->xAxis() ) || axisAutoScale( item->yAxis() ) ) + { + const QRectF rect = item->boundingRect(); + + if ( rect.width() >= 0.0 ) + intv[ item->xAxis().pos ] |= QwtInterval( rect.left(), rect.right() ); + + if ( rect.height() >= 0.0 ) + intv[ item->yAxis().pos ] |= QwtInterval( rect.top(), rect.bottom() ); + } + } + + // Adjust scales + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < d_scaleData->axesCount( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + + double minValue = d.minValue; + double maxValue = d.maxValue; + double stepSize = d.stepSize; + + if ( d.doAutoScale && intv[axisPos].isValid() ) + { + d.isValid = false; + + minValue = intv[axisPos].minValue(); + maxValue = intv[axisPos].maxValue(); + + d.scaleEngine->autoScale( d.maxMajor, + minValue, maxValue, stepSize ); + } + if ( !d.isValid ) + { + d.scaleDiv = d.scaleEngine->divideScale( + minValue, maxValue, + d.maxMajor, d.maxMinor, stepSize ); + d.isValid = true; + } + + QwtScaleWidget *scaleWidget = axisWidget( axisId ); + scaleWidget->setScaleDiv( d.scaleDiv ); + + int startDist, endDist; + scaleWidget->getBorderDistHint( startDist, endDist ); + scaleWidget->setBorderDist( startDist, endDist ); + } + } + + for ( it = itmList.begin(); it != itmList.end(); ++it ) + { + QwtPlotItem *item = *it; + if ( item->testItemInterest( QwtPlotItem::ScaleInterest ) ) + { + item->updateScaleDiv( axisScaleDiv( item->xAxis() ), + axisScaleDiv( item->yAxis() ) ); + } + } +} + diff --git a/qwt/src/qwt_plot_barchart.cpp b/qwt/src/qwt_plot_barchart.cpp new file mode 100644 index 000000000..b3455af5e --- /dev/null +++ b/qwt/src/qwt_plot_barchart.cpp @@ -0,0 +1,455 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_barchart.h" +#include "qwt_scale_map.h" +#include "qwt_column_symbol.h" +#include "qwt_painter.h" +#include + +class QwtPlotBarChart::PrivateData +{ +public: + PrivateData(): + symbol( NULL ), + legendMode( QwtPlotBarChart::LegendChartTitle ) + { + } + + ~PrivateData() + { + delete symbol; + } + + QwtColumnSymbol *symbol; + QwtPlotBarChart::LegendMode legendMode; +}; + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotBarChart::QwtPlotBarChart( const QwtText &title ): + QwtPlotAbstractBarChart( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotBarChart::QwtPlotBarChart( const QString &title ): + QwtPlotAbstractBarChart( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPlotBarChart::~QwtPlotBarChart() +{ + delete d_data; +} + +void QwtPlotBarChart::init() +{ + d_data = new PrivateData; + setData( new QwtPointSeriesData() ); +} + +//! \return QwtPlotItem::Rtti_PlotBarChart +int QwtPlotBarChart::rtti() const +{ + return QwtPlotItem::Rtti_PlotBarChart; +} + +/*! + Initialize data with an array of points + + \param samples Vector of points + \note QVector is implicitly shared + \note QPolygonF is derived from QVector +*/ +void QwtPlotBarChart::setSamples( + const QVector &samples ) +{ + setData( new QwtPointSeriesData( samples ) ); +} + +/*! + Initialize data with an array of doubles + + The indices in the array are taken as x coordinate, + while the doubles are interpreted as y values. + + \param samples Vector of y coordinates + \note QVector is implicitly shared +*/ +void QwtPlotBarChart::setSamples( + const QVector &samples ) +{ + QVector points; + for ( int i = 0; i < samples.size(); i++ ) + points += QPointF( i, samples[ i ] ); + + setData( new QwtPointSeriesData( points ) ); +} + +/*! + Assign a series of samples + + setSamples() is just a wrapper for setData() without any additional + value - beside that it is easier to find for the developer. + + \param data Data + \warning The item takes ownership of the data object, deleting + it when its not used anymore. +*/ +void QwtPlotBarChart::setSamples( QwtSeriesData *data ) +{ + setData( data ); +} + +/*! + \brief Assign a symbol + + The bar chart will take the ownership of the symbol, hence the previously + set symbol will be delete by setting a new one. If \p symbol is + \c NULL no symbol will be drawn. + + \param symbol Symbol + \sa symbol() +*/ +void QwtPlotBarChart::setSymbol( QwtColumnSymbol *symbol ) +{ + if ( symbol != d_data->symbol ) + { + delete d_data->symbol; + d_data->symbol = symbol; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() +*/ +const QwtColumnSymbol *QwtPlotBarChart::symbol() const +{ + return d_data->symbol; +} + +/*! + Set the mode that decides what to display on the legend + + In case of LegendBarTitles barTitle() needs to be overloaded + to return individual titles for each bar. + + \param mode New mode + \sa legendMode(), legendData(), barTitle(), QwtPlotItem::ItemAttribute + */ +void QwtPlotBarChart::setLegendMode( LegendMode mode ) +{ + if ( mode != d_data->legendMode ) + { + d_data->legendMode = mode; + legendChanged(); + } +} + +/*! + \return Legend mode + \sa setLegendMode() + */ +QwtPlotBarChart::LegendMode QwtPlotBarChart::legendMode() const +{ + return d_data->legendMode; +} + +/*! + \return Bounding rectangle of all samples. + For an empty series the rectangle is invalid. +*/ +QRectF QwtPlotBarChart::boundingRect() const +{ + const size_t numSamples = dataSize(); + if ( numSamples == 0 ) + return QwtPlotSeriesItem::boundingRect(); + + const double baseLine = baseline(); + + QRectF rect = QwtPlotSeriesItem::boundingRect(); + if ( rect.bottom() < baseLine ) + rect.setBottom( baseLine ); + if ( rect.top() > baseLine ) + rect.setTop( baseLine ); + + if ( rect.isValid() && ( orientation() == Qt::Horizontal ) ) + rect.setRect( rect.y(), rect.x(), rect.height(), rect.width() ); + + return rect; +} + +/*! + Draw an interval of the bar chart + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rect of the canvas + \param from Index of the first point to be painted + \param to Index of the last point to be painted. If to < 0 the + curve will be painted to its last point. + + \sa drawSymbols() +*/ +void QwtPlotBarChart::drawSeries( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + if ( to < 0 ) + to = dataSize() - 1; + + if ( from < 0 ) + from = 0; + + if ( from > to ) + return; + + + const QRectF br = data()->boundingRect(); + const QwtInterval interval( br.left(), br.right() ); + + painter->save(); + + for ( int i = from; i <= to; i++ ) + { + drawSample( painter, xMap, yMap, + canvasRect, interval, i, sample( i ) ); + } + + painter->restore(); +} + +/*! + Draw a sample + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rect of the canvas + \param boundingInterval Bounding interval of sample values + \param index Index of the sample + \param sample Value of the sample + + \sa drawSeries() +*/ +void QwtPlotBarChart::drawSample( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, const QwtInterval &boundingInterval, + int index, const QPointF &sample ) const +{ + QwtColumnRect barRect; + + if ( orientation() == Qt::Horizontal ) + { + const double barHeight = sampleWidth( yMap, canvasRect.height(), + boundingInterval.width(), sample.y() ); + + const double x1 = xMap.transform( baseline() ); + const double x2 = xMap.transform( sample.y() ); + + const double y = yMap.transform( sample.x() ); + const double y1 = y - 0.5 * barHeight; + const double y2 = y + 0.5 * barHeight; + + barRect.direction = ( x1 < x2 ) ? + QwtColumnRect::LeftToRight : QwtColumnRect::RightToLeft; + + barRect.hInterval = QwtInterval( x1, x2 ).normalized(); + barRect.vInterval = QwtInterval( y1, y2 ); + } + else + { + const double barWidth = sampleWidth( xMap, canvasRect.width(), + boundingInterval.width(), sample.y() ); + + const double x = xMap.transform( sample.x() ); + const double x1 = x - 0.5 * barWidth; + const double x2 = x + 0.5 * barWidth; + + const double y1 = yMap.transform( baseline() ); + const double y2 = yMap.transform( sample.y() ); + + barRect.direction = ( y1 < y2 ) ? + QwtColumnRect::TopToBottom : QwtColumnRect::BottomToTop; + + barRect.hInterval = QwtInterval( x1, x2 ); + barRect.vInterval = QwtInterval( y1, y2 ).normalized(); + } + + drawBar( painter, index, sample, barRect ); +} + +/*! + Draw a bar + + \param painter Painter + \param sampleIndex Index of the sample represented by the bar + \param sample Value of the sample + \param rect Bounding rectangle of the bar + */ +void QwtPlotBarChart::drawBar( QPainter *painter, + int sampleIndex, const QPointF &sample, + const QwtColumnRect &rect ) const +{ + const QwtColumnSymbol *specialSym = + specialSymbol( sampleIndex, sample ); + + const QwtColumnSymbol *sym = specialSym; + if ( sym == NULL ) + sym = d_data->symbol; + + if ( sym ) + { + sym->draw( painter, rect ); + } + else + { + // we build a temporary default symbol + QwtColumnSymbol sym( QwtColumnSymbol::Box ); + sym.setLineWidth( 1 ); + sym.setFrameStyle( QwtColumnSymbol::Plain ); + sym.draw( painter, rect ); + } + + delete specialSym; +} + +/*! + Needs to be overloaded to return a + non default symbol for a specific sample + + \param sampleIndex Index of the sample represented by the bar + \param sample Value of the sample + + \return NULL, indicating to use the default symbol + */ +QwtColumnSymbol *QwtPlotBarChart::specialSymbol( + int sampleIndex, const QPointF &sample ) const +{ + Q_UNUSED( sampleIndex ); + Q_UNUSED( sample ); + + return NULL; +} + +/*! + \brief Return the title of a bar + + In LegendBarTitles mode the title is displayed on + the legend entry corresponding to a bar. + + The default implementation is a dummy, that is intended + to be overloaded. + + \param sampleIndex Index of the bar + \return An empty text + \sa LegendBarTitles + */ +QwtText QwtPlotBarChart::barTitle( int sampleIndex ) const +{ + Q_UNUSED( sampleIndex ); + return QwtText(); +} + +/*! + \brief Return all information, that is needed to represent + the item on the legend + + In case of LegendBarTitles an entry for each bar is returned, + otherwise the chart is represented like any other plot item + from its title() and the legendIcon(). + + \return Information, that is needed to represent the item on the legend + \sa title(), setLegendMode(), barTitle(), QwtLegend, QwtPlotLegendItem + */ +QList QwtPlotBarChart::legendData() const +{ + QList list; + + if ( d_data->legendMode == LegendBarTitles ) + { + const size_t numSamples = dataSize(); + for ( size_t i = 0; i < numSamples; i++ ) + { + QwtLegendData data; + + QVariant titleValue; + qVariantSetValue( titleValue, barTitle( i ) ); + data.setValue( QwtLegendData::TitleRole, titleValue ); + + if ( !legendIconSize().isEmpty() ) + { + QVariant iconValue; + qVariantSetValue( iconValue, + legendIcon( i, legendIconSize() ) ); + + data.setValue( QwtLegendData::IconRole, iconValue ); + } + + list += data; + } + } + else + { + return QwtPlotAbstractBarChart::legendData(); + } + + return list; +} + +/*! + \return Icon representing a bar or the chart on the legend + + When the legendMode() is LegendBarTitles the icon shows + the bar corresponding to index - otherwise the bar + displays the default symbol. + + \param index Index of the legend entry + \param size Icon size + + \sa setLegendMode(), drawBar(), + QwtPlotItem::setLegendIconSize(), QwtPlotItem::legendData() + */ +QwtGraphic QwtPlotBarChart::legendIcon( + int index, const QSizeF &size ) const +{ + QwtColumnRect column; + column.hInterval = QwtInterval( 0.0, size.width() - 1.0 ); + column.vInterval = QwtInterval( 0.0, size.height() - 1.0 ); + + QwtGraphic icon; + icon.setDefaultSize( size ); + icon.setRenderHint( QwtGraphic::RenderPensUnscaled, true ); + + QPainter painter( &icon ); + painter.setRenderHint( QPainter::Antialiasing, + testRenderHint( QwtPlotItem::RenderAntialiased ) ); + + int barIndex = -1; + if ( d_data->legendMode == QwtPlotBarChart::LegendBarTitles ) + barIndex = index; + + drawBar( &painter, barIndex, QPointF(), column ); + + return icon; +} diff --git a/qwt/src/qwt_plot_barchart.h b/qwt/src/qwt_plot_barchart.h new file mode 100644 index 000000000..d47bfb972 --- /dev/null +++ b/qwt/src/qwt_plot_barchart.h @@ -0,0 +1,118 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_BAR_CHART_H +#define QWT_PLOT_BAR_CHART_H + +#include "qwt_global.h" +#include "qwt_plot_abstract_barchart.h" +#include "qwt_series_data.h" + +class QwtColumnRect; +class QwtColumnSymbol; + +/*! + \brief QwtPlotBarChart displays a series of a values as bars. + + Each bar might be customized individually by implementing + a specialSymbol(). Otherwise it is rendered using a default symbol. + + Depending on its orientation() the bars are displayed horizontally + or vertically. The bars cover the interval between the baseline() + and the value. + + By activating the LegendBarTitles mode each sample will have + its own entry on the legend. + + The most common use case of a bar chart is to display a + list of y coordinates, where the x coordinate is simply the index + in the list. But for other situations ( f.e. when values are related + to dates ) it is also possible to set x coordinates explicitly. + + \sa QwtPlotMultiBarChart, QwtPlotHistogram, QwtPlotCurve::Sticks, + QwtPlotSeriesItem::orientation(), QwtPlotAbstractBarChart::baseline() + */ +class QWT_EXPORT QwtPlotBarChart: + public QwtPlotAbstractBarChart, public QwtSeriesStore +{ +public: + /*! + \brief Legend modes. + + The default setting is QwtPlotBarChart::LegendChartTitle. + \sa setLegendMode(), legendMode() + */ + enum LegendMode + { + /*! + One entry on the legend showing the default symbol + and the title() of the chart + + \sa QwtPlotItem::title() + */ + LegendChartTitle, + + /*! + One entry for each value showing the individual symbol + of the corresponding bar and the bar title. + + \sa specialSymbol(), barTitle() + */ + LegendBarTitles + }; + + explicit QwtPlotBarChart( const QString &title = QString::null ); + explicit QwtPlotBarChart( const QwtText &title ); + + virtual ~QwtPlotBarChart(); + + virtual int rtti() const; + + void setSamples( const QVector & ); + void setSamples( const QVector & ); + void setSamples( QwtSeriesData *series ); + + void setSymbol( QwtColumnSymbol * ); + const QwtColumnSymbol *symbol() const; + + void setLegendMode( LegendMode ); + LegendMode legendMode() const; + + virtual void drawSeries( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual QRectF boundingRect() const; + + virtual QwtColumnSymbol *specialSymbol( + int sampleIndex, const QPointF& ) const; + + virtual QwtText barTitle( int sampleIndex ) const; + +protected: + virtual void drawSample( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, const QwtInterval &boundingInterval, + int index, const QPointF& sample ) const; + + virtual void drawBar( QPainter *, + int sampleIndex, const QPointF& point, + const QwtColumnRect & ) const; + + QList legendData() const; + QwtGraphic legendIcon( int index, const QSizeF & ) const; + +private: + void init(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_canvas.cpp b/qwt/src/qwt_plot_canvas.cpp new file mode 100644 index 000000000..0271713a8 --- /dev/null +++ b/qwt/src/qwt_plot_canvas.cpp @@ -0,0 +1,1097 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_canvas.h" +#include "qwt_painter.h" +#include "qwt_null_paintdevice.h" +#include "qwt_math.h" +#include "qwt_plot.h" +#include +#include +#include +#include +#include + +class QwtStyleSheetRecorder: public QwtNullPaintDevice +{ +public: + QwtStyleSheetRecorder( const QSize &size ): + d_size( size ) + { + } + + virtual void updateState( const QPaintEngineState &state ) + { + if ( state.state() & QPaintEngine::DirtyPen ) + { + d_pen = state.pen(); + } + if ( state.state() & QPaintEngine::DirtyBrush ) + { + d_brush = state.brush(); + } + if ( state.state() & QPaintEngine::DirtyBrushOrigin ) + { + d_origin = state.brushOrigin(); + } + } + + virtual void drawRects(const QRectF *rects, int count ) + { + for ( int i = 0; i < count; i++ ) + border.rectList += rects[i]; + } + + virtual void drawPath( const QPainterPath &path ) + { + const QRectF rect( QPointF( 0.0, 0.0 ), d_size ); + if ( path.controlPointRect().contains( rect.center() ) ) + { + setCornerRects( path ); + alignCornerRects( rect ); + + background.path = path; + background.brush = d_brush; + background.origin = d_origin; + } + else + { + border.pathList += path; + } + } + + void setCornerRects( const QPainterPath &path ) + { + QPointF pos( 0.0, 0.0 ); + + for ( int i = 0; i < path.elementCount(); i++ ) + { + QPainterPath::Element el = path.elementAt(i); + switch( el.type ) + { + case QPainterPath::MoveToElement: + case QPainterPath::LineToElement: + { + pos.setX( el.x ); + pos.setY( el.y ); + break; + } + case QPainterPath::CurveToElement: + { + QRectF r( pos, QPointF( el.x, el.y ) ); + clipRects += r.normalized(); + + pos.setX( el.x ); + pos.setY( el.y ); + + break; + } + case QPainterPath::CurveToDataElement: + { + if ( clipRects.size() > 0 ) + { + QRectF r = clipRects.last(); + r.setCoords( + qMin( r.left(), el.x ), + qMin( r.top(), el.y ), + qMax( r.right(), el.x ), + qMax( r.bottom(), el.y ) + ); + clipRects.last() = r.normalized(); + } + break; + } + } + } + } + +protected: + virtual QSize sizeMetrics() const + { + return d_size; + } + +private: + void alignCornerRects( const QRectF &rect ) + { + for ( int i = 0; i < clipRects.size(); i++ ) + { + QRectF &r = clipRects[i]; + if ( r.center().x() < rect.center().x() ) + r.setLeft( rect.left() ); + else + r.setRight( rect.right() ); + + if ( r.center().y() < rect.center().y() ) + r.setTop( rect.top() ); + else + r.setBottom( rect.bottom() ); + } + } + + +public: + QVector clipRects; + + struct Border + { + QList pathList; + QList rectList; + QRegion clipRegion; + } border; + + struct Background + { + QPainterPath path; + QBrush brush; + QPointF origin; + } background; + +private: + const QSize d_size; + + QPen d_pen; + QBrush d_brush; + QPointF d_origin; +}; + +static void qwtDrawBackground( QPainter *painter, QwtPlotCanvas *canvas ) +{ + painter->save(); + + const QPainterPath borderClip = canvas->borderPath( canvas->rect() ); + if ( !borderClip.isEmpty() ) + painter->setClipPath( borderClip, Qt::IntersectClip ); + + const QBrush &brush = + canvas->palette().brush( canvas->backgroundRole() ); + + if ( brush.style() == Qt::TexturePattern ) + { + QPixmap pm( canvas->size() ); + QwtPainter::fillPixmap( canvas, pm ); + painter->drawPixmap( 0, 0, pm ); + } + else if ( brush.gradient() ) + { + QVector rects; + + if ( brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode ) + { + rects += canvas->rect(); + } + else + { + rects = painter->clipRegion().rects(); + } + +#if 1 + bool useRaster = false; + + if ( painter->paintEngine()->type() == QPaintEngine::X11 ) + { + // Qt 4.7.1: gradients on X11 are broken ( subrects + + // QGradient::StretchToDeviceMode ) and horrible slow. + // As workaround we have to use the raster paintengine. + // Even if the QImage -> QPixmap translation is slow + // it is three times faster, than using X11 directly + + useRaster = true; + } +#endif + if ( useRaster ) + { + QImage::Format format = QImage::Format_RGB32; + + const QGradientStops stops = brush.gradient()->stops(); + for ( int i = 0; i < stops.size(); i++ ) + { + if ( stops[i].second.alpha() != 255 ) + { + // don't use Format_ARGB32_Premultiplied. It's + // recommended by the Qt docs, but QPainter::drawImage() + // is horrible slow on X11. + + format = QImage::Format_ARGB32; + break; + } + } + + QImage image( canvas->size(), format ); + + QPainter p( &image ); + p.setPen( Qt::NoPen ); + p.setBrush( brush ); + + p.drawRects( rects ); + + p.end(); + + painter->drawImage( 0, 0, image ); + } + else + { + painter->setPen( Qt::NoPen ); + painter->setBrush( brush ); + + painter->drawRects( rects ); + } + } + else + { + painter->setPen( Qt::NoPen ); + painter->setBrush( brush ); + + painter->drawRects( painter->clipRegion().rects() ); + + } + + painter->restore(); +} + +static inline void qwtRevertPath( QPainterPath &path ) +{ + if ( path.elementCount() == 4 ) + { + QPainterPath::Element el0 = path.elementAt(0); + QPainterPath::Element el3 = path.elementAt(3); + + path.setElementPositionAt( 0, el3.x, el3.y ); + path.setElementPositionAt( 3, el0.x, el0.y ); + } +} + +static QPainterPath qwtCombinePathList( const QRectF &rect, + const QList &pathList ) +{ + if ( pathList.isEmpty() ) + return QPainterPath(); + + QPainterPath ordered[8]; // starting top left + + for ( int i = 0; i < pathList.size(); i++ ) + { + int index = -1; + QPainterPath subPath = pathList[i]; + + const QRectF br = pathList[i].controlPointRect(); + if ( br.center().x() < rect.center().x() ) + { + if ( br.center().y() < rect.center().y() ) + { + if ( qAbs( br.top() - rect.top() ) < + qAbs( br.left() - rect.left() ) ) + { + index = 1; + } + else + { + index = 0; + } + } + else + { + if ( qAbs( br.bottom() - rect.bottom() ) < + qAbs( br.left() - rect.left() ) ) + { + index = 6; + } + else + { + index = 7; + } + } + + if ( subPath.currentPosition().y() > br.center().y() ) + qwtRevertPath( subPath ); + } + else + { + if ( br.center().y() < rect.center().y() ) + { + if ( qAbs( br.top() - rect.top() ) < + qAbs( br.right() - rect.right() ) ) + { + index = 2; + } + else + { + index = 3; + } + } + else + { + if ( qAbs( br.bottom() - rect.bottom() ) < + qAbs( br.right() - rect.right() ) ) + { + index = 5; + } + else + { + index = 4; + } + } + if ( subPath.currentPosition().y() < br.center().y() ) + qwtRevertPath( subPath ); + } + ordered[index] = subPath; + } + + for ( int i = 0; i < 4; i++ ) + { + if ( ordered[ 2 * i].isEmpty() != ordered[2 * i + 1].isEmpty() ) + { + // we don't accept incomplete rounded borders + return QPainterPath(); + } + } + + + const QPolygonF corners( rect ); + + QPainterPath path; + //path.moveTo( rect.topLeft() ); + + for ( int i = 0; i < 4; i++ ) + { + if ( ordered[2 * i].isEmpty() ) + { + path.lineTo( corners[i] ); + } + else + { + path.connectPath( ordered[2 * i] ); + path.connectPath( ordered[2 * i + 1] ); + } + } + + path.closeSubpath(); + +#if 0 + return path.simplified(); +#else + return path; +#endif +} + +static inline void qwtDrawStyledBackground( + QWidget *w, QPainter *painter ) +{ + QStyleOption opt; + opt.initFrom(w); + w->style()->drawPrimitive( QStyle::PE_Widget, &opt, painter, w); +} + +static QWidget *qwtBackgroundWidget( QWidget *w ) +{ + if ( w->parentWidget() == NULL ) + return w; + + if ( w->autoFillBackground() ) + { + const QBrush brush = w->palette().brush( w->backgroundRole() ); + if ( brush.color().alpha() > 0 ) + return w; + } + + if ( w->testAttribute( Qt::WA_StyledBackground ) ) + { + QImage image( 1, 1, QImage::Format_ARGB32 ); + image.fill( Qt::transparent ); + + QPainter painter( &image ); + painter.translate( -w->rect().center() ); + qwtDrawStyledBackground( w, &painter ); + painter.end(); + + if ( qAlpha( image.pixel( 0, 0 ) ) != 0 ) + return w; + } + + return qwtBackgroundWidget( w->parentWidget() ); +} + +static void qwtFillBackground( QPainter *painter, + QWidget *widget, const QVector &fillRects ) +{ + if ( fillRects.isEmpty() ) + return; + + QRegion clipRegion; + if ( painter->hasClipping() ) + clipRegion = painter->transform().map( painter->clipRegion() ); + else + clipRegion = widget->contentsRect(); + + // Try to find out which widget fills + // the unfilled areas of the styled background + + QWidget *bgWidget = qwtBackgroundWidget( widget->parentWidget() ); + + for ( int i = 0; i < fillRects.size(); i++ ) + { + const QRect rect = fillRects[i].toAlignedRect(); + if ( clipRegion.intersects( rect ) ) + { + QPixmap pm( rect.size() ); + QwtPainter::fillPixmap( bgWidget, pm, widget->mapTo( bgWidget, rect.topLeft() ) ); + painter->drawPixmap( rect, pm ); + } + } +} + +static void qwtFillBackground( QPainter *painter, QwtPlotCanvas *canvas ) +{ + QVector rects; + + if ( canvas->testAttribute( Qt::WA_StyledBackground ) ) + { + QwtStyleSheetRecorder recorder( canvas->size() ); + + QPainter p( &recorder ); + qwtDrawStyledBackground( canvas, &p ); + p.end(); + + if ( recorder.background.brush.isOpaque() ) + rects = recorder.clipRects; + else + rects += canvas->rect(); + } + else + { + const QRectF r = canvas->rect(); + const double radius = canvas->borderRadius(); + if ( radius > 0.0 ) + { + QSizeF sz( radius, radius ); + + rects += QRectF( r.topLeft(), sz ); + rects += QRectF( r.topRight() - QPointF( radius, 0 ), sz ); + rects += QRectF( r.bottomRight() - QPointF( radius, radius ), sz ); + rects += QRectF( r.bottomLeft() - QPointF( 0, radius ), sz ); + } + } + + qwtFillBackground( painter, canvas, rects); +} + + +class QwtPlotCanvas::PrivateData +{ +public: + PrivateData(): + focusIndicator( NoFocusIndicator ), + borderRadius( 0 ), + paintAttributes( 0 ), + backingStore( NULL ) + { + styleSheet.hasBorder = false; + } + + ~PrivateData() + { + delete backingStore; + } + + FocusIndicator focusIndicator; + double borderRadius; + QwtPlotCanvas::PaintAttributes paintAttributes; + QPixmap *backingStore; + + struct StyleSheet + { + bool hasBorder; + QPainterPath borderPath; + QVector cornerRects; + + struct StyleSheetBackground + { + QBrush brush; + QPointF origin; + } background; + + } styleSheet; + +}; + +/*! + \brief Constructor + + \param plot Parent plot widget + \sa QwtPlot::setCanvas() +*/ +QwtPlotCanvas::QwtPlotCanvas( QwtPlot *plot ): + QFrame( plot ) +{ + setFrameStyle( QFrame::Panel | QFrame::Sunken ); + setLineWidth( 2 ); + + d_data = new PrivateData; + +#ifndef QT_NO_CURSOR + setCursor( Qt::CrossCursor ); +#endif + + setAutoFillBackground( true ); + setPaintAttribute( QwtPlotCanvas::BackingStore, true ); + setPaintAttribute( QwtPlotCanvas::Opaque, true ); + setPaintAttribute( QwtPlotCanvas::HackStyledBackground, true ); +} + +//! Destructor +QwtPlotCanvas::~QwtPlotCanvas() +{ + delete d_data; +} + +//! Return parent plot widget +QwtPlot *QwtPlotCanvas::plot() +{ + return qobject_cast( parent() ); +} + +//! Return parent plot widget +const QwtPlot *QwtPlotCanvas::plot() const +{ + return qobject_cast( parent() ); +} + +/*! + \brief Changing the paint attributes + + \param attribute Paint attribute + \param on On/Off + + \sa testPaintAttribute(), backingStore() +*/ +void QwtPlotCanvas::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( bool( d_data->paintAttributes & attribute ) == on ) + return; + + if ( on ) + d_data->paintAttributes |= attribute; + else + d_data->paintAttributes &= ~attribute; + + switch ( attribute ) + { + case BackingStore: + { + if ( on ) + { + if ( d_data->backingStore == NULL ) + d_data->backingStore = new QPixmap(); + + if ( isVisible() ) + { +#if QT_VERSION >= 0x050000 + *d_data->backingStore = grab( rect() ); +#else + *d_data->backingStore = + QPixmap::grabWidget( this, rect() ); +#endif + } + } + else + { + delete d_data->backingStore; + d_data->backingStore = NULL; + } + break; + } + case Opaque: + { + if ( on ) + setAttribute( Qt::WA_OpaquePaintEvent, true ); + + break; + } + case HackStyledBackground: + case ImmediatePaint: + { + break; + } + } +} + +/*! + Test whether a paint attribute is enabled + + \param attribute Paint attribute + \return true, when attribute is enabled + \sa setPaintAttribute() +*/ +bool QwtPlotCanvas::testPaintAttribute( PaintAttribute attribute ) const +{ + return d_data->paintAttributes & attribute; +} + +//! \return Backing store, might be null +const QPixmap *QwtPlotCanvas::backingStore() const +{ + return d_data->backingStore; +} + +//! Invalidate the internal backing store +void QwtPlotCanvas::invalidateBackingStore() +{ + if ( d_data->backingStore ) + *d_data->backingStore = QPixmap(); +} + +/*! + Set the focus indicator + + \sa FocusIndicator, focusIndicator() +*/ +void QwtPlotCanvas::setFocusIndicator( FocusIndicator focusIndicator ) +{ + d_data->focusIndicator = focusIndicator; +} + +/*! + \return Focus indicator + + \sa FocusIndicator, setFocusIndicator() +*/ +QwtPlotCanvas::FocusIndicator QwtPlotCanvas::focusIndicator() const +{ + return d_data->focusIndicator; +} + +/*! + Set the radius for the corners of the border frame + + \param radius Radius of a rounded corner + \sa borderRadius() +*/ +void QwtPlotCanvas::setBorderRadius( double radius ) +{ + d_data->borderRadius = qMax( 0.0, radius ); +} + +/*! + \return Radius for the corners of the border frame + \sa setBorderRadius() +*/ +double QwtPlotCanvas::borderRadius() const +{ + return d_data->borderRadius; +} + +/*! + Qt event handler for QEvent::PolishRequest and QEvent::StyleChange + + \param event Qt Event + \return See QFrame::event() +*/ +bool QwtPlotCanvas::event( QEvent *event ) +{ + if ( event->type() == QEvent::PolishRequest ) + { + if ( testPaintAttribute( QwtPlotCanvas::Opaque ) ) + { + // Setting a style sheet changes the + // Qt::WA_OpaquePaintEvent attribute, but we insist + // on painting the background. + + setAttribute( Qt::WA_OpaquePaintEvent, true ); + } + } + + if ( event->type() == QEvent::PolishRequest || + event->type() == QEvent::StyleChange ) + { + updateStyleSheetInfo(); + } + + return QFrame::event( event ); +} + +/*! + Paint event + \param event Paint event +*/ +void QwtPlotCanvas::paintEvent( QPaintEvent *event ) +{ + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + if ( testPaintAttribute( QwtPlotCanvas::BackingStore ) && + d_data->backingStore != NULL ) + { + QPixmap &bs = *d_data->backingStore; + if ( bs.size() != size() ) + { + bs = QwtPainter::backingStore( this, size() ); + + if ( testAttribute(Qt::WA_StyledBackground) ) + { + QPainter p( &bs ); + qwtFillBackground( &p, this ); + drawCanvas( &p, true ); + } + else + { + QPainter p; + if ( d_data->borderRadius <= 0.0 ) + { + QwtPainter::fillPixmap( this, bs ); + p.begin( &bs ); + drawCanvas( &p, false ); + } + else + { + p.begin( &bs ); + qwtFillBackground( &p, this ); + drawCanvas( &p, true ); + } + + if ( frameWidth() > 0 ) + drawBorder( &p ); + } + } + + painter.drawPixmap( 0, 0, *d_data->backingStore ); + } + else + { + if ( testAttribute(Qt::WA_StyledBackground ) ) + { + if ( testAttribute( Qt::WA_OpaquePaintEvent ) ) + { + qwtFillBackground( &painter, this ); + drawCanvas( &painter, true ); + } + else + { + drawCanvas( &painter, false ); + } + } + else + { + if ( testAttribute( Qt::WA_OpaquePaintEvent ) ) + { + if ( autoFillBackground() ) + { + qwtFillBackground( &painter, this ); + qwtDrawBackground( &painter, this ); + } + } + else + { + if ( borderRadius() > 0.0 ) + { + QPainterPath clipPath; + clipPath.addRect( rect() ); + clipPath = clipPath.subtracted( borderPath( rect() ) ); + + painter.save(); + + painter.setClipPath( clipPath, Qt::IntersectClip ); + qwtFillBackground( &painter, this ); + qwtDrawBackground( &painter, this ); + + painter.restore(); + } + } + + drawCanvas( &painter, false ); + + if ( frameWidth() > 0 ) + drawBorder( &painter ); + } + } + + if ( hasFocus() && focusIndicator() == CanvasFocusIndicator ) + drawFocusIndicator( &painter ); +} + +void QwtPlotCanvas::drawCanvas( QPainter *painter, bool withBackground ) +{ + bool hackStyledBackground = false; + + if ( withBackground && testAttribute( Qt::WA_StyledBackground ) + && testPaintAttribute( HackStyledBackground ) ) + { + // Antialiasing rounded borders is done by + // inserting pixels with colors between the + // border color and the color on the canvas, + // When the border is painted before the plot items + // these colors are interpolated for the canvas + // and the plot items need to be clipped excluding + // the anialiased pixels. In situations, where + // the plot items fill the area at the rounded + // borders this is noticeable. + // The only way to avoid these annoying "artefacts" + // is to paint the border on top of the plot items. + + if ( d_data->styleSheet.hasBorder && + !d_data->styleSheet.borderPath.isEmpty() ) + { + // We have a border with at least one rounded corner + hackStyledBackground = true; + } + } + + if ( withBackground ) + { + painter->save(); + + if ( testAttribute( Qt::WA_StyledBackground ) ) + { + if ( hackStyledBackground ) + { + // paint background without border + + painter->setPen( Qt::NoPen ); + painter->setBrush( d_data->styleSheet.background.brush ); + painter->setBrushOrigin( d_data->styleSheet.background.origin ); + painter->setClipPath( d_data->styleSheet.borderPath ); + painter->drawRect( contentsRect() ); + } + else + { + qwtDrawStyledBackground( this, painter ); + } + } + else if ( autoFillBackground() ) + { + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().brush( backgroundRole() ) ); + + if ( d_data->borderRadius > 0.0 && ( rect() == frameRect() ) ) + { + if ( frameWidth() > 0 ) + { + painter->setClipPath( borderPath( rect() ) ); + painter->drawRect( rect() ); + } + else + { + painter->setRenderHint( QPainter::Antialiasing, true ); + painter->drawPath( borderPath( rect() ) ); + } + } + else + { + painter->drawRect( rect() ); + } + } + + painter->restore(); + } + + painter->save(); + + if ( !d_data->styleSheet.borderPath.isEmpty() ) + { + painter->setClipPath( + d_data->styleSheet.borderPath, Qt::IntersectClip ); + } + else + { + if ( d_data->borderRadius > 0.0 ) + painter->setClipPath( borderPath( frameRect() ), Qt::IntersectClip ); + else + painter->setClipRect( contentsRect(), Qt::IntersectClip ); + } + + plot()->drawCanvas( painter ); + + painter->restore(); + + if ( withBackground && hackStyledBackground ) + { + // Now paint the border on top + QStyleOptionFrame opt; + opt.initFrom(this); + style()->drawPrimitive( QStyle::PE_Frame, &opt, painter, this); + } +} + +/*! + Draw the border of the plot canvas + + \param painter Painter + \sa setBorderRadius() +*/ +void QwtPlotCanvas::drawBorder( QPainter *painter ) +{ + if ( d_data->borderRadius > 0 ) + { + if ( frameWidth() > 0 ) + { + QwtPainter::drawRoundedFrame( painter, QRectF( frameRect() ), + d_data->borderRadius, d_data->borderRadius, + palette(), frameWidth(), frameStyle() ); + } + } + else + { +#if QT_VERSION >= 0x040500 + QStyleOptionFrameV3 opt; + opt.init(this); + + int frameShape = frameStyle() & QFrame::Shape_Mask; + int frameShadow = frameStyle() & QFrame::Shadow_Mask; + + opt.frameShape = QFrame::Shape( int( opt.frameShape ) | frameShape ); +#if 0 + opt.rect = frameRect(); +#endif + + switch (frameShape) + { + case QFrame::Box: + case QFrame::HLine: + case QFrame::VLine: + case QFrame::StyledPanel: + case QFrame::Panel: + { + opt.lineWidth = lineWidth(); + opt.midLineWidth = midLineWidth(); + break; + } + default: + { + opt.lineWidth = frameWidth(); + break; + } + } + + if ( frameShadow == Sunken ) + opt.state |= QStyle::State_Sunken; + else if ( frameShadow == Raised ) + opt.state |= QStyle::State_Raised; + + style()->drawControl(QStyle::CE_ShapedFrame, &opt, painter, this); +#else + drawFrame( painter ); +#endif + } +} + +/*! + Resize event + \param event Resize event +*/ +void QwtPlotCanvas::resizeEvent( QResizeEvent *event ) +{ + QFrame::resizeEvent( event ); + updateStyleSheetInfo(); +} + +/*! + Draw the focus indication + \param painter Painter +*/ +void QwtPlotCanvas::drawFocusIndicator( QPainter *painter ) +{ + const int margin = 1; + + QRect focusRect = contentsRect(); + focusRect.setRect( focusRect.x() + margin, focusRect.y() + margin, + focusRect.width() - 2 * margin, focusRect.height() - 2 * margin ); + + QwtPainter::drawFocusRect( painter, this, focusRect ); +} + +/*! + Invalidate the paint cache and repaint the canvas + \sa invalidatePaintCache() +*/ +void QwtPlotCanvas::replot() +{ + invalidateBackingStore(); + + if ( testPaintAttribute( QwtPlotCanvas::ImmediatePaint ) ) + repaint( contentsRect() ); + else + update( contentsRect() ); +} + +//! Update the cached information about the current style sheet +void QwtPlotCanvas::updateStyleSheetInfo() +{ + if ( !testAttribute(Qt::WA_StyledBackground ) ) + return; + + QwtStyleSheetRecorder recorder( size() ); + + QPainter painter( &recorder ); + + QStyleOption opt; + opt.initFrom(this); + style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, this); + + painter.end(); + + d_data->styleSheet.hasBorder = !recorder.border.rectList.isEmpty(); + d_data->styleSheet.cornerRects = recorder.clipRects; + + if ( recorder.background.path.isEmpty() ) + { + if ( !recorder.border.rectList.isEmpty() ) + { + d_data->styleSheet.borderPath = + qwtCombinePathList( rect(), recorder.border.pathList ); + } + } + else + { + d_data->styleSheet.borderPath = recorder.background.path; + d_data->styleSheet.background.brush = recorder.background.brush; + d_data->styleSheet.background.origin = recorder.background.origin; + } +} + +/*! + Calculate the painter path for a styled or rounded border + + When the canvas has no styled background or rounded borders + the painter path is empty. + + \param rect Bounding rectangle of the canvas + \return Painter path, that can be used for clipping +*/ +QPainterPath QwtPlotCanvas::borderPath( const QRect &rect ) const +{ + if ( testAttribute(Qt::WA_StyledBackground ) ) + { + QwtStyleSheetRecorder recorder( rect.size() ); + + QPainter painter( &recorder ); + + QStyleOption opt; + opt.initFrom(this); + opt.rect = rect; + style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, this); + + painter.end(); + + if ( !recorder.background.path.isEmpty() ) + return recorder.background.path; + + if ( !recorder.border.rectList.isEmpty() ) + return qwtCombinePathList( rect, recorder.border.pathList ); + } + else if ( d_data->borderRadius > 0.0 ) + { + double fw2 = frameWidth() * 0.5; + QRectF r = QRectF(rect).adjusted( fw2, fw2, -fw2, -fw2 ); + + QPainterPath path; + path.addRoundedRect( r, d_data->borderRadius, d_data->borderRadius ); + return path; + } + + return QPainterPath(); +} diff --git a/qwt/src/qwt_plot_canvas.h b/qwt/src/qwt_plot_canvas.h new file mode 100644 index 000000000..daea8a086 --- /dev/null +++ b/qwt/src/qwt_plot_canvas.h @@ -0,0 +1,171 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_CANVAS_H +#define QWT_PLOT_CANVAS_H + +#include "qwt_global.h" +#include +#include + +class QwtPlot; +class QPixmap; + +/*! + \brief Canvas of a QwtPlot. + + Canvas is the widget where all plot items are displayed + + \sa QwtPlot::setCanvas(), QwtPlotGLCanvas +*/ +class QWT_EXPORT QwtPlotCanvas : public QFrame +{ + Q_OBJECT + + Q_PROPERTY( double borderRadius READ borderRadius WRITE setBorderRadius ) + +public: + + /*! + \brief Paint attributes + + The default setting enables BackingStore and Opaque. + + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + /*! + \brief Paint double buffered reusing the content + of the pixmap buffer when possible. + + Using a backing store might improve the performance + significantly, when working with widget overlays ( like rubber bands ). + Disabling the cache might improve the performance for + incremental paints (using QwtPlotDirectPainter ). + + \sa backingStore(), invalidateBackingStore() + */ + BackingStore = 1, + + /*! + \brief Try to fill the complete contents rectangle + of the plot canvas + + When using styled backgrounds Qt assumes, that the + canvas doesn't fill its area completely + ( f.e because of rounded borders ) and fills the area + below the canvas. When this is done with gradients it might + result in a serious performance bottleneck - depending on the size. + + When the Opaque attribute is enabled the canvas tries to + identify the gaps with some heuristics and to fill those only. + + \warning Will not work for semitransparent backgrounds + */ + Opaque = 2, + + /*! + \brief Try to improve painting of styled backgrounds + + QwtPlotCanvas supports the box model attributes for + customizing the layout with style sheets. Unfortunately + the design of Qt style sheets has no concept how to + handle backgrounds with rounded corners - beside of padding. + + When HackStyledBackground is enabled the plot canvas tries + to separate the background from the background border + by reverse engineering to paint the background before and + the border after the plot items. In this order the border + gets perfectly antialiased and you can avoid some pixel + artifacts in the corners. + */ + HackStyledBackground = 4, + + /*! + When ImmediatePaint is set replot() calls repaint() + instead of update(). + + \sa replot(), QWidget::repaint(), QWidget::update() + */ + ImmediatePaint = 8 + }; + + //! Paint attributes + typedef QFlags PaintAttributes; + + /*! + \brief Focus indicator + The default setting is NoFocusIndicator + \sa setFocusIndicator(), focusIndicator(), paintFocus() + */ + + enum FocusIndicator + { + //! Don't paint a focus indicator + NoFocusIndicator, + + /*! + The focus is related to the complete canvas. + Paint the focus indicator using paintFocus() + */ + CanvasFocusIndicator, + + /*! + The focus is related to an item (curve, point, ...) on + the canvas. It is up to the application to display a + focus indication using f.e. highlighting. + */ + ItemFocusIndicator + }; + + explicit QwtPlotCanvas( QwtPlot * = NULL ); + virtual ~QwtPlotCanvas(); + + QwtPlot *plot(); + const QwtPlot *plot() const; + + void setFocusIndicator( FocusIndicator ); + FocusIndicator focusIndicator() const; + + void setBorderRadius( double ); + double borderRadius() const; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + const QPixmap *backingStore() const; + void invalidateBackingStore(); + + virtual bool event( QEvent * ); + + Q_INVOKABLE QPainterPath borderPath( const QRect & ) const; + +public Q_SLOTS: + void replot(); + +protected: + virtual void paintEvent( QPaintEvent * ); + virtual void resizeEvent( QResizeEvent * ); + + virtual void drawFocusIndicator( QPainter * ); + virtual void drawBorder( QPainter * ); + + void updateStyleSheetInfo(); + +private: + void drawCanvas( QPainter *, bool withBackground ); + + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCanvas::PaintAttributes ) + +#endif diff --git a/qwt/src/qwt_plot_curve.cpp b/qwt/src/qwt_plot_curve.cpp new file mode 100644 index 000000000..140cc5996 --- /dev/null +++ b/qwt/src/qwt_plot_curve.cpp @@ -0,0 +1,1191 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_curve.h" +#include "qwt_point_data.h" +#include "qwt_math.h" +#include "qwt_clipper.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include "qwt_plot.h" +#include "qwt_curve_fitter.h" +#include "qwt_symbol.h" +#include "qwt_point_mapper.h" +#include +#include +#include +#include + +static void qwtUpdateLegendIconSize( QwtPlotCurve *curve ) +{ + if ( curve->symbol() && + curve->testLegendAttribute( QwtPlotCurve::LegendShowSymbol ) ) + { + QSize sz = curve->symbol()->boundingRect().size(); + sz += QSize( 2, 2 ); // margin + + if ( curve->testLegendAttribute( QwtPlotCurve::LegendShowLine ) ) + { + // Avoid, that the line is completely covered by the symbol + + int w = qCeil( 1.5 * sz.width() ); + if ( w % 2 ) + w++; + + sz.setWidth( qMax( 8, w ) ); + } + + curve->setLegendIconSize( sz ); + } +} + +static int qwtVerifyRange( int size, int &i1, int &i2 ) +{ + if ( size < 1 ) + return 0; + + i1 = qBound( 0, i1, size - 1 ); + i2 = qBound( 0, i2, size - 1 ); + + if ( i1 > i2 ) + qSwap( i1, i2 ); + + return ( i2 - i1 + 1 ); +} + +class QwtPlotCurve::PrivateData +{ +public: + PrivateData(): + style( QwtPlotCurve::Lines ), + baseline( 0.0 ), + symbol( NULL ), + attributes( 0 ), + paintAttributes( + QwtPlotCurve::ClipPolygons | QwtPlotCurve::FilterPoints ), + legendAttributes( 0 ) + { + pen = QPen( Qt::black ); + curveFitter = new QwtSplineCurveFitter; + } + + ~PrivateData() + { + delete symbol; + delete curveFitter; + } + + QwtPlotCurve::CurveStyle style; + double baseline; + + const QwtSymbol *symbol; + QwtCurveFitter *curveFitter; + + QPen pen; + QBrush brush; + + QwtPlotCurve::CurveAttributes attributes; + QwtPlotCurve::PaintAttributes paintAttributes; + + QwtPlotCurve::LegendAttributes legendAttributes; +}; + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotCurve::QwtPlotCurve( const QwtText &title ): + QwtPlotSeriesItem( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotCurve::QwtPlotCurve( const QString &title ): + QwtPlotSeriesItem( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPlotCurve::~QwtPlotCurve() +{ + delete d_data; +} + +//! Initialize internal members +void QwtPlotCurve::init() +{ + setItemAttribute( QwtPlotItem::Legend ); + setItemAttribute( QwtPlotItem::AutoScale ); + + d_data = new PrivateData; + setData( new QwtPointSeriesData() ); + + setZ( 20.0 ); +} + +//! \return QwtPlotItem::Rtti_PlotCurve +int QwtPlotCurve::rtti() const +{ + return QwtPlotItem::Rtti_PlotCurve; +} + +/*! + Specify an attribute how to draw the curve + + \param attribute Paint attribute + \param on On/Off + \sa testPaintAttribute() +*/ +void QwtPlotCurve::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( on ) + d_data->paintAttributes |= attribute; + else + d_data->paintAttributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa setPaintAttribute() +*/ +bool QwtPlotCurve::testPaintAttribute( PaintAttribute attribute ) const +{ + return ( d_data->paintAttributes & attribute ); +} + +/*! + Specify an attribute how to draw the legend icon + + \param attribute Attribute + \param on On/Off + /sa testLegendAttribute(). legendIcon() +*/ +void QwtPlotCurve::setLegendAttribute( LegendAttribute attribute, bool on ) +{ + if ( on != testLegendAttribute( attribute ) ) + { + if ( on ) + d_data->legendAttributes |= attribute; + else + d_data->legendAttributes &= ~attribute; + + qwtUpdateLegendIconSize( this ); + legendChanged(); + } +} + +/*! + \return True, when attribute is enabled + \sa setLegendAttribute() +*/ +bool QwtPlotCurve::testLegendAttribute( LegendAttribute attribute ) const +{ + return ( d_data->legendAttributes & attribute ); +} + +/*! + Set the curve's drawing style + + \param style Curve style + \sa style() +*/ +void QwtPlotCurve::setStyle( CurveStyle style ) +{ + if ( style != d_data->style ) + { + d_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Style of the curve + \sa setStyle() +*/ +QwtPlotCurve::CurveStyle QwtPlotCurve::style() const +{ + return d_data->style; +} + +/*! + \brief Assign a symbol + + The curve will take the ownership of the symbol, hence the previously + set symbol will be delete by setting a new one. If \p symbol is + \c NULL no symbol will be drawn. + + \param symbol Symbol + \sa symbol() +*/ +void QwtPlotCurve::setSymbol( QwtSymbol *symbol ) +{ + if ( symbol != d_data->symbol ) + { + delete d_data->symbol; + d_data->symbol = symbol; + + qwtUpdateLegendIconSize( this ); + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() +*/ +const QwtSymbol *QwtPlotCurve::symbol() const +{ + return d_data->symbol; +} + +/*! + Build and assign a pen + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotCurve::setPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen + + \param pen New pen + \sa pen(), brush() +*/ +void QwtPlotCurve::setPen( const QPen &pen ) +{ + if ( pen != d_data->pen ) + { + d_data->pen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Pen used to draw the lines + \sa setPen(), brush() +*/ +const QPen& QwtPlotCurve::pen() const +{ + return d_data->pen; +} + +/*! + \brief Assign a brush. + + In case of brush.style() != QBrush::NoBrush + and style() != QwtPlotCurve::Sticks + the area between the curve and the baseline will be filled. + + In case !brush.color().isValid() the area will be filled by + pen.color(). The fill algorithm simply connects the first and the + last curve point to the baseline. So the curve data has to be sorted + (ascending or descending). + + \param brush New brush + \sa brush(), setBaseline(), baseline() +*/ +void QwtPlotCurve::setBrush( const QBrush &brush ) +{ + if ( brush != d_data->brush ) + { + d_data->brush = brush; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Brush used to fill the area between lines and the baseline + \sa setBrush(), setBaseline(), baseline() +*/ +const QBrush& QwtPlotCurve::brush() const +{ + return d_data->brush; +} + +/*! + Draw an interval of the curve + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas + \param from Index of the first point to be painted + \param to Index of the last point to be painted. If to < 0 the + curve will be painted to its last point. + + \sa drawCurve(), drawSymbols(), +*/ +void QwtPlotCurve::drawSeries( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + const size_t numSamples = dataSize(); + + if ( !painter || numSamples <= 0 ) + return; + + if ( to < 0 ) + to = numSamples - 1; + + if ( qwtVerifyRange( numSamples, from, to ) > 0 ) + { + painter->save(); + painter->setPen( d_data->pen ); + + /* + Qt 4.0.0 is slow when drawing lines, but it's even + slower when the painter has a brush. So we don't + set the brush before we really need it. + */ + + drawCurve( painter, d_data->style, xMap, yMap, canvasRect, from, to ); + painter->restore(); + + if ( d_data->symbol && + ( d_data->symbol->style() != QwtSymbol::NoSymbol ) ) + { + painter->save(); + drawSymbols( painter, *d_data->symbol, + xMap, yMap, canvasRect, from, to ); + painter->restore(); + } + } +} + +/*! + \brief Draw the line part (without symbols) of a curve interval. + \param painter Painter + \param style curve style, see QwtPlotCurve::CurveStyle + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from index of the first point to be painted + \param to index of the last point to be painted + \sa draw(), drawDots(), drawLines(), drawSteps(), drawSticks() +*/ +void QwtPlotCurve::drawCurve( QPainter *painter, int style, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + switch ( style ) + { + case Lines: + if ( testCurveAttribute( Fitted ) ) + { + // we always need the complete + // curve for fitting + from = 0; + to = dataSize() - 1; + } + drawLines( painter, xMap, yMap, canvasRect, from, to ); + break; + case Sticks: + drawSticks( painter, xMap, yMap, canvasRect, from, to ); + break; + case Steps: + drawSteps( painter, xMap, yMap, canvasRect, from, to ); + break; + case Dots: + drawDots( painter, xMap, yMap, canvasRect, from, to ); + break; + case NoCurve: + default: + break; + } +} + +/*! + \brief Draw lines + + If the CurveAttribute Fitted is enabled a QwtCurveFitter tries + to interpolate/smooth the curve, before it is painted. + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from index of the first point to be painted + \param to index of the last point to be painted + + \sa setCurveAttribute(), setCurveFitter(), draw(), + drawLines(), drawDots(), drawSteps(), drawSticks() +*/ +void QwtPlotCurve::drawLines( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + if ( from > to ) + return; + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + const bool doFit = ( d_data->attributes & Fitted ) && d_data->curveFitter; + const bool doFill = ( d_data->brush.style() != Qt::NoBrush ) + && ( d_data->brush.color().alpha() > 0 ); + + QRectF clipRect; + if ( d_data->paintAttributes & ClipPolygons ) + { + qreal pw = qMax( qreal( 1.0 ), painter->pen().widthF()); + clipRect = canvasRect.adjusted(-pw, -pw, pw, pw); + } + + bool doIntegers = false; + +#if QT_VERSION < 0x040800 + + // For Qt <= 4.7 the raster paint engine is significantly faster + // for rendering QPolygon than for QPolygonF. So let's + // see if we can use it. + + if ( painter->paintEngine()->type() == QPaintEngine::Raster ) + { + // In case of filling or fitting performance doesn't count + // because both operations are much more expensive + // then drawing the polyline itself + + if ( !doFit && !doFill ) + doIntegers = true; + } +#endif + + const bool noDuplicates = d_data->paintAttributes & FilterPoints; + + QwtPointMapper mapper; + mapper.setFlag( QwtPointMapper::RoundPoints, doAlign ); + mapper.setFlag( QwtPointMapper::WeedOutPoints, noDuplicates ); + mapper.setBoundingRect( canvasRect ); + + if ( doIntegers ) + { + const QPolygon polyline = mapper.toPolygon( + xMap, yMap, data(), from, to ); + + if ( d_data->paintAttributes & ClipPolygons ) + { + const QPolygon clipped = QwtClipper::clipPolygon( + clipRect.toAlignedRect(), polyline, false ); + + QwtPainter::drawPolyline( painter, clipped ); + } + else + { + QwtPainter::drawPolyline( painter, polyline ); + } + } + else + { + QPolygonF polyline = mapper.toPolygonF( xMap, yMap, + data(), from, to ); + + if ( doFit ) + polyline = d_data->curveFitter->fitCurve( polyline ); + + if ( d_data->paintAttributes & ClipPolygons ) + { + const QPolygonF clipped = QwtClipper::clipPolygonF( + clipRect, polyline, false ); + + QwtPainter::drawPolyline( painter, clipped ); + } + else + { + QwtPainter::drawPolyline( painter, polyline ); + } + + if ( doFill ) + { + fillCurve( painter, xMap, yMap, canvasRect, polyline ); + } + } +} + +/*! + Draw sticks + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from index of the first point to be painted + \param to index of the last point to be painted + + \sa draw(), drawCurve(), drawDots(), drawLines(), drawSteps() +*/ +void QwtPlotCurve::drawSticks( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &, int from, int to ) const +{ + painter->save(); + painter->setRenderHint( QPainter::Antialiasing, false ); + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + double x0 = xMap.transform( d_data->baseline ); + double y0 = yMap.transform( d_data->baseline ); + if ( doAlign ) + { + x0 = qRound( x0 ); + y0 = qRound( y0 ); + } + + const Qt::Orientation o = orientation(); + + const QwtSeriesData *series = data(); + + for ( int i = from; i <= to; i++ ) + { + const QPointF sample = series->sample( i ); + double xi = xMap.transform( sample.x() ); + double yi = yMap.transform( sample.y() ); + if ( doAlign ) + { + xi = qRound( xi ); + yi = qRound( yi ); + } + + if ( o == Qt::Horizontal ) + QwtPainter::drawLine( painter, x0, yi, xi, yi ); + else + QwtPainter::drawLine( painter, xi, y0, xi, yi ); + } + + painter->restore(); +} + +/*! + Draw dots + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from index of the first point to be painted + \param to index of the last point to be painted + + \sa draw(), drawCurve(), drawSticks(), drawLines(), drawSteps() +*/ +void QwtPlotCurve::drawDots( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + const QColor color = painter->pen().color(); + + if ( painter->pen().style() == Qt::NoPen || color.alpha() == 0 ) + { + return; + } + + const bool doFill = ( d_data->brush.style() != Qt::NoBrush ) + && ( d_data->brush.color().alpha() > 0 ); + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + QwtPointMapper mapper; + mapper.setBoundingRect( canvasRect ); + mapper.setFlag( QwtPointMapper::RoundPoints, doAlign ); + + if ( d_data->paintAttributes & FilterPoints ) + { + if ( ( color.alpha() == 255 ) + && !( painter->renderHints() & QPainter::Antialiasing ) ) + { + mapper.setFlag( QwtPointMapper::WeedOutPoints, true ); + } + } + + if ( doFill ) + { + mapper.setFlag( QwtPointMapper::WeedOutPoints, false ); + + QPolygonF points = mapper.toPointsF( + xMap, yMap, data(), from, to ); + + QwtPainter::drawPoints( painter, points ); + fillCurve( painter, xMap, yMap, canvasRect, points ); + } + else if ( d_data->paintAttributes & ImageBuffer ) + { + const QImage image = mapper.toImage( xMap, yMap, + data(), from, to, d_data->pen, + painter->testRenderHint( QPainter::Antialiasing ), + renderThreadCount() ); + + painter->drawImage( canvasRect.toAlignedRect(), image ); + } + else if ( d_data->paintAttributes & MinimizeMemory ) + { + const QwtSeriesData *series = data(); + + for ( int i = from; i <= to; i++ ) + { + const QPointF sample = series->sample( i ); + + double xi = xMap.transform( sample.x() ); + double yi = yMap.transform( sample.y() ); + + if ( doAlign ) + { + xi = qRound( xi ); + yi = qRound( yi ); + } + + QwtPainter::drawPoint( painter, QPointF( xi, yi ) ); + } + } + else + { + if ( doAlign ) + { + const QPolygon points = mapper.toPoints( + xMap, yMap, data(), from, to ); + + QwtPainter::drawPoints( painter, points ); + } + else + { + const QPolygonF points = mapper.toPointsF( + xMap, yMap, data(), from, to ); + + QwtPainter::drawPoints( painter, points ); + } + } +} + +/*! + Draw step function + + The direction of the steps depends on Inverted attribute. + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from index of the first point to be painted + \param to index of the last point to be painted + + \sa CurveAttribute, setCurveAttribute(), + draw(), drawCurve(), drawDots(), drawLines(), drawSticks() +*/ +void QwtPlotCurve::drawSteps( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + QPolygonF polygon( 2 * ( to - from ) + 1 ); + QPointF *points = polygon.data(); + + bool inverted = orientation() == Qt::Vertical; + if ( d_data->attributes & Inverted ) + inverted = !inverted; + + const QwtSeriesData *series = data(); + + int i, ip; + for ( i = from, ip = 0; i <= to; i++, ip += 2 ) + { + const QPointF sample = series->sample( i ); + double xi = xMap.transform( sample.x() ); + double yi = yMap.transform( sample.y() ); + if ( doAlign ) + { + xi = qRound( xi ); + yi = qRound( yi ); + } + + if ( ip > 0 ) + { + const QPointF &p0 = points[ip - 2]; + QPointF &p = points[ip - 1]; + + if ( inverted ) + { + p.rx() = p0.x(); + p.ry() = yi; + } + else + { + p.rx() = xi; + p.ry() = p0.y(); + } + } + + points[ip].rx() = xi; + points[ip].ry() = yi; + } + + if ( d_data->paintAttributes & ClipPolygons ) + { + const QPolygonF clipped = QwtClipper::clipPolygonF( + canvasRect, polygon, false ); + + QwtPainter::drawPolyline( painter, clipped ); + } + else + { + QwtPainter::drawPolyline( painter, polygon ); + } + + if ( d_data->brush.style() != Qt::NoBrush ) + fillCurve( painter, xMap, yMap, canvasRect, polygon ); +} + + +/*! + Specify an attribute for drawing the curve + + \param attribute Curve attribute + \param on On/Off + + /sa testCurveAttribute(), setCurveFitter() +*/ +void QwtPlotCurve::setCurveAttribute( CurveAttribute attribute, bool on ) +{ + if ( bool( d_data->attributes & attribute ) == on ) + return; + + if ( on ) + d_data->attributes |= attribute; + else + d_data->attributes &= ~attribute; + + itemChanged(); +} + +/*! + \return true, if attribute is enabled + \sa setCurveAttribute() +*/ +bool QwtPlotCurve::testCurveAttribute( CurveAttribute attribute ) const +{ + return d_data->attributes & attribute; +} + +/*! + Assign a curve fitter + + The curve fitter "smooths" the curve points, when the Fitted + CurveAttribute is set. setCurveFitter(NULL) also disables curve fitting. + + The curve fitter operates on the translated points ( = widget coordinates) + to be functional for logarithmic scales. Obviously this is less performant + for fitting algorithms, that reduce the number of points. + + For situations, where curve fitting is used to improve the performance + of painting huge series of points it might be better to execute the fitter + on the curve points once and to cache the result in the QwtSeriesData object. + + \param curveFitter() Curve fitter + \sa Fitted +*/ +void QwtPlotCurve::setCurveFitter( QwtCurveFitter *curveFitter ) +{ + delete d_data->curveFitter; + d_data->curveFitter = curveFitter; + + itemChanged(); +} + +/*! + Get the curve fitter. If curve fitting is disabled NULL is returned. + + \return Curve fitter + \sa setCurveFitter(), Fitted +*/ +QwtCurveFitter *QwtPlotCurve::curveFitter() const +{ + return d_data->curveFitter; +} + +/*! + Fill the area between the curve and the baseline with + the curve brush + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param polygon Polygon - will be modified ! + + \sa setBrush(), setBaseline(), setStyle() +*/ +void QwtPlotCurve::fillCurve( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, QPolygonF &polygon ) const +{ + if ( d_data->brush.style() == Qt::NoBrush ) + return; + + closePolyline( painter, xMap, yMap, polygon ); + if ( polygon.count() <= 2 ) // a line can't be filled + return; + + QBrush brush = d_data->brush; + if ( !brush.color().isValid() ) + brush.setColor( d_data->pen.color() ); + + if ( d_data->paintAttributes & ClipPolygons ) + polygon = QwtClipper::clipPolygonF( canvasRect, polygon, true ); + + painter->save(); + + painter->setPen( Qt::NoPen ); + painter->setBrush( brush ); + + QwtPainter::drawPolygon( painter, polygon ); + + painter->restore(); +} + +/*! + \brief Complete a polygon to be a closed polygon including the + area between the original polygon and the baseline. + + \param painter Painter + \param xMap X map + \param yMap Y map + \param polygon Polygon to be completed +*/ +void QwtPlotCurve::closePolyline( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + QPolygonF &polygon ) const +{ + if ( polygon.size() < 2 ) + return; + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + double baseline = d_data->baseline; + + if ( orientation() == Qt::Vertical ) + { + if ( yMap.transformation() ) + baseline = yMap.transformation()->bounded( baseline ); + + double refY = yMap.transform( baseline ); + if ( doAlign ) + refY = qRound( refY ); + + polygon += QPointF( polygon.last().x(), refY ); + polygon += QPointF( polygon.first().x(), refY ); + } + else + { + if ( xMap.transformation() ) + baseline = xMap.transformation()->bounded( baseline ); + + double refX = xMap.transform( baseline ); + if ( doAlign ) + refX = qRound( refX ); + + polygon += QPointF( refX, polygon.last().y() ); + polygon += QPointF( refX, polygon.first().y() ); + } +} + +/*! + Draw symbols + + \param painter Painter + \param symbol Curve symbol + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from Index of the first point to be painted + \param to Index of the last point to be painted + + \sa setSymbol(), drawSeries(), drawCurve() +*/ +void QwtPlotCurve::drawSymbols( QPainter *painter, const QwtSymbol &symbol, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + QwtPointMapper mapper; + mapper.setFlag( QwtPointMapper::RoundPoints, + QwtPainter::roundingAlignment( painter ) ); + mapper.setFlag( QwtPointMapper::WeedOutPoints, + testPaintAttribute( QwtPlotCurve::FilterPoints ) ); + mapper.setBoundingRect( canvasRect ); + + const int chunkSize = 500; + + for ( int i = from; i <= to; i += chunkSize ) + { + const int n = qMin( chunkSize, to - i + 1 ); + + const QPolygonF points = mapper.toPointsF( xMap, yMap, + data(), i, i + n - 1 ); + + if ( points.size() > 0 ) + symbol.drawSymbols( painter, points ); + } +} + +/*! + \brief Set the value of the baseline + + The baseline is needed for filling the curve with a brush or + the Sticks drawing style. + + The interpretation of the baseline depends on the orientation(). + With Qt::Horizontal, the baseline is interpreted as a horizontal line + at y = baseline(), with Qt::Vertical, it is interpreted as a vertical + line at x = baseline(). + + The default value is 0.0. + + \param value Value of the baseline + \sa baseline(), setBrush(), setStyle(), QwtPlotAbstractSeriesItem::orientation() +*/ +void QwtPlotCurve::setBaseline( double value ) +{ + if ( d_data->baseline != value ) + { + d_data->baseline = value; + itemChanged(); + } +} + +/*! + \return Value of the baseline + \sa setBaseline() +*/ +double QwtPlotCurve::baseline() const +{ + return d_data->baseline; +} + +/*! + Find the closest curve point for a specific position + + \param pos Position, where to look for the closest curve point + \param dist If dist != NULL, closestPoint() returns the distance between + the position and the closest curve point + \return Index of the closest curve point, or -1 if none can be found + ( f.e when the curve has no points ) + \note closestPoint() implements a dumb algorithm, that iterates + over all points +*/ +int QwtPlotCurve::closestPoint( const QPoint &pos, double *dist ) const +{ + const size_t numSamples = dataSize(); + + if ( plot() == NULL || numSamples <= 0 ) + return -1; + + const QwtSeriesData *series = data(); + + const QwtScaleMap xMap = plot()->canvasMap( xAxis() ); + const QwtScaleMap yMap = plot()->canvasMap( yAxis() ); + + int index = -1; + double dmin = 1.0e10; + + for ( uint i = 0; i < numSamples; i++ ) + { + const QPointF sample = series->sample( i ); + + const double cx = xMap.transform( sample.x() ) - pos.x(); + const double cy = yMap.transform( sample.y() ) - pos.y(); + + const double f = qwtSqr( cx ) + qwtSqr( cy ); + if ( f < dmin ) + { + index = i; + dmin = f; + } + } + if ( dist ) + *dist = qSqrt( dmin ); + + return index; +} + +/*! + \return Icon representing the curve on the legend + + \param index Index of the legend entry + ( ignored as there is only one ) + \param size Icon size + + \sa QwtPlotItem::setLegendIconSize(), QwtPlotItem::legendData() + */ +QwtGraphic QwtPlotCurve::legendIcon( int index, + const QSizeF &size ) const +{ + Q_UNUSED( index ); + + if ( size.isEmpty() ) + return QwtGraphic(); + + QwtGraphic graphic; + graphic.setDefaultSize( size ); + graphic.setRenderHint( QwtGraphic::RenderPensUnscaled, true ); + + QPainter painter( &graphic ); + painter.setRenderHint( QPainter::Antialiasing, + testRenderHint( QwtPlotItem::RenderAntialiased ) ); + + if ( d_data->legendAttributes == 0 || + d_data->legendAttributes & QwtPlotCurve::LegendShowBrush ) + { + QBrush brush = d_data->brush; + + if ( brush.style() == Qt::NoBrush && + d_data->legendAttributes == 0 ) + { + if ( style() != QwtPlotCurve::NoCurve ) + { + brush = QBrush( pen().color() ); + } + else if ( d_data->symbol && + ( d_data->symbol->style() != QwtSymbol::NoSymbol ) ) + { + brush = QBrush( d_data->symbol->pen().color() ); + } + } + + if ( brush.style() != Qt::NoBrush ) + { + QRectF r( 0, 0, size.width(), size.height() ); + painter.fillRect( r, brush ); + } + } + + if ( d_data->legendAttributes & QwtPlotCurve::LegendShowLine ) + { + if ( pen() != Qt::NoPen ) + { + QPen pn = pen(); + pn.setCapStyle( Qt::FlatCap ); + + painter.setPen( pn ); + + const double y = 0.5 * size.height(); + QwtPainter::drawLine( &painter, 0.0, y, size.width(), y ); + } + } + + if ( d_data->legendAttributes & QwtPlotCurve::LegendShowSymbol ) + { + if ( d_data->symbol ) + { + QRectF r( 0, 0, size.width(), size.height() ); + d_data->symbol->drawSymbol( &painter, r ); + } + } + + return graphic; +} + +/*! + Initialize data with an array of points. + + \param samples Vector of points + \note QVector is implicitly shared + \note QPolygonF is derived from QVector +*/ +void QwtPlotCurve::setSamples( const QVector &samples ) +{ + setData( new QwtPointSeriesData( samples ) ); +} + +/*! + Assign a series of points + + setSamples() is just a wrapper for setData() without any additional + value - beside that it is easier to find for the developer. + + \param data Data + \warning The item takes ownership of the data object, deleting + it when its not used anymore. +*/ +void QwtPlotCurve::setSamples( QwtSeriesData *data ) +{ + setData( data ); +} + +#ifndef QWT_NO_COMPAT + +/*! + \brief Initialize the data by pointing to memory blocks which + are not managed by QwtPlotCurve. + + setRawSamples is provided for efficiency. + It is important to keep the pointers + during the lifetime of the underlying QwtCPointerData class. + + \param xData pointer to x data + \param yData pointer to y data + \param size size of x and y + + \sa QwtCPointerData +*/ +void QwtPlotCurve::setRawSamples( + const double *xData, const double *yData, int size ) +{ + setData( new QwtCPointerData( xData, yData, size ) ); +} + +/*! + Set data by copying x- and y-values from specified memory blocks. + Contrary to setRawSamples(), this function makes a 'deep copy' of + the data. + + \param xData pointer to x values + \param yData pointer to y values + \param size size of xData and yData + + \sa QwtPointArrayData +*/ +void QwtPlotCurve::setSamples( + const double *xData, const double *yData, int size ) +{ + setData( new QwtPointArrayData( xData, yData, size ) ); +} + +/*! + \brief Initialize data with x- and y-arrays (explicitly shared) + + \param xData x data + \param yData y data + + \sa QwtPointArrayData +*/ +void QwtPlotCurve::setSamples( const QVector &xData, + const QVector &yData ) +{ + setData( new QwtPointArrayData( xData, yData ) ); +} + +#endif // !QWT_NO_COMPAT + diff --git a/qwt/src/qwt_plot_curve.h b/qwt/src/qwt_plot_curve.h new file mode 100644 index 000000000..3421abf81 --- /dev/null +++ b/qwt/src/qwt_plot_curve.h @@ -0,0 +1,337 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_CURVE_H +#define QWT_PLOT_CURVE_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" +#include "qwt_series_data.h" +#include "qwt_text.h" +#include +#include + +class QPainter; +class QPolygonF; +class QwtScaleMap; +class QwtSymbol; +class QwtCurveFitter; + +/*! + \brief A plot item, that represents a series of points + + A curve is the representation of a series of points in the x-y plane. + It supports different display styles, interpolation ( f.e. spline ) + and symbols. + + \par Usage +
a) Assign curve properties
+
When a curve is created, it is configured to draw black solid lines + with in QwtPlotCurve::Lines style and no symbols. + You can change this by calling + setPen(), setStyle() and setSymbol().
+
b) Connect/Assign data.
+
QwtPlotCurve gets its points using a QwtSeriesData object offering + a bridge to the real storage of the points ( like QAbstractItemModel ). + There are several convenience classes derived from QwtSeriesData, that also store + the points inside ( like QStandardItemModel ). QwtPlotCurve also offers + a couple of variations of setSamples(), that build QwtSeriesData objects from + arrays internally.
+
c) Attach the curve to a plot
+
See QwtPlotItem::attach() +
+ + \par Example: + see examples/bode + + \sa QwtPointSeriesData, QwtSymbol, QwtScaleMap +*/ +class QWT_EXPORT QwtPlotCurve: + public QwtPlotSeriesItem, public QwtSeriesStore +{ +public: + /*! + Curve styles. + \sa setStyle(), style() + */ + enum CurveStyle + { + /*! + Don't draw a curve. Note: This doesn't affect the symbols. + */ + NoCurve = -1, + + /*! + Connect the points with straight lines. The lines might + be interpolated depending on the 'Fitted' attribute. Curve + fitting can be configured using setCurveFitter(). + */ + Lines, + + /*! + Draw vertical or horizontal sticks ( depending on the + orientation() ) from a baseline which is defined by setBaseline(). + */ + Sticks, + + /*! + Connect the points with a step function. The step function + is drawn from the left to the right or vice versa, + depending on the QwtPlotCurve::Inverted attribute. + */ + Steps, + + /*! + Draw dots at the locations of the data points. Note: + This is different from a dotted line (see setPen()), and faster + as a curve in QwtPlotCurve::NoStyle style and a symbol + painting a point. + */ + Dots, + + /*! + Styles >= QwtPlotCurve::UserCurve are reserved for derived + classes of QwtPlotCurve that overload drawCurve() with + additional application specific curve types. + */ + UserCurve = 100 + }; + + /*! + Attribute for drawing the curve + \sa setCurveAttribute(), testCurveAttribute(), curveFitter() + */ + enum CurveAttribute + { + /*! + For QwtPlotCurve::Steps only. + Draws a step function from the right to the left. + */ + Inverted = 0x01, + + /*! + Only in combination with QwtPlotCurve::Lines + A QwtCurveFitter tries to + interpolate/smooth the curve, before it is painted. + + \note Curve fitting requires temporary memory + for calculating coefficients and additional points. + If painting in QwtPlotCurve::Fitted mode is slow it might be better + to fit the points, before they are passed to QwtPlotCurve. + */ + Fitted = 0x02 + }; + + //! Curve attributes + typedef QFlags CurveAttributes; + + /*! + Attributes how to represent the curve on the legend + + \sa setLegendAttribute(), testLegendAttribute(), + QwtPlotItem::legendData(), legendIcon() + */ + + enum LegendAttribute + { + /*! + QwtPlotCurve tries to find a color representing the curve + and paints a rectangle with it. + */ + LegendNoAttribute = 0x00, + + /*! + If the style() is not QwtPlotCurve::NoCurve a line + is painted with the curve pen(). + */ + LegendShowLine = 0x01, + + /*! + If the curve has a valid symbol it is painted. + */ + LegendShowSymbol = 0x02, + + /*! + If the curve has a brush a rectangle filled with the + curve brush() is painted. + */ + LegendShowBrush = 0x04 + }; + + //! Legend attributes + typedef QFlags LegendAttributes; + + /*! + Attributes to modify the drawing algorithm. + The default setting enables ClipPolygons | FilterPoints + + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + /*! + Clip polygons before painting them. In situations, where points + are far outside the visible area (f.e when zooming deep) this + might be a substantial improvement for the painting performance + */ + ClipPolygons = 0x01, + + /*! + Tries to reduce the data that has to be painted, by sorting out + duplicates, or paintings outside the visible area. Might have a + notable impact on curves with many close points. + Only a couple of very basic filtering algorithms are implemented. + */ + FilterPoints = 0x02, + + /*! + Minimize memory usage that is temporarily needed for the + translated points, before they get painted. + This might slow down the performance of painting + */ + MinimizeMemory = 0x04, + + /*! + Render the points to a temporary image and paint the image. + This is a very special optimization for Dots style, when + having a huge amount of points. + With a reasonable number of points QPainter::drawPoints() + will be faster. + */ + ImageBuffer = 0x08 + }; + + //! Paint attributes + typedef QFlags PaintAttributes; + + explicit QwtPlotCurve( const QString &title = QString::null ); + explicit QwtPlotCurve( const QwtText &title ); + + virtual ~QwtPlotCurve(); + + virtual int rtti() const; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setLegendAttribute( LegendAttribute, bool on = true ); + bool testLegendAttribute( LegendAttribute ) const; + +#ifndef QWT_NO_COMPAT + void setRawSamples( const double *xData, const double *yData, int size ); + void setSamples( const double *xData, const double *yData, int size ); + void setSamples( const QVector &xData, const QVector &yData ); +#endif + void setSamples( const QVector & ); + void setSamples( QwtSeriesData * ); + + int closestPoint( const QPoint &pos, double *dist = NULL ) const; + + double minXValue() const; + double maxXValue() const; + double minYValue() const; + double maxYValue() const; + + void setCurveAttribute( CurveAttribute, bool on = true ); + bool testCurveAttribute( CurveAttribute ) const; + + void setPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen & ); + const QPen &pen() const; + + void setBrush( const QBrush & ); + const QBrush &brush() const; + + void setBaseline( double ); + double baseline() const; + + void setStyle( CurveStyle style ); + CurveStyle style() const; + + void setSymbol( QwtSymbol * ); + const QwtSymbol *symbol() const; + + void setCurveFitter( QwtCurveFitter * ); + QwtCurveFitter *curveFitter() const; + + virtual void drawSeries( QPainter *, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual QwtGraphic legendIcon( int index, const QSizeF & ) const; + +protected: + + void init(); + + virtual void drawCurve( QPainter *p, int style, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawSymbols( QPainter *p, const QwtSymbol &, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawLines( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawSticks( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawDots( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawSteps( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void fillCurve( QPainter *, + const QwtScaleMap &, const QwtScaleMap &, + const QRectF &canvasRect, QPolygonF & ) const; + + void closePolyline( QPainter *, + const QwtScaleMap &, const QwtScaleMap &, QPolygonF & ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +//! boundingRect().left() +inline double QwtPlotCurve::minXValue() const +{ + return boundingRect().left(); +} + +//! boundingRect().right() +inline double QwtPlotCurve::maxXValue() const +{ + return boundingRect().right(); +} + +//! boundingRect().top() +inline double QwtPlotCurve::minYValue() const +{ + return boundingRect().top(); +} + +//! boundingRect().bottom() +inline double QwtPlotCurve::maxYValue() const +{ + return boundingRect().bottom(); +} + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::PaintAttributes ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::LegendAttributes ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::CurveAttributes ) + +#endif diff --git a/qwt/src/qwt_plot_dict.cpp b/qwt/src/qwt_plot_dict.cpp new file mode 100644 index 000000000..17c61ed47 --- /dev/null +++ b/qwt/src/qwt_plot_dict.cpp @@ -0,0 +1,191 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_dict.h" + +class QwtPlotDict::PrivateData +{ +public: + + class ItemList: public QList + { + public: + void insertItem( QwtPlotItem *item ) + { + if ( item == NULL ) + return; + + QList::iterator it = + qUpperBound( begin(), end(), item, LessZThan() ); + insert( it, item ); + } + + void removeItem( QwtPlotItem *item ) + { + if ( item == NULL ) + return; + + QList::iterator it = + qLowerBound( begin(), end(), item, LessZThan() ); + + for ( ; it != end(); ++it ) + { + if ( item == *it ) + { + erase( it ); + break; + } + } + } + private: + class LessZThan + { + public: + inline bool operator()( const QwtPlotItem *item1, + const QwtPlotItem *item2 ) const + { + return item1->z() < item2->z(); + } + }; + }; + + ItemList itemList; + bool autoDelete; +}; + +/*! + Constructor + + Auto deletion is enabled. + \sa setAutoDelete(), QwtPlotItem::attach() +*/ +QwtPlotDict::QwtPlotDict() +{ + d_data = new QwtPlotDict::PrivateData; + d_data->autoDelete = true; +} + +/*! + Destructor + + If autoDelete() is on, all attached items will be deleted + \sa setAutoDelete(), autoDelete(), QwtPlotItem::attach() +*/ +QwtPlotDict::~QwtPlotDict() +{ + detachItems( QwtPlotItem::Rtti_PlotItem, d_data->autoDelete ); + delete d_data; +} + +/*! + En/Disable Auto deletion + + If Auto deletion is on all attached plot items will be deleted + in the destructor of QwtPlotDict. The default value is on. + + \sa autoDelete(), insertItem() +*/ +void QwtPlotDict::setAutoDelete( bool autoDelete ) +{ + d_data->autoDelete = autoDelete; +} + +/*! + \return true if auto deletion is enabled + \sa setAutoDelete(), insertItem() +*/ +bool QwtPlotDict::autoDelete() const +{ + return d_data->autoDelete; +} + +/*! + Insert a plot item + + \param item PlotItem + \sa removeItem() + */ +void QwtPlotDict::insertItem( QwtPlotItem *item ) +{ + d_data->itemList.insertItem( item ); +} + +/*! + Remove a plot item + + \param item PlotItem + \sa insertItem() + */ +void QwtPlotDict::removeItem( QwtPlotItem *item ) +{ + d_data->itemList.removeItem( item ); +} + +/*! + Detach items from the dictionary + + \param rtti In case of QwtPlotItem::Rtti_PlotItem detach all items + otherwise only those items of the type rtti. + \param autoDelete If true, delete all detached items +*/ +void QwtPlotDict::detachItems( int rtti, bool autoDelete ) +{ + PrivateData::ItemList list = d_data->itemList; + QwtPlotItemIterator it = list.begin(); + while ( it != list.end() ) + { + QwtPlotItem *item = *it; + + ++it; // increment before removing item from the list + + if ( rtti == QwtPlotItem::Rtti_PlotItem || item->rtti() == rtti ) + { + item->attach( NULL ); + if ( autoDelete ) + delete item; + } + } +} + +/*! + \brief A QwtPlotItemList of all attached plot items. + + Use caution when iterating these lists, as removing/detaching an item will + invalidate the iterator. Instead you can place pointers to objects to be + removed in a removal list, and traverse that list later. + + \return List of all attached plot items. +*/ +const QwtPlotItemList &QwtPlotDict::itemList() const +{ + return d_data->itemList; +} + +/*! + \return List of all attached plot items of a specific type. + \param rtti See QwtPlotItem::RttiValues + \sa QwtPlotItem::rtti() +*/ +QwtPlotItemList QwtPlotDict::itemList( int rtti ) const +{ + if ( rtti == QwtPlotItem::Rtti_PlotItem ) + return d_data->itemList; + + QwtPlotItemList items; + + PrivateData::ItemList list = d_data->itemList; + for ( QwtPlotItemIterator it = list.begin(); it != list.end(); ++it ) + { + QwtPlotItem *item = *it; + if ( item->rtti() == rtti ) + items += item; + } + + return items; +} diff --git a/qwt/src/qwt_plot_dict.h b/qwt/src/qwt_plot_dict.h new file mode 100644 index 000000000..5d34f0c05 --- /dev/null +++ b/qwt/src/qwt_plot_dict.h @@ -0,0 +1,58 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +/*! \file !*/ +#ifndef QWT_PLOT_DICT +#define QWT_PLOT_DICT + +#include "qwt_global.h" +#include "qwt_plot_item.h" +#include + +/// \var typedef QList< QwtPlotItem *> QwtPlotItemList +/// \brief See QT 4.x assistant documentation for QList +typedef QList QwtPlotItemList; +typedef QList::ConstIterator QwtPlotItemIterator; + +/*! + \brief A dictionary for plot items + + QwtPlotDict organizes plot items in increasing z-order. + If autoDelete() is enabled, all attached items will be deleted + in the destructor of the dictionary. + QwtPlotDict can be used to get access to all QwtPlotItem items - or all + items of a specific type - that are currently on the plot. + + \sa QwtPlotItem::attach(), QwtPlotItem::detach(), QwtPlotItem::z() +*/ +class QWT_EXPORT QwtPlotDict +{ +public: + explicit QwtPlotDict(); + virtual ~QwtPlotDict(); + + void setAutoDelete( bool ); + bool autoDelete() const; + + const QwtPlotItemList& itemList() const; + QwtPlotItemList itemList( int rtti ) const; + + void detachItems( int rtti = QwtPlotItem::Rtti_PlotItem, + bool autoDelete = true ); + +protected: + void insertItem( QwtPlotItem * ); + void removeItem( QwtPlotItem * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_directpainter.cpp b/qwt/src/qwt_plot_directpainter.cpp new file mode 100644 index 000000000..d78352740 --- /dev/null +++ b/qwt/src/qwt_plot_directpainter.cpp @@ -0,0 +1,317 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_directpainter.h" +#include "qwt_scale_map.h" +#include "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_plot_seriesitem.h" +#include +#include +#include +#include + +static inline void qwtRenderItem( + QPainter *painter, const QRect &canvasRect, + QwtPlotSeriesItem *seriesItem, int from, int to ) +{ + // A minor performance improvement is possible + // with caching the maps. TODO ... + + QwtPlot *plot = seriesItem->plot(); + const QwtScaleMap xMap = plot->canvasMap( seriesItem->xAxis() ); + const QwtScaleMap yMap = plot->canvasMap( seriesItem->yAxis() ); + + painter->setRenderHint( QPainter::Antialiasing, + seriesItem->testRenderHint( QwtPlotItem::RenderAntialiased ) ); + seriesItem->drawSeries( painter, xMap, yMap, canvasRect, from, to ); +} + +static inline bool qwtHasBackingStore( const QwtPlotCanvas *canvas ) +{ + return canvas->testPaintAttribute( QwtPlotCanvas::BackingStore ) + && canvas->backingStore() && !canvas->backingStore()->isNull(); +} + +class QwtPlotDirectPainter::PrivateData +{ +public: + PrivateData(): + attributes( 0 ), + hasClipping(false), + seriesItem( NULL ) + { + } + + QwtPlotDirectPainter::Attributes attributes; + + bool hasClipping; + QRegion clipRegion; + + QPainter painter; + + QwtPlotSeriesItem *seriesItem; + int from; + int to; +}; + +//! Constructor +QwtPlotDirectPainter::QwtPlotDirectPainter( QObject *parent ): + QObject( parent ) +{ + d_data = new PrivateData; +} + +//! Destructor +QwtPlotDirectPainter::~QwtPlotDirectPainter() +{ + delete d_data; +} + +/*! + Change an attribute + + \param attribute Attribute to change + \param on On/Off + + \sa Attribute, testAttribute() +*/ +void QwtPlotDirectPainter::setAttribute( Attribute attribute, bool on ) +{ + if ( bool( d_data->attributes & attribute ) != on ) + { + if ( on ) + d_data->attributes |= attribute; + else + d_data->attributes &= ~attribute; + + if ( ( attribute == AtomicPainter ) && on ) + reset(); + } +} + +/*! + \return True, when attribute is enabled + \param attribute Attribute to be tested + \sa Attribute, setAttribute() +*/ +bool QwtPlotDirectPainter::testAttribute( Attribute attribute ) const +{ + return d_data->attributes & attribute; +} + +/*! + En/Disables clipping + + \param enable Enables clipping is true, disable it otherwise + \sa hasClipping(), clipRegion(), setClipRegion() +*/ +void QwtPlotDirectPainter::setClipping( bool enable ) +{ + d_data->hasClipping = enable; +} + +/*! + \return true, when clipping is enabled + \sa setClipping(), clipRegion(), setClipRegion() +*/ +bool QwtPlotDirectPainter::hasClipping() const +{ + return d_data->hasClipping; +} + +/*! + \brief Assign a clip region and enable clipping + + Depending on the environment setting a proper clip region might improve + the performance heavily. F.e. on Qt embedded only the clipped part of + the backing store will be copied to a ( maybe unaccelerated ) frame buffer + device. + + \param region Clip region + \sa clipRegion(), hasClipping(), setClipping() +*/ +void QwtPlotDirectPainter::setClipRegion( const QRegion ®ion ) +{ + d_data->clipRegion = region; + d_data->hasClipping = true; +} + +/*! + \return Currently set clip region. + \sa setClipRegion(), setClipping(), hasClipping() +*/ +QRegion QwtPlotDirectPainter::clipRegion() const +{ + return d_data->clipRegion; +} + +/*! + \brief Draw a set of points of a seriesItem. + + When observing an measurement while it is running, new points have to be + added to an existing seriesItem. drawSeries() can be used to display them avoiding + a complete redraw of the canvas. + + Setting plot()->canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true); + will result in faster painting, if the paint engine of the canvas widget + supports this feature. + + \param seriesItem Item to be painted + \param from Index of the first point to be painted + \param to Index of the last point to be painted. If to < 0 the + series will be painted to its last point. +*/ +void QwtPlotDirectPainter::drawSeries( + QwtPlotSeriesItem *seriesItem, int from, int to ) +{ + if ( seriesItem == NULL || seriesItem->plot() == NULL ) + return; + + QWidget *canvas = seriesItem->plot()->canvas(); + const QRect canvasRect = canvas->contentsRect(); + + QwtPlotCanvas *plotCanvas = qobject_cast( canvas ); + + if ( plotCanvas && qwtHasBackingStore( plotCanvas ) ) + { + QPainter painter( const_cast( plotCanvas->backingStore() ) ); + + if ( d_data->hasClipping ) + painter.setClipRegion( d_data->clipRegion ); + + qwtRenderItem( &painter, canvasRect, seriesItem, from, to ); + + if ( testAttribute( QwtPlotDirectPainter::FullRepaint ) ) + { + plotCanvas->repaint(); + return; + } + } + + bool immediatePaint = true; + if ( !canvas->testAttribute( Qt::WA_WState_InPaintEvent ) ) + { +#if QT_VERSION < 0x050000 + if ( !canvas->testAttribute( Qt::WA_PaintOutsidePaintEvent ) ) +#endif + immediatePaint = false; + } + + if ( immediatePaint ) + { + if ( !d_data->painter.isActive() ) + { + reset(); + + d_data->painter.begin( canvas ); + canvas->installEventFilter( this ); + } + + if ( d_data->hasClipping ) + { + d_data->painter.setClipRegion( + QRegion( canvasRect ) & d_data->clipRegion ); + } + else + { + if ( !d_data->painter.hasClipping() ) + d_data->painter.setClipRect( canvasRect ); + } + + qwtRenderItem( &d_data->painter, canvasRect, seriesItem, from, to ); + + if ( d_data->attributes & QwtPlotDirectPainter::AtomicPainter ) + { + reset(); + } + else + { + if ( d_data->hasClipping ) + d_data->painter.setClipping( false ); + } + } + else + { + reset(); + + d_data->seriesItem = seriesItem; + d_data->from = from; + d_data->to = to; + + QRegion clipRegion = canvasRect; + if ( d_data->hasClipping ) + clipRegion &= d_data->clipRegion; + + canvas->installEventFilter( this ); + canvas->repaint(clipRegion); + canvas->removeEventFilter( this ); + + d_data->seriesItem = NULL; + } +} + +//! Close the internal QPainter +void QwtPlotDirectPainter::reset() +{ + if ( d_data->painter.isActive() ) + { + QWidget *w = static_cast( d_data->painter.device() ); + if ( w ) + w->removeEventFilter( this ); + + d_data->painter.end(); + } +} + +//! Event filter +bool QwtPlotDirectPainter::eventFilter( QObject *, QEvent *event ) +{ + if ( event->type() == QEvent::Paint ) + { + reset(); + + if ( d_data->seriesItem ) + { + const QPaintEvent *pe = static_cast< QPaintEvent *>( event ); + + QWidget *canvas = d_data->seriesItem->plot()->canvas(); + + QPainter painter( canvas ); + painter.setClipRegion( pe->region() ); + + bool doCopyCache = testAttribute( CopyBackingStore ); + + if ( doCopyCache ) + { + QwtPlotCanvas *plotCanvas = + qobject_cast( canvas ); + if ( plotCanvas ) + { + doCopyCache = qwtHasBackingStore( plotCanvas ); + if ( doCopyCache ) + { + painter.drawPixmap( plotCanvas->contentsRect().topLeft(), + *plotCanvas->backingStore() ); + } + } + } + + if ( !doCopyCache ) + { + qwtRenderItem( &painter, canvas->contentsRect(), + d_data->seriesItem, d_data->from, d_data->to ); + } + + return true; // don't call QwtPlotCanvas::paintEvent() + } + } + + return false; +} diff --git a/qwt/src/qwt_plot_directpainter.h b/qwt/src/qwt_plot_directpainter.h new file mode 100644 index 000000000..b555c87c3 --- /dev/null +++ b/qwt/src/qwt_plot_directpainter.h @@ -0,0 +1,100 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_DIRECT_PAINTER_H +#define QWT_PLOT_DIRECT_PAINTER_H + +#include "qwt_global.h" +#include + +class QRegion; +class QwtPlotSeriesItem; + +/*! + \brief Painter object trying to paint incrementally + + Often applications want to display samples while they are + collected. When there are too many samples complete replots + will be expensive to be processed in a collection cycle. + + QwtPlotDirectPainter offers an API to paint + subsets ( f.e all additions points ) without erasing/repainting + the plot canvas. + + On certain environments it might be important to calculate a proper + clip region before painting. F.e. for Qt Embedded only the clipped part + of the backing store will be copied to a ( maybe unaccelerated ) + frame buffer. + + \warning Incremental painting will only help when no replot is triggered + by another operation ( like changing scales ) and nothing needs + to be erased. +*/ +class QWT_EXPORT QwtPlotDirectPainter: public QObject +{ +public: + /*! + \brief Paint attributes + \sa setAttribute(), testAttribute(), drawSeries() + */ + enum Attribute + { + /*! + Initializing a QPainter is an expensive operation. + When AtomicPainter is set each call of drawSeries() opens/closes + a temporary QPainter. Otherwise QwtPlotDirectPainter tries to + use the same QPainter as long as possible. + */ + AtomicPainter = 0x01, + + /*! + When FullRepaint is set the plot canvas is explicitly repainted + after the samples have been rendered. + */ + FullRepaint = 0x02, + + /*! + When QwtPlotCanvas::BackingStore is enabled the painter + has to paint to the backing store and the widget. In certain + situations/environments it might be faster to paint to + the backing store only and then copy the backing store to the canvas. + This flag can also be useful for settings, where Qt fills the + the clip region with the widget background. + */ + CopyBackingStore = 0x04 + }; + + //! Paint attributes + typedef QFlags Attributes; + + QwtPlotDirectPainter( QObject *parent = NULL ); + virtual ~QwtPlotDirectPainter(); + + void setAttribute( Attribute, bool on ); + bool testAttribute( Attribute ) const; + + void setClipping( bool ); + bool hasClipping() const; + + void setClipRegion( const QRegion & ); + QRegion clipRegion() const; + + void drawSeries( QwtPlotSeriesItem *, int from, int to ); + void reset(); + + virtual bool eventFilter( QObject *, QEvent * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotDirectPainter::Attributes ) + +#endif diff --git a/qwt/src/qwt_plot_glcanvas.cpp b/qwt/src/qwt_plot_glcanvas.cpp new file mode 100644 index 000000000..87a4cfde7 --- /dev/null +++ b/qwt/src/qwt_plot_glcanvas.cpp @@ -0,0 +1,357 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_glcanvas.h" +#include "qwt_plot.h" +#include +#include +#include +#include +#include +#include "qwt_painter.h" + +static QWidget *qwtBGWidget( QWidget *widget ) +{ + QWidget *w = widget; + + for ( ; w->parentWidget() != NULL; w = w->parentWidget() ) + { + if ( w->autoFillBackground() || + w->testAttribute( Qt::WA_StyledBackground ) ) + { + return w; + } + } + + return w; +} + +static void qwtUpdateContentsRect( QwtPlotGLCanvas *canvas ) +{ + const int fw = canvas->frameWidth(); + canvas->setContentsMargins( fw, fw, fw, fw ); +} + +class QwtPlotGLCanvas::PrivateData +{ +public: + PrivateData(): + frameStyle( QFrame::Panel | QFrame::Sunken), + lineWidth( 2 ), + midLineWidth( 0 ) + { + } + + int frameStyle; + int lineWidth; + int midLineWidth; +}; + +/*! + \brief Constructor + + \param plot Parent plot widget + \sa QwtPlot::setCanvas() +*/ +QwtPlotGLCanvas::QwtPlotGLCanvas( QwtPlot *plot ): + QGLWidget( plot ) +{ + d_data = new PrivateData; + +#ifndef QT_NO_CURSOR + setCursor( Qt::CrossCursor ); +#endif + + setAutoFillBackground( true ); + qwtUpdateContentsRect( this ); +} + +//! Destructor +QwtPlotGLCanvas::~QwtPlotGLCanvas() +{ + delete d_data; +} + +/*! + Set the frame style + + \param style The bitwise OR between a shape and a shadow. + + \sa frameStyle(), QFrame::setFrameStyle(), + setFrameShadow(), setFrameShape() + */ +void QwtPlotGLCanvas::setFrameStyle( int style ) +{ + if ( style != d_data->frameStyle ) + { + d_data->frameStyle = style; + qwtUpdateContentsRect( this ); + + update(); + } +} + +/*! + \return The bitwise OR between a frameShape() and a frameShadow() + \sa setFrameStyle(), QFrame::frameStyle() + */ +int QwtPlotGLCanvas::frameStyle() const +{ + return d_data->frameStyle; +} + +/*! + Set the frame shadow + + \param shadow Frame shadow + \sa frameShadow(), setFrameShape(), QFrame::setFrameShadow() + */ +void QwtPlotGLCanvas::setFrameShadow( Shadow shadow ) +{ + setFrameStyle(( d_data->frameStyle & QFrame::Shape_Mask ) | shadow ); +} + +/*! + \return Frame shadow + \sa setFrameShadow(), QFrame::setFrameShadow() + */ +QwtPlotGLCanvas::Shadow QwtPlotGLCanvas::frameShadow() const +{ + return (Shadow) ( d_data->frameStyle & QFrame::Shadow_Mask ); +} + +/*! + Set the frame shape + + \param shape Frame shape + \sa frameShape(), setFrameShadow(), QFrame::frameShape() + */ +void QwtPlotGLCanvas::setFrameShape( Shape shape ) +{ + setFrameStyle( ( d_data->frameStyle & QFrame::Shadow_Mask ) | shape ); +} + +/*! + \return Frame shape + \sa setFrameShape(), QFrame::frameShape() + */ +QwtPlotGLCanvas::Shape QwtPlotGLCanvas::frameShape() const +{ + return (Shape) ( d_data->frameStyle & QFrame::Shape_Mask ); +} + +/*! + Set the frame line width + + The default line width is 2 pixels. + + \param width Line width of the frame + \sa lineWidth(), setMidLineWidth() +*/ +void QwtPlotGLCanvas::setLineWidth( int width ) +{ + width = qMax( width, 0 ); + if ( width != d_data->lineWidth ) + { + d_data->lineWidth = qMax( width, 0 ); + qwtUpdateContentsRect( this ); + update(); + } +} + +/*! + \return Line width of the frame + \sa setLineWidth(), midLineWidth() + */ +int QwtPlotGLCanvas::lineWidth() const +{ + return d_data->lineWidth; +} + +/*! + Set the frame mid line width + + The default midline width is 0 pixels. + + \param width Midline width of the frame + \sa midLineWidth(), setLineWidth() +*/ +void QwtPlotGLCanvas::setMidLineWidth( int width ) +{ + width = qMax( width, 0 ); + if ( width != d_data->midLineWidth ) + { + d_data->midLineWidth = width; + qwtUpdateContentsRect( this ); + update(); + } +} + +/*! + \return Midline width of the frame + \sa setMidLineWidth(), lineWidth() + */ +int QwtPlotGLCanvas::midLineWidth() const +{ + return d_data->midLineWidth; +} + +/*! + \return Frame width depending on the style, line width and midline width. + */ +int QwtPlotGLCanvas::frameWidth() const +{ + return ( frameStyle() != NoFrame ) ? d_data->lineWidth : 0; +} + +/*! + Paint event + + \param event Paint event + \sa QwtPlot::drawCanvas() +*/ +void QwtPlotGLCanvas::paintEvent( QPaintEvent *event ) +{ + Q_UNUSED( event ); + + QPainter painter( this ); + + drawBackground( &painter ); + drawItems( &painter ); + + if ( !testAttribute( Qt::WA_StyledBackground ) ) + { + if ( frameWidth() > 0 ) + drawBorder( &painter ); + } +} +/*! + Qt event handler for QEvent::PolishRequest and QEvent::StyleChange + \param event Qt Event + \return See QGLWidget::event() +*/ +bool QwtPlotGLCanvas::event( QEvent *event ) +{ + const bool ok = QGLWidget::event( event ); + + if ( event->type() == QEvent::PolishRequest || + event->type() == QEvent::StyleChange ) + { + // assuming, that we always have a styled background + // when we have a style sheet + + setAttribute( Qt::WA_StyledBackground, + testAttribute( Qt::WA_StyleSheet ) ); + } + + return ok; +} + +/*! + Draw the plot items + \param painter Painter + + \sa QwtPlot::drawCanvas() +*/ +void QwtPlotGLCanvas::drawItems( QPainter *painter ) +{ + painter->save(); + + painter->setClipRect( contentsRect(), Qt::IntersectClip ); + + QwtPlot *plot = qobject_cast< QwtPlot *>( parent() ); + if ( plot ) + plot->drawCanvas( painter ); + + painter->restore(); +} + +/*! + Draw the background of the canvas + \param painter Painter +*/ +void QwtPlotGLCanvas::drawBackground( QPainter *painter ) +{ + painter->save(); + + QWidget *w = qwtBGWidget( this ); + + const QPoint off = mapTo( w, QPoint() ); + painter->translate( -off ); + + const QRect fillRect = rect().translated( off ); + + if ( w->testAttribute( Qt::WA_StyledBackground ) ) + { + painter->setClipRect( fillRect ); + + QStyleOption opt; + opt.initFrom( w ); + w->style()->drawPrimitive( QStyle::PE_Widget, &opt, painter, w); + } + else + { + painter->fillRect( fillRect, + w->palette().brush( w->backgroundRole() ) ); + } + + painter->restore(); +} + +/*! + Draw the border of the canvas + \param painter Painter +*/ +void QwtPlotGLCanvas::drawBorder( QPainter *painter ) +{ + const int fw = frameWidth(); + if ( fw <= 0 ) + return; + + if ( frameShadow() == QwtPlotGLCanvas::Plain ) + { + qDrawPlainRect( painter, frameRect(), + palette().shadow().color(), lineWidth() ); + } + else + { + if ( frameShape() == QwtPlotGLCanvas::Box ) + { + qDrawShadeRect( painter, frameRect(), palette(), + frameShadow() == Sunken, lineWidth(), midLineWidth() ); + } + else + { + qDrawShadePanel( painter, frameRect(), palette(), + frameShadow() == Sunken, lineWidth() ); + } + } +} + +//! Calls repaint() +void QwtPlotGLCanvas::replot() +{ + repaint(); +} + +/*! + \return Empty path +*/ +QPainterPath QwtPlotGLCanvas::borderPath( const QRect &rect ) const +{ + Q_UNUSED( rect ); + return QPainterPath(); +} + +//! \return The rectangle where the frame is drawn in. +QRect QwtPlotGLCanvas::frameRect() const +{ + const int fw = frameWidth(); + return contentsRect().adjusted( -fw, -fw, fw, fw ); +} diff --git a/qwt/src/qwt_plot_glcanvas.h b/qwt/src/qwt_plot_glcanvas.h new file mode 100644 index 000000000..2ff1cf2e3 --- /dev/null +++ b/qwt/src/qwt_plot_glcanvas.h @@ -0,0 +1,136 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_GLCANVAS_H +#define QWT_PLOT_GLCANVAS_H + +#include "qwt_global.h" +#include +#include + +class QwtPlot; + +/*! + \brief An alternative canvas for a QwtPlot derived from QGLWidget + + QwtPlotGLCanvas implements the very basics to act as canvas + inside of a QwtPlot widget. It might be extended to a full + featured alternative to QwtPlotCanvas in a future version of Qwt. + + Even if QwtPlotGLCanvas is not derived from QFrame it imitates + its API. When using style sheets it supports the box model - beside + backgrounds with rounded borders. + + \sa QwtPlot::setCanvas(), QwtPlotCanvas + + \note You might want to use the QPaintEngine::OpenGL paint engine + ( see QGL::setPreferredPaintEngine() ). On a Linux test system + QPaintEngine::OpenGL2 shows very basic problems ( wrong + geometries of rectangles ) but also more advanced stuff + like antialiasing doesn't work. + + \note Another way to introduce OpenGL rendering to Qwt + is to use QGLPixelBuffer or QGLFramebufferObject. Both + type of buffers can be converted into a QImage and + used in combination with a regular QwtPlotCanvas. +*/ +class QWT_EXPORT QwtPlotGLCanvas: public QGLWidget +{ + Q_OBJECT + + Q_ENUMS( Shape Shadow ) + + Q_PROPERTY( Shadow frameShadow READ frameShadow WRITE setFrameShadow ) + Q_PROPERTY( Shape frameShape READ frameShape WRITE setFrameShape ) + Q_PROPERTY( int lineWidth READ lineWidth WRITE setLineWidth ) + Q_PROPERTY( int midLineWidth READ midLineWidth WRITE setMidLineWidth ) + Q_PROPERTY( int frameWidth READ frameWidth ) + Q_PROPERTY( QRect frameRect READ frameRect DESIGNABLE false ) + +public: + /*! + \brief Frame shadow + + Unfortunately it is not possible to use QFrame::Shadow + as a property of a widget that is not derived from QFrame. + The following enum is made for the designer only. It is safe + to use QFrame::Shadow instead. + */ + enum Shadow + { + //! QFrame::Plain + Plain = QFrame::Plain, + + //! QFrame::Raised + Raised = QFrame::Raised, + + //! QFrame::Sunken + Sunken = QFrame::Sunken + }; + + /*! + \brief Frame shape + + Unfortunately it is not possible to use QFrame::Shape + as a property of a widget that is not derived from QFrame. + The following enum is made for the designer only. It is safe + to use QFrame::Shadow instead. + + \note QFrame::StyledPanel and QFrame::WinPanel are unsuported + and will be displayed as QFrame::Panel. + */ + enum Shape + { + NoFrame = QFrame::NoFrame, + + Box = QFrame::Box, + Panel = QFrame::Panel + }; + + explicit QwtPlotGLCanvas( QwtPlot * = NULL ); + virtual ~QwtPlotGLCanvas(); + + void setFrameStyle( int style ); + int frameStyle() const; + + void setFrameShadow( Shadow ); + Shadow frameShadow() const; + + void setFrameShape( Shape ); + Shape frameShape() const; + + void setLineWidth( int ); + int lineWidth() const; + + void setMidLineWidth( int ); + int midLineWidth() const; + + int frameWidth() const; + QRect frameRect() const; + + Q_INVOKABLE QPainterPath borderPath( const QRect & ) const; + + virtual bool event( QEvent * ); + +public Q_SLOTS: + void replot(); + +protected: + virtual void paintEvent( QPaintEvent * ); + + virtual void drawBackground( QPainter * ); + virtual void drawBorder( QPainter * ); + virtual void drawItems( QPainter * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_grid.cpp b/qwt/src/qwt_plot_grid.cpp new file mode 100644 index 000000000..4375e53d7 --- /dev/null +++ b/qwt/src/qwt_plot_grid.cpp @@ -0,0 +1,438 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_grid.h" +#include "qwt_painter.h" +#include "qwt_text.h" +#include "qwt_scale_map.h" +#include "qwt_scale_div.h" +#include "qwt_math.h" +#include +#include + +class QwtPlotGrid::PrivateData +{ +public: + PrivateData(): + xEnabled( true ), + yEnabled( true ), + xMinEnabled( false ), + yMinEnabled( false ) + { + } + + bool xEnabled; + bool yEnabled; + bool xMinEnabled; + bool yMinEnabled; + + QwtScaleDiv xScaleDiv; + QwtScaleDiv yScaleDiv; + + QPen majorPen; + QPen minorPen; +}; + +//! Enables major grid, disables minor grid +QwtPlotGrid::QwtPlotGrid(): + QwtPlotItem( QwtText( "Grid" ) ) +{ + d_data = new PrivateData; + + setItemInterest( QwtPlotItem::ScaleInterest, true ); + setZ( 10.0 ); +} + +//! Destructor +QwtPlotGrid::~QwtPlotGrid() +{ + delete d_data; +} + +//! \return QwtPlotItem::Rtti_PlotGrid +int QwtPlotGrid::rtti() const +{ + return QwtPlotItem::Rtti_PlotGrid; +} + +/*! + \brief Enable or disable vertical grid lines + \param on Enable (true) or disable + + \sa Minor grid lines can be enabled or disabled with + enableXMin() +*/ +void QwtPlotGrid::enableX( bool on ) +{ + if ( d_data->xEnabled != on ) + { + d_data->xEnabled = on; + + legendChanged(); + itemChanged(); + } +} + +/*! + \brief Enable or disable horizontal grid lines + \param on Enable (true) or disable + \sa Minor grid lines can be enabled or disabled with enableYMin() +*/ +void QwtPlotGrid::enableY( bool on ) +{ + if ( d_data->yEnabled != on ) + { + d_data->yEnabled = on; + + legendChanged(); + itemChanged(); + } +} + +/*! + \brief Enable or disable minor vertical grid lines. + \param on Enable (true) or disable + \sa enableX() +*/ +void QwtPlotGrid::enableXMin( bool on ) +{ + if ( d_data->xMinEnabled != on ) + { + d_data->xMinEnabled = on; + + legendChanged(); + itemChanged(); + } +} + +/*! + \brief Enable or disable minor horizontal grid lines + \param on Enable (true) or disable + \sa enableY() +*/ +void QwtPlotGrid::enableYMin( bool on ) +{ + if ( d_data->yMinEnabled != on ) + { + d_data->yMinEnabled = on; + + legendChanged(); + itemChanged(); + } +} + +/*! + Assign an x axis scale division + + \param scaleDiv Scale division +*/ +void QwtPlotGrid::setXDiv( const QwtScaleDiv &scaleDiv ) +{ + if ( d_data->xScaleDiv != scaleDiv ) + { + d_data->xScaleDiv = scaleDiv; + itemChanged(); + } +} + +/*! + Assign a y axis division + + \param scaleDiv Scale division +*/ +void QwtPlotGrid::setYDiv( const QwtScaleDiv &scaleDiv ) +{ + if ( d_data->yScaleDiv != scaleDiv ) + { + d_data->yScaleDiv = scaleDiv; + itemChanged(); + } +} + +/*! + Build and assign a pen for both major and minor grid lines + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotGrid::setPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen for both major and minor grid lines + + \param pen Pen + \sa setMajorPen(), setMinorPen() +*/ +void QwtPlotGrid::setPen( const QPen &pen ) +{ + if ( d_data->majorPen != pen || d_data->minorPen != pen ) + { + d_data->majorPen = pen; + d_data->minorPen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + Build and assign a pen for both major grid lines + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotGrid::setMajorPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setMajorPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen for the major grid lines + + \param pen Pen + \sa majorPen(), setMinorPen(), setPen() +*/ +void QwtPlotGrid::setMajorPen( const QPen &pen ) +{ + if ( d_data->majorPen != pen ) + { + d_data->majorPen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + Build and assign a pen for the minor grid lines + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotGrid::setMinorPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setMinorPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen for the minor grid lines + + \param pen Pen + \sa minorPen(), setMajorPen(), setPen() +*/ +void QwtPlotGrid::setMinorPen( const QPen &pen ) +{ + if ( d_data->minorPen != pen ) + { + d_data->minorPen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \brief Draw the grid + + The grid is drawn into the bounding rectangle such that + grid lines begin and end at the rectangle's borders. The X and Y + maps are used to map the scale divisions into the drawing region + screen. + + \param painter Painter + \param xMap X axis map + \param yMap Y axis + \param canvasRect Contents rectangle of the plot canvas +*/ +void QwtPlotGrid::draw( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect ) const +{ + // draw minor grid lines + QPen minorPen = d_data->minorPen; + minorPen.setCapStyle( Qt::FlatCap ); + + painter->setPen( minorPen ); + + if ( d_data->xEnabled && d_data->xMinEnabled ) + { + drawLines( painter, canvasRect, Qt::Vertical, xMap, + d_data->xScaleDiv.ticks( QwtScaleDiv::MinorTick ) ); + drawLines( painter, canvasRect, Qt::Vertical, xMap, + d_data->xScaleDiv.ticks( QwtScaleDiv::MediumTick ) ); + } + + if ( d_data->yEnabled && d_data->yMinEnabled ) + { + drawLines( painter, canvasRect, Qt::Horizontal, yMap, + d_data->yScaleDiv.ticks( QwtScaleDiv::MinorTick ) ); + drawLines( painter, canvasRect, Qt::Horizontal, yMap, + d_data->yScaleDiv.ticks( QwtScaleDiv::MediumTick ) ); + } + + // draw major grid lines + QPen majorPen = d_data->majorPen; + majorPen.setCapStyle( Qt::FlatCap ); + + painter->setPen( majorPen ); + + if ( d_data->xEnabled ) + { + drawLines( painter, canvasRect, Qt::Vertical, xMap, + d_data->xScaleDiv.ticks( QwtScaleDiv::MajorTick ) ); + } + + if ( d_data->yEnabled ) + { + drawLines( painter, canvasRect, Qt::Horizontal, yMap, + d_data->yScaleDiv.ticks( QwtScaleDiv::MajorTick ) ); + } +} + +void QwtPlotGrid::drawLines( QPainter *painter, const QRectF &canvasRect, + Qt::Orientation orientation, const QwtScaleMap &scaleMap, + const QList &values ) const +{ + const double x1 = canvasRect.left(); + const double x2 = canvasRect.right() - 1.0; + const double y1 = canvasRect.top(); + const double y2 = canvasRect.bottom() - 1.0; + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + for ( int i = 0; i < values.count(); i++ ) + { + double value = scaleMap.transform( values[i] ); + if ( doAlign ) + value = qRound( value ); + + if ( orientation == Qt::Horizontal ) + { + if ( qwtFuzzyGreaterOrEqual( value, y1 ) && + qwtFuzzyLessOrEqual( value, y2 ) ) + { + QwtPainter::drawLine( painter, x1, value, x2, value ); + } + } + else + { + if ( qwtFuzzyGreaterOrEqual( value, x1 ) && + qwtFuzzyLessOrEqual( value, x2 ) ) + { + QwtPainter::drawLine( painter, value, y1, value, y2 ); + } + } + } +} + +/*! + \return the pen for the major grid lines + \sa setMajorPen(), setMinorPen(), setPen() +*/ +const QPen &QwtPlotGrid::majorPen() const +{ + return d_data->majorPen; +} + +/*! + \return the pen for the minor grid lines + \sa setMinorPen(), setMajorPen(), setPen() +*/ +const QPen &QwtPlotGrid::minorPen() const +{ + return d_data->minorPen; +} + +/*! + \return true if vertical grid lines are enabled + \sa enableX() +*/ +bool QwtPlotGrid::xEnabled() const +{ + return d_data->xEnabled; +} + +/*! + \return true if minor vertical grid lines are enabled + \sa enableXMin() +*/ +bool QwtPlotGrid::xMinEnabled() const +{ + return d_data->xMinEnabled; +} + +/*! + \return true if horizontal grid lines are enabled + \sa enableY() +*/ +bool QwtPlotGrid::yEnabled() const +{ + return d_data->yEnabled; +} + +/*! + \return true if minor horizontal grid lines are enabled + \sa enableYMin() +*/ +bool QwtPlotGrid::yMinEnabled() const +{ + return d_data->yMinEnabled; +} + + +/*! \return the scale division of the x axis */ +const QwtScaleDiv &QwtPlotGrid::xScaleDiv() const +{ + return d_data->xScaleDiv; +} + +/*! \return the scale division of the y axis */ +const QwtScaleDiv &QwtPlotGrid::yScaleDiv() const +{ + return d_data->yScaleDiv; +} + +/*! + Update the grid to changes of the axes scale division + + \param xScaleDiv Scale division of the x-axis + \param yScaleDiv Scale division of the y-axis + + \sa QwtPlot::updateAxes() +*/ +void QwtPlotGrid::updateScaleDiv( const QwtScaleDiv& xScaleDiv, + const QwtScaleDiv& yScaleDiv ) +{ + setXDiv( xScaleDiv ); + setYDiv( yScaleDiv ); +} diff --git a/qwt/src/qwt_plot_grid.h b/qwt/src/qwt_plot_grid.h new file mode 100644 index 000000000..16d984c72 --- /dev/null +++ b/qwt/src/qwt_plot_grid.h @@ -0,0 +1,87 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_GRID_H +#define QWT_PLOT_GRID_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" +#include "qwt_scale_div.h" + +class QPainter; +class QPen; +class QwtScaleMap; +class QwtScaleDiv; + +/*! + \brief A class which draws a coordinate grid + + The QwtPlotGrid class can be used to draw a coordinate grid. + A coordinate grid consists of major and minor vertical + and horizontal grid lines. The locations of the grid lines + are determined by the X and Y scale divisions which can + be assigned with setXDiv() and setYDiv(). + The draw() member draws the grid within a bounding + rectangle. +*/ + +class QWT_EXPORT QwtPlotGrid: public QwtPlotItem +{ +public: + explicit QwtPlotGrid(); + virtual ~QwtPlotGrid(); + + virtual int rtti() const; + + void enableX( bool tf ); + bool xEnabled() const; + + void enableY( bool tf ); + bool yEnabled() const; + + void enableXMin( bool tf ); + bool xMinEnabled() const; + + void enableYMin( bool tf ); + bool yMinEnabled() const; + + void setXDiv( const QwtScaleDiv &sx ); + const QwtScaleDiv &xScaleDiv() const; + + void setYDiv( const QwtScaleDiv &sy ); + const QwtScaleDiv &yScaleDiv() const; + + void setPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen & ); + + void setMajorPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setMajorPen( const QPen & ); + const QPen& majorPen() const; + + void setMinorPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setMinorPen( const QPen &p ); + const QPen& minorPen() const; + + virtual void draw( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &rect ) const; + + virtual void updateScaleDiv( + const QwtScaleDiv &xMap, const QwtScaleDiv &yMap ); + +private: + void drawLines( QPainter *painter, const QRectF &, + Qt::Orientation orientation, const QwtScaleMap &, + const QList & ) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_histogram.cpp b/qwt/src/qwt_plot_histogram.cpp new file mode 100644 index 000000000..4464f03ff --- /dev/null +++ b/qwt/src/qwt_plot_histogram.cpp @@ -0,0 +1,690 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_histogram.h" +#include "qwt_plot.h" +#include "qwt_painter.h" +#include "qwt_column_symbol.h" +#include "qwt_scale_map.h" +#include +#include + +static inline bool qwtIsCombinable( const QwtInterval &d1, + const QwtInterval &d2 ) +{ + if ( d1.isValid() && d2.isValid() ) + { + if ( d1.maxValue() == d2.minValue() ) + { + if ( !( d1.borderFlags() & QwtInterval::ExcludeMaximum + && d2.borderFlags() & QwtInterval::ExcludeMinimum ) ) + { + return true; + } + } + } + + return false; +} + +class QwtPlotHistogram::PrivateData +{ +public: + PrivateData(): + baseline( 0.0 ), + style( Columns ), + symbol( NULL ) + { + } + + ~PrivateData() + { + delete symbol; + } + + double baseline; + + QPen pen; + QBrush brush; + QwtPlotHistogram::HistogramStyle style; + const QwtColumnSymbol *symbol; +}; + +/*! + Constructor + \param title Title of the histogram. +*/ +QwtPlotHistogram::QwtPlotHistogram( const QwtText &title ): + QwtPlotSeriesItem( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the histogram. +*/ +QwtPlotHistogram::QwtPlotHistogram( const QString &title ): + QwtPlotSeriesItem( title ) +{ + init(); +} + +//! Destructor +QwtPlotHistogram::~QwtPlotHistogram() +{ + delete d_data; +} + +//! Initialize data members +void QwtPlotHistogram::init() +{ + d_data = new PrivateData(); + setData( new QwtIntervalSeriesData() ); + + setItemAttribute( QwtPlotItem::AutoScale, true ); + setItemAttribute( QwtPlotItem::Legend, true ); + + setZ( 20.0 ); +} + +/*! + Set the histogram's drawing style + + \param style Histogram style + \sa HistogramStyle, style() +*/ +void QwtPlotHistogram::setStyle( HistogramStyle style ) +{ + if ( style != d_data->style ) + { + d_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Style of the histogram + \sa HistogramStyle, setStyle() +*/ +QwtPlotHistogram::HistogramStyle QwtPlotHistogram::style() const +{ + return d_data->style; +} + +/*! + Build and assign a pen + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotHistogram::setPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen, that is used in a style() depending way. + + \param pen New pen + \sa pen(), brush() +*/ +void QwtPlotHistogram::setPen( const QPen &pen ) +{ + if ( pen != d_data->pen ) + { + d_data->pen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Pen used in a style() depending way. + \sa setPen(), brush() +*/ +const QPen &QwtPlotHistogram::pen() const +{ + return d_data->pen; +} + +/*! + Assign a brush, that is used in a style() depending way. + + \param brush New brush + \sa pen(), brush() +*/ +void QwtPlotHistogram::setBrush( const QBrush &brush ) +{ + if ( brush != d_data->brush ) + { + d_data->brush = brush; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Brush used in a style() depending way. + \sa setPen(), brush() +*/ +const QBrush &QwtPlotHistogram::brush() const +{ + return d_data->brush; +} + +/*! + \brief Assign a symbol + + In Column style an optional symbol can be assigned, that is responsible + for displaying the rectangle that is defined by the interval and + the distance between baseline() and value. When no symbol has been + defined the area is displayed as plain rectangle using pen() and brush(). + + \sa style(), symbol(), drawColumn(), pen(), brush() + + \note In applications, where different intervals need to be displayed + in a different way ( f.e different colors or even using different symbols) + it is recommended to overload drawColumn(). +*/ +void QwtPlotHistogram::setSymbol( const QwtColumnSymbol *symbol ) +{ + if ( symbol != d_data->symbol ) + { + delete d_data->symbol; + d_data->symbol = symbol; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() +*/ +const QwtColumnSymbol *QwtPlotHistogram::symbol() const +{ + return d_data->symbol; +} + +/*! + \brief Set the value of the baseline + + Each column representing an QwtIntervalSample is defined by its + interval and the interval between baseline and the value of the sample. + + The default value of the baseline is 0.0. + + \param value Value of the baseline + \sa baseline() +*/ +void QwtPlotHistogram::setBaseline( double value ) +{ + if ( d_data->baseline != value ) + { + d_data->baseline = value; + itemChanged(); + } +} + +/*! + \return Value of the baseline + \sa setBaseline() +*/ +double QwtPlotHistogram::baseline() const +{ + return d_data->baseline; +} + +/*! + \return Bounding rectangle of all samples. + For an empty series the rectangle is invalid. +*/ +QRectF QwtPlotHistogram::boundingRect() const +{ + QRectF rect = data()->boundingRect(); + if ( !rect.isValid() ) + return rect; + + if ( orientation() == Qt::Horizontal ) + { + rect = QRectF( rect.y(), rect.x(), + rect.height(), rect.width() ); + + if ( rect.left() > d_data->baseline ) + rect.setLeft( d_data->baseline ); + else if ( rect.right() < d_data->baseline ) + rect.setRight( d_data->baseline ); + } + else + { + if ( rect.bottom() < d_data->baseline ) + rect.setBottom( d_data->baseline ); + else if ( rect.top() > d_data->baseline ) + rect.setTop( d_data->baseline ); + } + + return rect; +} + +//! \return QwtPlotItem::Rtti_PlotHistogram +int QwtPlotHistogram::rtti() const +{ + return QwtPlotItem::Rtti_PlotHistogram; +} + +/*! + Initialize data with an array of samples. + \param samples Vector of points +*/ +void QwtPlotHistogram::setSamples( + const QVector &samples ) +{ + setData( new QwtIntervalSeriesData( samples ) ); +} + +/*! + Assign a series of samples + + setSamples() is just a wrapper for setData() without any additional + value - beside that it is easier to find for the developer. + + \param data Data + \warning The item takes ownership of the data object, deleting + it when its not used anymore. +*/ +void QwtPlotHistogram::setSamples( + QwtSeriesData *data ) +{ + setData( data ); +} + +/*! + Draw a subset of the histogram samples + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + series will be painted to its last sample. + + \sa drawOutline(), drawLines(), drawColumns +*/ +void QwtPlotHistogram::drawSeries( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &, int from, int to ) const +{ + if ( !painter || dataSize() <= 0 ) + return; + + if ( to < 0 ) + to = dataSize() - 1; + + switch ( d_data->style ) + { + case Outline: + drawOutline( painter, xMap, yMap, from, to ); + break; + case Lines: + drawLines( painter, xMap, yMap, from, to ); + break; + case Columns: + drawColumns( painter, xMap, yMap, from, to ); + break; + default: + break; + } +} + +/*! + Draw a histogram in Outline style() + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + histogram will be painted to its last point. + + \sa setStyle(), style() + \warning The outline style requires, that the intervals are in increasing + order and not overlapping. +*/ +void QwtPlotHistogram::drawOutline( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + double v0 = ( orientation() == Qt::Horizontal ) ? + xMap.transform( baseline() ) : yMap.transform( baseline() ); + if ( doAlign ) + v0 = qRound( v0 ); + + QwtIntervalSample previous; + + QPolygonF polygon; + for ( int i = from; i <= to; i++ ) + { + const QwtIntervalSample sample = this->sample( i ); + + if ( !sample.interval.isValid() ) + { + flushPolygon( painter, v0, polygon ); + previous = sample; + continue; + } + + if ( previous.interval.isValid() ) + { + if ( !qwtIsCombinable( previous.interval, sample.interval ) ) + flushPolygon( painter, v0, polygon ); + } + + if ( orientation() == Qt::Vertical ) + { + double x1 = xMap.transform( sample.interval.minValue() ); + double x2 = xMap.transform( sample.interval.maxValue() ); + double y = yMap.transform( sample.value ); + if ( doAlign ) + { + x1 = qRound( x1 ); + x2 = qRound( x2 ); + y = qRound( y ); + } + + if ( polygon.size() == 0 ) + polygon += QPointF( x1, v0 ); + + polygon += QPointF( x1, y ); + polygon += QPointF( x2, y ); + } + else + { + double y1 = yMap.transform( sample.interval.minValue() ); + double y2 = yMap.transform( sample.interval.maxValue() ); + double x = xMap.transform( sample.value ); + if ( doAlign ) + { + y1 = qRound( y1 ); + y2 = qRound( y2 ); + x = qRound( x ); + } + + if ( polygon.size() == 0 ) + polygon += QPointF( v0, y1 ); + + polygon += QPointF( x, y1 ); + polygon += QPointF( x, y2 ); + } + previous = sample; + } + + flushPolygon( painter, v0, polygon ); +} + +/*! + Draw a histogram in Columns style() + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + histogram will be painted to its last point. + + \sa setStyle(), style(), setSymbol(), drawColumn() +*/ +void QwtPlotHistogram::drawColumns( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const +{ + painter->setPen( d_data->pen ); + painter->setBrush( d_data->brush ); + + const QwtSeriesData *series = data(); + + for ( int i = from; i <= to; i++ ) + { + const QwtIntervalSample sample = series->sample( i ); + if ( !sample.interval.isNull() ) + { + const QwtColumnRect rect = columnRect( sample, xMap, yMap ); + drawColumn( painter, rect, sample ); + } + } +} + +/*! + Draw a histogram in Lines style() + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + histogram will be painted to its last point. + + \sa setStyle(), style(), setPen() +*/ +void QwtPlotHistogram::drawLines( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + painter->setPen( d_data->pen ); + painter->setBrush( Qt::NoBrush ); + + const QwtSeriesData *series = data(); + + for ( int i = from; i <= to; i++ ) + { + const QwtIntervalSample sample = series->sample( i ); + if ( !sample.interval.isNull() ) + { + const QwtColumnRect rect = columnRect( sample, xMap, yMap ); + + QRectF r = rect.toRect(); + if ( doAlign ) + { + r.setLeft( qRound( r.left() ) ); + r.setRight( qRound( r.right() ) ); + r.setTop( qRound( r.top() ) ); + r.setBottom( qRound( r.bottom() ) ); + } + + switch ( rect.direction ) + { + case QwtColumnRect::LeftToRight: + { + QwtPainter::drawLine( painter, + r.topRight(), r.bottomRight() ); + break; + } + case QwtColumnRect::RightToLeft: + { + QwtPainter::drawLine( painter, + r.topLeft(), r.bottomLeft() ); + break; + } + case QwtColumnRect::TopToBottom: + { + QwtPainter::drawLine( painter, + r.bottomRight(), r.bottomLeft() ); + break; + } + case QwtColumnRect::BottomToTop: + { + QwtPainter::drawLine( painter, + r.topRight(), r.topLeft() ); + break; + } + } + } + } +} + +//! Internal, used by the Outline style. +void QwtPlotHistogram::flushPolygon( QPainter *painter, + double baseLine, QPolygonF &polygon ) const +{ + if ( polygon.size() == 0 ) + return; + + if ( orientation() == Qt::Horizontal ) + polygon += QPointF( baseLine, polygon.last().y() ); + else + polygon += QPointF( polygon.last().x(), baseLine ); + + if ( d_data->brush.style() != Qt::NoBrush ) + { + painter->setPen( Qt::NoPen ); + painter->setBrush( d_data->brush ); + + if ( orientation() == Qt::Horizontal ) + { + polygon += QPointF( polygon.last().x(), baseLine ); + polygon += QPointF( polygon.first().x(), baseLine ); + } + else + { + polygon += QPointF( baseLine, polygon.last().y() ); + polygon += QPointF( baseLine, polygon.first().y() ); + } + + QwtPainter::drawPolygon( painter, polygon ); + + polygon.pop_back(); + polygon.pop_back(); + } + if ( d_data->pen.style() != Qt::NoPen ) + { + painter->setBrush( Qt::NoBrush ); + painter->setPen( d_data->pen ); + QwtPainter::drawPolyline( painter, polygon ); + } + polygon.clear(); +} + +/*! + Calculate the area that is covered by a sample + + \param sample Sample + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + + \return Rectangle, that is covered by a sample +*/ +QwtColumnRect QwtPlotHistogram::columnRect( const QwtIntervalSample &sample, + const QwtScaleMap &xMap, const QwtScaleMap &yMap ) const +{ + QwtColumnRect rect; + + const QwtInterval &iv = sample.interval; + if ( !iv.isValid() ) + return rect; + + if ( orientation() == Qt::Horizontal ) + { + const double x0 = xMap.transform( baseline() ); + const double x = xMap.transform( sample.value ); + const double y1 = yMap.transform( iv.minValue() ); + const double y2 = yMap.transform( iv.maxValue() ); + + rect.hInterval.setInterval( x0, x ); + rect.vInterval.setInterval( y1, y2, iv.borderFlags() ); + rect.direction = ( x < x0 ) ? QwtColumnRect::RightToLeft : + QwtColumnRect::LeftToRight; + } + else + { + const double x1 = xMap.transform( iv.minValue() ); + const double x2 = xMap.transform( iv.maxValue() ); + const double y0 = yMap.transform( baseline() ); + const double y = yMap.transform( sample.value ); + + rect.hInterval.setInterval( x1, x2, iv.borderFlags() ); + rect.vInterval.setInterval( y0, y ); + rect.direction = ( y < y0 ) ? QwtColumnRect::BottomToTop : + QwtColumnRect::TopToBottom; + } + + return rect; +} + +/*! + Draw a column for a sample in Columns style(). + + When a symbol() has been set the symbol is used otherwise the + column is displayed as plain rectangle using pen() and brush(). + + \param painter Painter + \param rect Rectangle where to paint the column in paint device coordinates + \param sample Sample to be displayed + + \note In applications, where different intervals need to be displayed + in a different way ( f.e different colors or even using different symbols) + it is recommended to overload drawColumn(). +*/ +void QwtPlotHistogram::drawColumn( QPainter *painter, + const QwtColumnRect &rect, const QwtIntervalSample &sample ) const +{ + Q_UNUSED( sample ); + + if ( d_data->symbol && + ( d_data->symbol->style() != QwtColumnSymbol::NoStyle ) ) + { + d_data->symbol->draw( painter, rect ); + } + else + { + QRectF r = rect.toRect(); + if ( QwtPainter::roundingAlignment( painter ) ) + { + r.setLeft( qRound( r.left() ) ); + r.setRight( qRound( r.right() ) ); + r.setTop( qRound( r.top() ) ); + r.setBottom( qRound( r.bottom() ) ); + } + + QwtPainter::drawRect( painter, r ); + } +} + +/*! + A plain rectangle without pen using the brush() + + \param index Index of the legend entry + ( ignored as there is only one ) + \param size Icon size + \return A graphic displaying the icon + + \sa QwtPlotItem::setLegendIconSize(), QwtPlotItem::legendData() +*/ +QwtGraphic QwtPlotHistogram::legendIcon( int index, + const QSizeF &size ) const +{ + Q_UNUSED( index ); + return defaultIcon( d_data->brush, size ); +} diff --git a/qwt/src/qwt_plot_histogram.h b/qwt/src/qwt_plot_histogram.h new file mode 100644 index 000000000..b96bdddc0 --- /dev/null +++ b/qwt/src/qwt_plot_histogram.h @@ -0,0 +1,139 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_HISTOGRAM_H +#define QWT_PLOT_HISTOGRAM_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" +#include "qwt_column_symbol.h" +#include +#include + +class QwtIntervalData; +class QString; +class QPolygonF; + +/*! + \brief QwtPlotHistogram represents a series of samples, where an interval + is associated with a value ( \f$y = f([x1,x2])\f$ ). + + The representation depends on the style() and an optional symbol() + that is displayed for each interval. + + \note The term "histogram" is used in a different way in the areas of + digital image processing and statistics. Wikipedia introduces the + terms "image histogram" and "color histogram" to avoid confusions. + While "image histograms" can be displayed by a QwtPlotCurve there + is no applicable plot item for a "color histogram" yet. + + \sa QwtPlotBarChart, QwtPlotMultiBarChart +*/ + +class QWT_EXPORT QwtPlotHistogram: + public QwtPlotSeriesItem, public QwtSeriesStore +{ +public: + /*! + Histogram styles. + The default style is QwtPlotHistogram::Columns. + + \sa setStyle(), style(), setSymbol(), symbol(), setBaseline() + */ + enum HistogramStyle + { + /*! + Draw an outline around the area, that is build by all intervals + using the pen() and fill it with the brush(). The outline style + requires, that the intervals are in increasing order and + not overlapping. + */ + Outline, + + /*! + Draw a column for each interval. When a symbol() has been set + the symbol is used otherwise the column is displayed as + plain rectangle using pen() and brush(). + */ + Columns, + + /*! + Draw a simple line using the pen() for each interval. + */ + Lines, + + /*! + Styles >= UserStyle are reserved for derived + classes that overload drawSeries() with + additional application specific ways to display a histogram. + */ + UserStyle = 100 + }; + + explicit QwtPlotHistogram( const QString &title = QString::null ); + explicit QwtPlotHistogram( const QwtText &title ); + virtual ~QwtPlotHistogram(); + + virtual int rtti() const; + + void setPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen & ); + const QPen &pen() const; + + void setBrush( const QBrush & ); + const QBrush &brush() const; + + void setSamples( const QVector & ); + void setSamples( QwtSeriesData * ); + + void setBaseline( double reference ); + double baseline() const; + + void setStyle( HistogramStyle style ); + HistogramStyle style() const; + + void setSymbol( const QwtColumnSymbol * ); + const QwtColumnSymbol *symbol() const; + + virtual void drawSeries( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual QRectF boundingRect() const; + + virtual QwtGraphic legendIcon( int index, const QSizeF & ) const; + +protected: + virtual QwtColumnRect columnRect( const QwtIntervalSample &, + const QwtScaleMap &, const QwtScaleMap & ) const; + + virtual void drawColumn( QPainter *, const QwtColumnRect &, + const QwtIntervalSample & ) const; + + void drawColumns( QPainter *, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const; + + void drawOutline( QPainter *, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const; + + void drawLines( QPainter *, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const; + +private: + void init(); + void flushPolygon( QPainter *, double baseLine, QPolygonF & ) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_intervalcurve.cpp b/qwt/src/qwt_plot_intervalcurve.cpp new file mode 100644 index 000000000..200ea39b5 --- /dev/null +++ b/qwt/src/qwt_plot_intervalcurve.cpp @@ -0,0 +1,603 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_intervalcurve.h" +#include "qwt_interval_symbol.h" +#include "qwt_scale_map.h" +#include "qwt_clipper.h" +#include "qwt_painter.h" +#include + +#include + +static inline bool qwtIsHSampleInside( const QwtIntervalSample &sample, + double xMin, double xMax, double yMin, double yMax ) +{ + const double y = sample.value; + const double x1 = sample.interval.minValue(); + const double x2 = sample.interval.maxValue(); + + const bool isOffScreen = ( y < yMin ) || ( y > yMax ) + || ( x1 < xMin && x2 < xMin ) || ( x1 > xMax && x2 > xMax ); + + return !isOffScreen; +} + +static inline bool qwtIsVSampleInside( const QwtIntervalSample &sample, + double xMin, double xMax, double yMin, double yMax ) +{ + const double x = sample.value; + const double y1 = sample.interval.minValue(); + const double y2 = sample.interval.maxValue(); + + const bool isOffScreen = ( x < xMin ) || ( x > xMax ) + || ( y1 < yMin && y2 < yMin ) || ( y1 > yMax && y2 > yMax ); + + return !isOffScreen; +} + +class QwtPlotIntervalCurve::PrivateData +{ +public: + PrivateData(): + style( QwtPlotIntervalCurve::Tube ), + symbol( NULL ), + pen( Qt::black ), + brush( Qt::white ) + { + paintAttributes = QwtPlotIntervalCurve::ClipPolygons; + paintAttributes |= QwtPlotIntervalCurve::ClipSymbol; + + pen.setCapStyle( Qt::FlatCap ); + } + + ~PrivateData() + { + delete symbol; + } + + QwtPlotIntervalCurve::CurveStyle style; + const QwtIntervalSymbol *symbol; + + QPen pen; + QBrush brush; + + QwtPlotIntervalCurve::PaintAttributes paintAttributes; +}; + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotIntervalCurve::QwtPlotIntervalCurve( const QwtText &title ): + QwtPlotSeriesItem( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotIntervalCurve::QwtPlotIntervalCurve( const QString &title ): + QwtPlotSeriesItem( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPlotIntervalCurve::~QwtPlotIntervalCurve() +{ + delete d_data; +} + +//! Initialize internal members +void QwtPlotIntervalCurve::init() +{ + setItemAttribute( QwtPlotItem::Legend, true ); + setItemAttribute( QwtPlotItem::AutoScale, true ); + + d_data = new PrivateData; + setData( new QwtIntervalSeriesData() ); + + setZ( 19.0 ); +} + +//! \return QwtPlotItem::Rtti_PlotIntervalCurve +int QwtPlotIntervalCurve::rtti() const +{ + return QwtPlotIntervalCurve::Rtti_PlotIntervalCurve; +} + +/*! + Specify an attribute how to draw the curve + + \param attribute Paint attribute + \param on On/Off + \sa testPaintAttribute() +*/ +void QwtPlotIntervalCurve::setPaintAttribute( + PaintAttribute attribute, bool on ) +{ + if ( on ) + d_data->paintAttributes |= attribute; + else + d_data->paintAttributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa PaintAttribute, setPaintAttribute() +*/ +bool QwtPlotIntervalCurve::testPaintAttribute( + PaintAttribute attribute ) const +{ + return ( d_data->paintAttributes & attribute ); +} + +/*! + Initialize data with an array of samples. + \param samples Vector of samples +*/ +void QwtPlotIntervalCurve::setSamples( + const QVector &samples ) +{ + setData( new QwtIntervalSeriesData( samples ) ); +} + +/*! + Assign a series of samples + + setSamples() is just a wrapper for setData() without any additional + value - beside that it is easier to find for the developer. + + \param data Data + \warning The item takes ownership of the data object, deleting + it when its not used anymore. +*/ +void QwtPlotIntervalCurve::setSamples( + QwtSeriesData *data ) +{ + setData( data ); +} + +/*! + Set the curve's drawing style + + \param style Curve style + \sa CurveStyle, style() +*/ +void QwtPlotIntervalCurve::setStyle( CurveStyle style ) +{ + if ( style != d_data->style ) + { + d_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Style of the curve + \sa setStyle() +*/ +QwtPlotIntervalCurve::CurveStyle QwtPlotIntervalCurve::style() const +{ + return d_data->style; +} + +/*! + Assign a symbol. + + \param symbol Symbol + \sa symbol() +*/ +void QwtPlotIntervalCurve::setSymbol( const QwtIntervalSymbol *symbol ) +{ + if ( symbol != d_data->symbol ) + { + delete d_data->symbol; + d_data->symbol = symbol; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() +*/ +const QwtIntervalSymbol *QwtPlotIntervalCurve::symbol() const +{ + return d_data->symbol; +} + +/*! + Build and assign a pen + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotIntervalCurve::setPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + \brief Assign a pen + \param pen New pen + \sa pen(), brush() +*/ +void QwtPlotIntervalCurve::setPen( const QPen &pen ) +{ + if ( pen != d_data->pen ) + { + d_data->pen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Pen used to draw the lines + \sa setPen(), brush() +*/ +const QPen& QwtPlotIntervalCurve::pen() const +{ + return d_data->pen; +} + +/*! + Assign a brush. + + The brush is used to fill the area in Tube style(). + + \param brush Brush + \sa brush(), pen(), setStyle(), CurveStyle +*/ +void QwtPlotIntervalCurve::setBrush( const QBrush &brush ) +{ + if ( brush != d_data->brush ) + { + d_data->brush = brush; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Brush used to fill the area in Tube style() + \sa setBrush(), setStyle(), CurveStyle +*/ +const QBrush& QwtPlotIntervalCurve::brush() const +{ + return d_data->brush; +} + +/*! + \return Bounding rectangle of all samples. + For an empty series the rectangle is invalid. +*/ +QRectF QwtPlotIntervalCurve::boundingRect() const +{ + QRectF rect = QwtPlotSeriesItem::boundingRect(); + if ( rect.isValid() && orientation() == Qt::Vertical ) + rect.setRect( rect.y(), rect.x(), rect.height(), rect.width() ); + + return rect; +} + +/*! + Draw a subset of the samples + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + series will be painted to its last sample. + + \sa drawTube(), drawSymbols() +*/ +void QwtPlotIntervalCurve::drawSeries( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + if ( to < 0 ) + to = dataSize() - 1; + + if ( from < 0 ) + from = 0; + + if ( from > to ) + return; + + switch ( d_data->style ) + { + case Tube: + drawTube( painter, xMap, yMap, canvasRect, from, to ); + break; + + case NoCurve: + default: + break; + } + + if ( d_data->symbol && + ( d_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) ) + { + drawSymbols( painter, *d_data->symbol, + xMap, yMap, canvasRect, from, to ); + } +} + +/*! + Draw a tube + + Builds 2 curves from the upper and lower limits of the intervals + and draws them with the pen(). The area between the curves is + filled with the brush(). + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + series will be painted to its last sample. + + \sa drawSeries(), drawSymbols() +*/ +void QwtPlotIntervalCurve::drawTube( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + painter->save(); + + const size_t size = to - from + 1; + QPolygonF polygon( 2 * size ); + QPointF *points = polygon.data(); + + for ( uint i = 0; i < size; i++ ) + { + QPointF &minValue = points[i]; + QPointF &maxValue = points[2 * size - 1 - i]; + + const QwtIntervalSample intervalSample = sample( from + i ); + if ( orientation() == Qt::Vertical ) + { + double x = xMap.transform( intervalSample.value ); + double y1 = yMap.transform( intervalSample.interval.minValue() ); + double y2 = yMap.transform( intervalSample.interval.maxValue() ); + if ( doAlign ) + { + x = qRound( x ); + y1 = qRound( y1 ); + y2 = qRound( y2 ); + } + + minValue.rx() = x; + minValue.ry() = y1; + maxValue.rx() = x; + maxValue.ry() = y2; + } + else + { + double y = yMap.transform( intervalSample.value ); + double x1 = xMap.transform( intervalSample.interval.minValue() ); + double x2 = xMap.transform( intervalSample.interval.maxValue() ); + if ( doAlign ) + { + y = qRound( y ); + x1 = qRound( x1 ); + x2 = qRound( x2 ); + } + + minValue.rx() = x1; + minValue.ry() = y; + maxValue.rx() = x2; + maxValue.ry() = y; + } + } + + if ( d_data->brush.style() != Qt::NoBrush ) + { + painter->setPen( QPen( Qt::NoPen ) ); + painter->setBrush( d_data->brush ); + + if ( d_data->paintAttributes & ClipPolygons ) + { + const qreal m = 1.0; + const QPolygonF p = QwtClipper::clipPolygonF( + canvasRect.adjusted( -m, -m, m, m ), polygon, true ); + + QwtPainter::drawPolygon( painter, p ); + } + else + { + QwtPainter::drawPolygon( painter, polygon ); + } + } + + if ( d_data->pen.style() != Qt::NoPen ) + { + painter->setPen( d_data->pen ); + painter->setBrush( Qt::NoBrush ); + + if ( d_data->paintAttributes & ClipPolygons ) + { + qreal pw = qMax( qreal( 1.0 ), painter->pen().widthF() ); + const QRectF clipRect = canvasRect.adjusted( -pw, -pw, pw, pw ); + + QPolygonF p; + + p.resize( size ); + ::memcpy( p.data(), points, size * sizeof( QPointF ) ); + p = QwtClipper::clipPolygonF( clipRect, p ); + QwtPainter::drawPolyline( painter, p ); + + p.resize( size ); + ::memcpy( p.data(), points + size, size * sizeof( QPointF ) ); + p = QwtClipper::clipPolygonF( clipRect, p ); + QwtPainter::drawPolyline( painter, p ); + } + else + { + QwtPainter::drawPolyline( painter, points, size ); + QwtPainter::drawPolyline( painter, points + size, size ); + } + } + + painter->restore(); +} + +/*! + Draw symbols for a subset of the samples + + \param painter Painter + \param symbol Interval symbol + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted + + \sa setSymbol(), drawSeries(), drawTube() +*/ +void QwtPlotIntervalCurve::drawSymbols( + QPainter *painter, const QwtIntervalSymbol &symbol, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + painter->save(); + + QPen pen = symbol.pen(); + pen.setCapStyle( Qt::FlatCap ); + + painter->setPen( pen ); + painter->setBrush( symbol.brush() ); + + const QRectF tr = QwtScaleMap::invTransform( xMap, yMap, canvasRect ); + + const double xMin = tr.left(); + const double xMax = tr.right(); + const double yMin = tr.top(); + const double yMax = tr.bottom(); + + const bool doClip = d_data->paintAttributes & ClipSymbol; + + for ( int i = from; i <= to; i++ ) + { + const QwtIntervalSample s = sample( i ); + + if ( orientation() == Qt::Vertical ) + { + if ( !doClip || qwtIsVSampleInside( s, xMin, xMax, yMin, yMax ) ) + { + const double x = xMap.transform( s.value ); + const double y1 = yMap.transform( s.interval.minValue() ); + const double y2 = yMap.transform( s.interval.maxValue() ); + + symbol.draw( painter, orientation(), + QPointF( x, y1 ), QPointF( x, y2 ) ); + } + } + else + { + if ( !doClip || qwtIsHSampleInside( s, xMin, xMax, yMin, yMax ) ) + { + const double y = yMap.transform( s.value ); + const double x1 = xMap.transform( s.interval.minValue() ); + const double x2 = xMap.transform( s.interval.maxValue() ); + + symbol.draw( painter, orientation(), + QPointF( x1, y ), QPointF( x2, y ) ); + } + } + } + + painter->restore(); +} + +/*! + \return Icon for the legend + + In case of Tube style() the icon is a plain rectangle filled with the brush(). + If a symbol is assigned it is scaled to size. + + \param index Index of the legend entry + ( ignored as there is only one ) + \param size Icon size + + \sa QwtPlotItem::setLegendIconSize(), QwtPlotItem::legendData() +*/ +QwtGraphic QwtPlotIntervalCurve::legendIcon( + int index, const QSizeF &size ) const +{ + Q_UNUSED( index ); + + if ( size.isEmpty() ) + return QwtGraphic(); + + QwtGraphic icon; + icon.setDefaultSize( size ); + icon.setRenderHint( QwtGraphic::RenderPensUnscaled, true ); + + QPainter painter( &icon ); + painter.setRenderHint( QPainter::Antialiasing, + testRenderHint( QwtPlotItem::RenderAntialiased ) ); + + if ( d_data->style == Tube ) + { + QRectF r( 0, 0, size.width(), size.height() ); + painter.fillRect( r, d_data->brush ); + } + + if ( d_data->symbol && + ( d_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) ) + { + QPen pen = d_data->symbol->pen(); + pen.setWidthF( pen.widthF() ); + pen.setCapStyle( Qt::FlatCap ); + + painter.setPen( pen ); + painter.setBrush( d_data->symbol->brush() ); + + if ( orientation() == Qt::Vertical ) + { + const double x = 0.5 * size.width(); + + d_data->symbol->draw( &painter, orientation(), + QPointF( x, 0 ), QPointF( x, size.height() - 1.0 ) ); + } + else + { + const double y = 0.5 * size.height(); + + d_data->symbol->draw( &painter, orientation(), + QPointF( 0.0, y ), QPointF( size.width() - 1.0, y ) ); + } + } + + return icon; +} diff --git a/qwt/src/qwt_plot_intervalcurve.h b/qwt/src/qwt_plot_intervalcurve.h new file mode 100644 index 000000000..624d82f1b --- /dev/null +++ b/qwt/src/qwt_plot_intervalcurve.h @@ -0,0 +1,132 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_INTERVAL_CURVE_H +#define QWT_PLOT_INTERVAL_CURVE_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" +#include "qwt_series_data.h" + +class QwtIntervalSymbol; + +/*! + \brief QwtPlotIntervalCurve represents a series of samples, where each value + is associated with an interval ( \f$[y1,y2] = f(x)\f$ ). + + The representation depends on the style() and an optional symbol() + that is displayed for each interval. QwtPlotIntervalCurve might be used + to display error bars or the area between 2 curves. +*/ +class QWT_EXPORT QwtPlotIntervalCurve: + public QwtPlotSeriesItem, public QwtSeriesStore +{ +public: + /*! + \brief Curve styles. + The default setting is QwtPlotIntervalCurve::Tube. + + \sa setStyle(), style() + */ + enum CurveStyle + { + /*! + Don't draw a curve. Note: This doesn't affect the symbols. + */ + NoCurve, + + /*! + Build 2 curves from the upper and lower limits of the intervals + and draw them with the pen(). The area between the curves is + filled with the brush(). + */ + Tube, + + /*! + Styles >= QwtPlotIntervalCurve::UserCurve are reserved for derived + classes that overload drawSeries() with + additional application specific curve types. + */ + UserCurve = 100 + }; + + /*! + Attributes to modify the drawing algorithm. + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + /*! + Clip polygons before painting them. In situations, where points + are far outside the visible area (f.e when zooming deep) this + might be a substantial improvement for the painting performance. + */ + ClipPolygons = 0x01, + + //! Check if a symbol is on the plot canvas before painting it. + ClipSymbol = 0x02 + }; + + //! Paint attributes + typedef QFlags PaintAttributes; + + explicit QwtPlotIntervalCurve( const QString &title = QString::null ); + explicit QwtPlotIntervalCurve( const QwtText &title ); + + virtual ~QwtPlotIntervalCurve(); + + virtual int rtti() const; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setSamples( const QVector & ); + void setSamples( QwtSeriesData * ); + + void setPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen & ); + const QPen &pen() const; + + void setBrush( const QBrush & ); + const QBrush &brush() const; + + void setStyle( CurveStyle style ); + CurveStyle style() const; + + void setSymbol( const QwtIntervalSymbol * ); + const QwtIntervalSymbol *symbol() const; + + virtual void drawSeries( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual QRectF boundingRect() const; + + virtual QwtGraphic legendIcon( int index, const QSizeF & ) const; + +protected: + + void init(); + + virtual void drawTube( QPainter *, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawSymbols( QPainter *, const QwtIntervalSymbol &, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotIntervalCurve::PaintAttributes ) + +#endif diff --git a/qwt/src/qwt_plot_item.cpp b/qwt/src/qwt_plot_item.cpp new file mode 100644 index 000000000..d6265d614 --- /dev/null +++ b/qwt/src/qwt_plot_item.cpp @@ -0,0 +1,709 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_item.h" +#include "qwt_text.h" +#include "qwt_plot.h" +#include "qwt_legend_data.h" +#include "qwt_scale_div.h" +#include "qwt_graphic.h" +#include + +class QwtPlotItem::PrivateData +{ +public: + PrivateData(): + plot( NULL ), + isVisible( true ), + attributes( 0 ), + interests( 0 ), + renderHints( 0 ), + renderThreadCount( 1 ), + z( 0.0 ), + xAxis( QwtAxis::xBottom ), + yAxis( QwtAxis::yLeft ), + legendIconSize( 8, 8 ) + { + } + + mutable QwtPlot *plot; + + bool isVisible; + + QwtPlotItem::ItemAttributes attributes; + QwtPlotItem::ItemInterests interests; + + QwtPlotItem::RenderHints renderHints; + uint renderThreadCount; + + double z; + + QwtAxisId xAxis; + QwtAxisId yAxis; + + QwtText title; + QSize legendIconSize; +}; + +/*! + Constructor + \param title Title of the item +*/ +QwtPlotItem::QwtPlotItem( const QwtText &title ) +{ + d_data = new PrivateData; + d_data->title = title; +} + +//! Destroy the QwtPlotItem +QwtPlotItem::~QwtPlotItem() +{ + attach( NULL ); + delete d_data; +} + +/*! + \brief Attach the item to a plot. + + This method will attach a QwtPlotItem to the QwtPlot argument. It will first + detach the QwtPlotItem from any plot from a previous call to attach (if + necessary). If a NULL argument is passed, it will detach from any QwtPlot it + was attached to. + + \param plot Plot widget + \sa detach() +*/ +void QwtPlotItem::attach( QwtPlot *plot ) +{ + if ( plot == d_data->plot ) + return; + + if ( d_data->plot ) + d_data->plot->attachItem( this, false ); + + d_data->plot = plot; + + if ( d_data->plot ) + d_data->plot->attachItem( this, true ); +} + +/*! + \brief This method detaches a QwtPlotItem from any + QwtPlot it has been associated with. + + detach() is equivalent to calling attach( NULL ) + \sa attach() +*/ +void QwtPlotItem::detach() +{ + attach( NULL ); +} + +/*! + Return rtti for the specific class represented. QwtPlotItem is simply + a virtual interface class, and base classes will implement this method + with specific rtti values so a user can differentiate them. + + The rtti value is useful for environments, where the + runtime type information is disabled and it is not possible + to do a dynamic_cast<...>. + + \return rtti value + \sa RttiValues +*/ +int QwtPlotItem::rtti() const +{ + return Rtti_PlotItem; +} + +//! Return attached plot +QwtPlot *QwtPlotItem::plot() const +{ + return d_data->plot; +} + +/*! + Plot items are painted in increasing z-order. + + \return setZ(), QwtPlotDict::itemList() +*/ +double QwtPlotItem::z() const +{ + return d_data->z; +} + +/*! + \brief Set the z value + + Plot items are painted in increasing z-order. + + \param z Z-value + \sa z(), QwtPlotDict::itemList() +*/ +void QwtPlotItem::setZ( double z ) +{ + if ( d_data->z != z ) + { + if ( d_data->plot ) // update the z order + d_data->plot->attachItem( this, false ); + + d_data->z = z; + + if ( d_data->plot ) + d_data->plot->attachItem( this, true ); + + itemChanged(); + } +} + +/*! + Set a new title + + \param title Title + \sa title() +*/ +void QwtPlotItem::setTitle( const QString &title ) +{ + setTitle( QwtText( title ) ); +} + +/*! + Set a new title + + \param title Title + \sa title() +*/ +void QwtPlotItem::setTitle( const QwtText &title ) +{ + if ( d_data->title != title ) + { + d_data->title = title; + + legendChanged(); +#if 0 + itemChanged(); +#endif + } +} + +/*! + \return Title of the item + \sa setTitle() +*/ +const QwtText &QwtPlotItem::title() const +{ + return d_data->title; +} + +/*! + Toggle an item attribute + + \param attribute Attribute type + \param on true/false + + \sa testItemAttribute(), ItemInterest +*/ +void QwtPlotItem::setItemAttribute( ItemAttribute attribute, bool on ) +{ + if ( d_data->attributes.testFlag( attribute ) != on ) + { + if ( on ) + d_data->attributes |= attribute; + else + d_data->attributes &= ~attribute; + + if ( attribute == QwtPlotItem::Legend ) + legendChanged(); + + itemChanged(); + } +} + +/*! + Test an item attribute + + \param attribute Attribute type + \return true/false + \sa setItemAttribute(), ItemInterest +*/ +bool QwtPlotItem::testItemAttribute( ItemAttribute attribute ) const +{ + return d_data->attributes.testFlag( attribute ); +} + +/*! + Toggle an item interest + + \param interest Interest type + \param on true/false + + \sa testItemInterest(), ItemAttribute +*/ +void QwtPlotItem::setItemInterest( ItemInterest interest, bool on ) +{ + if ( d_data->interests.testFlag( interest ) != on ) + { + if ( on ) + d_data->interests |= interest; + else + d_data->interests &= ~interest; + + itemChanged(); + } +} + +/*! + Test an item interest + + \param interest Interest type + \return true/false + \sa setItemInterest(), ItemAttribute +*/ +bool QwtPlotItem::testItemInterest( ItemInterest interest ) const +{ + return d_data->interests.testFlag( interest ); +} + +/*! + Toggle an render hint + + \param hint Render hint + \param on true/false + + \sa testRenderHint(), RenderHint +*/ +void QwtPlotItem::setRenderHint( RenderHint hint, bool on ) +{ + if ( d_data->renderHints.testFlag( hint ) != on ) + { + if ( on ) + d_data->renderHints |= hint; + else + d_data->renderHints &= ~hint; + + itemChanged(); + } +} + +/*! + Test a render hint + + \param hint Render hint + \return true/false + \sa setRenderHint(), RenderHint +*/ +bool QwtPlotItem::testRenderHint( RenderHint hint ) const +{ + return d_data->renderHints.testFlag( hint ); +} + +/*! + On multi core systems rendering of certain plot item + ( f.e QwtPlotRasterItem ) can be done in parallel in + several threads. + + The default setting is set to 1. + + \param numThreads Number of threads to be used for rendering. + If numThreads is set to 0, the system specific + ideal thread count is used. + + The default thread count is 1 ( = no additional threads ) +*/ +void QwtPlotItem::setRenderThreadCount( uint numThreads ) +{ + d_data->renderThreadCount = numThreads; +} + +/*! + \return Number of threads to be used for rendering. + If numThreads() is set to 0, the system specific + ideal thread count is used. +*/ +uint QwtPlotItem::renderThreadCount() const +{ + return d_data->renderThreadCount; +} + +/*! + Set the size of the legend icon + + The default setting is 8x8 pixels + + \param size Size + \sa legendIconSize(), legendIcon() +*/ +void QwtPlotItem::setLegendIconSize( const QSize &size ) +{ + if ( d_data->legendIconSize != size ) + { + d_data->legendIconSize = size; + legendChanged(); + } +} + +/*! + \return Legend icon size + \sa setLegendIconSize(), legendIcon() +*/ +QSize QwtPlotItem::legendIconSize() const +{ + return d_data->legendIconSize; +} + +/*! + \return Icon representing the item on the legend + + The default implementation returns an invalid icon + + \param index Index of the legend entry + ( usually there is only one ) + \param size Icon size + + \sa setLegendIconSize(), legendData() + */ +QwtGraphic QwtPlotItem::legendIcon( + int index, const QSizeF &size ) const +{ + Q_UNUSED( index ) + Q_UNUSED( size ) + + return QwtGraphic(); +} + +/*! + \brief Return a default icon from a brush + + The default icon is a filled rectangle used + in several derived classes as legendIcon(). + + \param brush Fill brush + \param size Icon size + + \return A filled rectangle + */ +QwtGraphic QwtPlotItem::defaultIcon( + const QBrush &brush, const QSizeF &size ) const +{ + QwtGraphic icon; + if ( !size.isEmpty() ) + { + icon.setDefaultSize( size ); + + QRectF r( 0, 0, size.width(), size.height() ); + + QPainter painter( &icon ); + painter.fillRect( r, brush ); + } + + return icon; +} + +//! Show the item +void QwtPlotItem::show() +{ + setVisible( true ); +} + +//! Hide the item +void QwtPlotItem::hide() +{ + setVisible( false ); +} + +/*! + Show/Hide the item + + \param on Show if true, otherwise hide + \sa isVisible(), show(), hide() +*/ +void QwtPlotItem::setVisible( bool on ) +{ + if ( on != d_data->isVisible ) + { + d_data->isVisible = on; + itemChanged(); + } +} + +/*! + \return true if visible + \sa setVisible(), show(), hide() +*/ +bool QwtPlotItem::isVisible() const +{ + return d_data->isVisible; +} + +/*! + Update the legend and call QwtPlot::autoRefresh() for the + parent plot. + + \sa QwtPlot::legendChanged(), QwtPlot::autoRefresh() +*/ +void QwtPlotItem::itemChanged() +{ + if ( d_data->plot ) + d_data->plot->autoRefresh(); +} + +/*! + Update the legend of the parent plot. + \sa QwtPlot::updateLegend(), itemChanged() +*/ +void QwtPlotItem::legendChanged() +{ + if ( testItemAttribute( QwtPlotItem::Legend ) && d_data->plot ) + d_data->plot->updateLegend( this ); +} + +/*! + Set X and Y axis + + The item will painted according to the coordinates of its Axes. + + \param xAxis X Axis ( QwtAxis::xBottom or QwtAxis::xTop ) + \param yAxis Y Axis ( QwtAxis::yLeft or QwtAxis::yRight ) + + \sa setXAxis(), setYAxis(), xAxis(), yAxis() +*/ +void QwtPlotItem::setAxes( QwtAxisId xAxis, QwtAxisId yAxis ) +{ + if ( xAxis.id >= 0 && + ( xAxis.pos == QwtAxis::xBottom || xAxis.pos == QwtAxis::xTop ) ) + { + d_data->xAxis = xAxis; + } + + if ( yAxis.id >= 0 && QwtAxis::isYAxis( yAxis.pos ) ) + { + d_data->yAxis = yAxis; + } + + itemChanged(); +} + +/*! + Set the X axis + + The item will painted according to the coordinates its Axes. + + \param axis X Axis ( QwtAxis::xBottom or QwtAxis::xTop ) + \sa setAxes(), setYAxis(), xAxis() +*/ +void QwtPlotItem::setXAxis( QwtAxisId axisId ) +{ + if ( axisId.pos == QwtAxis::xBottom || axisId.pos == QwtAxis::xTop ) + { + if ( axisId.id >= 0 ) + { + d_data->xAxis = axisId; + itemChanged(); + } + } +} + +/*! + Set the Y axis + + The item will painted according to the coordinates its Axes. + + \param axis Y Axis ( QwtAxis::yLeft or QwtAxis::yRight ) + \sa setAxes(), setXAxis(), yAxis() +*/ +void QwtPlotItem::setYAxis( QwtAxisId axisId ) +{ + if ( QwtAxis::isYAxis( axisId.pos ) ) + { + if ( axisId.id >= 0 ) + { + d_data->yAxis = axisId; + itemChanged(); + } + } +} + +//! Return xAxis +QwtAxisId QwtPlotItem::xAxis() const +{ + return d_data->xAxis; +} + +//! Return yAxis +QwtAxisId QwtPlotItem::yAxis() const +{ + return d_data->yAxis; +} + +/*! + \return An invalid bounding rect: QRectF(1.0, 1.0, -2.0, -2.0) + \note A width or height < 0.0 is ignored by the autoscaler +*/ +QRectF QwtPlotItem::boundingRect() const +{ + return QRectF( 1.0, 1.0, -2.0, -2.0 ); // invalid +} + +/*! + \brief Calculate a hint for the canvas margin + + When the QwtPlotItem::Margins flag is enabled the plot item + indicates, that it needs some margins at the borders of the canvas. + This is f.e. used by bar charts to reserve space for displaying + the bars. + + The margins are in target device coordinates ( pixels on screen ) + + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas in painter coordinates + \param left Returns the left margin + \param top Returns the top margin + \param right Returns the right margin + \param bottom Returns the bottom margin + + \return The default implementation returns 0 for all margins + + \sa QwtPlot::getCanvasMarginsHint(), QwtPlot::updateCanvasMargins() + */ +void QwtPlotItem::getCanvasMarginHint( const QwtScaleMap &xMap, + const QwtScaleMap &yMap, const QRectF &canvasRect, + double &left, double &top, double &right, double &bottom ) const +{ + Q_UNUSED( xMap ); + Q_UNUSED( yMap ); + Q_UNUSED( canvasRect ); + + // use QMargins, when we don't need to support Qt < 4.6 anymore + left = top = right = bottom = 0.0; +} + +/*! + \brief Return all information, that is needed to represent + the item on the legend + + Most items are represented by one entry on the legend + showing an icon and a text, but f.e. QwtPlotMultiBarChart + displays one entry for each bar. + + QwtLegendData is basically a list of QVariants that makes it + possible to overload and reimplement legendData() to + return almost any type of information, that is understood + by the receiver that acts as the legend. + + The default implementation returns one entry with + the title() of the item and the legendIcon(). + + \return Data, that is needed to represent the item on the legend + \sa title(), legendIcon(), QwtLegend, QwtPlotLegendItem + */ +QList QwtPlotItem::legendData() const +{ + QwtLegendData data; + + QwtText label = title(); + label.setRenderFlags( label.renderFlags() & Qt::AlignLeft ); + + QVariant titleValue; + qVariantSetValue( titleValue, label ); + data.setValue( QwtLegendData::TitleRole, titleValue ); + + const QwtGraphic graphic = legendIcon( 0, legendIconSize() ); + if ( !graphic.isNull() ) + { + QVariant iconValue; + qVariantSetValue( iconValue, graphic ); + data.setValue( QwtLegendData::IconRole, iconValue ); + } + + QList list; + list += data; + + return list; +} + +/*! + \brief Update the item to changes of the axes scale division + + Update the item, when the axes of plot have changed. + The default implementation does nothing, but items that depend + on the scale division (like QwtPlotGrid()) have to reimplement + updateScaleDiv() + + updateScaleDiv() is only called when the ScaleInterest interest + is enabled. The default implementation does nothing. + + \param xScaleDiv Scale division of the x-axis + \param yScaleDiv Scale division of the y-axis + + \sa QwtPlot::updateAxes(), ScaleInterest +*/ +void QwtPlotItem::updateScaleDiv( const QwtScaleDiv &xScaleDiv, + const QwtScaleDiv &yScaleDiv ) +{ + Q_UNUSED( xScaleDiv ); + Q_UNUSED( yScaleDiv ); +} + +/*! + \brief Update the item to changes of the legend info + + Plot items that want to display a legend ( not those, that want to + be displayed on a legend ! ) will have to implement updateLegend(). + + updateLegend() is only called when the LegendInterest interest + is enabled. The default implementation does nothing. + + \param item Plot item to be displayed on a legend + \param data Attributes how to display item on the legend + + \sa QwtPlotLegendItem + + \note Plot items, that want to be displayed on a legend + need to enable the QwtPlotItem::Legend flag and to implement + legendData() and legendIcon() + */ +void QwtPlotItem::updateLegend( const QwtPlotItem *item, + const QList &data ) +{ + Q_UNUSED( item ); + Q_UNUSED( data ); +} + +/*! + \brief Calculate the bounding scale rectangle of 2 maps + + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + + \return Bounding scale rect of the scale maps, not normalized +*/ +QRectF QwtPlotItem::scaleRect( const QwtScaleMap &xMap, + const QwtScaleMap &yMap ) const +{ + return QRectF( xMap.s1(), yMap.s1(), + xMap.sDist(), yMap.sDist() ); +} + +/*! + \brief Calculate the bounding paint rectangle of 2 maps + + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + + \return Bounding paint rectangle of the scale maps, not normalized +*/ +QRectF QwtPlotItem::paintRect( const QwtScaleMap &xMap, + const QwtScaleMap &yMap ) const +{ + const QRectF rect( xMap.p1(), yMap.p1(), + xMap.pDist(), yMap.pDist() ); + + return rect; +} diff --git a/qwt/src/qwt_plot_item.h b/qwt/src/qwt_plot_item.h new file mode 100644 index 000000000..f17201c8b --- /dev/null +++ b/qwt/src/qwt_plot_item.h @@ -0,0 +1,308 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_ITEM_H +#define QWT_PLOT_ITEM_H + +#include "qwt_global.h" +#include "qwt_text.h" +#include "qwt_legend_data.h" +#include "qwt_graphic.h" +#include "qwt_axis_id.h" +#include +#include +#include + +class QPainter; +class QwtScaleMap; +class QwtScaleDiv; +class QwtPlot; + +/*! + \brief Base class for items on the plot canvas + + A plot item is "something", that can be painted on the plot canvas, + or only affects the scales of the plot widget. They can be categorized as: + + - Representator\n + A "Representator" is an item that represents some sort of data + on the plot canvas. The different representator classes are organized + according to the characteristics of the data: + - QwtPlotMarker + Represents a point or a horizontal/vertical coordinate + - QwtPlotCurve + Represents a series of points + - QwtPlotSpectrogram ( QwtPlotRasterItem ) + Represents raster data + - ... + + - Decorators\n + A "Decorator" is an item, that displays additional information, that + is not related to any data: + - QwtPlotGrid + - QwtPlotScaleItem + - QwtPlotSvgItem + - ... + + Depending on the QwtPlotItem::ItemAttribute flags, an item is included + into autoscaling or has an entry on the legend. + + Before misusing the existing item classes it might be better to + implement a new type of plot item + ( don't implement a watermark as spectrogram ). + Deriving a new type of QwtPlotItem primarily means to implement + the YourPlotItem::draw() method. + + \sa The cpuplot example shows the implementation of additional plot items. +*/ + +class QWT_EXPORT QwtPlotItem +{ +public: + /*! + \brief Runtime type information + + RttiValues is used to cast plot items, without + having to enable runtime type information of the compiler. + */ + enum RttiValues + { + //! Unspecific value, that can be used, when it doesn't matter + Rtti_PlotItem = 0, + + //! For QwtPlotGrid + Rtti_PlotGrid, + + //! For QwtPlotScaleItem + Rtti_PlotScale, + + //! For QwtPlotLegendItem + Rtti_PlotLegend, + + //! For QwtPlotMarker + Rtti_PlotMarker, + + //! For QwtPlotCurve + Rtti_PlotCurve, + + //! For QwtPlotSpectroCurve + Rtti_PlotSpectroCurve, + + //! For QwtPlotIntervalCurve + Rtti_PlotIntervalCurve, + + //! For QwtPlotHistogram + Rtti_PlotHistogram, + + //! For QwtPlotSpectrogram + Rtti_PlotSpectrogram, + + //! For QwtPlotSvgItem + Rtti_PlotSVG, + + //! For QwtPlotTradingCurve + Rtti_PlotTradingCurve, + + //! For QwtPlotBarChart + Rtti_PlotBarChart, + + //! For QwtPlotMultiBarChart + Rtti_PlotMultiBarChart, + + //! For QwtPlotShapeItem + Rtti_PlotShape, + + //! For QwtPlotTextLabel + Rtti_PlotTextLabel, + + //! For QwtPlotZoneItem + Rtti_PlotZone, + + /*! + Values >= Rtti_PlotUserItem are reserved for plot items + not implemented in the Qwt library. + */ + Rtti_PlotUserItem = 1000 + }; + + /*! + \brief Plot Item Attributes + + Various aspects of a plot widget depend on the attributes of + the attached plot items. If and how a single plot item + participates in these updates depends on its attributes. + + \sa setItemAttribute(), testItemAttribute(), ItemInterest + */ + enum ItemAttribute + { + //! The item is represented on the legend. + Legend = 0x01, + + /*! + The boundingRect() of the item is included in the + autoscaling calculation as long as its width or height + is >= 0.0. + */ + AutoScale = 0x02, + + /*! + The item needs extra space to display something outside + its bounding rectangle. + \sa getCanvasMarginHint() + */ + Margins = 0x04 + }; + + //! Plot Item Attributes + typedef QFlags ItemAttributes; + + /*! + \brief Plot Item Interests + + Plot items might depend on the situation of the corresponding + plot widget. By enabling an interest the plot item will be + notified, when the corresponding attribute of the plot widgets + has changed. + + \sa setItemAttribute(), testItemAttribute(), ItemInterest + */ + enum ItemInterest + { + /*! + The item is interested in updates of the scales + \sa updateScaleDiv() + */ + ScaleInterest = 0x01, + + /*! + The item is interested in updates of the legend ( of other items ) + This flag is intended for items, that want to implement a legend + for displaying entries of other plot item. + + \note If the plot item wants to be represented on a legend + enable QwtPlotItem::Legend instead. + + \sa updateLegend() + */ + LegendInterest = 0x02 + }; + + //! Plot Item Interests + typedef QFlags ItemInterests; + + //! Render hints + enum RenderHint + { + //! Enable antialiasing + RenderAntialiased = 0x1 + }; + + //! Render hints + typedef QFlags RenderHints; + + explicit QwtPlotItem( const QwtText &title = QwtText() ); + virtual ~QwtPlotItem(); + + void attach( QwtPlot *plot ); + void detach(); + + QwtPlot *plot() const; + + void setTitle( const QString &title ); + void setTitle( const QwtText &title ); + const QwtText &title() const; + + virtual int rtti() const; + + void setItemAttribute( ItemAttribute, bool on = true ); + bool testItemAttribute( ItemAttribute ) const; + + void setItemInterest( ItemInterest, bool on = true ); + bool testItemInterest( ItemInterest ) const; + + void setRenderHint( RenderHint, bool on = true ); + bool testRenderHint( RenderHint ) const; + + void setRenderThreadCount( uint numThreads ); + uint renderThreadCount() const; + + void setLegendIconSize( const QSize & ); + QSize legendIconSize() const; + + double z() const; + void setZ( double z ); + + void show(); + void hide(); + virtual void setVisible( bool ); + bool isVisible () const; + + void setAxes( QwtAxisId xAxis, QwtAxisId yAxis ); + + void setXAxis( QwtAxisId ); + QwtAxisId xAxis() const; + + void setYAxis( QwtAxisId ); + QwtAxisId yAxis() const; + + virtual void itemChanged(); + virtual void legendChanged(); + + /*! + \brief Draw the item + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rect of the canvas in painter coordinates + */ + virtual void draw( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect ) const = 0; + + virtual QRectF boundingRect() const; + + virtual void getCanvasMarginHint( + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasSize, + double &left, double &top, double &right, double &bottom) const; + + virtual void updateScaleDiv( + const QwtScaleDiv&, const QwtScaleDiv& ); + + virtual void updateLegend( const QwtPlotItem *, + const QList & ); + + QRectF scaleRect( const QwtScaleMap &, const QwtScaleMap & ) const; + QRectF paintRect( const QwtScaleMap &, const QwtScaleMap & ) const; + + virtual QList legendData() const; + + virtual QwtGraphic legendIcon( int index, const QSizeF & ) const; + +protected: + QwtGraphic defaultIcon( const QBrush &, const QSizeF & ) const; + +private: + // Disabled copy constructor and operator= + QwtPlotItem( const QwtPlotItem & ); + QwtPlotItem &operator=( const QwtPlotItem & ); + + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotItem::ItemAttributes ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotItem::ItemInterests ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotItem::RenderHints ) + +Q_DECLARE_METATYPE( QwtPlotItem * ) + +#endif diff --git a/qwt/src/qwt_plot_layout.cpp b/qwt/src/qwt_plot_layout.cpp new file mode 100644 index 000000000..f4418566e --- /dev/null +++ b/qwt/src/qwt_plot_layout.cpp @@ -0,0 +1,1805 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_layout.h" +#include "qwt_text.h" +#include "qwt_text_label.h" +#include "qwt_scale_widget.h" +#include "qwt_abstract_legend.h" +#include +#include + +class QwtPlotLayoutData +{ +public: + struct LegendData + { + void init( const QwtAbstractLegend *legend ) + { + if ( legend ) + { + frameWidth = legend->frameWidth(); + hScrollExtent = legend->scrollExtent( Qt::Horizontal ); + vScrollExtent = legend->scrollExtent( Qt::Vertical ); + + hint = legend->sizeHint(); + } + } + + QSize legendHint( const QwtAbstractLegend *legend, const QRectF &rect ) const + { + int w = qMin( hint.width(), qFloor( rect.width() ) ); + + int h = legend->heightForWidth( w ); + if ( h <= 0 ) + h = hint.height(); + + if ( h > rect.height() ) + w += hScrollExtent; + + return QSize( w, h ); + } + + int frameWidth; + int hScrollExtent; + int vScrollExtent; + QSize hint; + }; + + struct LabelData + { + void init( const QwtTextLabel *label ) + { + frameWidth = 0; + text = QwtText(); + + if ( label ) + { + text = label->text(); + if ( !( text.testPaintAttribute( QwtText::PaintUsingTextFont ) ) ) + text.setFont( label->font() ); + + frameWidth = label->frameWidth(); + } + } + + QwtText text; + int frameWidth; + }; + + struct ScaleData + { + void init( const QwtScaleWidget *axisWidget ) + { + if ( axisWidget ) + { + isVisible = true; + + scaleWidget = axisWidget; + scaleFont = axisWidget->font(); + + start = axisWidget->startBorderDist(); + end = axisWidget->endBorderDist(); + + baseLineOffset = axisWidget->margin(); + + dimWithoutTitle = axisWidget->dimForLength( + QWIDGETSIZE_MAX, scaleFont ); + + if ( !axisWidget->title().isEmpty() ) + { + dimWithoutTitle -= + axisWidget->titleHeightForWidth( QWIDGETSIZE_MAX ); + } + } + else + { + isVisible = false; + axisWidget = NULL; + start = 0; + end = 0; + baseLineOffset = 0; + dimWithoutTitle = 0; + } + + } + + bool isVisible; + const QwtScaleWidget *scaleWidget; + QFont scaleFont; + int start; + int end; + int baseLineOffset; + int dimWithoutTitle; + }; + + struct CanvasData + { + void init( const QWidget *canvas ) + { + canvas->getContentsMargins( + &contentsMargins[ QwtAxis::yLeft ], + &contentsMargins[ QwtAxis::xTop ], + &contentsMargins[ QwtAxis::yRight ], + &contentsMargins[ QwtAxis::xBottom ] ); + } + + int contentsMargins[ QwtAxis::PosCount ]; + }; + +public: + enum Label + { + Title, + Footer, + + NumLabels + }; + + QwtPlotLayoutData( const QwtPlot * ); + bool hasSymmetricYAxes() const; + + int numAxes( int axisPos ) const + { + return scaleData[ axisPos ].size(); + } + + ScaleData &axisData( QwtAxisId axisId ) + { + return scaleData[ axisId.pos ][ axisId.id ]; + } + + const ScaleData &axisData( QwtAxisId axisId ) const + { + return scaleData[ axisId.pos ][ axisId.id ]; + } + + LegendData legendData; + LabelData labelData[ NumLabels ]; + QVector scaleData[ QwtAxis::PosCount ]; + CanvasData canvasData; + + double tickOffset[ QwtAxis::PosCount ]; + int numVisibleScales[ QwtAxis::PosCount ]; +}; + +/* + Extract all layout relevant data from the plot components +*/ +QwtPlotLayoutData::QwtPlotLayoutData( const QwtPlot *plot ) +{ + legendData.init( plot->legend() ); + labelData[ Title ].init( plot->titleLabel() ); + labelData[ Footer ].init( plot->footerLabel() ); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + numVisibleScales[ axisPos ] = 0; + + const int axesCount = plot->axesCount( axisPos ); + scaleData[ axisPos ].resize( axesCount ); + tickOffset[ axisPos ] = 0; + + for ( int i = 0; i < axesCount; i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + ScaleData &sclData = axisData( axisId ); + + if ( plot->isAxisVisible( axisId ) ) + { + const QwtScaleWidget *axisWidget = plot->axisWidget( axisId ); + + if ( numVisibleScales[ axisPos ] == 0 ) + { + tickOffset[ axisPos ] = axisWidget->margin(); + + const QwtScaleDraw *scaleDraw = axisWidget->scaleDraw(); + if ( scaleDraw->hasComponent( QwtAbstractScaleDraw::Ticks ) ) + { + tickOffset[ axisPos ] += scaleDraw->maxTickLength(); + } + } + + numVisibleScales[ axisPos ]++; + sclData.init( axisWidget ); + } + else + { + sclData.init( NULL ); + } + } + } + + canvasData.init( plot->canvas() ); +} + +bool QwtPlotLayoutData::hasSymmetricYAxes() const +{ + return numVisibleScales[ QwtAxis::yLeft ] == + numVisibleScales[ QwtAxis::yRight ]; +} + +class QwtPlotLayoutHintData +{ +public: + QwtPlotLayoutHintData( const QwtPlot *plot ); + + bool hasSymmetricYAxes() const + { + return numVisibleScales[ QwtAxis::yLeft ] == + numVisibleScales[ QwtAxis::yRight ]; + } + + int yAxesWidth() const + { + return m_axesSize[ QwtAxis::yLeft ].width() + + m_axesSize[ QwtAxis::yRight ].width(); + } + + int yAxesHeight() const + { + return qMax( m_axesSize[ QwtAxis::yLeft ].height(), + m_axesSize[ QwtAxis::yRight ].height() ); + } + + int xAxesHeight() const + { + return m_axesSize[ QwtAxis::xTop ].height() + + m_axesSize[ QwtAxis::xBottom ].height(); + } + + int xAxesWidth() const + { + return qMax( m_axesSize[ QwtAxis::xTop ].width(), + m_axesSize[ QwtAxis::xBottom ].width() ); + } + + int alignedSize( const QwtAxisId ) const; + + struct ScaleData + { + ScaleData() + { + w = h = minLeft = minRight = 0; + } + + int w; + int h; + int minLeft; + int minRight; + }; + + int canvasBorder[QwtAxis::PosCount]; + int tickOffset[QwtAxis::PosCount]; + int numVisibleScales[ QwtAxis::PosCount ]; + +private: + const ScaleData &axisData( QwtAxisId axisId ) const + { + return scaleData[ axisId.pos ][ axisId.id ]; + } + + ScaleData &axisData( QwtAxisId axisId ) + { + return scaleData[ axisId.pos ][ axisId.id ]; + } + + int axesWidth( int axisPos ) const + { + return m_axesSize[axisPos].width(); + } + + int axesHeight( int axisPos ) const + { + return m_axesSize[axisPos].height(); + } + + QSize m_axesSize[ QwtAxis::PosCount ]; + + QVector scaleData[ QwtAxis::PosCount ]; +}; + +QwtPlotLayoutHintData::QwtPlotLayoutHintData( const QwtPlot *plot ) +{ + int contentsMargins[ 4 ]; + + plot->canvas()->getContentsMargins( + &contentsMargins[ QwtAxis::yLeft ], + &contentsMargins[ QwtAxis::xTop ], + &contentsMargins[ QwtAxis::yRight ], + &contentsMargins[ QwtAxis::xBottom ] ); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + QSize &axesSize = m_axesSize[ axisPos ]; + axesSize = QSize( 0, 0 ); + + canvasBorder[ axisPos ] = contentsMargins[axisPos] + + plot->plotLayout()->canvasMargin( axisPos ) + 1; + + numVisibleScales[ axisPos ] = 0; + tickOffset[ axisPos ] = 0; + + const int axesCount = plot->axesCount( axisPos ); + scaleData[ axisPos ].resize( axesCount ); + + for ( int i = 0; i < axesCount; i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + if ( plot->isAxisVisible( axisId ) ) + { + const QwtScaleWidget *scl = plot->axisWidget( axisId ); + + const QSize hint = scl->minimumSizeHint(); + + ScaleData &sd = axisData( axisId ); + sd.w = hint.width(); + sd.h = hint.height(); + scl->getBorderDistHint( sd.minLeft, sd.minRight ); + + if ( numVisibleScales[axisPos] == 0 ) + { + tickOffset[axisPos] = scl->margin(); + if ( scl->scaleDraw()->hasComponent( QwtAbstractScaleDraw::Ticks ) ) + tickOffset[axisPos] += qCeil( scl->scaleDraw()->maxTickLength() ); + } + + numVisibleScales[axisPos]++; + + if ( QwtAxis::isYAxis( axisId.pos ) ) + { + axesSize.setWidth( axesSize.width() + sd.w ); + axesSize.setHeight( qMax( axesSize.height(), sd.h ) ); + } + else + { + axesSize.setHeight( axesSize.height() + sd.h ); + axesSize.setWidth( qMax( axesSize.width(), sd.w ) ); + } + } + } + + if ( axesCount > 1 ) + { + // The width of the y axes and the height of the x axes depends + // on the line breaks in the scale title. So after knowning the + // bounding height/width we might have some to subtract some line + // breaks, we don't have anymore. + + for ( int i = 0; i < axesCount; i++ ) + { + QSize &axesSize = m_axesSize[ axisPos ]; + + const QwtAxisId axisId( axisPos, i ); + + if ( plot->isAxisVisible( axisId ) ) + { + const QwtScaleWidget *scl = plot->axisWidget( axisId ); + ScaleData &sd = axisData( axisId ); + + if ( QwtAxis::isYAxis( axisId.pos ) ) + { + int off = scl->titleHeightForWidth( sd.h ) - + scl->titleHeightForWidth( yAxesHeight() ); + + axesSize.setWidth( axesSize.width() - off ); + } + else + { + int off = scl->titleHeightForWidth( sd.w ) - + scl->titleHeightForWidth( xAxesWidth() ); + + axesSize.setHeight( axesSize.height() - off ); + } + } + } + } + } +} + +int QwtPlotLayoutHintData::alignedSize( const QwtAxisId axisId ) const +{ + const QwtPlotLayoutHintData::ScaleData &sd = axisData( axisId ); + if ( QwtAxis::isXAxis( axisId.pos ) && sd.w ) + { + int w = sd.w; + const int leftW = m_axesSize[QwtAxis::yLeft].width(); + const int rightW = m_axesSize[QwtAxis::yRight].width(); + + const int shiftLeft = sd.minLeft - canvasBorder[QwtAxis::yLeft]; + if ( shiftLeft > 0 && leftW ) + w -= qMin( shiftLeft, leftW ); + + const int shiftRight = sd.minRight - canvasBorder[QwtAxis::yRight]; + if ( shiftRight > 0 && rightW ) + w -= qMin( shiftRight, rightW ); + + return w; + } + if ( QwtAxis::isYAxis( axisId.pos ) && sd.h ) + { + int h = sd.h; + + const int topH = m_axesSize[QwtAxis::xTop].height(); + const int bottomH = m_axesSize[QwtAxis::xBottom].height(); + + const int shiftBottom = sd.minLeft - canvasBorder[QwtAxis::xBottom]; + if ( shiftBottom > 0 && bottomH ) + h -= qMin( shiftBottom, tickOffset[QwtAxis::xBottom] ); + + const int shiftTop = sd.minRight - canvasBorder[QwtAxis::xTop]; + if ( shiftTop > 0 && topH ) + h -= qMin( shiftTop, tickOffset[QwtAxis::xTop] ); + + return h; + } + + return 0; +} + +class QwtPlotLayoutEngine +{ +public: + class Dimensions + { + public: + Dimensions( const QwtPlotLayoutData& layoutData ) + { + dimTitle = dimFooter = 0; + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + dimAxisVector[axisPos] = + QVector( layoutData.numAxes( axisPos ), 0 ); + } + } + + int dimAxis( QwtAxisId axisId ) const + { + return dimAxisVector[ axisId.pos ].at( axisId.id ); + } + + void setDimAxis( QwtAxisId axisId, int dim ) + { + dimAxisVector[ axisId.pos ][ axisId.id ] = dim; + } + + inline int dimAxes( int axisPos ) const + { + const QVector &dims = dimAxisVector[ axisPos ]; + + int dim = 0; + for ( int i = 0; i < dims.size(); i++ ) + dim += dims[i]; + + return dim; + } + + inline int dimYAxes() const + { + return dimAxes( QwtAxis::yLeft ) + dimAxes( QwtAxis::yRight ); + } + + inline int dimXAxes() const + { + return dimAxes( QwtAxis::xTop ) + dimAxes( QwtAxis::xBottom ); + } + + inline QRectF centered( const QRectF &rect, const QRectF &labelRect ) const + { + QRectF r = labelRect; + r.setX( rect.left() + dimAxes( QwtAxis::yLeft ) ); + r.setWidth( rect.width() - dimYAxes() ); + + return r; + } + + inline QRectF innerRect( const QRectF &rect ) const + { + QRectF r( + rect.x() + dimAxes( QwtAxis::yLeft ), + rect.y() + dimAxes( QwtAxis::xTop ), + rect.width() - dimYAxes(), + rect.height() - dimXAxes() ); + + if ( r.width() < 0 ) + { + r.setX( rect.center().x() ); + r.setWidth( 0 ); + } + if ( r.height() < 0 ) + { + r.setY( rect.center().x() ); + r.setHeight( 0 ); + } + + return r; + } + + int dimTitle; + int dimFooter; + + private: + QVector dimAxisVector[QwtAxis::PosCount]; + }; + + QwtPlotLayoutEngine(): + d_spacing( 5 ) + { + } + + QRectF layoutLegend( QwtPlotLayout::Options, + const QwtPlotLayoutData::LegendData &, const QRectF &, const QSize & ) const; + + QRectF alignLegend( const QSize &legendHint, + const QRectF &canvasRect, const QRectF &legendRect ) const; + + void alignScales( QwtPlotLayout::Options, const QwtPlotLayoutData &, + QRectF &canvasRect, + QVector scaleRect[QwtAxis::PosCount] ) const; + + Dimensions layoutDimensions( QwtPlotLayout::Options, + const QwtPlotLayoutData &, const QRectF &rect ) const; + + inline void setSpacing( int spacing ) { d_spacing = spacing; } + inline int spacing() const { return d_spacing; } + + inline void setAlignCanvas( int axisPos, bool on ) { d_alignCanvas[ axisPos ] = on; } + inline bool alignCanvas( int axisPos ) const { return d_alignCanvas[ axisPos ]; } + + inline void setCanvasMargin( int axisPos, int margin ) { d_canvasMargin[ axisPos ] = margin; } + inline int canvasMargin( int axisPos ) const { return d_canvasMargin[ axisPos ]; } + + inline void setLegendPos( QwtPlot::LegendPosition pos ) { d_legendPos = pos; } + inline QwtPlot::LegendPosition legendPos() const { return d_legendPos; } + + inline void setLegendRatio( double ratio ) { d_legendRatio = ratio; } + inline double legendRatio() const { return d_legendRatio; } + +private: + int heightForWidth( QwtPlotLayoutData::Label, const QwtPlotLayoutData &, + QwtPlotLayout::Options, double width, int axesWidth ) const; + + QwtPlot::LegendPosition d_legendPos; + double d_legendRatio; + + int d_canvasMargin[QwtAxis::PosCount]; + bool d_alignCanvas[QwtAxis::PosCount]; + + int d_spacing; +}; + +QRectF QwtPlotLayoutEngine::layoutLegend( QwtPlotLayout::Options options, + const QwtPlotLayoutData::LegendData &legendData, + const QRectF &rect, const QSize &legendHint ) const +{ + int dim; + if ( d_legendPos == QwtPlot::LeftLegend + || d_legendPos == QwtPlot::RightLegend ) + { + // We don't allow vertical legends to take more than + // half of the available space. + + dim = qMin( legendHint.width(), int( rect.width() * d_legendRatio ) ); + + if ( !( options & QwtPlotLayout::IgnoreScrollbars ) ) + { + if ( legendHint.height() > rect.height() ) + { + // The legend will need additional + // space for the vertical scrollbar. + + dim += legendData.hScrollExtent; + } + } + } + else + { + dim = qMin( legendHint.height(), int( rect.height() * d_legendRatio ) ); + dim = qMax( dim, legendData.vScrollExtent ); + } + + QRectF legendRect = rect; + switch ( d_legendPos ) + { + case QwtPlot::LeftLegend: + { + legendRect.setWidth( dim ); + break; + } + case QwtPlot::RightLegend: + { + legendRect.setX( rect.right() - dim ); + legendRect.setWidth( dim ); + break; + } + case QwtPlot::TopLegend: + { + legendRect.setHeight( dim ); + break; + } + case QwtPlot::BottomLegend: + { + legendRect.setY( rect.bottom() - dim ); + legendRect.setHeight( dim ); + break; + } + } + + return legendRect; +} + +QRectF QwtPlotLayoutEngine::alignLegend( const QSize &legendHint, + const QRectF &canvasRect, const QRectF &legendRect ) const +{ + QRectF alignedRect = legendRect; + + if ( d_legendPos == QwtPlot::BottomLegend + || d_legendPos == QwtPlot::TopLegend ) + { + if ( legendHint.width() < canvasRect.width() ) + { + alignedRect.setX( canvasRect.x() ); + alignedRect.setWidth( canvasRect.width() ); + } + } + else + { + if ( legendHint.height() < canvasRect.height() ) + { + alignedRect.setY( canvasRect.y() ); + alignedRect.setHeight( canvasRect.height() ); + } + } + + return alignedRect; +} + +int QwtPlotLayoutEngine::heightForWidth( + QwtPlotLayoutData::Label labelType, const QwtPlotLayoutData &layoutData, + QwtPlotLayout::Options options, + double width, int axesWidth ) const +{ + const QwtPlotLayoutData::LabelData &labelData = layoutData.labelData[ labelType ]; + + if ( labelData.text.isEmpty() ) + return 0; + + double w = width; + + if ( !layoutData.hasSymmetricYAxes() ) + { + // center to the canvas + w -= axesWidth; + } + + int d = qCeil( labelData.text.heightForWidth( w ) ); + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + d += 2 * labelData.frameWidth; + + return d; +} + +QwtPlotLayoutEngine::Dimensions +QwtPlotLayoutEngine::layoutDimensions( QwtPlotLayout::Options options, + const QwtPlotLayoutData &layoutData, const QRectF &rect ) const +{ + Dimensions dimensions( layoutData ); + + int backboneOffset[QwtAxis::PosCount]; + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + backboneOffset[ axisPos ] = 0; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + backboneOffset[ axisPos ] += layoutData.canvasData.contentsMargins[ axisPos ]; + + if ( !d_alignCanvas[ axisPos ] ) + backboneOffset[ axisPos ] += d_canvasMargin[ axisPos ]; + } + + bool done = false; + while ( !done ) + { + done = true; + + // the size for the 4 axis depend on each other. Expanding + // the height of a horizontal axis will shrink the height + // for the vertical axis, shrinking the height of a vertical + // axis will result in a line break what will expand the + // width and results in shrinking the width of a horizontal + // axis what might result in a line break of a horizontal + // axis ... . So we loop as long until no size changes. + + if ( !( options & QwtPlotLayout::IgnoreTitle ) ) + { + const int d = heightForWidth( QwtPlotLayoutData::Title, layoutData, options, + rect.width(), dimensions.dimYAxes() ); + + if ( d > dimensions.dimTitle ) + { + dimensions.dimTitle = d; + done = false; + } + } + + if ( !( options & QwtPlotLayout::IgnoreFooter ) ) + { + const int d = heightForWidth( QwtPlotLayoutData::Footer, layoutData, options, + rect.width(), dimensions.dimYAxes() ); + + if ( d > dimensions.dimFooter ) + { + dimensions.dimFooter = d; + done = false; + } + } + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < layoutData.numAxes( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + const struct QwtPlotLayoutData::ScaleData &scaleData = layoutData.axisData( axisId ); + + if ( scaleData.isVisible ) + { + double length; + if ( QwtAxis::isXAxis( axisPos ) ) + { + length = rect.width() - dimensions.dimYAxes(); + length -= scaleData.start + scaleData.end; + + if ( dimensions.dimAxes( QwtAxis::yRight ) > 0 ) + length -= 1; + + length += qMin( dimensions.dimAxes( QwtAxis::yLeft ), + scaleData.start - backboneOffset[QwtAxis::yLeft] ); + + length += qMin( dimensions.dimAxes( QwtAxis::yRight ), + scaleData.end - backboneOffset[QwtAxis::yRight] ); + } + else // y axis + { + length = rect.height() - dimensions.dimXAxes(); + length -= scaleData.start + scaleData.end; + length -= 1; + + if ( dimensions.dimAxes( QwtAxis::xBottom ) <= 0 ) + length -= 1; + + if ( dimensions.dimAxes( QwtAxis::xTop ) <= 0 ) + length -= 1; + + /* + The tick labels of the y axes are always left/right from the + backbone/ticks of the x axes - but we have to take care, + that te labels don't overlap. + */ + if ( dimensions.dimAxes( QwtAxis::xBottom ) > 0 ) + { + length += qMin( + layoutData.tickOffset[QwtAxis::xBottom], + double( scaleData.start - backboneOffset[QwtAxis::xBottom] ) ); + } + if ( dimensions.dimAxes( QwtAxis::xTop ) > 0 ) + { + length += qMin( + layoutData.tickOffset[QwtAxis::xTop], + double( scaleData.end - backboneOffset[QwtAxis::xTop] ) ); + } + + if ( dimensions.dimTitle > 0 ) + length -= dimensions.dimTitle + d_spacing; + } + + int d = scaleData.dimWithoutTitle; + if ( !scaleData.scaleWidget->title().isEmpty() ) + { + d += scaleData.scaleWidget->titleHeightForWidth( qFloor( length ) ); + } + + + if ( d > dimensions.dimAxis( axisId ) ) + { + dimensions.setDimAxis( axisId, d ); + done = false; + } + } + } + } + } + + return dimensions; +} + +void QwtPlotLayoutEngine::alignScales( QwtPlotLayout::Options options, + const QwtPlotLayoutData &layoutData, QRectF &canvasRect, + QVector scaleRect[QwtAxis::PosCount] ) const +{ + int backboneOffset[QwtAxis::PosCount]; + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + backboneOffset[ axisPos ] = 0; + + if ( !d_alignCanvas[ axisPos ] ) + { + backboneOffset[ axisPos ] += d_canvasMargin[ axisPos ]; + } + + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + { + backboneOffset[ axisPos ] += + layoutData.canvasData.contentsMargins[ axisPos ]; + } + } + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < layoutData.numAxes( axisPos ); i++ ) + { + QRectF &axisRect = scaleRect[ axisPos ][ i ]; + if ( !axisRect.isValid() ) + continue; + + const QwtAxisId axisId( axisPos, i ); + + const int startDist = layoutData.axisData( axisId ).start; + const int endDist = layoutData.axisData( axisId ).end; + + if ( QwtAxis::isXAxis( axisPos ) ) + { + const QRectF &leftScaleRect = scaleRect[QwtAxis::yLeft][ QWT_DUMMY_ID ]; + const int leftOffset = backboneOffset[QwtAxis::yLeft] - startDist; + + if ( leftScaleRect.isValid() ) + { + const double dx = leftOffset + leftScaleRect.width(); + if ( d_alignCanvas[QwtAxis::yLeft] && dx < 0.0 ) + { + /* + The axis needs more space than the width + of the left scale. + */ + const double cLeft = canvasRect.left(); // qreal -> double + canvasRect.setLeft( qMax( cLeft, axisRect.left() - dx ) ); + } + else + { + const double minLeft = leftScaleRect.left(); + const double left = axisRect.left() + leftOffset; + axisRect.setLeft( qMax( left, minLeft ) ); + } + } + else + { + if ( d_alignCanvas[QwtAxis::yLeft] && leftOffset < 0 ) + { + canvasRect.setLeft( qMax( canvasRect.left(), + axisRect.left() - leftOffset ) ); + } + else + { + if ( leftOffset > 0 ) + axisRect.setLeft( axisRect.left() + leftOffset ); + } + } + + const QRectF &rightScaleRect = scaleRect[QwtAxis::yRight][ QWT_DUMMY_ID ]; + const int rightOffset = + backboneOffset[QwtAxis::yRight] - endDist + 1; + + if ( rightScaleRect.isValid() ) + { + const double dx = rightOffset + rightScaleRect.width(); + if ( d_alignCanvas[QwtAxis::yRight] && dx < 0 ) + { + /* + The axis needs more space than the width + of the right scale. + */ + const double cRight = canvasRect.right(); // qreal -> double + canvasRect.setRight( qMin( cRight, axisRect.right() + dx ) ); + } + + const double maxRight = rightScaleRect.right(); + const double right = axisRect.right() - rightOffset; + axisRect.setRight( qMin( right, maxRight ) ); + } + else + { + if ( d_alignCanvas[QwtAxis::yRight] && rightOffset < 0 ) + { + canvasRect.setRight( qMin( canvasRect.right(), + axisRect.right() + rightOffset ) ); + } + else + { + if ( rightOffset > 0 ) + axisRect.setRight( axisRect.right() - rightOffset ); + } + } + } + else // y axes + { + const QRectF &bottomScaleRect = scaleRect[QwtAxis::xBottom][ QWT_DUMMY_ID ]; + const int bottomOffset = + backboneOffset[QwtAxis::xBottom] - endDist + 1; + + if ( bottomScaleRect.isValid() ) + { + const double dy = bottomOffset + bottomScaleRect.height(); + if ( d_alignCanvas[QwtAxis::xBottom] && dy < 0 ) + { + /* + The axis needs more space than the height + of the bottom scale. + */ + const double cBottom = canvasRect.bottom(); // qreal -> double + canvasRect.setBottom( qMin( cBottom, axisRect.bottom() + dy ) ); + } + else + { + const double maxBottom = bottomScaleRect.top() + + layoutData.tickOffset[QwtAxis::xBottom]; + const double bottom = axisRect.bottom() - bottomOffset; + axisRect.setBottom( qMin( bottom, maxBottom ) ); + } + } + else + { + if ( d_alignCanvas[QwtAxis::xBottom] && bottomOffset < 0 ) + { + canvasRect.setBottom( qMin( canvasRect.bottom(), + axisRect.bottom() + bottomOffset ) ); + } + else + { + if ( bottomOffset > 0 ) + axisRect.setBottom( axisRect.bottom() - bottomOffset ); + } + } + + const QRectF &topScaleRect = scaleRect[QwtAxis::xTop][ QWT_DUMMY_ID ]; + const int topOffset = backboneOffset[QwtAxis::xTop] - startDist; + + if ( topScaleRect.isValid() ) + { + const double dy = topOffset + topScaleRect.height(); + if ( d_alignCanvas[QwtAxis::xTop] && dy < 0 ) + { + /* + The axis needs more space than the height + of the top scale. + */ + const double cTop = canvasRect.top(); // qreal -> double + canvasRect.setTop( qMax( cTop, axisRect.top() - dy ) ); + } + else + { + const double minTop = topScaleRect.bottom() - + layoutData.tickOffset[QwtAxis::xTop]; + const double top = axisRect.top() + topOffset; + axisRect.setTop( qMax( top, minTop ) ); + } + } + else + { + if ( d_alignCanvas[QwtAxis::xTop] && topOffset < 0 ) + { + canvasRect.setTop( qMax( canvasRect.top(), + axisRect.top() - topOffset ) ); + } + else + { + if ( topOffset > 0 ) + axisRect.setTop( axisRect.top() + topOffset ); + } + } + } + } + } + + /* + The canvas has been aligned to the scale with largest + border distances. Now we have to realign the other scale. + */ + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < layoutData.numAxes( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + QRectF &sRect = scaleRect[ axisId.pos ][ axisId.id ]; + const QwtPlotLayoutData::ScaleData &axisData = layoutData.axisData( axisId ); + + if ( !sRect.isValid() ) + continue; + + if ( QwtAxis::isXAxis( axisId.pos ) ) + { + if ( d_alignCanvas[QwtAxis::yLeft] ) + { + double y = canvasRect.left() - axisData.start; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + y += layoutData.canvasData.contentsMargins[ QwtAxis::yLeft ]; + + sRect.setLeft( y ); + } + if ( d_alignCanvas[QwtAxis::yRight] ) + { + double y = canvasRect.right() - 1 + axisData.end; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + y -= layoutData.canvasData.contentsMargins[ QwtAxis::yRight ]; + + sRect.setRight( y ); + } + + if ( d_alignCanvas[ axisId.pos ] ) + { + if ( axisId.pos == QwtAxis::xTop ) + sRect.setBottom( canvasRect.top() ); + else + sRect.setTop( canvasRect.bottom() ); + } + } + else + { + if ( d_alignCanvas[QwtAxis::xTop] ) + { + double x = canvasRect.top() - axisData.start; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + x += layoutData.canvasData.contentsMargins[ QwtAxis::xTop ]; + + sRect.setTop( x ); + } + if ( d_alignCanvas[QwtAxis::xBottom] ) + { + double x = canvasRect.bottom() - 1 + axisData.end; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + x -= layoutData.canvasData.contentsMargins[ QwtAxis::xBottom ]; + + sRect.setBottom( x ); + } + + if ( d_alignCanvas[ axisId.pos ] ) + { + if ( axisId.pos == QwtAxis::yLeft ) + sRect.setRight( canvasRect.left() ); + else + sRect.setLeft( canvasRect.right() ); + } + } + } + } +} + +class QwtPlotLayout::PrivateData +{ +public: + QRectF titleRect; + QRectF footerRect; + QRectF legendRect; + QVector scaleRects[QwtAxis::PosCount]; + QRectF canvasRect; + + QwtPlotLayoutEngine layoutEngine; +}; + +/*! + \brief Constructor + */ + +QwtPlotLayout::QwtPlotLayout() +{ + d_data = new PrivateData; + + setLegendPosition( QwtPlot::BottomLegend ); + setCanvasMargin( 4 ); + setAlignCanvasToScales( false ); + + invalidate(); +} + +//! Destructor +QwtPlotLayout::~QwtPlotLayout() +{ + delete d_data; +} + +/*! + Change a margin of the canvas. The margin is the space + above/below the scale ticks. A negative margin will + be set to -1, excluding the borders of the scales. + + \param margin New margin + \param axisPos One of QwtAxis::Position. Specifies where the position of the margin. + -1 means margin at all borders. + \sa canvasMargin() + + \warning The margin will have no effect when alignCanvasToScale() is true +*/ + +void QwtPlotLayout::setCanvasMargin( int margin, int axisPos ) +{ + if ( margin < -1 ) + margin = -1; + + if ( axisPos == -1 ) + { + for ( axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + d_data->layoutEngine.setCanvasMargin( axisPos, margin ); + } + else if ( QwtAxis::isValid( axisPos ) ) + { + d_data->layoutEngine.setCanvasMargin( axisPos, margin ); + } +} + +/*! + \param axisPos Axis position + \return Margin around the scale tick borders + \sa setCanvasMargin() +*/ +int QwtPlotLayout::canvasMargin( int axisPos ) const +{ + if ( !QwtAxis::isValid( axisPos ) ) + return 0; + + return d_data->layoutEngine.canvasMargin( axisPos ); +} + +/*! + \brief Set the align-canvas-to-axis-scales flag for all axes + + \param on True/False + \sa setAlignCanvasToScale(), alignCanvasToScale() +*/ +void QwtPlotLayout::setAlignCanvasToScales( bool on ) +{ + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + d_data->layoutEngine.setAlignCanvas( axisPos, on ); +} + +/*! + Change the align-canvas-to-axis-scales setting. The canvas may: + + - extend beyond the axis scale ends to maximize its size, + - align with the axis scale ends to control its size. + + The axisId parameter is somehow confusing as it identifies a border + of the plot and not the axes, that are aligned. F.e when QwtAxis::yLeft + is set, the left end of the the x-axes ( QwtAxis::xTop, QwtAxis::xBottom ) + is aligned. + + \param axisId Axis index + \param on New align-canvas-to-axis-scales setting + + \sa setCanvasMargin(), alignCanvasToScale(), setAlignCanvasToScales() + \warning In case of on == true canvasMargin() will have no effect +*/ +void QwtPlotLayout::setAlignCanvasToScale( int axisPos, bool on ) +{ + if ( QwtAxis::isValid( axisPos ) ) + d_data->layoutEngine.setAlignCanvas( axisPos, on ); +} + +/*! + Return the align-canvas-to-axis-scales setting. The canvas may: + - extend beyond the axis scale ends to maximize its size + - align with the axis scale ends to control its size. + + \param axisId Axis index + \return align-canvas-to-axis-scales setting + \sa setAlignCanvasToScale(), setAlignCanvasToScale(), setCanvasMargin() +*/ +bool QwtPlotLayout::alignCanvasToScale( int axisPos ) const +{ + if ( !QwtAxis::isValid( axisPos ) ) + return false; + + return d_data->layoutEngine.alignCanvas( axisPos ); +} + +/*! + Change the spacing of the plot. The spacing is the distance + between the plot components. + + \param spacing New spacing + \sa setCanvasMargin(), spacing() +*/ +void QwtPlotLayout::setSpacing( int spacing ) +{ + d_data->layoutEngine.setSpacing( qMax( 0, spacing ) ); +} + +/*! + \return Spacing + \sa margin(), setSpacing() +*/ +int QwtPlotLayout::spacing() const +{ + return d_data->layoutEngine.spacing(); +} + +/*! + \brief Specify the position of the legend + \param pos The legend's position. + \param ratio Ratio between legend and the bounding rectangle + of title, footer, canvas and axes. The legend will be shrunk + if it would need more space than the given ratio. + The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 + it will be reset to the default ratio. + The default vertical/horizontal ratio is 0.33/0.5. + + \sa QwtPlot::setLegendPosition() +*/ + +void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos, double ratio ) +{ + if ( ratio > 1.0 ) + ratio = 1.0; + + switch ( pos ) + { + case QwtPlot::TopLegend: + case QwtPlot::BottomLegend: + { + if ( ratio <= 0.0 ) + ratio = 0.33; + + d_data->layoutEngine.setLegendRatio( ratio ); + d_data->layoutEngine.setLegendPos( pos ); + break; + } + case QwtPlot::LeftLegend: + case QwtPlot::RightLegend: + { + if ( ratio <= 0.0 ) + ratio = 0.5; + + d_data->layoutEngine.setLegendRatio( ratio ); + d_data->layoutEngine.setLegendPos( pos ); + break; + } + default: + break; + } +} + +/*! + \brief Specify the position of the legend + \param pos The legend's position. Valid values are + \c QwtPlot::LeftLegend, \c QwtPlot::RightLegend, + \c QwtPlot::TopLegend, \c QwtPlot::BottomLegend. + + \sa QwtPlot::setLegendPosition() +*/ +void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos ) +{ + setLegendPosition( pos, 0.0 ); +} + +/*! + \return Position of the legend + \sa setLegendPosition(), QwtPlot::setLegendPosition(), + QwtPlot::legendPosition() +*/ +QwtPlot::LegendPosition QwtPlotLayout::legendPosition() const +{ + return d_data->layoutEngine.legendPos(); +} + +/*! + Specify the relative size of the legend in the plot + \param ratio Ratio between legend and the bounding rectangle + of title, footer, canvas and axes. The legend will be shrunk + if it would need more space than the given ratio. + The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 + it will be reset to the default ratio. + The default vertical/horizontal ratio is 0.33/0.5. +*/ +void QwtPlotLayout::setLegendRatio( double ratio ) +{ + setLegendPosition( legendPosition(), ratio ); +} + +/*! + \return The relative size of the legend in the plot. + \sa setLegendPosition() +*/ +double QwtPlotLayout::legendRatio() const +{ + return d_data->layoutEngine.legendRatio(); +} + +/*! + \brief Set the geometry for the title + + This method is intended to be used from derived layouts + overloading activate() + + \sa titleRect(), activate() + */ +void QwtPlotLayout::setTitleRect( const QRectF &rect ) +{ + d_data->titleRect = rect; +} + +/*! + \return Geometry for the title + \sa activate(), invalidate() +*/ +QRectF QwtPlotLayout::titleRect() const +{ + return d_data->titleRect; +} + +/*! + \brief Set the geometry for the footer + + This method is intended to be used from derived layouts + overloading activate() + + \sa footerRect(), activate() + */ +void QwtPlotLayout::setFooterRect( const QRectF &rect ) +{ + d_data->footerRect = rect; +} + +/*! + \return Geometry for the footer + \sa activate(), invalidate() +*/ +QRectF QwtPlotLayout::footerRect() const +{ + return d_data->footerRect; +} + +/*! + \brief Set the geometry for the legend + + This method is intended to be used from derived layouts + overloading activate() + + \param rect Rectangle for the legend + + \sa legendRect(), activate() + */ +void QwtPlotLayout::setLegendRect( const QRectF &rect ) +{ + d_data->legendRect = rect; +} + +/*! + \return Geometry for the legend + \sa activate(), invalidate() +*/ +QRectF QwtPlotLayout::legendRect() const +{ + return d_data->legendRect; +} + +/*! + \brief Set the geometry for an axis + + This method is intended to be used from derived layouts + overloading activate() + + \param axis Axis index + \param rect Rectangle for the scale + + \sa scaleRect(), activate() + */ +void QwtPlotLayout::setScaleRect( QwtAxisId axisId, const QRectF &rect ) +{ + if ( QwtAxis::isValid( axisId.pos ) ) + { + QVector &scaleRects = d_data->scaleRects[ axisId.pos ]; + + if ( axisId.id >= 0 && axisId.id < scaleRects.size() ) + scaleRects[axisId.id] = rect; + } +} + +/*! + \param axis Axis index + \return Geometry for the scale + \sa activate(), invalidate() +*/ +QRectF QwtPlotLayout::scaleRect( QwtAxisId axisId ) const +{ + if ( QwtAxis::isValid( axisId.pos ) ) + { + QVector &scaleRects = d_data->scaleRects[ axisId.pos ]; + if ( axisId.id >= 0 && axisId.id < scaleRects.size() ) + return scaleRects[axisId.id]; + } + + return QRectF(); +} + +/*! + \brief Set the geometry for the canvas + + This method is intended to be used from derived layouts + overloading activate() + + \sa canvasRect(), activate() + */ +void QwtPlotLayout::setCanvasRect( const QRectF &rect ) +{ + d_data->canvasRect = rect; +} + +/*! + \return Geometry for the canvas + \sa activate(), invalidate() +*/ +QRectF QwtPlotLayout::canvasRect() const +{ + return d_data->canvasRect; +} + +/*! + Invalidate the geometry of all components. + \sa activate() +*/ +void QwtPlotLayout::invalidate() +{ + d_data->titleRect = d_data->footerRect = + d_data->legendRect = d_data->canvasRect = QRectF(); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + QVector &scaleRects = d_data->scaleRects[axisPos]; + + scaleRects.resize( 1 ); + scaleRects[0] = QRectF(); + } +} + +/*! + \return Minimum size hint + \param plot Plot widget + + \sa QwtPlot::minimumSizeHint() +*/ +QSize QwtPlotLayout::minimumSizeHint( const QwtPlot *plot ) const +{ + QwtPlotLayoutHintData hintData( plot ); + + /* + When having x and y axes, we try to use the empty + corners for the tick labels that are exceeding the + scale backbones. + */ + int xAxesWidth = 0; + int yAxesHeight = 0; + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < plot->axesCount( axisPos ); i++ ) + { + const int sz = hintData.alignedSize( QwtAxisId( axisPos, i ) ); + + if ( QwtAxis::isXAxis( axisPos ) ) + xAxesWidth = qMax( xAxesWidth, sz ); + else + yAxesHeight = qMax( yAxesHeight, sz ); + } + } + + const QWidget *canvas = plot->canvas(); + + int left, top, right, bottom; + canvas->getContentsMargins( &left, &top, &right, &bottom ); + + const QSize minCanvasSize = canvas->minimumSize(); + + int w = hintData.yAxesWidth(); + int cw = xAxesWidth + left + 1 + right + 1; + w += qMax( cw, minCanvasSize.width() ); + + int h = hintData.xAxesHeight(); + int ch = yAxesHeight + top + 1 + bottom + 1; + h += qMax( ch, minCanvasSize.height() ); + + const QwtTextLabel *labels[2]; + labels[0] = plot->titleLabel(); + labels[1] = plot->footerLabel(); + + for ( int i = 0; i < 2; i++ ) + { + const QwtTextLabel *label = labels[i]; + if ( label && !label->text().isEmpty() ) + { + // we center on the plot canvas. + const bool centerOnCanvas = hintData.hasSymmetricYAxes(); + + int labelW = w; + if ( centerOnCanvas ) + { + labelW -= hintData.yAxesWidth(); + } + + int labelH = label->heightForWidth( labelW ); + if ( labelH > labelW ) // Compensate for a long title + { + w = labelW = labelH; + if ( centerOnCanvas ) + w += hintData.yAxesWidth(); + + labelH = label->heightForWidth( labelW ); + } + h += labelH + spacing(); + } + } + + // Compute the legend contribution + + const QwtAbstractLegend *legend = plot->legend(); + if ( legend && !legend->isEmpty() ) + { + if ( d_data->layoutEngine.legendPos() == QwtPlot::LeftLegend + || d_data->layoutEngine.legendPos() == QwtPlot::RightLegend ) + { + int legendW = legend->sizeHint().width(); + int legendH = legend->heightForWidth( legendW ); + + if ( legend->frameWidth() > 0 ) + w += spacing(); + + if ( legendH > h ) + legendW += legend->scrollExtent( Qt::Horizontal ); + + if ( d_data->layoutEngine.legendRatio() < 1.0 ) + legendW = qMin( legendW, int( w / ( 1.0 - d_data->layoutEngine.legendRatio() ) ) ); + + w += legendW + spacing(); + } + else + { + int legendW = qMin( legend->sizeHint().width(), w ); + int legendH = legend->heightForWidth( legendW ); + + if ( legend->frameWidth() > 0 ) + h += spacing(); + + if ( d_data->layoutEngine.legendRatio() < 1.0 ) + legendH = qMin( legendH, int( h / ( 1.0 - d_data->layoutEngine.legendRatio() ) ) ); + + h += legendH + spacing(); + } + } + + return QSize( w, h ); +} + +void QwtPlotLayout::update( const QwtPlot *plot, + const QRectF &plotRect, Options options ) +{ + invalidate(); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + d_data->scaleRects[ axisPos ].resize( plot->axesCount( axisPos ) ); + + activate( plot, plotRect, options ); +} + +/*! + \brief Recalculate the geometry of all components. + + \param plot Plot to be layout + \param plotRect Rectangle where to place the components + \param options Layout options + + \sa invalidate(), titleRect(), footerRect() + legendRect(), scaleRect(), canvasRect() +*/ +void QwtPlotLayout::activate( const QwtPlot *plot, + const QRectF &plotRect, Options options ) +{ + QRectF rect( plotRect ); // undistributed rest of the plot rect + + // We extract all layout relevant parameters from the widgets, + // and save them to d_data->layoutData. + + QwtPlotLayoutData layoutData( plot ); + + QSize legendHint; + + if ( !( options & IgnoreLegend ) + && plot->legend() && !plot->legend()->isEmpty() ) + { + legendHint = layoutData.legendData.legendHint( plot->legend(), rect ); + + d_data->legendRect = d_data->layoutEngine.layoutLegend( + options, layoutData.legendData, rect, legendHint ); + + // subtract d_data->legendRect from rect + + const QRegion region( rect.toRect() ); + rect = region.subtracted( d_data->legendRect.toRect() ).boundingRect(); + + switch ( d_data->layoutEngine.legendPos() ) + { + case QwtPlot::LeftLegend: + { + rect.setLeft( rect.left() + spacing() ); + break; + } + case QwtPlot::RightLegend: + { + rect.setRight( rect.right() - spacing() ); + break; + } + case QwtPlot::TopLegend: + { + rect.setTop( rect.top() + spacing() ); + break; + } + case QwtPlot::BottomLegend: + { + rect.setBottom( rect.bottom() - spacing() ); + break; + } + } + } + + /* + +---+-----------+---+ + | Title | + +---+-----------+---+ + | | Axis | | + +---+-----------+---+ + | A | | A | + | x | Canvas | x | + | i | | i | + | s | | s | + +---+-----------+---+ + | | Axis | | + +---+-----------+---+ + | Footer | + +---+-----------+---+ + */ + + // title, footer and axes include text labels. The height of each + // label depends on its line breaks, that depend on the width + // for the label. A line break in a horizontal text will reduce + // the available width for vertical texts and vice versa. + // layoutDimensions finds the height/width for title, footer and axes + // including all line breaks. + + const QwtPlotLayoutEngine::Dimensions dimensions = + d_data->layoutEngine.layoutDimensions( options, layoutData, rect ); + + if ( dimensions.dimTitle > 0 ) + { + QRectF &labelRect = d_data->titleRect; + + labelRect.setRect( rect.left(), rect.top(), rect.width(), dimensions.dimTitle ); + + rect.setTop( labelRect.bottom() + spacing() ); + + if ( !layoutData.hasSymmetricYAxes() ) + { + // if only one of the y axes is missing we align + // the title centered to the canvas + + labelRect = dimensions.centered( rect, labelRect ); + } + } + + if ( dimensions.dimFooter > 0 ) + { + QRectF &labelRect = d_data->footerRect; + + labelRect.setRect( rect.left(), rect.bottom() - dimensions.dimFooter, + rect.width(), dimensions.dimFooter ); + + rect.setBottom( labelRect.top() - spacing() ); + + if ( !layoutData.hasSymmetricYAxes() ) + { + // if only one of the y axes is missing we align + // the footer centered to the canvas + + labelRect = dimensions.centered( rect, labelRect ); + } + } + + d_data->canvasRect = dimensions.innerRect( rect ); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + // set the rects for the axes + + int pos = 0; + for ( int i = 0; i < d_data->scaleRects[ axisPos ].size(); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + if ( dimensions.dimAxis( axisId ) ) + { + const int dim = dimensions.dimAxis( axisId ); + + const QRectF &canvasRect = d_data->canvasRect; + + QRectF &scaleRect = d_data->scaleRects[ axisId.pos ][ axisId.id ]; + scaleRect = canvasRect; + + switch ( axisPos ) + { + case QwtAxis::yLeft: + { + scaleRect.setX( canvasRect.left() - pos - dim ); + scaleRect.setWidth( dim ); + break; + } + case QwtAxis::yRight: + { + scaleRect.setX( canvasRect.right() + pos ); + scaleRect.setWidth( dim ); + break; + } + case QwtAxis::xBottom: + { + scaleRect.setY( canvasRect.bottom() + pos ); + scaleRect.setHeight( dim ); + break; + } + case QwtAxis::xTop: + { + scaleRect.setY( canvasRect.top() - pos - dim ); + scaleRect.setHeight( dim ); + break; + } + } + scaleRect = scaleRect.normalized(); + pos += dim; + } + } + } + + // +---+-----------+---+ + // | <- Axis -> | + // +-^-+-----------+-^-+ + // | | | | | | + // | | | | + // | A | | A | + // | x | Canvas | x | + // | i | | i | + // | s | | s | + // | | | | + // | | | | | | + // +-V-+-----------+-V-+ + // | <- Axis -> | + // +---+-----------+---+ + + // The ticks of the axes - not the labels above - should + // be aligned to the canvas. So we try to use the empty + // corners to extend the axes, so that the label texts + // left/right of the min/max ticks are moved into them. + + d_data->layoutEngine.alignScales( options, layoutData, + d_data->canvasRect, d_data->scaleRects ); + + if ( !d_data->legendRect.isEmpty() ) + { + // We prefer to align the legend to the canvas - not to + // the complete plot - if possible. + + d_data->legendRect = d_data->layoutEngine.alignLegend( + legendHint, d_data->canvasRect, d_data->legendRect ); + } +} diff --git a/qwt/src/qwt_plot_layout.h b/qwt/src/qwt_plot_layout.h new file mode 100644 index 000000000..a25acb9cb --- /dev/null +++ b/qwt/src/qwt_plot_layout.h @@ -0,0 +1,113 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_LAYOUT_H +#define QWT_PLOT_LAYOUT_H + +#include "qwt_global.h" +#include "qwt_plot.h" +#include "qwt_axis_id.h" + +/*! + \brief Layout engine for QwtPlot. + + It is used by the QwtPlot widget to organize its internal widgets + or by QwtPlot::print() to render its content to a QPaintDevice like + a QPrinter, QPixmap/QImage or QSvgRenderer. + + \sa QwtPlot::setPlotLayout() +*/ + +class QWT_EXPORT QwtPlotLayout +{ +public: + /*! + Options to configure the plot layout engine + \sa update(), QwtPlotRenderer + */ + enum Option + { + //! Unused + AlignScales = 0x01, + + /*! + Ignore the dimension of the scrollbars. There are no + scrollbars, when the plot is not rendered to widgets. + */ + IgnoreScrollbars = 0x02, + + //! Ignore all frames. + IgnoreFrames = 0x04, + + //! Ignore the legend. + IgnoreLegend = 0x08, + + //! Ignore the title. + IgnoreTitle = 0x10, + + //! Ignore the footer. + IgnoreFooter = 0x20 + }; + + //! Layout options + typedef QFlags