diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index 91b66333..be61d0d2 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -2,40 +2,562 @@ Serial Studio makes use of several libraries in order to provide functionality. Below, you can find the libraries used and their respective licenses: -# CuteLogger +# Acknowledgements -Copyright (c) 2010 Boris Moiseev (cyberbobs at gmail dot com) +Serial Studio makes use of several libraries in order to provide functionality. Below, you can find the libraries used and their respective licenses: -This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation and appearing in the file LICENSE.LGPL included in the packaging of this file. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +## Qwt -# QSimpleUpdater +The Qwt library and included programs are provided under the terms +of the GNU LESSER GENERAL PUBLIC LICENSE (LGPL) with the following +exceptions: -Copyright (C) 2020 Alex Spataru ([https://alex-spataru.com/](https://alex-spataru.com)) + 1. Widgets that are subclassed from Qwt widgets do not + constitute a derivative work. -Everyone is permitted to copy and distribute verbatim or modified -copies of this license document. + 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. -> DON'T BE A DICK PUBLIC LICENSE -> TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + 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. -1. Do whatever you like with the original work, just don't be a dick. + 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. - Being a dick includes - but is not limited to - the following instances: - 1a. Outright copyright infringement - Don't just copy this and change the name. - 1b. Selling the unmodified original with no work done what-so-ever, that's REALLY being a dick. - 1c. Modifying the original work to contain hidden harmful content. That would make you a PROPER dick. + However, programs must still identify their use of Qwt. + The following example statement can be included in user + documentation to satisfy this requirement: -2. If you become rich through modifications, related works/services, or supporting the original work, -share the love. Only a dick would make loads off this work and not buy the original work's -creator(s) a pint. + [program/widget] is based in part on the work of + the Qwt project (http://qwt.sf.net). -3. Code is provided with no warranty. Using somebody else's code and bitching when it goes wrong makes -you a DONKEY dick. Fix the problem yourself. A non-dick would submit the fix back. +---------------------------------------------------------------------- -# qtcsv + + 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! + +## QSimpleUpdater + +Copyright (C) 2014-2021 Alex Spataru ([https://alex-spataru.com/](https://alex-spataru.com)) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## qtcsv Copyright (c) 2015 Antony Cherepanov (antony.cherepanov@gmail.com) @@ -57,12 +579,34 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# qmqtt +## QFlightInstruments + +Copyright (C) 2013 Marek M. Cel + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +## qmqtt This project is dual licensed under the Eclipse Public License 1.0 and the Eclipse Distribution License 1.0 as described in the epl-v10 and edl-v10 files. -## Eclipse Distribution License - v 1.0 +### Eclipse Distribution License - v 1.0 Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. @@ -93,7 +637,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -## Eclipse Public License - v 1.0 +### Eclipse Public License - v 1.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM @@ -312,4 +856,182 @@ including all Contributors. intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial - in any resulting litigation. \ No newline at end of file + in any resulting litigation. + + +## KDMacTouchBar + + KDMacTouchBar is Copyright (C) 2019-2021 Klaralvdalens Datakonsult AB. + + You may use, distribute and copy KDMacTouchBar under the terms of + GNU Lesser General Public License version 3, which is displayed below. + + You may even contact us at info@kdab.com for different licensing options. + +------------------------------------------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + 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 that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU 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 as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/assets/messages/Acknowledgements.txt b/assets/messages/Acknowledgements.txt index 25946763..4d7be135 100644 --- a/assets/messages/Acknowledgements.txt +++ b/assets/messages/Acknowledgements.txt @@ -2,38 +2,559 @@ Serial Studio makes use of several libraries in order to provide functionality. Below, you can find the libraries used and their respective licenses: -## CuteLogger +## Qwt -Copyright (c) 2010 Boris Moiseev (cyberbobs at gmail dot com) + Qwt License + Version 1.0, January 1, 2003 -This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation and appearing in the file LICENSE.LGPL included in the packaging of this file. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +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! ## QSimpleUpdater -Copyright (C) 2020 Alex Spataru ([https://alex-spataru.com/](https://alex-spataru.com)) +Copyright (C) 2014-2021 Alex Spataru ([https://alex-spataru.com/](https://alex-spataru.com)) -Everyone is permitted to copy and distribute verbatim or modified -copies of this license document. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -> DON'T BE A DICK PUBLIC LICENSE -> TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -1. Do whatever you like with the original work, just don't be a dick. - - Being a dick includes - but is not limited to - the following instances: - - 1a. Outright copyright infringement - Don't just copy this and change the name. - 1b. Selling the unmodified original with no work done what-so-ever, that's REALLY being a dick. - 1c. Modifying the original work to contain hidden harmful content. That would make you a PROPER dick. - -2. If you become rich through modifications, related works/services, or supporting the original work, -share the love. Only a dick would make loads off this work and not buy the original work's -creator(s) a pint. - -3. Code is provided with no warranty. Using somebody else's code and bitching when it goes wrong makes -you a DONKEY dick. Fix the problem yourself. A non-dick would submit the fix back. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## qtcsv diff --git a/libs/Libraries.pri b/libs/Libraries.pri index 7f4c768a..a97a93fc 100644 --- a/libs/Libraries.pri +++ b/libs/Libraries.pri @@ -30,6 +30,7 @@ DEFINES += QTCSV_STATIC_LIB # Include *.pri files #------------------------------------------------------------------------------- +include($$PWD/qwt/qwt.pri) include($$PWD/qtcsv/qtcsv.pri) include($$PWD/qmqtt/qmqtt.pri) include($$PWD/QSimpleUpdater/QSimpleUpdater.pri) diff --git a/libs/qwt/CHANGES-6.2 b/libs/qwt/CHANGES-6.2 new file mode 100644 index 00000000..0ae4a9b4 --- /dev/null +++ b/libs/qwt/CHANGES-6.2 @@ -0,0 +1,84 @@ +Qwt 6.2.0 +========= + +0) Requirement for Qt >= 4.8 + +1) Class Includes added + +Include files, that match the class names are available now. So +it is possible to write "#include " now instead of "include qwt_plot.h" + +2) BSD License for examples + +Where possible the code of the examples is available under the 3-clause BSD License + +3) MathML text renderer removed + +The code can be found at https://github.com/uwerat/qwt-mml-dev now and is intended +to become a standalone lib. Anyone who is interested to workon it, please let me know. + +4) Spline interpolation + +The broken implementation of QwtSpline has been replaced by a bunch of classes +offering all sort of functionalities around splines. + +The most popular spline approximation/interpolation algos have been implemented: + + - Basis + - Cardinal + - ParabolicBlending + - Akima + - The one used in MS Excel + - Cubic + +An implementation of the de Casteljau’s algorithm has been added + + - QwtBezier + +5) New plot items + + - QwtPlotVectorField + A new type of plot item for vector fields + + - QwtPlotGraphicItem + An item displaying a QwtGraphic image ( f.e used by QwtPlotSvgItem ) + +6) Plot Canvas + + - QwtAbstractPlotCanvas introduced + - QwtPlotOpenGLCanvas added to support QOpenGLWidget + +7) QwtPlotCurve + + - QwtPlotCurve::FilterPointsAggressive mode added - a fast weeding algo + for huge datasets with increasing x or y values + + - QwtPlotCurve::closestPoint is virtual now + - QwtPlotCurve: polygon clipping includes the painter clip + - QwtPlotCurve::setLegendAttributes added + - QwtValuePointData added for curves, where the x values are the index + - a couple of new QwtPlotCurve::setSamples alternatives + +8) QwtPlotSpectrogram + + - QwtPlotSpectrogram::setColorTableSize added + - QwtRasterData::setInterval/interval changed into a pure virtual getter + - QwtMatrixRasterData::BicubicInterpolation added + - QwtMatrixRasterData::interval: API cleanup + - QwtHueColorMap, QwtSaturationValueColorMap added + +9) QwtPlotRenderer + + - using QPdfWriter where possible + +10) + - LOG_MIN/LOG_MAX removed, use QwtTransform::LogMin/LogMax instead ( values differ ! ) + - qwt_compat.h removed + - qwtFuzzyGreaterOrEqual/qwtFuzzyLessOrEqual removed + - qwtGetMin/qwtGetMax removed + +11) + - Not aligning unknown paint engines ( f.e EMF ) + - QwtNullPaintDevice is using a different type than QPaintEngine::User now + +12) Many other changes ... diff --git a/libs/qwt/COPYING b/libs/qwt/COPYING new file mode 100644 index 00000000..9c01f7e2 --- /dev/null +++ b/libs/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/libs/qwt/INSTALL b/libs/qwt/INSTALL new file mode 100644 index 00000000..bafcbe03 --- /dev/null +++ b/libs/qwt/INSTALL @@ -0,0 +1 @@ +see doc/html/qwtinstall.html diff --git a/libs/qwt/README b/libs/qwt/README new file mode 100644 index 00000000..46ee34e4 --- /dev/null +++ b/libs/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/libs/qwt/classincludes/QwtAbstractLegend b/libs/qwt/classincludes/QwtAbstractLegend new file mode 100644 index 00000000..c74a5106 --- /dev/null +++ b/libs/qwt/classincludes/QwtAbstractLegend @@ -0,0 +1 @@ +#include "qwt_abstract_legend.h" diff --git a/libs/qwt/classincludes/QwtAbstractScale b/libs/qwt/classincludes/QwtAbstractScale new file mode 100644 index 00000000..c7fcd102 --- /dev/null +++ b/libs/qwt/classincludes/QwtAbstractScale @@ -0,0 +1 @@ +#include "qwt_abstract_scale.h" diff --git a/libs/qwt/classincludes/QwtAbstractScaleDraw b/libs/qwt/classincludes/QwtAbstractScaleDraw new file mode 100644 index 00000000..336bf483 --- /dev/null +++ b/libs/qwt/classincludes/QwtAbstractScaleDraw @@ -0,0 +1 @@ +#include "qwt_abstract_scale_draw.h" diff --git a/libs/qwt/classincludes/QwtAbstractSlider b/libs/qwt/classincludes/QwtAbstractSlider new file mode 100644 index 00000000..2637fe29 --- /dev/null +++ b/libs/qwt/classincludes/QwtAbstractSlider @@ -0,0 +1 @@ +#include "qwt_abstract_slider.h" diff --git a/libs/qwt/classincludes/QwtAlphaColorMap b/libs/qwt/classincludes/QwtAlphaColorMap new file mode 100644 index 00000000..a4115653 --- /dev/null +++ b/libs/qwt/classincludes/QwtAlphaColorMap @@ -0,0 +1 @@ +#include "qwt_color_map.h" diff --git a/libs/qwt/classincludes/QwtAnalogClock b/libs/qwt/classincludes/QwtAnalogClock new file mode 100644 index 00000000..d82dd337 --- /dev/null +++ b/libs/qwt/classincludes/QwtAnalogClock @@ -0,0 +1 @@ +#include "qwt_analog_clock.h" diff --git a/libs/qwt/classincludes/QwtArrowButton b/libs/qwt/classincludes/QwtArrowButton new file mode 100644 index 00000000..e6a810af --- /dev/null +++ b/libs/qwt/classincludes/QwtArrowButton @@ -0,0 +1 @@ +#include "qwt_arrow_button.h" diff --git a/libs/qwt/classincludes/QwtAxis b/libs/qwt/classincludes/QwtAxis new file mode 100644 index 00000000..f8da645e --- /dev/null +++ b/libs/qwt/classincludes/QwtAxis @@ -0,0 +1 @@ +#include "qwt_axis.h" diff --git a/libs/qwt/classincludes/QwtAxisId b/libs/qwt/classincludes/QwtAxisId new file mode 100644 index 00000000..343d2e2f --- /dev/null +++ b/libs/qwt/classincludes/QwtAxisId @@ -0,0 +1 @@ +#include "qwt_axis_id.h" diff --git a/libs/qwt/classincludes/QwtBezier b/libs/qwt/classincludes/QwtBezier new file mode 100644 index 00000000..d5a4a894 --- /dev/null +++ b/libs/qwt/classincludes/QwtBezier @@ -0,0 +1 @@ +#include "qwt_bezier.h" diff --git a/libs/qwt/classincludes/QwtCPointerData b/libs/qwt/classincludes/QwtCPointerData new file mode 100644 index 00000000..09055392 --- /dev/null +++ b/libs/qwt/classincludes/QwtCPointerData @@ -0,0 +1 @@ +#include "qwt_point_data.h" diff --git a/libs/qwt/classincludes/QwtClipper b/libs/qwt/classincludes/QwtClipper new file mode 100644 index 00000000..2fed706a --- /dev/null +++ b/libs/qwt/classincludes/QwtClipper @@ -0,0 +1 @@ +#include "qwt_clipper.h" diff --git a/libs/qwt/classincludes/QwtColorMap b/libs/qwt/classincludes/QwtColorMap new file mode 100644 index 00000000..a4115653 --- /dev/null +++ b/libs/qwt/classincludes/QwtColorMap @@ -0,0 +1 @@ +#include "qwt_color_map.h" diff --git a/libs/qwt/classincludes/QwtColumnRect b/libs/qwt/classincludes/QwtColumnRect new file mode 100644 index 00000000..4bf5172d --- /dev/null +++ b/libs/qwt/classincludes/QwtColumnRect @@ -0,0 +1 @@ +#include "qwt_column_symbol.h" diff --git a/libs/qwt/classincludes/QwtColumnSymbol b/libs/qwt/classincludes/QwtColumnSymbol new file mode 100644 index 00000000..4bf5172d --- /dev/null +++ b/libs/qwt/classincludes/QwtColumnSymbol @@ -0,0 +1 @@ +#include "qwt_column_symbol.h" diff --git a/libs/qwt/classincludes/QwtCompass b/libs/qwt/classincludes/QwtCompass new file mode 100644 index 00000000..51980de4 --- /dev/null +++ b/libs/qwt/classincludes/QwtCompass @@ -0,0 +1 @@ +#include "qwt_compass.h" diff --git a/libs/qwt/classincludes/QwtCompassMagnetNeedle b/libs/qwt/classincludes/QwtCompassMagnetNeedle new file mode 100644 index 00000000..7f4fb650 --- /dev/null +++ b/libs/qwt/classincludes/QwtCompassMagnetNeedle @@ -0,0 +1 @@ +#include "qwt_dial_needle.h" diff --git a/libs/qwt/classincludes/QwtCompassRose b/libs/qwt/classincludes/QwtCompassRose new file mode 100644 index 00000000..86713f71 --- /dev/null +++ b/libs/qwt/classincludes/QwtCompassRose @@ -0,0 +1 @@ +#include "qwt_compass_rose.h" diff --git a/libs/qwt/classincludes/QwtCompassScaleDraw b/libs/qwt/classincludes/QwtCompassScaleDraw new file mode 100644 index 00000000..51980de4 --- /dev/null +++ b/libs/qwt/classincludes/QwtCompassScaleDraw @@ -0,0 +1 @@ +#include "qwt_compass.h" diff --git a/libs/qwt/classincludes/QwtCompassWindArrow b/libs/qwt/classincludes/QwtCompassWindArrow new file mode 100644 index 00000000..7f4fb650 --- /dev/null +++ b/libs/qwt/classincludes/QwtCompassWindArrow @@ -0,0 +1 @@ +#include "qwt_dial_needle.h" diff --git a/libs/qwt/classincludes/QwtCounter b/libs/qwt/classincludes/QwtCounter new file mode 100644 index 00000000..fbf410d4 --- /dev/null +++ b/libs/qwt/classincludes/QwtCounter @@ -0,0 +1 @@ +#include "qwt_counter.h" diff --git a/libs/qwt/classincludes/QwtCurveFitter b/libs/qwt/classincludes/QwtCurveFitter new file mode 100644 index 00000000..4b3b269c --- /dev/null +++ b/libs/qwt/classincludes/QwtCurveFitter @@ -0,0 +1 @@ +#include "qwt_curve_fitter.h" diff --git a/libs/qwt/classincludes/QwtDate b/libs/qwt/classincludes/QwtDate new file mode 100644 index 00000000..cfc0ca98 --- /dev/null +++ b/libs/qwt/classincludes/QwtDate @@ -0,0 +1 @@ +#include "qwt_date.h" diff --git a/libs/qwt/classincludes/QwtDateScaleDraw b/libs/qwt/classincludes/QwtDateScaleDraw new file mode 100644 index 00000000..b7e884c5 --- /dev/null +++ b/libs/qwt/classincludes/QwtDateScaleDraw @@ -0,0 +1 @@ +#include "qwt_date_scale_draw.h" diff --git a/libs/qwt/classincludes/QwtDateScaleEngine b/libs/qwt/classincludes/QwtDateScaleEngine new file mode 100644 index 00000000..c719b2d2 --- /dev/null +++ b/libs/qwt/classincludes/QwtDateScaleEngine @@ -0,0 +1 @@ +#include "qwt_date_scale_engine.h" diff --git a/libs/qwt/classincludes/QwtDial b/libs/qwt/classincludes/QwtDial new file mode 100644 index 00000000..d2ee6f7c --- /dev/null +++ b/libs/qwt/classincludes/QwtDial @@ -0,0 +1 @@ +#include "qwt_dial.h" diff --git a/libs/qwt/classincludes/QwtDialNeedle b/libs/qwt/classincludes/QwtDialNeedle new file mode 100644 index 00000000..7f4fb650 --- /dev/null +++ b/libs/qwt/classincludes/QwtDialNeedle @@ -0,0 +1 @@ +#include "qwt_dial_needle.h" diff --git a/libs/qwt/classincludes/QwtDialSimpleNeedle b/libs/qwt/classincludes/QwtDialSimpleNeedle new file mode 100644 index 00000000..7f4fb650 --- /dev/null +++ b/libs/qwt/classincludes/QwtDialSimpleNeedle @@ -0,0 +1 @@ +#include "qwt_dial_needle.h" diff --git a/libs/qwt/classincludes/QwtDynGridLayout b/libs/qwt/classincludes/QwtDynGridLayout new file mode 100644 index 00000000..44cd49a2 --- /dev/null +++ b/libs/qwt/classincludes/QwtDynGridLayout @@ -0,0 +1 @@ +#include "qwt_dyngrid_layout.h" diff --git a/libs/qwt/classincludes/QwtEventPattern b/libs/qwt/classincludes/QwtEventPattern new file mode 100644 index 00000000..178e50f7 --- /dev/null +++ b/libs/qwt/classincludes/QwtEventPattern @@ -0,0 +1 @@ +#include "qwt_event_pattern.h" diff --git a/libs/qwt/classincludes/QwtGlobal b/libs/qwt/classincludes/QwtGlobal new file mode 100644 index 00000000..98235d27 --- /dev/null +++ b/libs/qwt/classincludes/QwtGlobal @@ -0,0 +1 @@ +#include "qwt_global.h" diff --git a/libs/qwt/classincludes/QwtGraphic b/libs/qwt/classincludes/QwtGraphic new file mode 100644 index 00000000..5fad6f02 --- /dev/null +++ b/libs/qwt/classincludes/QwtGraphic @@ -0,0 +1 @@ +#include "qwt_graphic.h" diff --git a/libs/qwt/classincludes/QwtHueColorMap b/libs/qwt/classincludes/QwtHueColorMap new file mode 100644 index 00000000..a4115653 --- /dev/null +++ b/libs/qwt/classincludes/QwtHueColorMap @@ -0,0 +1 @@ +#include "qwt_color_map.h" diff --git a/libs/qwt/classincludes/QwtInterval b/libs/qwt/classincludes/QwtInterval new file mode 100644 index 00000000..1ab464ae --- /dev/null +++ b/libs/qwt/classincludes/QwtInterval @@ -0,0 +1 @@ +#include "qwt_interval.h" diff --git a/libs/qwt/classincludes/QwtIntervalSample b/libs/qwt/classincludes/QwtIntervalSample new file mode 100644 index 00000000..249dcb29 --- /dev/null +++ b/libs/qwt/classincludes/QwtIntervalSample @@ -0,0 +1 @@ +#include "qwt_samples.h" diff --git a/libs/qwt/classincludes/QwtIntervalSeriesData b/libs/qwt/classincludes/QwtIntervalSeriesData new file mode 100644 index 00000000..aff29be3 --- /dev/null +++ b/libs/qwt/classincludes/QwtIntervalSeriesData @@ -0,0 +1 @@ +#include "qwt_series_data.h" diff --git a/libs/qwt/classincludes/QwtIntervalSymbol b/libs/qwt/classincludes/QwtIntervalSymbol new file mode 100644 index 00000000..9c3e6d71 --- /dev/null +++ b/libs/qwt/classincludes/QwtIntervalSymbol @@ -0,0 +1 @@ +#include "qwt_interval_symbol.h" diff --git a/libs/qwt/classincludes/QwtKnob b/libs/qwt/classincludes/QwtKnob new file mode 100644 index 00000000..410e5e42 --- /dev/null +++ b/libs/qwt/classincludes/QwtKnob @@ -0,0 +1 @@ +#include "qwt_knob.h" diff --git a/libs/qwt/classincludes/QwtLegend b/libs/qwt/classincludes/QwtLegend new file mode 100644 index 00000000..55800738 --- /dev/null +++ b/libs/qwt/classincludes/QwtLegend @@ -0,0 +1 @@ +#include "qwt_legend.h" diff --git a/libs/qwt/classincludes/QwtLegendData b/libs/qwt/classincludes/QwtLegendData new file mode 100644 index 00000000..266f5a21 --- /dev/null +++ b/libs/qwt/classincludes/QwtLegendData @@ -0,0 +1 @@ +#include "qwt_legend_data.h" diff --git a/libs/qwt/classincludes/QwtLegendLabel b/libs/qwt/classincludes/QwtLegendLabel new file mode 100644 index 00000000..97917994 --- /dev/null +++ b/libs/qwt/classincludes/QwtLegendLabel @@ -0,0 +1 @@ +#include "qwt_legend_label.h" diff --git a/libs/qwt/classincludes/QwtLinearColorMap b/libs/qwt/classincludes/QwtLinearColorMap new file mode 100644 index 00000000..a4115653 --- /dev/null +++ b/libs/qwt/classincludes/QwtLinearColorMap @@ -0,0 +1 @@ +#include "qwt_color_map.h" diff --git a/libs/qwt/classincludes/QwtLinearScaleEngine b/libs/qwt/classincludes/QwtLinearScaleEngine new file mode 100644 index 00000000..2a51dfdf --- /dev/null +++ b/libs/qwt/classincludes/QwtLinearScaleEngine @@ -0,0 +1 @@ +#include "qwt_scale_engine.h" diff --git a/libs/qwt/classincludes/QwtLogScaleEngine b/libs/qwt/classincludes/QwtLogScaleEngine new file mode 100644 index 00000000..2a51dfdf --- /dev/null +++ b/libs/qwt/classincludes/QwtLogScaleEngine @@ -0,0 +1 @@ +#include "qwt_scale_engine.h" diff --git a/libs/qwt/classincludes/QwtLogTransform b/libs/qwt/classincludes/QwtLogTransform new file mode 100644 index 00000000..ebd40244 --- /dev/null +++ b/libs/qwt/classincludes/QwtLogTransform @@ -0,0 +1 @@ +#include "qwt_transform.h" diff --git a/libs/qwt/classincludes/QwtMagnifier b/libs/qwt/classincludes/QwtMagnifier new file mode 100644 index 00000000..4d8494fe --- /dev/null +++ b/libs/qwt/classincludes/QwtMagnifier @@ -0,0 +1 @@ +#include "qwt_magnifier.h" diff --git a/libs/qwt/classincludes/QwtMath b/libs/qwt/classincludes/QwtMath new file mode 100644 index 00000000..e5a170a9 --- /dev/null +++ b/libs/qwt/classincludes/QwtMath @@ -0,0 +1 @@ +#include "qwt_math.h" diff --git a/libs/qwt/classincludes/QwtMatrixRasterData b/libs/qwt/classincludes/QwtMatrixRasterData new file mode 100644 index 00000000..0d186b6e --- /dev/null +++ b/libs/qwt/classincludes/QwtMatrixRasterData @@ -0,0 +1 @@ +#include "qwt_matrix_raster_data.h" diff --git a/libs/qwt/classincludes/QwtNullPaintDevice b/libs/qwt/classincludes/QwtNullPaintDevice new file mode 100644 index 00000000..7fb1937c --- /dev/null +++ b/libs/qwt/classincludes/QwtNullPaintDevice @@ -0,0 +1 @@ +#include "qwt_null_paintdevice.h" diff --git a/libs/qwt/classincludes/QwtNullTransform b/libs/qwt/classincludes/QwtNullTransform new file mode 100644 index 00000000..ebd40244 --- /dev/null +++ b/libs/qwt/classincludes/QwtNullTransform @@ -0,0 +1 @@ +#include "qwt_transform.h" diff --git a/libs/qwt/classincludes/QwtOHLCSample b/libs/qwt/classincludes/QwtOHLCSample new file mode 100644 index 00000000..249dcb29 --- /dev/null +++ b/libs/qwt/classincludes/QwtOHLCSample @@ -0,0 +1 @@ +#include "qwt_samples.h" diff --git a/libs/qwt/classincludes/QwtPainter b/libs/qwt/classincludes/QwtPainter new file mode 100644 index 00000000..29968a95 --- /dev/null +++ b/libs/qwt/classincludes/QwtPainter @@ -0,0 +1 @@ +#include "qwt_painter.h" diff --git a/libs/qwt/classincludes/QwtPainterCommand b/libs/qwt/classincludes/QwtPainterCommand new file mode 100644 index 00000000..2abf1c4b --- /dev/null +++ b/libs/qwt/classincludes/QwtPainterCommand @@ -0,0 +1 @@ +#include "qwt_painter_command.h" diff --git a/libs/qwt/classincludes/QwtPanner b/libs/qwt/classincludes/QwtPanner new file mode 100644 index 00000000..61ed2e2c --- /dev/null +++ b/libs/qwt/classincludes/QwtPanner @@ -0,0 +1 @@ +#include "qwt_panner.h" diff --git a/libs/qwt/classincludes/QwtPicker b/libs/qwt/classincludes/QwtPicker new file mode 100644 index 00000000..7aef8ba3 --- /dev/null +++ b/libs/qwt/classincludes/QwtPicker @@ -0,0 +1 @@ +#include "qwt_picker.h" diff --git a/libs/qwt/classincludes/QwtPickerClickPointMachine b/libs/qwt/classincludes/QwtPickerClickPointMachine new file mode 100644 index 00000000..f015f147 --- /dev/null +++ b/libs/qwt/classincludes/QwtPickerClickPointMachine @@ -0,0 +1 @@ +#include "qwt_picker_machine.h" diff --git a/libs/qwt/classincludes/QwtPickerClickRectMachine b/libs/qwt/classincludes/QwtPickerClickRectMachine new file mode 100644 index 00000000..f015f147 --- /dev/null +++ b/libs/qwt/classincludes/QwtPickerClickRectMachine @@ -0,0 +1 @@ +#include "qwt_picker_machine.h" diff --git a/libs/qwt/classincludes/QwtPickerDragLineMachine b/libs/qwt/classincludes/QwtPickerDragLineMachine new file mode 100644 index 00000000..f015f147 --- /dev/null +++ b/libs/qwt/classincludes/QwtPickerDragLineMachine @@ -0,0 +1 @@ +#include "qwt_picker_machine.h" diff --git a/libs/qwt/classincludes/QwtPickerDragPointMachine b/libs/qwt/classincludes/QwtPickerDragPointMachine new file mode 100644 index 00000000..f015f147 --- /dev/null +++ b/libs/qwt/classincludes/QwtPickerDragPointMachine @@ -0,0 +1 @@ +#include "qwt_picker_machine.h" diff --git a/libs/qwt/classincludes/QwtPickerDragRectMachine b/libs/qwt/classincludes/QwtPickerDragRectMachine new file mode 100644 index 00000000..f015f147 --- /dev/null +++ b/libs/qwt/classincludes/QwtPickerDragRectMachine @@ -0,0 +1 @@ +#include "qwt_picker_machine.h" diff --git a/libs/qwt/classincludes/QwtPickerMachine b/libs/qwt/classincludes/QwtPickerMachine new file mode 100644 index 00000000..f015f147 --- /dev/null +++ b/libs/qwt/classincludes/QwtPickerMachine @@ -0,0 +1 @@ +#include "qwt_picker_machine.h" diff --git a/libs/qwt/classincludes/QwtPickerPolygonMachine b/libs/qwt/classincludes/QwtPickerPolygonMachine new file mode 100644 index 00000000..f015f147 --- /dev/null +++ b/libs/qwt/classincludes/QwtPickerPolygonMachine @@ -0,0 +1 @@ +#include "qwt_picker_machine.h" diff --git a/libs/qwt/classincludes/QwtPickerTrackerMachine b/libs/qwt/classincludes/QwtPickerTrackerMachine new file mode 100644 index 00000000..f015f147 --- /dev/null +++ b/libs/qwt/classincludes/QwtPickerTrackerMachine @@ -0,0 +1 @@ +#include "qwt_picker_machine.h" diff --git a/libs/qwt/classincludes/QwtPixelMatrix b/libs/qwt/classincludes/QwtPixelMatrix new file mode 100644 index 00000000..c3dc4bbc --- /dev/null +++ b/libs/qwt/classincludes/QwtPixelMatrix @@ -0,0 +1 @@ +#include "qwt_pixel_matrix.h" diff --git a/libs/qwt/classincludes/QwtPlainTextEngine b/libs/qwt/classincludes/QwtPlainTextEngine new file mode 100644 index 00000000..89c7c388 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlainTextEngine @@ -0,0 +1 @@ +#include "qwt_text_engine.h" diff --git a/libs/qwt/classincludes/QwtPlot b/libs/qwt/classincludes/QwtPlot new file mode 100644 index 00000000..836bcef9 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlot @@ -0,0 +1 @@ +#include "qwt_plot.h" diff --git a/libs/qwt/classincludes/QwtPlotAbstractBarChart b/libs/qwt/classincludes/QwtPlotAbstractBarChart new file mode 100644 index 00000000..a66b060d --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotAbstractBarChart @@ -0,0 +1 @@ +#include "qwt_plot_abstract_barchart.h" diff --git a/libs/qwt/classincludes/QwtPlotAbstractCanvas b/libs/qwt/classincludes/QwtPlotAbstractCanvas new file mode 100644 index 00000000..fb97db21 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotAbstractCanvas @@ -0,0 +1 @@ +#include "qwt_plot_abstract_canvas.h" diff --git a/libs/qwt/classincludes/QwtPlotBarChart b/libs/qwt/classincludes/QwtPlotBarChart new file mode 100644 index 00000000..d09f5f3a --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotBarChart @@ -0,0 +1 @@ +#include "qwt_plot_barchart.h" diff --git a/libs/qwt/classincludes/QwtPlotCanvas b/libs/qwt/classincludes/QwtPlotCanvas new file mode 100644 index 00000000..264d4520 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotCanvas @@ -0,0 +1 @@ +#include "qwt_plot_canvas.h" diff --git a/libs/qwt/classincludes/QwtPlotCurve b/libs/qwt/classincludes/QwtPlotCurve new file mode 100644 index 00000000..0ab61693 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotCurve @@ -0,0 +1 @@ +#include "qwt_plot_curve.h" diff --git a/libs/qwt/classincludes/QwtPlotDict b/libs/qwt/classincludes/QwtPlotDict new file mode 100644 index 00000000..8221583f --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotDict @@ -0,0 +1 @@ +#include "qwt_plot_dict.h" diff --git a/libs/qwt/classincludes/QwtPlotDirectPainter b/libs/qwt/classincludes/QwtPlotDirectPainter new file mode 100644 index 00000000..77680bbf --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotDirectPainter @@ -0,0 +1 @@ +#include "qwt_plot_directpainter.h" diff --git a/libs/qwt/classincludes/QwtPlotGLCanvas b/libs/qwt/classincludes/QwtPlotGLCanvas new file mode 100644 index 00000000..96ff3e98 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotGLCanvas @@ -0,0 +1 @@ +#include "qwt_plot_glcanvas.h" diff --git a/libs/qwt/classincludes/QwtPlotGraphicItem b/libs/qwt/classincludes/QwtPlotGraphicItem new file mode 100644 index 00000000..688dde0a --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotGraphicItem @@ -0,0 +1 @@ +#include "qwt_plot_graphicitem.h" diff --git a/libs/qwt/classincludes/QwtPlotGrid b/libs/qwt/classincludes/QwtPlotGrid new file mode 100644 index 00000000..2187ab45 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotGrid @@ -0,0 +1 @@ +#include "qwt_plot_grid.h" diff --git a/libs/qwt/classincludes/QwtPlotHistogram b/libs/qwt/classincludes/QwtPlotHistogram new file mode 100644 index 00000000..27cc0848 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotHistogram @@ -0,0 +1 @@ +#include "qwt_plot_histogram.h" diff --git a/libs/qwt/classincludes/QwtPlotIntervalCurve b/libs/qwt/classincludes/QwtPlotIntervalCurve new file mode 100644 index 00000000..9afab729 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotIntervalCurve @@ -0,0 +1 @@ +#include "qwt_plot_intervalcurve.h" diff --git a/libs/qwt/classincludes/QwtPlotItem b/libs/qwt/classincludes/QwtPlotItem new file mode 100644 index 00000000..9b3c2fbf --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotItem @@ -0,0 +1 @@ +#include "qwt_plot_item.h" diff --git a/libs/qwt/classincludes/QwtPlotLayout b/libs/qwt/classincludes/QwtPlotLayout new file mode 100644 index 00000000..3400eb1f --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotLayout @@ -0,0 +1 @@ +#include "qwt_plot_layout.h" diff --git a/libs/qwt/classincludes/QwtPlotLegendItem b/libs/qwt/classincludes/QwtPlotLegendItem new file mode 100644 index 00000000..b9d9ab88 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotLegendItem @@ -0,0 +1 @@ +#include "qwt_plot_legenditem.h" diff --git a/libs/qwt/classincludes/QwtPlotMagnifier b/libs/qwt/classincludes/QwtPlotMagnifier new file mode 100644 index 00000000..443766f0 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotMagnifier @@ -0,0 +1 @@ +#include "qwt_plot_magnifier.h" diff --git a/libs/qwt/classincludes/QwtPlotMarker b/libs/qwt/classincludes/QwtPlotMarker new file mode 100644 index 00000000..14a3a54e --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotMarker @@ -0,0 +1 @@ +#include "qwt_plot_marker.h" diff --git a/libs/qwt/classincludes/QwtPlotMultiBarChart b/libs/qwt/classincludes/QwtPlotMultiBarChart new file mode 100644 index 00000000..18e3d0a8 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotMultiBarChart @@ -0,0 +1 @@ +#include "qwt_plot_multi_barchart.h" diff --git a/libs/qwt/classincludes/QwtPlotOpenGLCanvas b/libs/qwt/classincludes/QwtPlotOpenGLCanvas new file mode 100644 index 00000000..a2b339eb --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotOpenGLCanvas @@ -0,0 +1 @@ +#include "qwt_plot_opengl_canvas.h" diff --git a/libs/qwt/classincludes/QwtPlotPanner b/libs/qwt/classincludes/QwtPlotPanner new file mode 100644 index 00000000..2e246224 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotPanner @@ -0,0 +1 @@ +#include "qwt_plot_panner.h" diff --git a/libs/qwt/classincludes/QwtPlotPicker b/libs/qwt/classincludes/QwtPlotPicker new file mode 100644 index 00000000..d1a1fe22 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotPicker @@ -0,0 +1 @@ +#include "qwt_plot_picker.h" diff --git a/libs/qwt/classincludes/QwtPlotRasterItem b/libs/qwt/classincludes/QwtPlotRasterItem new file mode 100644 index 00000000..f1dfa27b --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotRasterItem @@ -0,0 +1 @@ +#include "qwt_plot_rasteritem.h" diff --git a/libs/qwt/classincludes/QwtPlotRenderer b/libs/qwt/classincludes/QwtPlotRenderer new file mode 100644 index 00000000..edab396d --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotRenderer @@ -0,0 +1 @@ +#include "qwt_plot_renderer.h" diff --git a/libs/qwt/classincludes/QwtPlotRescaler b/libs/qwt/classincludes/QwtPlotRescaler new file mode 100644 index 00000000..beaf2094 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotRescaler @@ -0,0 +1 @@ +#include "qwt_plot_rescaler.h" diff --git a/libs/qwt/classincludes/QwtPlotScaleItem b/libs/qwt/classincludes/QwtPlotScaleItem new file mode 100644 index 00000000..9b56aec6 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotScaleItem @@ -0,0 +1 @@ +#include "qwt_plot_scaleitem.h" diff --git a/libs/qwt/classincludes/QwtPlotSeriesItem b/libs/qwt/classincludes/QwtPlotSeriesItem new file mode 100644 index 00000000..180360ac --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotSeriesItem @@ -0,0 +1 @@ +#include "qwt_plot_seriesitem.h" diff --git a/libs/qwt/classincludes/QwtPlotShapeItem b/libs/qwt/classincludes/QwtPlotShapeItem new file mode 100644 index 00000000..a3affe43 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotShapeItem @@ -0,0 +1 @@ +#include "qwt_plot_shapeitem.h" diff --git a/libs/qwt/classincludes/QwtPlotSpectroCurve b/libs/qwt/classincludes/QwtPlotSpectroCurve new file mode 100644 index 00000000..b6f84c23 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotSpectroCurve @@ -0,0 +1 @@ +#include "qwt_plot_spectrocurve.h" diff --git a/libs/qwt/classincludes/QwtPlotSpectrogram b/libs/qwt/classincludes/QwtPlotSpectrogram new file mode 100644 index 00000000..ff8f2444 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotSpectrogram @@ -0,0 +1 @@ +#include "qwt_plot_spectrogram.h" diff --git a/libs/qwt/classincludes/QwtPlotSvgItem b/libs/qwt/classincludes/QwtPlotSvgItem new file mode 100644 index 00000000..8a8f3d4e --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotSvgItem @@ -0,0 +1 @@ +#include "qwt_plot_svgitem.h" diff --git a/libs/qwt/classincludes/QwtPlotTextLabel b/libs/qwt/classincludes/QwtPlotTextLabel new file mode 100644 index 00000000..6f02bd56 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotTextLabel @@ -0,0 +1 @@ +#include "qwt_plot_textlabel.h" diff --git a/libs/qwt/classincludes/QwtPlotTradingCurve b/libs/qwt/classincludes/QwtPlotTradingCurve new file mode 100644 index 00000000..ffe60b36 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotTradingCurve @@ -0,0 +1 @@ +#include "qwt_plot_tradingcurve.h" diff --git a/libs/qwt/classincludes/QwtPlotVectorField b/libs/qwt/classincludes/QwtPlotVectorField new file mode 100644 index 00000000..19e8de68 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotVectorField @@ -0,0 +1 @@ +#include "qwt_plot_vectorfield.h" diff --git a/libs/qwt/classincludes/QwtPlotZoneItem b/libs/qwt/classincludes/QwtPlotZoneItem new file mode 100644 index 00000000..28c316a0 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotZoneItem @@ -0,0 +1 @@ +#include "qwt_plot_zoneitem.h" diff --git a/libs/qwt/classincludes/QwtPlotZoomer b/libs/qwt/classincludes/QwtPlotZoomer new file mode 100644 index 00000000..2f1f1e14 --- /dev/null +++ b/libs/qwt/classincludes/QwtPlotZoomer @@ -0,0 +1 @@ +#include "qwt_plot_zoomer.h" diff --git a/libs/qwt/classincludes/QwtPoint3D b/libs/qwt/classincludes/QwtPoint3D new file mode 100644 index 00000000..73a275b4 --- /dev/null +++ b/libs/qwt/classincludes/QwtPoint3D @@ -0,0 +1 @@ +#include "qwt_point_3d.h" diff --git a/libs/qwt/classincludes/QwtPoint3DSeriesData b/libs/qwt/classincludes/QwtPoint3DSeriesData new file mode 100644 index 00000000..aff29be3 --- /dev/null +++ b/libs/qwt/classincludes/QwtPoint3DSeriesData @@ -0,0 +1 @@ +#include "qwt_series_data.h" diff --git a/libs/qwt/classincludes/QwtPointArrayData b/libs/qwt/classincludes/QwtPointArrayData new file mode 100644 index 00000000..09055392 --- /dev/null +++ b/libs/qwt/classincludes/QwtPointArrayData @@ -0,0 +1 @@ +#include "qwt_point_data.h" diff --git a/libs/qwt/classincludes/QwtPointMapper b/libs/qwt/classincludes/QwtPointMapper new file mode 100644 index 00000000..e8699a8a --- /dev/null +++ b/libs/qwt/classincludes/QwtPointMapper @@ -0,0 +1 @@ +#include "qwt_point_mapper.h" diff --git a/libs/qwt/classincludes/QwtPointPolar b/libs/qwt/classincludes/QwtPointPolar new file mode 100644 index 00000000..a8144add --- /dev/null +++ b/libs/qwt/classincludes/QwtPointPolar @@ -0,0 +1 @@ +#include "qwt_point_polar.h" diff --git a/libs/qwt/classincludes/QwtPointSeriesData b/libs/qwt/classincludes/QwtPointSeriesData new file mode 100644 index 00000000..aff29be3 --- /dev/null +++ b/libs/qwt/classincludes/QwtPointSeriesData @@ -0,0 +1 @@ +#include "qwt_series_data.h" diff --git a/libs/qwt/classincludes/QwtPolarCanvas b/libs/qwt/classincludes/QwtPolarCanvas new file mode 100644 index 00000000..4d797ca6 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarCanvas @@ -0,0 +1 @@ +#include "qwt_polar_canvas.h" diff --git a/libs/qwt/classincludes/QwtPolarCurve b/libs/qwt/classincludes/QwtPolarCurve new file mode 100644 index 00000000..67f3c670 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarCurve @@ -0,0 +1 @@ +#include "qwt_polar_curve.h" diff --git a/libs/qwt/classincludes/QwtPolarFitter b/libs/qwt/classincludes/QwtPolarFitter new file mode 100644 index 00000000..535e5881 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarFitter @@ -0,0 +1 @@ +#include "qwt_polar_fitter.h" diff --git a/libs/qwt/classincludes/QwtPolarGrid b/libs/qwt/classincludes/QwtPolarGrid new file mode 100644 index 00000000..c9e25ac3 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarGrid @@ -0,0 +1 @@ +#include "qwt_polar_grid.h" diff --git a/libs/qwt/classincludes/QwtPolarItem b/libs/qwt/classincludes/QwtPolarItem new file mode 100644 index 00000000..22fca5d9 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarItem @@ -0,0 +1 @@ +#include "qwt_polar_item.h" diff --git a/libs/qwt/classincludes/QwtPolarItemDict b/libs/qwt/classincludes/QwtPolarItemDict new file mode 100644 index 00000000..e82d0790 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarItemDict @@ -0,0 +1 @@ +#include "qwt_polar_itemdict.h" diff --git a/libs/qwt/classincludes/QwtPolarLayout b/libs/qwt/classincludes/QwtPolarLayout new file mode 100644 index 00000000..741852f0 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarLayout @@ -0,0 +1 @@ +#include "qwt_polar_layout.h" diff --git a/libs/qwt/classincludes/QwtPolarMagnifier b/libs/qwt/classincludes/QwtPolarMagnifier new file mode 100644 index 00000000..037fab6b --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarMagnifier @@ -0,0 +1 @@ +#include "qwt_polar_magnifier.h" diff --git a/libs/qwt/classincludes/QwtPolarMarker b/libs/qwt/classincludes/QwtPolarMarker new file mode 100644 index 00000000..dc92cee7 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarMarker @@ -0,0 +1 @@ +#include "qwt_polar_marker.h" diff --git a/libs/qwt/classincludes/QwtPolarPanner b/libs/qwt/classincludes/QwtPolarPanner new file mode 100644 index 00000000..a72df769 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarPanner @@ -0,0 +1 @@ +#include "qwt_polar_panner.h" diff --git a/libs/qwt/classincludes/QwtPolarPicker b/libs/qwt/classincludes/QwtPolarPicker new file mode 100644 index 00000000..994e4d70 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarPicker @@ -0,0 +1 @@ +#include "qwt_polar_picker.h" diff --git a/libs/qwt/classincludes/QwtPolarPlot b/libs/qwt/classincludes/QwtPolarPlot new file mode 100644 index 00000000..d8945a3c --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarPlot @@ -0,0 +1 @@ +#include "qwt_polar_plot.h" diff --git a/libs/qwt/classincludes/QwtPolarRenderer b/libs/qwt/classincludes/QwtPolarRenderer new file mode 100644 index 00000000..50a95f1f --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarRenderer @@ -0,0 +1 @@ +#include "qwt_polar_renderer.h" diff --git a/libs/qwt/classincludes/QwtPolarSpectrogram b/libs/qwt/classincludes/QwtPolarSpectrogram new file mode 100644 index 00000000..de6d1ce5 --- /dev/null +++ b/libs/qwt/classincludes/QwtPolarSpectrogram @@ -0,0 +1 @@ +#include "qwt_polar_spectrogram.h" diff --git a/libs/qwt/classincludes/QwtPowerTransform b/libs/qwt/classincludes/QwtPowerTransform new file mode 100644 index 00000000..ebd40244 --- /dev/null +++ b/libs/qwt/classincludes/QwtPowerTransform @@ -0,0 +1 @@ +#include "qwt_transform.h" diff --git a/libs/qwt/classincludes/QwtRasterData b/libs/qwt/classincludes/QwtRasterData new file mode 100644 index 00000000..fd9364d5 --- /dev/null +++ b/libs/qwt/classincludes/QwtRasterData @@ -0,0 +1 @@ +#include "qwt_raster_data.h" diff --git a/libs/qwt/classincludes/QwtRichTextEngine b/libs/qwt/classincludes/QwtRichTextEngine new file mode 100644 index 00000000..89c7c388 --- /dev/null +++ b/libs/qwt/classincludes/QwtRichTextEngine @@ -0,0 +1 @@ +#include "qwt_text_engine.h" diff --git a/libs/qwt/classincludes/QwtRoundScaleDraw b/libs/qwt/classincludes/QwtRoundScaleDraw new file mode 100644 index 00000000..2219c330 --- /dev/null +++ b/libs/qwt/classincludes/QwtRoundScaleDraw @@ -0,0 +1 @@ +#include "qwt_round_scale_draw.h" diff --git a/libs/qwt/classincludes/QwtSamplingThread b/libs/qwt/classincludes/QwtSamplingThread new file mode 100644 index 00000000..0abbc415 --- /dev/null +++ b/libs/qwt/classincludes/QwtSamplingThread @@ -0,0 +1 @@ +#include "qwt_sampling_thread.h" diff --git a/libs/qwt/classincludes/QwtSaturationValueColorMap b/libs/qwt/classincludes/QwtSaturationValueColorMap new file mode 100644 index 00000000..a4115653 --- /dev/null +++ b/libs/qwt/classincludes/QwtSaturationValueColorMap @@ -0,0 +1 @@ +#include "qwt_color_map.h" diff --git a/libs/qwt/classincludes/QwtScaleArithmetic b/libs/qwt/classincludes/QwtScaleArithmetic new file mode 100644 index 00000000..2a51dfdf --- /dev/null +++ b/libs/qwt/classincludes/QwtScaleArithmetic @@ -0,0 +1 @@ +#include "qwt_scale_engine.h" diff --git a/libs/qwt/classincludes/QwtScaleDiv b/libs/qwt/classincludes/QwtScaleDiv new file mode 100644 index 00000000..786edf6c --- /dev/null +++ b/libs/qwt/classincludes/QwtScaleDiv @@ -0,0 +1 @@ +#include "qwt_scale_div.h" diff --git a/libs/qwt/classincludes/QwtScaleDraw b/libs/qwt/classincludes/QwtScaleDraw new file mode 100644 index 00000000..0207bee6 --- /dev/null +++ b/libs/qwt/classincludes/QwtScaleDraw @@ -0,0 +1 @@ +#include "qwt_scale_draw.h" diff --git a/libs/qwt/classincludes/QwtScaleEngine b/libs/qwt/classincludes/QwtScaleEngine new file mode 100644 index 00000000..2a51dfdf --- /dev/null +++ b/libs/qwt/classincludes/QwtScaleEngine @@ -0,0 +1 @@ +#include "qwt_scale_engine.h" diff --git a/libs/qwt/classincludes/QwtScaleMap b/libs/qwt/classincludes/QwtScaleMap new file mode 100644 index 00000000..78d00582 --- /dev/null +++ b/libs/qwt/classincludes/QwtScaleMap @@ -0,0 +1 @@ +#include "qwt_scale_map.h" diff --git a/libs/qwt/classincludes/QwtScaleWidget b/libs/qwt/classincludes/QwtScaleWidget new file mode 100644 index 00000000..df125b63 --- /dev/null +++ b/libs/qwt/classincludes/QwtScaleWidget @@ -0,0 +1 @@ +#include "qwt_scale_widget.h" diff --git a/libs/qwt/classincludes/QwtSeriesData b/libs/qwt/classincludes/QwtSeriesData new file mode 100644 index 00000000..aff29be3 --- /dev/null +++ b/libs/qwt/classincludes/QwtSeriesData @@ -0,0 +1 @@ +#include "qwt_series_data.h" diff --git a/libs/qwt/classincludes/QwtSetSample b/libs/qwt/classincludes/QwtSetSample new file mode 100644 index 00000000..249dcb29 --- /dev/null +++ b/libs/qwt/classincludes/QwtSetSample @@ -0,0 +1 @@ +#include "qwt_samples.h" diff --git a/libs/qwt/classincludes/QwtSetSeriesData b/libs/qwt/classincludes/QwtSetSeriesData new file mode 100644 index 00000000..aff29be3 --- /dev/null +++ b/libs/qwt/classincludes/QwtSetSeriesData @@ -0,0 +1 @@ +#include "qwt_series_data.h" diff --git a/libs/qwt/classincludes/QwtSimpleCompassRose b/libs/qwt/classincludes/QwtSimpleCompassRose new file mode 100644 index 00000000..86713f71 --- /dev/null +++ b/libs/qwt/classincludes/QwtSimpleCompassRose @@ -0,0 +1 @@ +#include "qwt_compass_rose.h" diff --git a/libs/qwt/classincludes/QwtSlider b/libs/qwt/classincludes/QwtSlider new file mode 100644 index 00000000..66840477 --- /dev/null +++ b/libs/qwt/classincludes/QwtSlider @@ -0,0 +1 @@ +#include "qwt_slider.h" diff --git a/libs/qwt/classincludes/QwtSpline b/libs/qwt/classincludes/QwtSpline new file mode 100644 index 00000000..646c1e81 --- /dev/null +++ b/libs/qwt/classincludes/QwtSpline @@ -0,0 +1 @@ +#include "qwt_spline.h" diff --git a/libs/qwt/classincludes/QwtSplineBasis b/libs/qwt/classincludes/QwtSplineBasis new file mode 100644 index 00000000..0e28fa10 --- /dev/null +++ b/libs/qwt/classincludes/QwtSplineBasis @@ -0,0 +1 @@ +#include "qwt_spline_basis.h" diff --git a/libs/qwt/classincludes/QwtSplineC1 b/libs/qwt/classincludes/QwtSplineC1 new file mode 100644 index 00000000..646c1e81 --- /dev/null +++ b/libs/qwt/classincludes/QwtSplineC1 @@ -0,0 +1 @@ +#include "qwt_spline.h" diff --git a/libs/qwt/classincludes/QwtSplineC2 b/libs/qwt/classincludes/QwtSplineC2 new file mode 100644 index 00000000..646c1e81 --- /dev/null +++ b/libs/qwt/classincludes/QwtSplineC2 @@ -0,0 +1 @@ +#include "qwt_spline.h" diff --git a/libs/qwt/classincludes/QwtSplineCubic b/libs/qwt/classincludes/QwtSplineCubic new file mode 100644 index 00000000..4f54997d --- /dev/null +++ b/libs/qwt/classincludes/QwtSplineCubic @@ -0,0 +1 @@ +#include "qwt_spline_cubic.h" diff --git a/libs/qwt/classincludes/QwtSplineCurveFitter b/libs/qwt/classincludes/QwtSplineCurveFitter new file mode 100644 index 00000000..25bdfa88 --- /dev/null +++ b/libs/qwt/classincludes/QwtSplineCurveFitter @@ -0,0 +1 @@ +#include "qwt_spline_curve_fitter.h" diff --git a/libs/qwt/classincludes/QwtSplineG1 b/libs/qwt/classincludes/QwtSplineG1 new file mode 100644 index 00000000..646c1e81 --- /dev/null +++ b/libs/qwt/classincludes/QwtSplineG1 @@ -0,0 +1 @@ +#include "qwt_spline.h" diff --git a/libs/qwt/classincludes/QwtSplineInterpolating b/libs/qwt/classincludes/QwtSplineInterpolating new file mode 100644 index 00000000..646c1e81 --- /dev/null +++ b/libs/qwt/classincludes/QwtSplineInterpolating @@ -0,0 +1 @@ +#include "qwt_spline.h" diff --git a/libs/qwt/classincludes/QwtSplineLocal b/libs/qwt/classincludes/QwtSplineLocal new file mode 100644 index 00000000..30d55e4e --- /dev/null +++ b/libs/qwt/classincludes/QwtSplineLocal @@ -0,0 +1 @@ +#include "qwt_spline_local.h" diff --git a/libs/qwt/classincludes/QwtSplineParameter b/libs/qwt/classincludes/QwtSplineParameter new file mode 100644 index 00000000..db458be3 --- /dev/null +++ b/libs/qwt/classincludes/QwtSplineParameter @@ -0,0 +1 @@ +#include "qwt_spline_parameter.h" diff --git a/libs/qwt/classincludes/QwtSplineParametrization b/libs/qwt/classincludes/QwtSplineParametrization new file mode 100644 index 00000000..31485537 --- /dev/null +++ b/libs/qwt/classincludes/QwtSplineParametrization @@ -0,0 +1 @@ +#include "qwt_spline_parametrization.h" diff --git a/libs/qwt/classincludes/QwtSplinePleasing b/libs/qwt/classincludes/QwtSplinePleasing new file mode 100644 index 00000000..0b60cb0a --- /dev/null +++ b/libs/qwt/classincludes/QwtSplinePleasing @@ -0,0 +1 @@ +#include "qwt_spline_pleasing.h" diff --git a/libs/qwt/classincludes/QwtSplinePolynomial b/libs/qwt/classincludes/QwtSplinePolynomial new file mode 100644 index 00000000..2783d981 --- /dev/null +++ b/libs/qwt/classincludes/QwtSplinePolynomial @@ -0,0 +1 @@ +#include "qwt_spline_polynomial.h" diff --git a/libs/qwt/classincludes/QwtSymbol b/libs/qwt/classincludes/QwtSymbol new file mode 100644 index 00000000..33d01d84 --- /dev/null +++ b/libs/qwt/classincludes/QwtSymbol @@ -0,0 +1 @@ +#include "qwt_symbol.h" diff --git a/libs/qwt/classincludes/QwtSyntheticPointData b/libs/qwt/classincludes/QwtSyntheticPointData new file mode 100644 index 00000000..09055392 --- /dev/null +++ b/libs/qwt/classincludes/QwtSyntheticPointData @@ -0,0 +1 @@ +#include "qwt_point_data.h" diff --git a/libs/qwt/classincludes/QwtSystemClock b/libs/qwt/classincludes/QwtSystemClock new file mode 100644 index 00000000..5ae66c41 --- /dev/null +++ b/libs/qwt/classincludes/QwtSystemClock @@ -0,0 +1 @@ +#include "qwt_system_clock.h" diff --git a/libs/qwt/classincludes/QwtText b/libs/qwt/classincludes/QwtText new file mode 100644 index 00000000..ae4fcf3b --- /dev/null +++ b/libs/qwt/classincludes/QwtText @@ -0,0 +1 @@ +#include "qwt_text.h" diff --git a/libs/qwt/classincludes/QwtTextEngine b/libs/qwt/classincludes/QwtTextEngine new file mode 100644 index 00000000..89c7c388 --- /dev/null +++ b/libs/qwt/classincludes/QwtTextEngine @@ -0,0 +1 @@ +#include "qwt_text_engine.h" diff --git a/libs/qwt/classincludes/QwtTextLabel b/libs/qwt/classincludes/QwtTextLabel new file mode 100644 index 00000000..313634b7 --- /dev/null +++ b/libs/qwt/classincludes/QwtTextLabel @@ -0,0 +1 @@ +#include "qwt_text_label.h" diff --git a/libs/qwt/classincludes/QwtThermo b/libs/qwt/classincludes/QwtThermo new file mode 100644 index 00000000..c4638dd3 --- /dev/null +++ b/libs/qwt/classincludes/QwtThermo @@ -0,0 +1 @@ +#include "qwt_thermo.h" diff --git a/libs/qwt/classincludes/QwtTradingChartData b/libs/qwt/classincludes/QwtTradingChartData new file mode 100644 index 00000000..aff29be3 --- /dev/null +++ b/libs/qwt/classincludes/QwtTradingChartData @@ -0,0 +1 @@ +#include "qwt_series_data.h" diff --git a/libs/qwt/classincludes/QwtTransform b/libs/qwt/classincludes/QwtTransform new file mode 100644 index 00000000..ebd40244 --- /dev/null +++ b/libs/qwt/classincludes/QwtTransform @@ -0,0 +1 @@ +#include "qwt_transform.h" diff --git a/libs/qwt/classincludes/QwtVectorFieldArrow b/libs/qwt/classincludes/QwtVectorFieldArrow new file mode 100644 index 00000000..a8a25558 --- /dev/null +++ b/libs/qwt/classincludes/QwtVectorFieldArrow @@ -0,0 +1 @@ +#include "qwt_vectorfield_symbol.h" diff --git a/libs/qwt/classincludes/QwtVectorFieldData b/libs/qwt/classincludes/QwtVectorFieldData new file mode 100644 index 00000000..aff29be3 --- /dev/null +++ b/libs/qwt/classincludes/QwtVectorFieldData @@ -0,0 +1 @@ +#include "qwt_series_data.h" diff --git a/libs/qwt/classincludes/QwtVectorFieldSample b/libs/qwt/classincludes/QwtVectorFieldSample new file mode 100644 index 00000000..249dcb29 --- /dev/null +++ b/libs/qwt/classincludes/QwtVectorFieldSample @@ -0,0 +1 @@ +#include "qwt_samples.h" diff --git a/libs/qwt/classincludes/QwtVectorFieldSymbol b/libs/qwt/classincludes/QwtVectorFieldSymbol new file mode 100644 index 00000000..a8a25558 --- /dev/null +++ b/libs/qwt/classincludes/QwtVectorFieldSymbol @@ -0,0 +1 @@ +#include "qwt_vectorfield_symbol.h" diff --git a/libs/qwt/classincludes/QwtVectorFieldThinArrow b/libs/qwt/classincludes/QwtVectorFieldThinArrow new file mode 100644 index 00000000..a8a25558 --- /dev/null +++ b/libs/qwt/classincludes/QwtVectorFieldThinArrow @@ -0,0 +1 @@ +#include "qwt_vectorfield_symbol.h" diff --git a/libs/qwt/classincludes/QwtWeedingCurveFitter b/libs/qwt/classincludes/QwtWeedingCurveFitter new file mode 100644 index 00000000..05bf16c9 --- /dev/null +++ b/libs/qwt/classincludes/QwtWeedingCurveFitter @@ -0,0 +1 @@ +#include "qwt_weeding_curve_fitter.h" diff --git a/libs/qwt/classincludes/QwtWheel b/libs/qwt/classincludes/QwtWheel new file mode 100644 index 00000000..c5165f71 --- /dev/null +++ b/libs/qwt/classincludes/QwtWheel @@ -0,0 +1 @@ +#include "qwt_wheel.h" diff --git a/libs/qwt/classincludes/QwtWidgetOverlay b/libs/qwt/classincludes/QwtWidgetOverlay new file mode 100644 index 00000000..d32d14ce --- /dev/null +++ b/libs/qwt/classincludes/QwtWidgetOverlay @@ -0,0 +1 @@ +#include "qwt_widget_overlay.h" diff --git a/libs/qwt/classincludes/classincludes.pri b/libs/qwt/classincludes/classincludes.pri new file mode 100644 index 00000000..1b72b11b --- /dev/null +++ b/libs/qwt/classincludes/classincludes.pri @@ -0,0 +1,192 @@ +################################################################ +# 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 +################################################################ + +INCLUDEPATH += $$PWD + +CLASSHEADERS = \ + $$PWD/QwtAbstractScaleDraw \ + $$PWD/QwtAlphaColorMap \ + $$PWD/QwtAxis \ + $$PWD/QwtAxisId \ + $$PWD/QwtBezier \ + $$PWD/QwtClipper \ + $$PWD/QwtColorMap \ + $$PWD/QwtColumnRect \ + $$PWD/QwtColumnSymbol \ + $$PWD/QwtDate \ + $$PWD/QwtDateScaleDraw \ + $$PWD/QwtDateScaleEngine \ + $$PWD/QwtDynGridLayout \ + $$PWD/QwtGlobal \ + $$PWD/QwtGraphic \ + $$PWD/QwtHueColorMap \ + $$PWD/QwtInterval \ + $$PWD/QwtIntervalSymbol \ + $$PWD/QwtLinearColorMap \ + $$PWD/QwtLinearScaleEngine \ + $$PWD/QwtLogScaleEngine \ + $$PWD/QwtLogTransform \ + $$PWD/QwtMagnifier \ + $$PWD/QwtMath \ + $$PWD/QwtNullPaintDevice \ + $$PWD/QwtNullTransform \ + $$PWD/QwtPainter \ + $$PWD/QwtPainterCommand \ + $$PWD/QwtPanner \ + $$PWD/QwtPicker \ + $$PWD/QwtPickerClickPointMachine \ + $$PWD/QwtPickerClickRectMachine \ + $$PWD/QwtPickerDragLineMachine \ + $$PWD/QwtPickerDragPointMachine \ + $$PWD/QwtPickerDragRectMachine \ + $$PWD/QwtPickerMachine \ + $$PWD/QwtPickerPolygonMachine \ + $$PWD/QwtPickerTrackerMachine \ + $$PWD/QwtPixelMatrix \ + $$PWD/QwtPlainTextEngine \ + $$PWD/QwtPoint3D \ + $$PWD/QwtPointPolar \ + $$PWD/QwtPowerTransform \ + $$PWD/QwtRichTextEngine \ + $$PWD/QwtRoundScaleDraw \ + $$PWD/QwtSaturationValueColorMap \ + $$PWD/QwtScaleArithmetic \ + $$PWD/QwtScaleDiv \ + $$PWD/QwtScaleDraw \ + $$PWD/QwtScaleEngine \ + $$PWD/QwtScaleMap \ + $$PWD/QwtSimpleCompassRose \ + $$PWD/QwtSplineBasis \ + $$PWD/QwtSpline \ + $$PWD/QwtSplineC1 \ + $$PWD/QwtSplineC2 \ + $$PWD/QwtSplineCubic \ + $$PWD/QwtSplineG1 \ + $$PWD/QwtSplineInterpolating \ + $$PWD/QwtSplineLocal \ + $$PWD/QwtSplineParameter \ + $$PWD/QwtSplineParametrization \ + $$PWD/QwtSplinePleasing \ + $$PWD/QwtSplinePolynomial \ + $$PWD/QwtSymbol \ + $$PWD/QwtSystemClock \ + $$PWD/QwtText \ + $$PWD/QwtTextEngine \ + $$PWD/QwtTextLabel \ + $$PWD/QwtTransform \ + $$PWD/QwtWidgetOverlay + +contains(QWT_CONFIG, QwtPlot) { + CLASSHEADERS += \ + $$PWD/QwtAbstractLegend \ + $$PWD/QwtCurveFitter \ + $$PWD/QwtEventPattern \ + $$PWD/QwtIntervalSample \ + $$PWD/QwtLegend \ + $$PWD/QwtLegendData \ + $$PWD/QwtLegendLabel \ + $$PWD/QwtPointMapper \ + $$PWD/QwtMatrixRasterData \ + $$PWD/QwtOHLCSample \ + $$PWD/QwtPlot \ + $$PWD/QwtPlotAbstractBarChart \ + $$PWD/QwtPlotAbstractCanvas \ + $$PWD/QwtPlotBarChart \ + $$PWD/QwtPlotCanvas \ + $$PWD/QwtPlotCurve \ + $$PWD/QwtPlotDict \ + $$PWD/QwtPlotDirectPainter \ + $$PWD/QwtPlotGraphicItem \ + $$PWD/QwtPlotGrid \ + $$PWD/QwtPlotHistogram \ + $$PWD/QwtPlotIntervalCurve \ + $$PWD/QwtPlotItem \ + $$PWD/QwtPlotLayout \ + $$PWD/QwtPlotLegendItem \ + $$PWD/QwtPlotMagnifier \ + $$PWD/QwtPlotMarker \ + $$PWD/QwtPlotMultiBarChart \ + $$PWD/QwtPlotPanner \ + $$PWD/QwtPlotPicker \ + $$PWD/QwtPlotRasterItem \ + $$PWD/QwtPlotRenderer \ + $$PWD/QwtPlotRescaler \ + $$PWD/QwtPlotScaleItem \ + $$PWD/QwtPlotSeriesItem \ + $$PWD/QwtPlotShapeItem \ + $$PWD/QwtPlotSpectroCurve \ + $$PWD/QwtPlotSpectrogram \ + $$PWD/QwtPlotTextLabel \ + $$PWD/QwtPlotTradingCurve \ + $$PWD/QwtPlotVectorField \ + $$PWD/QwtPlotZoneItem \ + $$PWD/QwtPlotZoomer \ + $$PWD/QwtScaleWidget \ + $$PWD/QwtRasterData \ + $$PWD/QwtSeriesData \ + $$PWD/QwtSetSample \ + $$PWD/QwtSamplingThread \ + $$PWD/QwtSplineCurveFitter \ + $$PWD/QwtWeedingCurveFitter \ + $$PWD/QwtIntervalSeriesData \ + $$PWD/QwtPoint3DSeriesData \ + $$PWD/QwtPointSeriesData \ + $$PWD/QwtSetSeriesData \ + $$PWD/QwtSyntheticPointData \ + $$PWD/QwtPointArrayData \ + $$PWD/QwtTradingChartData \ + $$PWD/QwtVectorFieldSymbol \ + $$PWD/QwtVectorFieldArrow \ + $$PWD/QwtVectorFieldThinArrow \ + $$PWD/QwtVectorFieldData \ + $$PWD/QwtVectorFieldSample \ + $$PWD/QwtCPointerData +} + +contains(QWT_CONFIG, QwtOpenGL) { + + CLASSHEADERS += \ + $$PWD/QwtPlotGLCanvas + + greaterThan(QT_MAJOR_VERSION, 4) { + + greaterThan(QT_MINOR_VERSION, 3) { + + CLASSHEADERS += \ + $$PWD/QwtPlotOpenGLCanvas + } + } +} + +contains(QWT_CONFIG, QwtWidgets) { + CLASSHEADERS += \ + $$PWD/QwtAbstractSlider \ + $$PWD/QwtAbstractScale \ + $$PWD/QwtArrowButton \ + $$PWD/QwtAnalogClock \ + $$PWD/QwtCompass \ + $$PWD/QwtCompassMagnetNeedle \ + $$PWD/QwtCompassRose \ + $$PWD/QwtCompassScaleDraw \ + $$PWD/QwtCompassWindArrow \ + $$PWD/QwtCounter \ + $$PWD/QwtDial \ + $$PWD/QwtDialNeedle \ + $$PWD/QwtDialSimpleNeedle \ + $$PWD/QwtKnob \ + $$PWD/QwtSlider \ + $$PWD/QwtThermo \ + $$PWD/QwtWheel +} + +contains(QWT_CONFIG, QwtSvg) { + CLASSHEADERS += \ + $$PWD/QwtPlotSvgItem +} + diff --git a/libs/qwt/qwt.pri b/libs/qwt/qwt.pri new file mode 100644 index 00000000..ae127bdb --- /dev/null +++ b/libs/qwt/qwt.pri @@ -0,0 +1,24 @@ +CONFIG += silent +CONFIG -= depend_includepath + +QWT_CONFIG += QwtPlot +QWT_CONFIG += QwtPolar +QWT_CONFIG += QwtWidgets + +c++11 { + CONFIG += strict_c++ +} + +sanitize { + CONFIG += sanitizer + CONFIG += sanitize_address + CONFIG *= sanitize_undefined +} + +QMAKE_CXXFLAGS *= -fno-math-errno +QMAKE_CXXFLAGS *= -funsafe-math-optimizations + +DEFINES += QWT_MOC_INCLUDE=1 + +include($$PWD/src/src.pri) +include($$PWD/classincludes/classincludes.pri) diff --git a/libs/qwt/src/qwt.cpp b/libs/qwt/src/qwt.cpp new file mode 100644 index 00000000..8804441e --- /dev/null +++ b/libs/qwt/src/qwt.cpp @@ -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 "qwt.h" +#include + +#define QWT_GLOBAL_STRUT + +#if QT_VERSION >= 0x050000 + #if QT_VERSION >= 0x060000 || !QT_DEPRECATED_SINCE(5, 15) + #undef QWT_GLOBAL_STRUT + #endif +#endif + +QSize qwtExpandedToGlobalStrut( const QSize& size ) +{ +#ifdef QWT_GLOBAL_STRUT + return size.expandedTo( QApplication::globalStrut() ); +#else + return size; +#endif +} diff --git a/libs/qwt/src/qwt.h b/libs/qwt/src/qwt.h new file mode 100644 index 00000000..b6105500 --- /dev/null +++ b/libs/qwt/src/qwt.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_H +#define QWT_H + +#include "qwt_global.h" + +class QSize; + +/*! + Some constants for use within Qwt. + */ +namespace Qwt +{ +} + +QWT_EXPORT QSize qwtExpandedToGlobalStrut( const QSize& ); + +#endif diff --git a/libs/qwt/src/qwt_abstract_legend.cpp b/libs/qwt/src/qwt_abstract_legend.cpp new file mode 100644 index 00000000..739ef441 --- /dev/null +++ b/libs/qwt/src/qwt_abstract_legend.cpp @@ -0,0 +1,43 @@ +/****************************************************************************** + * 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" +#include "qwt_legend_data.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; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_abstract_legend.cpp" +#endif diff --git a/libs/qwt/src/qwt_abstract_legend.h b/libs/qwt/src/qwt_abstract_legend.h new file mode 100644 index 00000000..576a6bee --- /dev/null +++ b/libs/qwt/src/qwt_abstract_legend.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_ABSTRACT_LEGEND_H +#define QWT_ABSTRACT_LEGEND_H + +#include "qwt_global.h" +#include + +class QwtLegendData; +template< typename T > class QList; +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< QwtLegendData >& data ) = 0; +}; + +#endif diff --git a/libs/qwt/src/qwt_abstract_scale.cpp b/libs/qwt/src/qwt_abstract_scale.cpp new file mode 100644 index 00000000..b04bfc95 --- /dev/null +++ b/libs/qwt/src/qwt_abstract_scale.cpp @@ -0,0 +1,474 @@ +/****************************************************************************** + * 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" + +#include + +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 ) +{ + m_data = new PrivateData; + rescale( 0.0, 100.0, m_data->stepSize ); +} + +//! Destructor +QwtAbstractScale::~QwtAbstractScale() +{ + delete m_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 m_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 m_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, m_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 != m_data->scaleDraw->scaleDiv() ) + { +#if 1 + if ( m_data->scaleEngine ) + { + m_data->scaleDraw->setTransformation( + m_data->scaleEngine->transformation() ); + } +#endif + + m_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 != m_data->maxMajor ) + { + m_data->maxMajor = ticks; + updateScaleDraw(); + } +} + +/*! + \return Maximal number of major tick intervals + \sa setScaleMaxMajor(), scaleMaxMinor() + */ +int QwtAbstractScale::scaleMaxMajor() const +{ + return m_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 != m_data->maxMinor ) + { + m_data->maxMinor = ticks; + updateScaleDraw(); + } +} + +/*! + \return Maximal number of minor tick intervals + \sa setScaleMaxMinor(), scaleMaxMajor() + */ +int QwtAbstractScale::scaleMaxMinor() const +{ + return m_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 != m_data->stepSize ) + { + m_data->stepSize = stepSize; + updateScaleDraw(); + } +} + +/*! + \return Hint for the step size of the scale + \sa setScaleStepSize(), QwtScaleEngine::divideScale() + */ +double QwtAbstractScale::scaleStepSize() const +{ + return m_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 == m_data->scaleDraw ) + return; + + if ( m_data->scaleDraw != NULL ) + scaleDraw->setScaleDiv( m_data->scaleDraw->scaleDiv() ); + + delete m_data->scaleDraw; + m_data->scaleDraw = scaleDraw; +} + +/*! + \return Scale draw + \sa setAbstractScaleDraw() + */ +QwtAbstractScaleDraw* QwtAbstractScale::abstractScaleDraw() +{ + return m_data->scaleDraw; +} + +/*! + \return Scale draw + \sa setAbstractScaleDraw() + */ +const QwtAbstractScaleDraw* QwtAbstractScale::abstractScaleDraw() const +{ + return m_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 != m_data->scaleEngine ) + { + delete m_data->scaleEngine; + m_data->scaleEngine = scaleEngine; + } +} + +/*! + \return Scale engine + \sa setScaleEngine() + */ +const QwtScaleEngine* QwtAbstractScale::scaleEngine() const +{ + return m_data->scaleEngine; +} + +/*! + \return Scale engine + \sa setScaleEngine() + */ +QwtScaleEngine* QwtAbstractScale::scaleEngine() +{ + return m_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 m_data->scaleDraw->scaleDiv(); +} + +/*! + \return Map to translate between scale and widget coordinates + */ +const QwtScaleMap& QwtAbstractScale::scaleMap() const +{ + return m_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( m_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 m_data->scaleDraw->scaleMap().invTransform( value ); +} + +/*! + \return True, when the scale is increasing in opposite direction + to the widget coordinates + */ +bool QwtAbstractScale::isInverted() const +{ + return m_data->scaleDraw->scaleMap().isInverting(); +} + +/*! + \return The boundary with the smaller value + \sa maximum(), lowerBound(), upperBound() + */ +double QwtAbstractScale::minimum() const +{ + return qMin( m_data->scaleDraw->scaleDiv().lowerBound(), + m_data->scaleDraw->scaleDiv().upperBound() ); +} + +/*! + \return The boundary with the larger value + \sa minimum(), lowerBound(), upperBound() + */ +double QwtAbstractScale::maximum() const +{ + return qMax( m_data->scaleDraw->scaleDiv().lowerBound(), + m_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 = m_data->scaleEngine->divideScale( + lowerBound, upperBound, m_data->maxMajor, m_data->maxMinor, stepSize ); + + if ( scaleDiv != m_data->scaleDraw->scaleDiv() ) + { +#if 1 + m_data->scaleDraw->setTransformation( + m_data->scaleEngine->transformation() ); +#endif + + m_data->scaleDraw->setScaleDiv( scaleDiv ); + scaleChange(); + } +} + +/*! + Change Event handler + \param event Change event + + Invalidates internal caches if necessary + */ +void QwtAbstractScale::changeEvent( QEvent* event ) +{ + if ( event->type() == QEvent::LocaleChange ) + { + m_data->scaleDraw->invalidateCache(); + } + + QWidget::changeEvent( event ); +} + +/*! + Recalculate ticks and scale boundaries. + */ +void QwtAbstractScale::updateScaleDraw() +{ + rescale( m_data->scaleDraw->scaleDiv().lowerBound(), + m_data->scaleDraw->scaleDiv().upperBound(), m_data->stepSize ); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_abstract_scale.cpp" +#endif diff --git a/libs/qwt/src/qwt_abstract_scale.h b/libs/qwt/src/qwt_abstract_scale.h new file mode 100644 index 00000000..ec55f2a4 --- /dev/null +++ b/libs/qwt/src/qwt_abstract_scale.h @@ -0,0 +1,106 @@ +/****************************************************************************** + * 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: + explicit 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: + virtual void changeEvent( QEvent* ) QWT_OVERRIDE; + + void rescale( double lowerBound, + double upperBound, double stepSize ); + + void setAbstractScaleDraw( QwtAbstractScaleDraw* ); + + const QwtAbstractScaleDraw* abstractScaleDraw() const; + QwtAbstractScaleDraw* abstractScaleDraw(); + + void updateScaleDraw(); + virtual void scaleChange(); + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_abstract_scale_draw.cpp b/libs/qwt/src/qwt_abstract_scale_draw.cpp new file mode 100644 index 00000000..abbcb91a --- /dev/null +++ b/libs/qwt/src/qwt_abstract_scale_draw.cpp @@ -0,0 +1,420 @@ +/****************************************************************************** + * 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_text.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include "qwt_math.h" + +#include +#include +#include +#include +#include + +class QwtAbstractScaleDraw::PrivateData +{ + public: + PrivateData(): + spacing( 4.0 ), + penWidthF( 0.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]; + qreal penWidthF; + + double minExtent; + + QMap< double, QwtText > 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() +{ + m_data = new QwtAbstractScaleDraw::PrivateData; +} + +//! Destructor +QwtAbstractScaleDraw::~QwtAbstractScaleDraw() +{ + delete m_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 ) + m_data->components |= component; + else + m_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 ( m_data->components & component ); +} + +/*! + Change the scale division + \param scaleDiv New scale division + */ +void QwtAbstractScaleDraw::setScaleDiv( const QwtScaleDiv& scaleDiv ) +{ + m_data->scaleDiv = scaleDiv; + m_data->map.setScaleInterval( scaleDiv.lowerBound(), scaleDiv.upperBound() ); + m_data->labelCache.clear(); +} + +/*! + Change the transformation of the scale + \param transformation New scale transformation + */ +void QwtAbstractScaleDraw::setTransformation( QwtTransform* transformation ) +{ + m_data->map.setTransformation( transformation ); +} + +//! \return Map how to translate between scale and pixel values +const QwtScaleMap& QwtAbstractScaleDraw::scaleMap() const +{ + return m_data->map; +} + +//! \return Map how to translate between scale and pixel values +QwtScaleMap& QwtAbstractScaleDraw::scaleMap() +{ + return m_data->map; +} + +//! \return scale division +const QwtScaleDiv& QwtAbstractScaleDraw::scaleDiv() const +{ + return m_data->scaleDiv; +} + +/*! + \brief Specify the width of the scale pen + \param width Pen width + + \sa penWidth() + */ +void QwtAbstractScaleDraw::setPenWidthF( qreal width ) +{ + if ( width < 0.0 ) + width = 0.0; + + m_data->penWidthF = width; +} + +/*! + \return Scale pen width + \sa setPenWidth() + */ +qreal QwtAbstractScaleDraw::penWidthF() const +{ + return m_data->penWidthF; +} + +/*! + \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.setWidthF( m_data->penWidthF ); + + painter->setPen( pen ); + + if ( hasComponent( QwtAbstractScaleDraw::Labels ) ) + { + painter->save(); + painter->setPen( palette.color( QPalette::Text ) ); // ignore pen style + + const QList< double >& majorTicks = + m_data->scaleDiv.ticks( QwtScaleDiv::MajorTick ); + + for ( int i = 0; i < majorTicks.count(); i++ ) + { + const double v = majorTicks[i]; + if ( m_data->scaleDiv.contains( v ) ) + drawLabel( painter, v ); + } + + painter->restore(); + } + + if ( hasComponent( QwtAbstractScaleDraw::Ticks ) ) + { + painter->save(); + + 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 double tickLen = m_data->tickLength[tickType]; + if ( tickLen <= 0.0 ) + continue; + + const QList< double >& ticks = m_data->scaleDiv.ticks( tickType ); + for ( int i = 0; i < ticks.count(); i++ ) + { + const double v = ticks[i]; + if ( m_data->scaleDiv.contains( v ) ) + drawTick( painter, v, tickLen ); + } + } + + painter->restore(); + } + + if ( hasComponent( QwtAbstractScaleDraw::Backbone ) ) + { + painter->save(); + + 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; + + m_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 m_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; + + m_data->minExtent = minExtent; +} + +/*! + Get the minimum extent + \return Minimum extent + \sa extent(), setMinimumExtent() + */ +double QwtAbstractScaleDraw::minimumExtent() const +{ + return m_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; + + m_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 m_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 = qwtMaxF( length, m_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< double, QwtText >::const_iterator it1 = m_data->labelCache.constFind( value ); + if ( it1 != m_data->labelCache.constEnd() ) + return *it1; + + QwtText lbl = label( value ); + lbl.setRenderFlags( 0 ); + lbl.setLayoutAttribute( QwtText::MinimumLayout ); + + ( void )lbl.textSize( font ); // initialize the internal cache + + QMap< double, QwtText >::iterator it2 = m_data->labelCache.insert( value, lbl ); + return *it2; +} + +/*! + 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() +{ + m_data->labelCache.clear(); +} diff --git a/libs/qwt/src/qwt_abstract_scale_draw.h b/libs/qwt/src/qwt_abstract_scale_draw.h new file mode 100644 index 00000000..969deba8 --- /dev/null +++ b/libs/qwt/src/qwt_abstract_scale_draw.h @@ -0,0 +1,140 @@ +/****************************************************************************** + * 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" + +class QwtText; +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 + }; + + Q_DECLARE_FLAGS( ScaleComponents, ScaleComponent ) + + QwtAbstractScaleDraw(); + virtual ~QwtAbstractScaleDraw(); + + void setScaleDiv( const QwtScaleDiv& ); + 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 ); + double spacing() const; + + void setPenWidthF( qreal width ); + qreal penWidthF() 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; + + void invalidateCache(); + + 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; + + const QwtText& tickLabel( const QFont&, double value ) const; + + private: + Q_DISABLE_COPY(QwtAbstractScaleDraw) + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtAbstractScaleDraw::ScaleComponents ) + +#endif diff --git a/libs/qwt/src/qwt_abstract_slider.cpp b/libs/qwt/src/qwt_abstract_slider.cpp new file mode 100644 index 00000000..6f1510dc --- /dev/null +++ b/libs/qwt/src/qwt_abstract_slider.cpp @@ -0,0 +1,836 @@ +/****************************************************************************** + * 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_scale_map.h" +#include "qwt_scale_div.h" +#include "qwt_math.h" + +#include + +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.upperBound() ) ) + return sd.upperBound(); + + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + { + const QList< double > 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 ) +{ + m_data = new QwtAbstractSlider::PrivateData; + + setScale( 0.0, 100.0 ); + setFocusPolicy( Qt::StrongFocus ); +} + +//! Destructor +QwtAbstractSlider::~QwtAbstractSlider() +{ + delete m_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 != m_data->isValid ) + { + m_data->isValid = on; + sliderChange(); + + Q_EMIT valueChanged( m_data->value ); + } +} + +//! \return True, when the value is invalid +bool QwtAbstractSlider::isValid() const +{ + return m_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 ( m_data->readOnly != on ) + { + m_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 m_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 ) +{ + m_data->isTracking = on; +} + +/*! + \return True, when tracking has been enabled + \sa setTracking() + */ +bool QwtAbstractSlider::isTracking() const +{ + return m_data->isTracking; +} + +/*! + Mouse press event handler + \param event Mouse event + */ +void QwtAbstractSlider::mousePressEvent( QMouseEvent* event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( !m_data->isValid || lowerBound() == upperBound() ) + return; + + m_data->isScrolling = isScrollPosition( event->pos() ); + + if ( m_data->isScrolling ) + { + m_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 ( m_data->isValid && m_data->isScrolling ) + { + double value = scrolledTo( event->pos() ); + if ( value != m_data->value ) + { + value = boundedValue( value ); + + if ( m_data->stepAlignment ) + { + value = alignedValue( value ); + } + else + { + value = qwtAlignToScaleDiv( this, value ); + } + + if ( value != m_data->value ) + { + m_data->value = value; + + sliderChange(); + + Q_EMIT sliderMoved( m_data->value ); + + if ( m_data->isTracking ) + Q_EMIT valueChanged( m_data->value ); + else + m_data->pendingValueChanged = true; + } + } + } +} + +/*! + Mouse Release Event handler + \param event Mouse event + */ +void QwtAbstractSlider::mouseReleaseEvent( QMouseEvent* event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( m_data->isScrolling && m_data->isValid ) + { + m_data->isScrolling = false; + + if ( m_data->pendingValueChanged ) + Q_EMIT valueChanged( m_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 ( !m_data->isValid || m_data->isScrolling ) + return; + +#if QT_VERSION < 0x050000 + const int wheelDelta = event->delta(); +#else + const QPoint delta = event->angleDelta(); + const int wheelDelta = ( qAbs( delta.x() ) > qAbs( delta.y() ) ) + ? delta.x() : delta.y(); +#endif + + int numSteps = 0; + + if ( ( event->modifiers() & Qt::ControlModifier ) || + ( event->modifiers() & Qt::ShiftModifier ) ) + { + // one page regardless of delta + numSteps = m_data->pageSteps; + if ( wheelDelta < 0 ) + numSteps = -numSteps; + } + else + { + const int numTurns = ( wheelDelta / 120 ); + numSteps = numTurns * m_data->singleSteps; + } + + if ( m_data->invertedControls ) + numSteps = -numSteps; + + const double value = incrementedValue( m_data->value, numSteps ); + if ( value != m_data->value ) + { + m_data->value = value; + sliderChange(); + + Q_EMIT sliderMoved( m_data->value ); + Q_EMIT valueChanged( m_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 ( !m_data->isValid || m_data->isScrolling ) + return; + + int numSteps = 0; + double value = m_data->value; + + switch ( event->key() ) + { + case Qt::Key_Left: + { + numSteps = -static_cast< int >( m_data->singleSteps ); + if ( isInverted() ) + numSteps = -numSteps; + + break; + } + case Qt::Key_Right: + { + numSteps = m_data->singleSteps; + if ( isInverted() ) + numSteps = -numSteps; + + break; + } + case Qt::Key_Down: + { + numSteps = -static_cast< int >( m_data->singleSteps ); + if ( m_data->invertedControls ) + numSteps = -numSteps; + break; + } + case Qt::Key_Up: + { + numSteps = m_data->singleSteps; + if ( m_data->invertedControls ) + numSteps = -numSteps; + + break; + } + case Qt::Key_PageUp: + { + numSteps = m_data->pageSteps; + if ( m_data->invertedControls ) + numSteps = -numSteps; + break; + } + case Qt::Key_PageDown: + { + numSteps = -static_cast< int >( m_data->pageSteps ); + if ( m_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( m_data->value, numSteps ); + } + + if ( value != m_data->value ) + { + m_data->value = value; + sliderChange(); + + Q_EMIT sliderMoved( m_data->value ); + Q_EMIT valueChanged( m_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 ) +{ + m_data->totalSteps = stepCount; +} + +/*! + \return Number of steps + \sa setTotalSteps(), singleSteps(), pageSteps() + */ +uint QwtAbstractSlider::totalSteps() const +{ + return m_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 ) +{ + m_data->singleSteps = stepCount; +} + +/*! + \return Number of steps + \sa setSingleSteps(), totalSteps(), pageSteps() + */ +uint QwtAbstractSlider::singleSteps() const +{ + return m_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 ) +{ + m_data->pageSteps = stepCount; +} + +/*! + \return Number of steps + \sa setPageSteps(), totalSteps(), singleSteps() + */ +uint QwtAbstractSlider::pageSteps() const +{ + return m_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 != m_data->stepAlignment ) + { + m_data->stepAlignment = on; + } +} + +/*! + \return True, when step alignment is enabled + \sa setStepAlignment() + */ +bool QwtAbstractSlider::stepAlignment() const +{ + return m_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 = ( m_data->value != value ) || !m_data->isValid; + + m_data->value = value; + m_data->isValid = true; + + if ( changed ) + { + sliderChange(); + Q_EMIT valueChanged( m_data->value ); + } +} + +//! Returns the current value. +double QwtAbstractSlider::value() const +{ + return m_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 ) +{ + m_data->wrapping = on; +} + +/*! + \return True, when wrapping is set + \sa setWrapping() + */ +bool QwtAbstractSlider::wrapping() const +{ + return m_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 ) +{ + m_data->invertedControls = on; +} + +/*! + \return True, when the controls are inverted + \sa setInvertedControls() + */ +bool QwtAbstractSlider::invertedControls() const +{ + return m_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( + m_data->value, stepCount ); + + if ( value != m_data->value ) + { + m_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 ( m_data->totalSteps == 0 ) + return value; + + const QwtTransform* transformation = + scaleMap().transformation(); + + if ( transformation == NULL ) + { + const double range = maximum() - minimum(); + value += stepCount * range / m_data->totalSteps; + } + else + { + QwtScaleMap map = scaleMap(); + map.setPaintInterval( 0, m_data->totalSteps ); + + // we need equidistant steps according to + // paint device coordinates + const double range = transformation->transform( maximum() ) + - transformation->transform( minimum() ); + + const double stepSize = range / m_data->totalSteps; + + double v = transformation->transform( value ); + + v = qRound( v / stepSize ) * stepSize; + v += stepCount * range / m_data->totalSteps; + + value = transformation->invTransform( v ); + } + + value = boundedValue( value ); + + if ( m_data->stepAlignment ) + value = alignedValue( value ); + + return value; +} + +double QwtAbstractSlider::boundedValue( double value ) const +{ + const double vmin = minimum(); + const double vmax = maximum(); + + if ( m_data->wrapping && vmin != vmax ) + { + if ( qFuzzyCompare( scaleMap().pDist(), 360.0 ) ) + { + // full circle scales: min and max are the same + + if ( qFuzzyCompare( value, vmax ) ) + { + value = vmin; + } + else + { + const double range = vmax - vmin; + + if ( value < vmin ) + { + value += std::ceil( ( vmin - value ) / range ) * range; + } + else if ( value > vmax ) + { + value -= std::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 ( m_data->totalSteps == 0 ) + return value; + + double stepSize; + + if ( scaleMap().transformation() == NULL ) + { + stepSize = ( maximum() - minimum() ) / m_data->totalSteps; + if ( stepSize > 0.0 ) + { + value = lowerBound() + + qRound( ( value - lowerBound() ) / stepSize ) * stepSize; + } + } + else + { + stepSize = ( scaleMap().p2() - scaleMap().p1() ) / m_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(), m_data->value, maximum() ); + + const bool changed = ( value != m_data->value ); + if ( changed ) + { + m_data->value = value; + } + + if ( m_data->isValid || changed ) + Q_EMIT valueChanged( m_data->value ); + + updateGeometry(); + update(); +} + +//! Calling update() +void QwtAbstractSlider::sliderChange() +{ + update(); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_abstract_slider.cpp" +#endif diff --git a/libs/qwt/src/qwt_abstract_slider.h b/libs/qwt/src/qwt_abstract_slider.h new file mode 100644 index 00000000..38ca55a7 --- /dev/null +++ b/libs/qwt/src/qwt_abstract_slider.h @@ -0,0 +1,167 @@ +/****************************************************************************** + * 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 NOTIFY valueChanged USER true ) + + 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 value ); + + 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* ) QWT_OVERRIDE; + virtual void mouseReleaseEvent( QMouseEvent* ) QWT_OVERRIDE; + virtual void mouseMoveEvent( QMouseEvent* ) QWT_OVERRIDE; + virtual void keyPressEvent( QKeyEvent* ) QWT_OVERRIDE; + virtual void wheelEvent( QWheelEvent* ) QWT_OVERRIDE; + + /*! + \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 stepCount ); + + virtual void scaleChange() QWT_OVERRIDE; + + protected: + virtual void sliderChange(); + + double incrementedValue( + double value, int stepCount ) const; + + private: + double alignedValue( double ) const; + double boundedValue( double ) const; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_analog_clock.cpp b/libs/qwt/src/qwt_analog_clock.cpp new file mode 100644 index 00000000..81a25b63 --- /dev/null +++ b/libs/qwt/src/qwt_analog_clock.cpp @@ -0,0 +1,255 @@ +/****************************************************************************** + * 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_dial_needle.h" +#include "qwt_round_scale_draw.h" +#include "qwt_text.h" +#include "qwt_math.h" + +#include +#include + +namespace +{ + class QwtAnalogClockScaleDraw QWT_FINAL : public QwtRoundScaleDraw + { + public: + QwtAnalogClockScaleDraw() + { + setSpacing( 8 ); + + enableComponent( QwtAbstractScaleDraw::Backbone, false ); + + setTickLength( QwtScaleDiv::MinorTick, 2 ); + setTickLength( QwtScaleDiv::MediumTick, 4 ); + setTickLength( QwtScaleDiv::MajorTick, 8 ); + + setPenWidthF( 1.0 ); + } + + virtual QwtText label( double value ) const QWT_OVERRIDE + { + 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< double > majorTicks; + QList< double > 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.darker( 120 ); + + QColor handColor; + int width; + + for ( int i = 0; i < NHands; i++ ) + { + if ( i == SecondHand ) + { + width = 2; + handColor = knobColor.darker( 120 ); + } + else + { + width = 8; + handColor = knobColor; + } + + QwtDialSimpleNeedle* hand = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, handColor, knobColor ); + hand->setWidth( width ); + + m_hand[i] = NULL; + setHand( static_cast< Hand >( i ), hand ); + } +} + +//! Destructor +QwtAnalogClock::~QwtAnalogClock() +{ + for ( int i = 0; i < NHands; i++ ) + delete m_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 m_hand[hand]; + m_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 m_hand[hd]; +} + +/*! + \return Clock hand + \param hd Specifies the type of hand + \sa setHand() + */ +const QwtDialNeedle* QwtAnalogClock::hand( Hand hd ) const +{ + return const_cast< QwtAnalogClock* >( 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 direction Dummy, not used. + \param colorGroup ColorGroup + + \sa drawHand() + */ +void QwtAnalogClock::drawNeedle( QPainter* painter, const QPointF& center, + double radius, double direction, QPalette::ColorGroup colorGroup ) const +{ + Q_UNUSED( direction ); + + if ( isValid() ) + { + const double hours = value() / ( 60.0 * 60.0 ); + const double minutes = + ( value() - std::floor(hours) * 60.0 * 60.0 ) / 60.0; + const double seconds = value() - std::floor(hours) * 60.0 * 60.0 + - std::floor(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 >( 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& center, 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 ); + } +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_analog_clock.cpp" +#endif diff --git a/libs/qwt/src/qwt_analog_clock.h b/libs/qwt/src/qwt_analog_clock.h new file mode 100644 index 00000000..fb848302 --- /dev/null +++ b/libs/qwt/src/qwt_analog_clock.h @@ -0,0 +1,93 @@ +/****************************************************************************** + * 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" + +class QwtDialNeedle; + +/*! + \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 QWT_OVERRIDE; + + virtual void drawHand( QPainter*, Hand, const QPointF&, + double radius, double direction, QPalette::ColorGroup ) const; + + private: + // use setHand instead + void setNeedle( QwtDialNeedle* ); + + QwtDialNeedle* m_hand[NHands]; +}; + +#endif diff --git a/libs/qwt/src/qwt_arrow_button.cpp b/libs/qwt/src/qwt_arrow_button.cpp new file mode 100644 index 00000000..bae57f15 --- /dev/null +++ b/libs/qwt/src/qwt_arrow_button.cpp @@ -0,0 +1,332 @@ +/****************************************************************************** + * 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.h" + +#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.initFrom( 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 ) +{ + m_data = new PrivateData; + m_data->num = qBound( 1, num, MaxNum ); + m_data->arrowType = arrowType; + + setAutoRepeat( true ); + setAutoDefault( false ); + + switch ( m_data->arrowType ) + { + case Qt::LeftArrow: + case Qt::RightArrow: + setSizePolicy( QSizePolicy::Expanding, + QSizePolicy::Fixed ); + break; + default: + setSizePolicy( QSizePolicy::Fixed, + QSizePolicy::Expanding ); + } +} + +//! Destructor +QwtArrowButton::~QwtArrowButton() +{ + delete m_data; + m_data = NULL; +} + +/*! + \brief The direction of the arrows + */ +Qt::ArrowType QwtArrowButton::arrowType() const +{ + return m_data->arrowType; +} + +/*! + \brief The number of arrows + */ +int QwtArrowButton::num() const +{ + return m_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 = m_data->arrowType == Qt::UpArrow || + m_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 ( m_data->arrowType == Qt::LeftArrow || m_data->arrowType == Qt::RightArrow ) + { + contentsSize.setWidth( m_data->num * arrow.width() + + ( m_data->num - 1 ) * Spacing ); + contentsSize.setHeight( arrow.height() ); + } + else + { + contentsSize.setWidth( arrow.width() ); + contentsSize.setHeight( m_data->num * arrow.height() + + ( m_data->num - 1 ) * Spacing ); + } + + QRect arrowRect( contentsSize ); + arrowRect.moveCenter( r.center() ); + arrowRect.setSize( arrow ); + + painter->save(); + for ( int i = 0; i < m_data->num; i++ ) + { + drawArrow( painter, arrowRect, m_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.initFrom( 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 qwtExpandedToGlobalStrut( hint ); +} + +/*! + \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 ( m_data->arrowType == Qt::UpArrow || m_data->arrowType == Qt::DownArrow ) + sz.transpose(); + + QStyleOption styleOption; + styleOption.initFrom( 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/libs/qwt/src/qwt_arrow_button.h b/libs/qwt/src/qwt_arrow_button.h new file mode 100644 index 00000000..85f42102 --- /dev/null +++ b/libs/qwt/src/qwt_arrow_button.h @@ -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 + *****************************************************************************/ + +#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 QWT_OVERRIDE; + virtual QSize minimumSizeHint() const QWT_OVERRIDE; + + protected: + virtual void paintEvent( QPaintEvent*) QWT_OVERRIDE; + virtual void keyPressEvent( QKeyEvent* ) QWT_OVERRIDE; + + virtual void drawButtonLabel( QPainter* ); + virtual void drawArrow( QPainter*, + const QRect&, Qt::ArrowType ) const; + virtual QRect labelRect() const; + virtual QSize arrowSize( Qt::ArrowType, + const QSize& boundingSize ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_axis.h b/libs/qwt/src/qwt_axis.h new file mode 100644 index 00000000..433abae8 --- /dev/null +++ b/libs/qwt/src/qwt_axis.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * 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_H +#define QWT_AXIS_H + +#include "qwt_global.h" + +/*! + Enums and methods for axes + */ +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 { AxisPositions = XTop + 1 }; + + bool isValid( int axisPos ); + bool isYAxis( int axisPos ); + bool isXAxis( int axisPos ); +} + +//! \return true, when axisPos is in the valid range [ YLeft, XTop ] +inline bool QwtAxis::isValid( int axisPos ) +{ + return ( axisPos >= 0 && axisPos < AxisPositions ); +} + +//! \return true, when axisPos is XBottom or XTop +inline bool QwtAxis::isXAxis( int axisPos ) +{ + return ( axisPos == XBottom ) || ( axisPos == XTop ); +} + +//! \return true, when axisPos is YLeft or YRight +inline bool QwtAxis::isYAxis( int axisPos ) +{ + return ( axisPos == YLeft ) || ( axisPos == YRight ); +} + +#endif diff --git a/libs/qwt/src/qwt_axis_id.h b/libs/qwt/src/qwt_axis_id.h new file mode 100644 index 00000000..9d28c7e8 --- /dev/null +++ b/libs/qwt/src/qwt_axis_id.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_AXIS_ID_H +#define QWT_AXIS_ID_H + +#include "qwt_global.h" +#include "qwt_axis.h" + +/*! + \brief Axis identifier + + An axis id is one of values of QwtAxis::Position. + + QwtAxisId is a placeholder for future releases ( -> multiaxes branch ), + where it is possible to have more than one axis at each side of a plot. + + \sa QwtAxis + */ +typedef int QwtAxisId; + +#endif diff --git a/libs/qwt/src/qwt_bezier.cpp b/libs/qwt/src/qwt_bezier.cpp new file mode 100644 index 00000000..11229eae --- /dev/null +++ b/libs/qwt/src/qwt_bezier.cpp @@ -0,0 +1,252 @@ +/****************************************************************************** + * 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_bezier.h" +#include "qwt_math.h" + +#include +#include + +namespace +{ + class BezierData + { + public: + inline BezierData() + { + // default constructor with uninitialized points + } + + inline BezierData( const QPointF& p1, const QPointF& cp1, + const QPointF& cp2, const QPointF& p2 ): + m_x1( p1.x() ), + m_y1( p1.y() ), + m_cx1( cp1.x() ), + m_cy1( cp1.y() ), + m_cx2( cp2.x() ), + m_cy2( cp2.y() ), + m_x2( p2.x() ), + m_y2( p2.y() ) + { + } + + static inline double minFlatness( double tolerance ) + { + // we can simplify the tolerance criterion check in + // the subdivision loop, by precalculating some + // flatness value. + + return 16 * ( tolerance * tolerance ); + } + + inline double flatness() const + { + // algo by Roger Willcocks ( http://www.rops.org ) + + const double ux = 3.0 * m_cx1 - 2.0 * m_x1 - m_x2; + const double uy = 3.0 * m_cy1 - 2.0 * m_y1 - m_y2; + const double vx = 3.0 * m_cx2 - 2.0 * m_x2 - m_x1; + const double vy = 3.0 * m_cy2 - 2.0 * m_y2 - m_y1; + + const double ux2 = ux * ux; + const double uy2 = uy * uy; + + const double vx2 = vx * vx; + const double vy2 = vy * vy; + + return qwtMaxF( ux2, vx2 ) + qwtMaxF( uy2, vy2 ); + } + + inline BezierData subdivided() + { + BezierData bz; + + const double c1 = midValue( m_cx1, m_cx2 ); + + bz.m_cx1 = midValue( m_x1, m_cx1 ); + m_cx2 = midValue( m_cx2, m_x2 ); + bz.m_x1 = m_x1; + bz.m_cx2 = midValue( bz.m_cx1, c1 ); + m_cx1 = midValue( c1, m_cx2 ); + bz.m_x2 = m_x1 = midValue( bz.m_cx2, m_cx1 ); + + const double c2 = midValue( m_cy1, m_cy2 ); + + bz.m_cy1 = midValue( m_y1, m_cy1 ); + m_cy2 = midValue( m_cy2, m_y2 ); + bz.m_y1 = m_y1; + bz.m_cy2 = midValue( bz.m_cy1, c2 ); + m_cy1 = midValue( m_cy2, c2 ); + bz.m_y2 = m_y1 = midValue( bz.m_cy2, m_cy1 ); + + return bz; + } + + inline QPointF p2() const + { + return QPointF( m_x2, m_y2 ); + } + + private: + inline double midValue( double v1, double v2 ) + { + return 0.5 * ( v1 + v2 ); + } + + double m_x1, m_y1; + double m_cx1, m_cy1; + double m_cx2, m_cy2; + double m_x2, m_y2; + }; +} + +/*! + \brief Constructor + + \param tolerance Termination criterion for the subdivision + \sa setTolerance() + */ + +QwtBezier::QwtBezier( double tolerance ) + : m_tolerance( qwtMaxF( tolerance, 0.0 ) ) + , m_flatness( BezierData::minFlatness( m_tolerance ) ) +{ +} + +//! Destructor +QwtBezier::~QwtBezier() +{ +} + +/*! + Set the tolerance + + The tolerance is a measurement for the flatness of a curve. + A curve with a flatness below the tolerance is considered as being flat + terminating the subdivision algorithm. + + When interpolating a Bezier curve to render it as a sequence of lines + to some sort of raster ( f.e to screen ) a value of 0.5 of the pixel size + is a good value for the tolerance. + + \param tolerance Termination criterion for the subdivision + \sa tolerance() + */ +void QwtBezier::setTolerance( double tolerance ) +{ + m_tolerance = qwtMaxF( tolerance, 0.0 ); + m_flatness = BezierData::minFlatness( m_tolerance ); +} + +/*! + \brief Interpolate a Bézier curve by a polygon + + \param p1 Start point + \param cp1 First control point + \param cp2 Second control point + \param p2 End point + + \return Interpolating polygon + */ +QPolygonF QwtBezier::toPolygon( const QPointF& p1, + const QPointF& cp1, const QPointF& cp2, const QPointF& p2 ) const +{ + QPolygonF polygon; + + if ( m_flatness > 0.0 ) + { + // a flatness of 0.0 is not achievable + appendToPolygon( p1, cp1, cp2, p2, polygon ); + } + + return polygon; +} + +/*! + \brief Interpolate a Bézier curve by a polygon + + appendToPolygon() is tailored for cumulating points from a sequence + of bezier curves like being created by a spline interpolation. + + \param p1 Start point + \param cp1 First control point + \param cp2 Second control point + \param p2 End point + \param polygon Polygon, where the interpolating points are added + + \note If the last point of the incoming polygon matches p1 it won't be + inserted a second time. + */ +void QwtBezier::appendToPolygon( const QPointF& p1, const QPointF& cp1, + const QPointF& cp2, const QPointF& p2, QPolygonF& polygon ) const +{ + if ( m_flatness <= 0.0 ) + { + // a flatness of 0.0 is not achievable + return; + } + + if ( polygon.isEmpty() || polygon.last() != p1 ) + polygon += p1; + + // to avoid deep stacks we convert the recursive algo + // to something iterative, where the parameters of the + // recursive class are pushed to a stack instead + + QStack< BezierData > stack; + stack.push( BezierData( p1, cp1, cp2, p2 ) ); + + while( true ) + { + BezierData& bz = stack.top(); + + if ( bz.flatness() < m_flatness ) + { + if ( stack.size() == 1 ) + { + polygon += p2; + return; + } + + polygon += bz.p2(); + stack.pop(); + } + else + { + stack.push( bz.subdivided() ); + } + } + +} + +/*! + Find a point on a Bézier Curve + + \param p1 Start point + \param cp1 First control point + \param cp2 Second control point + \param p2 End point + \param t Parameter value, something between [0,1] + + \return Point on the curve + */ +QPointF QwtBezier::pointAt( const QPointF& p1, + const QPointF& cp1, const QPointF& cp2, const QPointF& p2, double t ) +{ + const double d1 = 3.0 * t; + const double d2 = 3.0 * t * t; + const double d3 = t * t * t; + const double s = 1.0 - t; + + const double x = ( ( s * p1.x() + d1 * cp1.x() ) * s + d2 * cp2.x() ) * s + d3 * p2.x(); + const double y = ( ( s * p1.y() + d1 * cp1.y() ) * s + d2 * cp2.y() ) * s + d3 * p2.y(); + + return QPointF( x, y ); +} + diff --git a/libs/qwt/src/qwt_bezier.h b/libs/qwt/src/qwt_bezier.h new file mode 100644 index 00000000..7b0cbe33 --- /dev/null +++ b/libs/qwt/src/qwt_bezier.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * 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_BEZIER_H +#define QWT_BEZIER_H + +#include "qwt_global.h" + +class QPointF; +class QPolygonF; + +/*! + \brief An implementation of the de Casteljau’s Algorithm for interpolating + Bézier curves + + The flatness criterion for terminating the subdivision is based on + "Piecewise Linear Approximation of Bézier Curves" by + Roger Willcocks ( http://www.rops.org ) + + This article explains the maths behind in a very nice way: + https://jeremykun.com/2013/05/11/bezier-curves-and-picasso + */ +class QWT_EXPORT QwtBezier +{ + public: + QwtBezier( double tolerance = 0.5 ); + ~QwtBezier(); + + void setTolerance( double tolerance ); + double tolerance() const; + + QPolygonF toPolygon( const QPointF& p1, const QPointF& cp1, + const QPointF& cp2, const QPointF& p2 ) const; + + void appendToPolygon( const QPointF& p1, const QPointF& cp1, + const QPointF& cp2, const QPointF& p2, QPolygonF& polygon ) const; + + static QPointF pointAt( const QPointF& p1, const QPointF& cp1, + const QPointF& cp2, const QPointF& p2, double t ); + + private: + double m_tolerance; + double m_flatness; +}; + +/*! + \return Tolerance, that is used as criterion for the subdivision + \sa setTolerance() + */ +inline double QwtBezier::tolerance() const +{ + return m_tolerance; +} + +#endif diff --git a/libs/qwt/src/qwt_clipper.cpp b/libs/qwt/src/qwt_clipper.cpp new file mode 100644 index 00000000..f58ad79b --- /dev/null +++ b/libs/qwt/src/qwt_clipper.cpp @@ -0,0 +1,482 @@ +/****************************************************************************** + * 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 "qwt_interval.h" +#include "qwt_math.h" + +#include +#include + +#include + +namespace QwtClip +{ + // some templates used for inlining + template< class Point, typename T > class LeftEdge; + template< class Point, typename T > class RightEdge; + template< class Point, typename T > class TopEdge; + template< class Point, typename T > class BottomEdge; +} + +template< class Point, typename Value > +class QwtClip::LeftEdge +{ + public: + inline LeftEdge( Value x1, Value, Value, Value ): + m_x1( x1 ) + { + } + + inline bool isInside( const Point& p ) const + { + return p.x() >= m_x1; + } + + inline Point intersection( const Point& p1, const Point& p2 ) const + { + double dy = ( p1.y() - p2.y() ) / double( p1.x() - p2.x() ); + return Point( m_x1, static_cast< Value >( p2.y() + ( m_x1 - p2.x() ) * dy ) ); + } + private: + const Value m_x1; +}; + +template< class Point, typename Value > +class QwtClip::RightEdge +{ + public: + inline RightEdge( Value, Value x2, Value, Value ): + m_x2( x2 ) + { + } + + inline bool isInside( const Point& p ) const + { + return p.x() <= m_x2; + } + + inline Point intersection( const Point& p1, const Point& p2 ) const + { + double dy = ( p1.y() - p2.y() ) / double( p1.x() - p2.x() ); + return Point( m_x2, static_cast< Value >( p2.y() + ( m_x2 - p2.x() ) * dy ) ); + } + + private: + const Value m_x2; +}; + +template< class Point, typename Value > +class QwtClip::TopEdge +{ + public: + inline TopEdge( Value, Value, Value y1, Value ): + m_y1( y1 ) + { + } + + inline bool isInside( const Point& p ) const + { + return p.y() >= m_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< Value >( p2.x() + ( m_y1 - p2.y() ) * dx ), m_y1 ); + } + + private: + const Value m_y1; +}; + +template< class Point, typename Value > +class QwtClip::BottomEdge +{ + public: + inline BottomEdge( Value, Value, Value, Value y2 ): + m_y2( y2 ) + { + } + + inline bool isInside( const Point& p ) const + { + return p.y() <= m_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< Value >( p2.x() + ( m_y2 - p2.y() ) * dx ), m_y2 ); + } + + private: + const Value m_y2; +}; + +using namespace QwtClip; + +template< class Polygon, class Rect, typename T > +class QwtPolygonClipper +{ + typedef typename Polygon::value_type Point; + public: + explicit QwtPolygonClipper( const Rect& clipRect ): + m_clipRect( clipRect ) + { + } + + void clipPolygon( Polygon& points1, bool closePolygon ) const + { +#if 0 + if ( m_clipRect.contains( points1.boundingRect() ) ) + return polygon; +#endif + + Polygon points2; + points2.reserve( qMin( 256, points1.size() ) ); + + clipEdge< LeftEdge< Point, T > >( closePolygon, points1, points2 ); + clipEdge< RightEdge< Point, T > >( closePolygon, points2, points1 ); + clipEdge< TopEdge< Point, T > >( closePolygon, points1, points2 ); + clipEdge< BottomEdge< Point, T > >( closePolygon, points2, points1 ); + } + + private: + template< class Edge > + inline void clipEdge( bool closePolygon, + const Polygon& points, Polygon& clippedPoints ) const + { + clippedPoints.clear(); + + if ( points.size() < 2 ) + { + if ( points.size() == 1 ) + clippedPoints += points[0]; + + return; + } + + const Edge edge( m_clipRect.x(), m_clipRect.x() + m_clipRect.width(), + m_clipRect.y(), m_clipRect.y() + m_clipRect.height() ); + + if ( !closePolygon ) + { + const Point& p1 = points.first(); + + if ( edge.isInside( p1 ) ) + clippedPoints += p1; + } + else + { + const Point& p1 = points.first(); + const Point& p2 = points.last(); + + if ( edge.isInside( p1 ) ) + { + if ( !edge.isInside( p2 ) ) + clippedPoints += edge.intersection( p1, p2 ); + + clippedPoints += p1; + } + else if ( edge.isInside( p2 ) ) + { + clippedPoints += edge.intersection( p1, p2 ); + } + } + + const uint nPoints = points.size(); + const Point* p = points.constData(); + + for ( uint i = 1; i < nPoints; i++ ) + { + const Point& p1 = p[i]; + const Point& p2 = p[i - 1]; + + if ( edge.isInside( p1 ) ) + { + if ( !edge.isInside( p2 ) ) + clippedPoints += edge.intersection( p1, p2 ); + + clippedPoints += p1; + } + else if ( edge.isInside( p2 ) ) + { + clippedPoints += edge.intersection( p1, p2 ); + } + } + } + + const Rect m_clipRect; +}; + +class QwtCircleClipper +{ + public: + explicit QwtCircleClipper( const QRectF& r ); + QVector< QwtInterval > clipCircle( const QPointF&, double radius ) const; + + private: + enum Edge + { + Left, + Top, + Right, + Bottom, + + NEdges + }; + + QVector< QPointF > cuttingPoints( + Edge, const QPointF& pos, double radius ) const; + + double toAngle( const QPointF&, const QPointF& ) const; + + const QRectF m_rect; +}; + + +QwtCircleClipper::QwtCircleClipper( const QRectF& r ) + : m_rect( r ) +{ +} + +QVector< QwtInterval > QwtCircleClipper::clipCircle( + const QPointF& pos, double radius ) const +{ + // using QVarLengthArray TODO ... + + QVector< QPointF > points; + for ( int edge = 0; edge < NEdges; edge++ ) + points += cuttingPoints( static_cast< Edge >( edge ), pos, radius ); + + QVector< QwtInterval > intv; + if ( points.size() <= 0 ) + { + QRectF cRect( 0, 0, 2 * radius, 2 * radius ); + cRect.moveCenter( pos ); + if ( m_rect.contains( cRect ) ) + intv += QwtInterval( 0.0, 2 * M_PI ); + } + else + { + QVector< double > angles; + angles.reserve( points.size() ); + + for ( int i = 0; i < points.size(); i++ ) + angles += toAngle( pos, points[i] ); + + std::sort( angles.begin(), angles.end() ); + + const int in = m_rect.contains( qwtPolar2Pos( pos, radius, + angles[0] + ( angles[1] - angles[0] ) / 2 ) ); + + intv.reserve( angles.size() / 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 = std::atan( 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; +} + +QVector< QPointF > QwtCircleClipper::cuttingPoints( + Edge edge, const QPointF& pos, double radius ) const +{ + QVector< QPointF > points; + + if ( edge == Left || edge == Right ) + { + const double x = ( edge == Left ) ? m_rect.left() : m_rect.right(); + if ( qAbs( pos.x() - x ) < radius ) + { + const double off = std::sqrt( qwtSqr( radius ) - qwtSqr( pos.x() - x ) ); + const double m_y1 = pos.y() + off; + if ( m_y1 >= m_rect.top() && m_y1 <= m_rect.bottom() ) + points += QPointF( x, m_y1 ); + + const double m_y2 = pos.y() - off; + if ( m_y2 >= m_rect.top() && m_y2 <= m_rect.bottom() ) + points += QPointF( x, m_y2 ); + } + } + else + { + const double y = ( edge == Top ) ? m_rect.top() : m_rect.bottom(); + if ( qAbs( pos.y() - y ) < radius ) + { + const double off = std::sqrt( qwtSqr( radius ) - qwtSqr( pos.y() - y ) ); + const double x1 = pos.x() + off; + if ( x1 >= m_rect.left() && x1 <= m_rect.right() ) + points += QPointF( x1, y ); + + const double m_x2 = pos.x() - off; + if ( m_x2 >= m_rect.left() && m_x2 <= m_rect.right() ) + points += QPointF( m_x2, y ); + } + } + return points; +} + +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon IN/OUT + \param closePolygon True, when the polygon is closed + */ +void QwtClipper::clipPolygon( + const QRectF& clipRect, 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< QPolygon, QRect, int > clipper( r ); + clipper.clipPolygon( polygon, closePolygon ); +} + +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon IN/OUT + \param closePolygon True, when the polygon is closed + */ +void QwtClipper::clipPolygon( + const QRect& clipRect, QPolygon& polygon, bool closePolygon ) +{ + QwtPolygonClipper< QPolygon, QRect, int > clipper( clipRect ); + clipper.clipPolygon( polygon, closePolygon ); +} + +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon IN/OUT + \param closePolygon True, when the polygon is closed + */ +void QwtClipper::clipPolygonF( + const QRectF& clipRect, QPolygonF& polygon, bool closePolygon ) +{ + QwtPolygonClipper< QPolygonF, QRectF, double > clipper( clipRect ); + 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::clippedPolygon( + const QRectF& clipRect, const QPolygon& polygon, bool closePolygon ) +{ + QPolygon points( polygon ); + clipPolygon( clipRect, points, closePolygon ); + + 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::clippedPolygon( + const QRect& clipRect, const QPolygon& polygon, bool closePolygon ) +{ + QPolygon points( polygon ); + clipPolygon( clipRect, points, closePolygon ); + + return points; +} + +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon + \param closePolygon True, when the polygon is closed + + \return Clipped polygon + */ +QPolygonF QwtClipper::clippedPolygonF( + const QRectF& clipRect, const QPolygonF& polygon, bool closePolygon ) +{ + QPolygonF points( polygon ); + clipPolygonF( clipRect, points, closePolygon ); + + return points; +} + +/*! + 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< QwtInterval > QwtClipper::clipCircle( const QRectF& clipRect, + const QPointF& center, double radius ) +{ + QwtCircleClipper clipper( clipRect ); + return clipper.clipCircle( center, radius ); +} diff --git a/libs/qwt/src/qwt_clipper.h b/libs/qwt/src/qwt_clipper.h new file mode 100644 index 00000000..346d616e --- /dev/null +++ b/libs/qwt/src/qwt_clipper.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * 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" + +class QwtInterval; +class QPointF; +class QRect; +class QRectF; +class QPolygon; +class QPolygonF; + +#if QT_VERSION < 0x060000 +template< typename T > class QVector; +#endif + +/*! + \brief Some clipping algorithms + */ + +namespace QwtClipper +{ + QWT_EXPORT void clipPolygon( const QRect&, + QPolygon&, bool closePolygon = false ); + + QWT_EXPORT void clipPolygon( const QRectF&, + QPolygon&, bool closePolygon = false ); + + QWT_EXPORT void clipPolygonF( const QRectF&, + QPolygonF&, bool closePolygon = false ); + + QWT_EXPORT QPolygon clippedPolygon( const QRect&, + const QPolygon&, bool closePolygon = false ); + + QWT_EXPORT QPolygon clippedPolygon( const QRectF&, + const QPolygon&, bool closePolygon = false ); + + QWT_EXPORT QPolygonF clippedPolygonF( const QRectF&, + const QPolygonF&, bool closePolygon = false ); + + QWT_EXPORT QVector< QwtInterval > clipCircle( + const QRectF&, const QPointF&, double radius ); +}; + +#endif diff --git a/libs/qwt/src/qwt_color_map.cpp b/libs/qwt/src/qwt_color_map.cpp new file mode 100644 index 00000000..e797d1d3 --- /dev/null +++ b/libs/qwt/src/qwt_color_map.cpp @@ -0,0 +1,1203 @@ +/****************************************************************************** + * 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_interval.h" + +#include + +static inline QRgb qwtHsvToRgb( int h, int s, int v, int a ) +{ +#if 0 + return QColor::fromHsv( h, s, v, a ).rgb(); +#else + + const double vs = v * s / 255.0; + const int p = v - qRound( vs ); + + switch( h / 60 ) + { + case 0: + { + const double r = ( 60 - h ) / 60.0; + return qRgba( v, v - qRound( r * vs ), p, a ); + } + case 1: + { + const double r = ( h - 60 ) / 60.0; + return qRgba( v - qRound( r * vs ), v, p, a ); + } + case 2: + { + const double r = ( 180 - h ) / 60.0; + return qRgba( p, v, v - qRound( r * vs ), a ); + } + case 3: + { + const double r = ( h - 180 ) / 60.0; + return qRgba( p, v - qRound( r * vs ), v, a ); + } + case 4: + { + const double r = ( 300 - h ) / 60.0; + return qRgba( v - qRound( r * vs ), p, v, a ); + } + case 5: + default: + { + const double r = ( h - 300 ) / 60.0; + return qRgba( v, p, v - qRound( r * vs ), a ); + } + } +#endif +} + +class QwtLinearColorMap::ColorStops +{ + public: + ColorStops(): + m_doAlpha( false ) + { + m_stops.reserve( 256 ); + } + + void insert( double pos, const QColor& color ); + QRgb rgb( QwtLinearColorMap::Mode, double pos ) const; + + QVector< double > stops() const; + + private: + + class ColorStop + { + public: + ColorStop(): + pos( 0.0 ), + rgb( 0 ) + { + }; + + ColorStop( double p, const QColor& c ): + pos( p ), + rgb( c.rgba() ) + { + r = qRed( rgb ); + g = qGreen( rgb ); + b = qBlue( rgb ); + a = qAlpha( rgb ); + + /* + when mapping a value to rgb we will have to calculate: + - const int v = int( ( s1.v0 + ratio * s1.vStep ) + 0.5 ); + + Thus adding 0.5 ( for rounding ) can be done in advance + */ + r0 = r + 0.5; + g0 = g + 0.5; + b0 = b + 0.5; + a0 = a + 0.5; + + rStep = gStep = bStep = aStep = 0.0; + posStep = 0.0; + } + + void updateSteps( const ColorStop& nextStop ) + { + rStep = nextStop.r - r; + gStep = nextStop.g - g; + bStep = nextStop.b - b; + aStep = nextStop.a - a; + posStep = nextStop.pos - pos; + } + + double pos; + QRgb rgb; + int r, g, b, a; + + // precalculated values + double rStep, gStep, bStep, aStep; + double r0, g0, b0, a0; + double posStep; + }; + + inline int findUpper( double pos ) const; + QVector< ColorStop > m_stops; + bool m_doAlpha; +}; + +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 ( m_stops.size() == 0 ) + { + index = 0; + m_stops.resize( 1 ); + } + else + { + index = findUpper( pos ); + if ( index == m_stops.size() || + qAbs( m_stops[index].pos - pos ) >= 0.001 ) + { + m_stops.resize( m_stops.size() + 1 ); + for ( int i = m_stops.size() - 1; i > index; i-- ) + m_stops[i] = m_stops[i - 1]; + } + } + + m_stops[index] = ColorStop( pos, color ); + if ( color.alpha() != 255 ) + m_doAlpha = true; + + if ( index > 0 ) + m_stops[index - 1].updateSteps( m_stops[index] ); + + if ( index < m_stops.size() - 1 ) + m_stops[index].updateSteps( m_stops[index + 1] ); +} + +inline QVector< double > QwtLinearColorMap::ColorStops::stops() const +{ + QVector< double > positions( m_stops.size() ); + for ( int i = 0; i < m_stops.size(); i++ ) + positions[i] = m_stops[i].pos; + return positions; +} + +inline int QwtLinearColorMap::ColorStops::findUpper( double pos ) const +{ + int index = 0; + int n = m_stops.size(); + + const ColorStop* stops = m_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 m_stops[0].rgb; + if ( pos >= 1.0 ) + return m_stops[ m_stops.size() - 1 ].rgb; + + const int index = findUpper( pos ); + if ( mode == FixedColors ) + { + return m_stops[index - 1].rgb; + } + else + { + const ColorStop& s1 = m_stops[index - 1]; + + const double ratio = ( pos - s1.pos ) / ( s1.posStep ); + + const int r = int( s1.r0 + ratio * s1.rStep ); + const int g = int( s1.g0 + ratio * s1.gStep ); + const int b = int( s1.b0 + ratio * s1.bStep ); + + if ( m_doAlpha ) + { + if ( s1.aStep ) + { + const int a = int( s1.a0 + ratio * s1.aStep ); + return qRgba( r, g, b, a ); + } + else + { + return qRgba( r, g, b, s1.a ); + } + } + else + { + return qRgb( r, g, b ); + } + } +} + +/*! + Constructor + \param format Format of the color map + */ +QwtColorMap::QwtColorMap( Format format ) + : m_format( format ) +{ +} + +//! Destructor +QwtColorMap::~QwtColorMap() +{ +} + +/*! + Set the format of the color map + + \param format Format of the color map + */ +void QwtColorMap::setFormat( Format format ) +{ + m_format = format; +} + +/*! + \brief Map a value of a given interval into a color index + + \param numColors Number of colors + \param interval Range for all values + \param value Value to map into a color index + + \return Index, between 0 and numColors - 1, or -1 for an invalid value + */ +uint QwtColorMap::colorIndex( int numColors, + const QwtInterval& interval, double value ) const +{ + const double width = interval.width(); + if ( width <= 0.0 ) + return 0; + + if ( value <= interval.minValue() ) + return 0; + + const int maxIndex = numColors - 1; + if ( value >= interval.maxValue() ) + return maxIndex; + + const double v = maxIndex * ( ( value - interval.minValue() ) / width ); + return static_cast< unsigned int >( v + 0.5 ); +} + +/*! + Build and return a color map of 256 colors + + The color table is needed for rendering indexed images in combination + with using colorIndex(). + + \return A color table, that can be used for a QImage + */ +QVector< QRgb > QwtColorMap::colorTable256() const +{ + QVector< QRgb > table( 256 ); + + const QwtInterval interval( 0, 256 ); + + for ( int i = 0; i < 256; i++ ) + table[i] = rgb( interval, i ); + + return table; +} + +/*! + Build and return a color map of arbitrary number of colors + + The color table is needed for rendering indexed images in combination + with using colorIndex(). + + \param numColors Number of colors + \return A color table + */ +QVector< QRgb > QwtColorMap::colorTable( int numColors ) const +{ + QVector< QRgb > table( numColors ); + + const QwtInterval interval( 0.0, 1.0 ); + + const double step = 1.0 / ( numColors - 1 ); + for ( int i = 0; i < numColors; i++ ) + table[i] = rgb( interval, 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 ) +{ + m_data = new PrivateData; + m_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 ) +{ + m_data = new PrivateData; + m_data->mode = ScaledColors; + setColorInterval( color1, color2 ); +} + +//! Destructor +QwtLinearColorMap::~QwtLinearColorMap() +{ + delete m_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 ) +{ + m_data->mode = mode; +} + +/*! + \return Mode of the color map + \sa setMode() + */ +QwtLinearColorMap::Mode QwtLinearColorMap::mode() const +{ + return m_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 ) +{ + m_data->colorStops = ColorStops(); + m_data->colorStops.insert( 0.0, color1 ); + m_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 ) + m_data->colorStops.insert( value, color ); +} + +/*! + \return Positions of color stops in increasing order + */ +QVector< double > QwtLinearColorMap::colorStops() const +{ + return m_data->colorStops.stops(); +} + +/*! + \return the first color of the color range + \sa setColorInterval() + */ +QColor QwtLinearColorMap::color1() const +{ + return QColor::fromRgba( m_data->colorStops.rgb( m_data->mode, 0.0 ) ); +} + +/*! + \return the second color of the color range + \sa setColorInterval() + */ +QColor QwtLinearColorMap::color2() const +{ + return QColor::fromRgba( m_data->colorStops.rgb( m_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 +{ + const double width = interval.width(); + if ( width <= 0.0 ) + return 0u; + + const double ratio = ( value - interval.minValue() ) / width; + return m_data->colorStops.rgb( m_data->mode, ratio ); +} + +/*! + \brief Map a value of a given interval into a color index + + \param numColors Size of the color table + \param interval Range for all values + \param value Value to map into a color index + + \return Index, between 0 and 255 + \note NaN values are mapped to 0 + */ +uint QwtLinearColorMap::colorIndex( int numColors, + const QwtInterval& interval, double value ) const +{ + const double width = interval.width(); + if ( width <= 0.0 ) + return 0; + + if ( value <= interval.minValue() ) + return 0; + + if ( value >= interval.maxValue() ) + return numColors - 1; + + const double v = ( numColors - 1 ) * ( value - interval.minValue() ) / width; + return static_cast< unsigned int >( ( m_data->mode == FixedColors ) ? v : v + 0.5 ); +} + +class QwtAlphaColorMap::PrivateData +{ + public: + PrivateData() + : alpha1(0) + , alpha2(255) + { + } + + int alpha1, alpha2; + + QColor color; + QRgb rgb; + + QRgb rgbMin; + QRgb rgbMax; +}; + + +/*! + \brief Constructor + + The alpha interval is initialized by 0 to 255. + + \param color Color of the map + + \sa setColor(), setAlphaInterval() + */ +QwtAlphaColorMap::QwtAlphaColorMap( const QColor& color ) + : QwtColorMap( QwtColorMap::RGB ) +{ + m_data = new PrivateData; + setColor( color ); +} + +//! Destructor +QwtAlphaColorMap::~QwtAlphaColorMap() +{ + delete m_data; +} + +/*! + Set the color + + \param color Color + \sa color() + */ +void QwtAlphaColorMap::setColor( const QColor& color ) +{ + m_data->color = color; + m_data->rgb = color.rgb() & qRgba( 255, 255, 255, 0 ); + + m_data->rgbMin = m_data->rgb | ( m_data->alpha1 << 24 ); + m_data->rgbMax = m_data->rgb | ( m_data->alpha2 << 24 ); +} + +/*! + \return the color + \sa setColor() + */ +QColor QwtAlphaColorMap::color() const +{ + return m_data->color; +} + +/*! + Set the interval for the alpha coordinate + + alpha1/alpha2 need to be in the range 0 to 255, + where 255 means opaque and 0 means transparent. + + \param alpha1 First alpha coordinate + \param alpha2 Second alpha coordinate + + \sa alpha1(), alpha2() + */ +void QwtAlphaColorMap::setAlphaInterval( int alpha1, int alpha2 ) +{ + m_data->alpha1 = qBound( 0, alpha1, 255 ); + m_data->alpha2 = qBound( 0, alpha2, 255 ); + + m_data->rgbMin = m_data->rgb | ( alpha1 << 24 ); + m_data->rgbMax = m_data->rgb | ( alpha2 << 24 ); +} + +/*! + \return First alpha coordinate + \sa setAlphaInterval() + */ +int QwtAlphaColorMap::alpha1() const +{ + return m_data->alpha1; +} + +/*! + \return Second alpha coordinate + \sa setAlphaInterval() + */ +int QwtAlphaColorMap::alpha2() const +{ + return m_data->alpha2; +} + +/*! + \brief Map a value of a given interval into a alpha value + + \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 ( width <= 0.0 ) + return 0u; + + if ( value <= interval.minValue() ) + return m_data->rgb; + + if ( value >= interval.maxValue() ) + return m_data->rgbMax; + + const double ratio = ( value - interval.minValue() ) / width; + const int alpha = m_data->alpha1 + qRound( ratio * ( m_data->alpha2 - m_data->alpha1 ) ); + + return m_data->rgb | ( alpha << 24 ); +} + +class QwtHueColorMap::PrivateData +{ + public: + PrivateData(); + + void updateTable(); + + int hue1, hue2; + int saturation; + int value; + int alpha; + + QRgb rgbMin; + QRgb rgbMax; + + QRgb rgbTable[360]; +}; + +QwtHueColorMap::PrivateData::PrivateData() + : hue1(0) + , hue2(359) + , saturation(255) + , value(255) + , alpha(255) +{ + updateTable(); +} + +void QwtHueColorMap::PrivateData::updateTable() +{ + const int p = qRound( value * ( 255 - saturation ) / 255.0 ); + const double vs = value * saturation / 255.0; + + for ( int i = 0; i < 60; i++ ) + { + const double r = ( 60 - i ) / 60.0; + rgbTable[i] = qRgba( value, qRound( value - r * vs ), p, alpha ); + } + + for ( int i = 60; i < 120; i++ ) + { + const double r = ( i - 60 ) / 60.0; + rgbTable[i] = qRgba( qRound( value - r * vs ), value, p, alpha ); + } + + for ( int i = 120; i < 180; i++ ) + { + const double r = ( 180 - i ) / 60.0; + rgbTable[i] = qRgba( p, value, qRound( value - r * vs ), alpha ); + } + + for ( int i = 180; i < 240; i++ ) + { + const double r = ( i - 180 ) / 60.0; + rgbTable[i] = qRgba( p, qRound( value - r * vs ), value, alpha ); + } + + for ( int i = 240; i < 300; i++ ) + { + const double r = ( 300 - i ) / 60.0; + rgbTable[i] = qRgba( qRound( value - r * vs ), p, value, alpha ); + } + + for ( int i = 300; i < 360; i++ ) + { + const double r = ( i - 300 ) / 60.0; + rgbTable[i] = qRgba( value, p, qRound( value - r * vs ), alpha ); + } + + rgbMin = rgbTable[ hue1 % 360 ]; + rgbMax = rgbTable[ hue2 % 360 ]; +} + +/*! + \brief Constructor + + The hue interval is initialized by 0 to 359. All other coordinates + are set to 255. + + \param format Format of the color map + + \sa setHueInterval(), setSaturation(), setValue(), setValue() + */ +QwtHueColorMap::QwtHueColorMap( QwtColorMap::Format format ) + : QwtColorMap( format ) +{ + m_data = new PrivateData; +} + +//! Destructor +QwtHueColorMap::~QwtHueColorMap() +{ + delete m_data; +} + +/*! + Set the interval for the hue coordinate + + hue1/hue2 need to be positive number and can be > 360 to define cycles. + F.e. 420 to 240 defines a map yellow/red/magenta/blue. + + \param hue1 First hue coordinate + \param hue2 Second hue coordinate + + \sa hue1(), hue2() + */ +void QwtHueColorMap::setHueInterval( int hue1, int hue2 ) +{ + m_data->hue1 = qMax( hue1, 0 ); + m_data->hue2 = qMax( hue2, 0 ); + + m_data->rgbMin = m_data->rgbTable[ hue1 % 360 ]; + m_data->rgbMax = m_data->rgbTable[ hue2 % 360 ]; +} + +/*! + \brief Set the the saturation coordinate + + saturation needs to be in the range 0 to 255, + + \param saturation Saturation coordinate + + \sa saturation() + */ +void QwtHueColorMap::setSaturation( int saturation ) +{ + saturation = qBound( 0, saturation, 255 ); + + if ( saturation != m_data->saturation ) + { + m_data->saturation = saturation; + m_data->updateTable(); + } +} + +/*! + \brief Set the the value coordinate + + value needs to be in the range 0 to 255, + + \param value Value coordinate + + \sa value() + */ +void QwtHueColorMap::setValue( int value ) +{ + value = qBound( 0, value, 255 ); + + if ( value != m_data->value ) + { + m_data->value = value; + m_data->updateTable(); + } +} + +/*! + \brief Set the the alpha coordinate + + alpha needs to be in the range 0 to 255, + where 255 means opaque and 0 means transparent. + + \param alpha Alpha coordinate + + \sa alpha() + */ +void QwtHueColorMap::setAlpha( int alpha ) +{ + alpha = qBound( 0, alpha, 255 ); + + if ( alpha != m_data->alpha ) + { + m_data->alpha = alpha; + m_data->updateTable(); + } +} + +/*! + \return First hue coordinate + \sa setHueInterval() + */ +int QwtHueColorMap::hue1() const +{ + return m_data->hue1; +} + +/*! + \return Second hue coordinate + \sa setHueInterval() + */ +int QwtHueColorMap::hue2() const +{ + return m_data->hue2; +} + +/*! + \return Saturation coordinate + \sa setSaturation() + */ +int QwtHueColorMap::saturation() const +{ + return m_data->saturation; +} + +/*! + \return Value coordinate + \sa setValue() + */ +int QwtHueColorMap::value() const +{ + return m_data->value; +} + +/*! + \return Alpha coordinate + \sa setAlpha() + */ +int QwtHueColorMap::alpha() const +{ + return m_data->alpha; +} + +/*! + 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 QwtHueColorMap::rgb( const QwtInterval& interval, double value ) const +{ + const double width = interval.width(); + if ( width <= 0 ) + return 0u; + + if ( value <= interval.minValue() ) + return m_data->rgbMin; + + if ( value >= interval.maxValue() ) + return m_data->rgbMax; + + const double ratio = ( value - interval.minValue() ) / width; + + int hue = m_data->hue1 + qRound( ratio * ( m_data->hue2 - m_data->hue1 ) ); + if ( hue >= 360 ) + { + hue -= 360; + + if ( hue >= 360 ) + hue = hue % 360; + } + + return m_data->rgbTable[hue]; +} + +class QwtSaturationValueColorMap::PrivateData +{ + public: + PrivateData() + : hue(0) + , sat1(255) + , sat2(255) + , value1(0) + , value2(255) + , alpha(255) + , tableType(Invalid) + { + updateTable(); + } + + void updateTable() + { + tableType = Invalid; + + if ( ( value1 == value2 ) && ( sat1 != sat2 ) ) + { + rgbTable.resize( 256 ); + + for ( int i = 0; i < 256; i++ ) + rgbTable[i] = qwtHsvToRgb( hue, i, value1, alpha ); + + tableType = Saturation; + } + else if ( ( value1 != value2 ) && ( sat1 == sat2 ) ) + { + rgbTable.resize( 256 ); + + for ( int i = 0; i < 256; i++ ) + rgbTable[i] = qwtHsvToRgb( hue, sat1, i, alpha ); + + tableType = Value; + } + else + { + rgbTable.resize( 256 * 256 ); + + for ( int s = 0; s < 256; s++ ) + { + const int v0 = s * 256; + + for ( int v = 0; v < 256; v++ ) + rgbTable[v0 + v] = qwtHsvToRgb( hue, s, v, alpha ); + } + } + } + + int hue; + int sat1, sat2; + int value1, value2; + int alpha; + + enum + { + Invalid, + Value, + Saturation + + } tableType; + + QVector< QRgb > rgbTable; +}; + +/*! + \brief Constructor + + The value interval is initialized by 0 to 255, + saturation by 255 to 255. Hue to 0 and alpha to 255. + + So the default setting interpolates the value coordinate only. + + \sa setHueInterval(), setSaturation(), setValue(), setValue() + */ +QwtSaturationValueColorMap::QwtSaturationValueColorMap() +{ + m_data = new PrivateData; +} + +//! Destructor +QwtSaturationValueColorMap::~QwtSaturationValueColorMap() +{ + delete m_data; +} + +/*! + \brief Set the the hue coordinate + + Hue coordinates outside 0 to 359 will be interpreted as hue % 360.. + + \param hue Hue coordinate + + \sa hue() + */ +void QwtSaturationValueColorMap::setHue( int hue ) +{ + hue = hue % 360; + + if ( hue != m_data->hue ) + { + m_data->hue = hue; + m_data->updateTable(); + } +} + +/*! + \brief Set the interval for the saturation coordinate + + When saturation1 == saturation2 the map interpolates between + the value coordinates only + + saturation1/saturation2 need to be in the range 0 to 255. + + \param saturation1 First saturation + \param saturation2 Second saturation + + \sa saturation1(), saturation2(), setValueInterval() + */ +void QwtSaturationValueColorMap::setSaturationInterval( + int saturation1, int saturation2 ) +{ + saturation1 = qBound( 0, saturation1, 255 ); + saturation2 = qBound( 0, saturation2, 255 ); + + if ( ( saturation1 != m_data->sat1 ) || ( saturation2 != m_data->sat2 ) ) + { + m_data->sat1 = saturation1; + m_data->sat2 = saturation2; + + m_data->updateTable(); + } +} + +/*! + \brief Set the interval for the value coordinate + + When value1 == value2 the map interpolates between the saturation coordinates only. + + value1/value2 need to be in the range 0 to 255. + + \param value1 First value + \param value2 Second value + + \sa value1(), value2(), setSaturationInterval() + */ +void QwtSaturationValueColorMap::setValueInterval( int value1, int value2 ) +{ + value1 = qBound( 0, value1, 255 ); + value2 = qBound( 0, value2, 255 ); + + if ( ( value1 != m_data->value1 ) || ( value2 != m_data->value2 ) ) + { + m_data->value1 = value1; + m_data->value2 = value2; + + m_data->updateTable(); + } +} + +/*! + \brief Set the the alpha coordinate + + alpha needs to be in the range 0 to 255, + where 255 means opaque and 0 means transparent. + + \param alpha Alpha coordinate + + \sa alpha() + */ +void QwtSaturationValueColorMap::setAlpha( int alpha ) +{ + alpha = qBound( 0, alpha, 255 ); + + if ( alpha != m_data->alpha ) + { + m_data->alpha = alpha; + m_data->updateTable(); + } +} + +/*! + \return Hue coordinate + \sa setHue() + */ +int QwtSaturationValueColorMap::hue() const +{ + return m_data->hue; +} + +/*! + \return First saturation coordinate + \sa setSaturationInterval() + */ +int QwtSaturationValueColorMap::saturation1() const +{ + return m_data->sat1; +} + +/*! + \return Second saturation coordinate + \sa setSaturationInterval() + */ +int QwtSaturationValueColorMap::saturation2() const +{ + return m_data->sat2; +} + +/*! + \return First value coordinate + \sa setValueInterval() + */ +int QwtSaturationValueColorMap::value1() const +{ + return m_data->value1; +} + +/*! + \return Second value coordinate + \sa setValueInterval() + */ +int QwtSaturationValueColorMap::value2() const +{ + return m_data->value2; +} + +/*! + \return Alpha coordinate + \sa setAlpha() + */ +int QwtSaturationValueColorMap::alpha() const +{ + return m_data->alpha; +} + +/*! + 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 QwtSaturationValueColorMap::rgb( + const QwtInterval& interval, double value ) const +{ + const double width = interval.width(); + if ( width <= 0 ) + return 0u; + + const QRgb* rgbTable = m_data->rgbTable.constData(); + + switch( m_data->tableType ) + { + case PrivateData::Saturation: + { + if ( value <= interval.minValue() ) + return m_data->rgbTable[m_data->sat1]; + + if ( value >= interval.maxValue() ) + return m_data->rgbTable[m_data->sat2]; + + const double ratio = ( value - interval.minValue() ) / width; + const int sat = m_data->sat1 + + qRound( ratio * ( m_data->sat2 - m_data->sat1 ) ); + + return rgbTable[sat]; + } + case PrivateData::Value: + { + if ( value <= interval.minValue() ) + return m_data->rgbTable[m_data->value1]; + + if ( value >= interval.maxValue() ) + return m_data->rgbTable[m_data->value2]; + + const double ratio = ( value - interval.minValue() ) / width; + const int v = m_data->value1 + + qRound( ratio * ( m_data->value2 - m_data->value1 ) ); + + return rgbTable[ v ]; + } + default: + { + int s, v; + if ( value <= interval.minValue() ) + { + s = m_data->sat1; + v = m_data->value1; + } + else if ( value >= interval.maxValue() ) + { + s = m_data->sat2; + v = m_data->value2; + } + else + { + const double ratio = ( value - interval.minValue() ) / width; + + v = m_data->value1 + qRound( ratio * ( m_data->value2 - m_data->value1 ) ); + s = m_data->sat1 + qRound( ratio * ( m_data->sat2 - m_data->sat1 ) ); + } + + return rgbTable[ 256 * s + v ]; + } + } +} diff --git a/libs/qwt/src/qwt_color_map.h b/libs/qwt/src/qwt_color_map.h new file mode 100644 index 00000000..247e7b84 --- /dev/null +++ b/libs/qwt/src/qwt_color_map.h @@ -0,0 +1,262 @@ +/****************************************************************************** + * 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 + +class QwtInterval; + +#if QT_VERSION < 0x060000 +template< typename T > class QVector; +#endif + +/*! + \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, + + /*! + Map values into 8 bit values, that + are used as indexes into the color table. + + Indexed color maps are used to generate QImage::Format_Indexed8 + images. The calculation of the color index is usually faster + and the resulting image has a lower memory footprint. + + \sa colorIndex(), colorTable() + */ + Indexed + }; + + explicit QwtColorMap( Format = QwtColorMap::RGB ); + virtual ~QwtColorMap(); + + void setFormat( Format ); + 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; + + virtual uint colorIndex( int numColors, + const QwtInterval& interval, double value ) const; + + QColor color( const QwtInterval&, double value ) const; + virtual QVector< QRgb > colorTable( int numColors ) const; + virtual QVector< QRgb > colorTable256() const; + + private: + Q_DISABLE_COPY(QwtColorMap) + + Format m_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 + }; + + explicit 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< double > colorStops() const; + + QColor color1() const; + QColor color2() const; + + virtual QRgb rgb( const QwtInterval&, + double value ) const QWT_OVERRIDE; + + virtual uint colorIndex( int numColors, + const QwtInterval&, double value ) const QWT_OVERRIDE; + + class ColorStops; + + private: + class PrivateData; + PrivateData* m_data; +}; + +/*! + \brief QwtAlphaColorMap varies the alpha value of a color + */ +class QWT_EXPORT QwtAlphaColorMap : public QwtColorMap +{ + public: + explicit QwtAlphaColorMap( const QColor& = QColor( Qt::gray ) ); + virtual ~QwtAlphaColorMap(); + + void setAlphaInterval( int alpha1, int alpha2 ); + + int alpha1() const; + int alpha2() const; + + void setColor( const QColor& ); + QColor color() const; + + virtual QRgb rgb( const QwtInterval&, + double value ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +/*! + \brief QwtHueColorMap varies the hue value of the HSV color model. + + QwtHueColorMap can be used to set up a color map easily, that runs cyclic over + all colors. Each cycle has 360 different steps. + + The values for value and saturation are in the range of 0 to 255 and doesn't + depend on the data value to be mapped. + + \sa QwtSaturationValueColorMap + */ +class QWT_EXPORT QwtHueColorMap : public QwtColorMap +{ + public: + explicit QwtHueColorMap( QwtColorMap::Format = QwtColorMap::RGB ); + virtual ~QwtHueColorMap(); + + void setHueInterval( int hue1, int hue2 ); // direction ? + void setSaturation( int saturation ); + void setValue( int value ); + void setAlpha( int alpha ); + + int hue1() const; + int hue2() const; + int saturation() const; + int value() const; + int alpha() const; + + virtual QRgb rgb( const QwtInterval&, + double value ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +/*! + \brief QwtSaturationValueColorMap varies the saturation and/or value for a given + hue in the HSV color model. + + Value and saturation are in the range of 0 to 255 while hue is in the range + of 0 to 259. + + \sa QwtHueColorMap + */ +class QWT_EXPORT QwtSaturationValueColorMap : public QwtColorMap +{ + public: + QwtSaturationValueColorMap(); + virtual ~QwtSaturationValueColorMap(); + + void setHue( int hue ); + void setSaturationInterval( int sat1, int sat2 ); + void setValueInterval( int value1, int value2 ); + void setAlpha( int alpha ); + + int hue() const; + int saturation1() const; + int saturation2() const; + int value1() const; + int value2() const; + int alpha() const; + + virtual QRgb rgb( const QwtInterval&, + double value ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +/*! + Map a value into a color + + \param interval Valid interval for values + \param value Value + + \return Color corresponding to value + */ +inline QColor QwtColorMap::color( const QwtInterval& interval, double value ) const +{ + return QColor::fromRgba( rgb( interval, value ) ); +} + +/*! + \return Intended format of the color map + \sa Format + */ +inline QwtColorMap::Format QwtColorMap::format() const +{ + return m_format; +} + +#endif diff --git a/libs/qwt/src/qwt_column_symbol.cpp b/libs/qwt/src/qwt_column_symbol.cpp new file mode 100644 index 00000000..c2cc6989 --- /dev/null +++ b/libs/qwt/src/qwt_column_symbol.cpp @@ -0,0 +1,318 @@ +/****************************************************************************** + * 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_painter.h" +#include "qwt_math.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 = qwtMinF( lw, rect.height() / 2.0 - 1.0 ); + lw = qwtMinF( 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 = qwtMinF( lw, rect.height() / 2.0 - 1.0 ); + lw = qwtMinF( 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 ) + , palette( Qt::gray ) + , lineWidth( 2 ) + { + } + + 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 ) +{ + m_data = new PrivateData(); + m_data->style = style; +} + +//! Destructor +QwtColumnSymbol::~QwtColumnSymbol() +{ + delete m_data; +} + +/*! + Specify the symbol style + + \param style Style + \sa style(), setPalette() + */ +void QwtColumnSymbol::setStyle( Style style ) +{ + m_data->style = style; +} + +/*! + \return Current symbol style + \sa setStyle() + */ +QwtColumnSymbol::Style QwtColumnSymbol::style() const +{ + return m_data->style; +} + +/*! + Assign a palette for the symbol + + \param palette Palette + \sa palette(), setStyle() + */ +void QwtColumnSymbol::setPalette( const QPalette& palette ) +{ + m_data->palette = palette; +} + +/*! + \return Current palette + \sa setPalette() + */ +const QPalette& QwtColumnSymbol::palette() const +{ + return m_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 ) +{ + m_data->frameStyle = frameStyle; +} + +/*! + \return Current frame style, that is used for the Box style. + \sa setFrameStyle(), lineWidth(), setStyle() + */ +QwtColumnSymbol::FrameStyle QwtColumnSymbol::frameStyle() const +{ + return m_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; + + m_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 m_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 ( m_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 ( m_data->frameStyle ) + { + case QwtColumnSymbol::Raised: + { + qwtDrawPanel( painter, r, m_data->palette, m_data->lineWidth ); + break; + } + case QwtColumnSymbol::Plain: + { + qwtDrawBox( painter, r, m_data->palette, m_data->lineWidth ); + break; + } + default: + { + painter->fillRect( r.adjusted( 0, 0, 1, 1 ), m_data->palette.window() ); + } + } +} + +//! \return A normalized QRect built from the intervals +QRectF QwtColumnRect::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; +} + diff --git a/libs/qwt/src/qwt_column_symbol.h b/libs/qwt/src/qwt_column_symbol.h new file mode 100644 index 00000000..a29ff51c --- /dev/null +++ b/libs/qwt/src/qwt_column_symbol.h @@ -0,0 +1,144 @@ +/****************************************************************************** + * 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 + +class QPainter; +class QPalette; +class QRectF; + +/*! + \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; + + //! \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: + explicit QwtColumnSymbol( Style = NoStyle ); + virtual ~QwtColumnSymbol(); + + void setFrameStyle( FrameStyle ); + 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: + Q_DISABLE_COPY(QwtColumnSymbol) + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_compass.cpp b/libs/qwt/src/qwt_compass.cpp new file mode 100644 index 00000000..783edf74 --- /dev/null +++ b/libs/qwt/src/qwt_compass.cpp @@ -0,0 +1,329 @@ +/****************************************************************************** + * 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_text.h" + +#include +#include + +class QwtCompassScaleDraw::PrivateData +{ + public: + QMap< double, QString > labelMap; +}; + +/*! + \brief Constructor + + Initializes a label map for multiples of 45 degrees + */ +QwtCompassScaleDraw::QwtCompassScaleDraw() +{ + m_data = new PrivateData; + + enableComponent( QwtAbstractScaleDraw::Backbone, false ); + enableComponent( QwtAbstractScaleDraw::Ticks, false ); + + QMap< double, QString >& map = m_data->labelMap; + + map.insert( 0.0, QString::fromLatin1( "N" ) ); + map.insert( 45.0, QString::fromLatin1( "NE" ) ); + map.insert( 90.0, QString::fromLatin1( "E" ) ); + map.insert( 135.0, QString::fromLatin1( "SE" ) ); + map.insert( 180.0, QString::fromLatin1( "S" ) ); + map.insert( 225.0, QString::fromLatin1( "SW" ) ); + map.insert( 270.0, QString::fromLatin1( "W" ) ); + map.insert( 315.0, QString::fromLatin1( "NW" ) ); + +#if 0 + map.insert( 22.5, QString::fromLatin1( "NNE" ) ); + map.insert( 67.5, QString::fromLatin1( "NEE" ) ); + map.insert( 112.5, QString::fromLatin1( "SEE" ) ); + map.insert( 157.5, QString::fromLatin1( "SSE" ) ); + map.insert( 202.5, QString::fromLatin1( "SSW" ) ); + map.insert( 247.5, QString::fromLatin1( "SWW" ) ); + map.insert( 292.5, QString::fromLatin1( "NWW" ) ); + map.insert( 337.5, QString::fromLatin1( "NNW" ) ); +#endif +} + +/*! + \brief Constructor + + \param map Value to label map + */ +QwtCompassScaleDraw::QwtCompassScaleDraw( const QMap< double, QString >& map ) +{ + m_data = new PrivateData; + m_data->labelMap = map; + + enableComponent( QwtAbstractScaleDraw::Backbone, false ); + enableComponent( QwtAbstractScaleDraw::Ticks, false ); +} + +//! Destructor +QwtCompassScaleDraw::~QwtCompassScaleDraw() +{ + delete m_data; +} + +/*! + \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< double, QString >& map ) +{ + m_data->labelMap = map; +} + +/*! + \return map, mapping values to labels + \sa setLabelMap() + */ +QMap< double, QString > QwtCompassScaleDraw::labelMap() const +{ + return m_data->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 + \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; + + QMap< double, QString >::const_iterator it = + m_data->labelMap.constFind( value ); + + if ( it != m_data->labelMap.constEnd() ) + return *it; + + 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 ) +{ + m_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 m_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& center, 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& center, + double radius, double north, QPalette::ColorGroup cg ) const +{ + if ( m_data->rose ) + m_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 != m_data->rose ) + { + if ( m_data->rose ) + delete m_data->rose; + + m_data->rose = rose; + update(); + } +} + +/*! + \return rose + \sa setRose() + */ +const QwtCompassRose* QwtCompass::rose() const +{ + return m_data->rose; +} + +/*! + \return rose + \sa setRose() + */ +QwtCompassRose* QwtCompass::rose() +{ + return m_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 ); + } +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_compass.cpp" +#endif diff --git a/libs/qwt/src/qwt_compass.h b/libs/qwt/src/qwt_compass.h new file mode 100644 index 00000000..23466d42 --- /dev/null +++ b/libs/qwt/src/qwt_compass.h @@ -0,0 +1,87 @@ +/****************************************************************************** + * 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 + +#include "qwt_global.h" +#include "qwt_dial.h" +#include "qwt_round_scale_draw.h" + +class QwtCompassRose; +class QString; +template< class Key, class T > class QMap; + + +/*! + \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< double, QString >& map ); + + virtual ~QwtCompassScaleDraw(); + + void setLabelMap( const QMap< double, QString >& map ); + QMap< double, QString > labelMap() const; + + virtual QwtText label( double value ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +/*! + \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& center, + double radius, double north, QPalette::ColorGroup ) const; + + virtual void drawScaleContents( QPainter*, + const QPointF& center, double radius ) const QWT_OVERRIDE; + + virtual void keyPressEvent( QKeyEvent* ) QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_compass_rose.cpp b/libs/qwt/src/qwt_compass_rose.cpp new file mode 100644 index 00000000..1903f707 --- /dev/null +++ b/libs/qwt/src/qwt_compass_rose.cpp @@ -0,0 +1,296 @@ +/****************************************************************************** + * 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 +#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 QT_VERSION >= 0x050e00 + if ( line1.intersects( line2, &pos ) == QLineF::NoIntersection ) +#else + if ( line1.intersect( line2, &pos ) == QLineF::NoIntersection ) +#endif + return QPointF(); + + return pos; +} + +//! Constructor +QwtCompassRose::QwtCompassRose() +{ +} + +//! Destructor +QwtCompassRose::~QwtCompassRose() +{ +} + +//! Assign a palette +void QwtCompassRose::setPalette( const QPalette& p ) +{ + m_palette = p; +} + +//! \return Current palette +const QPalette& QwtCompassRose::palette() const +{ + return m_palette; +} + +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 ) +{ + m_data = new PrivateData(); + m_data->numThorns = numThorns; + m_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 m_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 ) +{ + m_data->shrinkFactor = factor; +} + +/*! + \return Factor how to shrink the thorns with each level + \sa setShrinkFactor() + */ +double QwtSimpleCompassRose::shrinkFactor() const +{ + return m_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& center, + double radius, double north, QPalette::ColorGroup cg ) const +{ + QPalette pal = palette(); + pal.setCurrentColorGroup( cg ); + + drawRose( painter, pal, center, radius, north, m_data->width, + m_data->numThorns, m_data->numThornLevels, m_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& center, 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 = std::pow( 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 ) +{ + m_data->width = width; + if ( m_data->width < 0.03 ) + m_data->width = 0.03; + + if ( m_data->width > 0.4 ) + m_data->width = 0.4; +} + +/*! + \return Width of the rose + \sa setWidth() + */ +double QwtSimpleCompassRose::width() const +{ + return m_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; + + m_data->numThorns = numThorns; +} + +/*! + \return Number of thorns + \sa setNumThorns(), setNumThornLevels() + */ +int QwtSimpleCompassRose::numThorns() const +{ + return m_data->numThorns; +} + +/*! + Set the of thorns levels + + \param numThornLevels Number of thorns levels + \sa setNumThorns(), numThornLevels() + */ +void QwtSimpleCompassRose::setNumThornLevels( int numThornLevels ) +{ + m_data->numThornLevels = numThornLevels; +} + +/*! + \return Number of thorn levels + \sa setNumThorns(), setNumThornLevels() + */ +int QwtSimpleCompassRose::numThornLevels() const +{ + return m_data->numThornLevels; +} diff --git a/libs/qwt/src/qwt_compass_rose.h b/libs/qwt/src/qwt_compass_rose.h new file mode 100644 index 00000000..a4f7f330 --- /dev/null +++ b/libs/qwt/src/qwt_compass_rose.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * 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 + +#include "qwt_global.h" +#include + +class QPainter; + +/*! + \brief Abstract base class for a compass rose + */ +class QWT_EXPORT QwtCompassRose +{ + public: + QwtCompassRose(); + virtual ~QwtCompassRose(); + + virtual void setPalette( const QPalette& ); + const QPalette& palette() const; + + /*! + 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& center, double radius, double north, + QPalette::ColorGroup colorGroup = QPalette::Active ) const = 0; + + private: + Q_DISABLE_COPY(QwtCompassRose) + + QPalette m_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 ); + double width() const; + + void setNumThorns( int ); + int numThorns() const; + + void setNumThornLevels( int ); + int numThornLevels() const; + + void setShrinkFactor( double factor ); + double shrinkFactor() const; + + virtual void draw( QPainter*, + const QPointF& center, double radius, double north, + QPalette::ColorGroup = QPalette::Active ) const QWT_OVERRIDE; + + static void drawRose( QPainter*, const QPalette&, + const QPointF& center, double radius, double north, double width, + int numThorns, int numThornLevels, double shrinkFactor ); + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_counter.cpp b/libs/qwt/src/qwt_counter.cpp new file mode 100644 index 00000000..e9be373f --- /dev/null +++ b/libs/qwt/src/qwt_counter.cpp @@ -0,0 +1,794 @@ +/****************************************************************************** + * 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_counter.h" +#include "qwt_painter.h" +#include "qwt_math.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() +{ + m_data = new PrivateData; + + QHBoxLayout* layout = new QHBoxLayout( this ); + layout->setSpacing( 0 ); + layout->setContentsMargins( QMargins() ); + + for ( int i = ButtonCnt - 1; i >= 0; i-- ) + { + QwtArrowButton* btn = + new QwtArrowButton( i + 1, Qt::DownArrow, this ); + btn->setFocusPolicy( Qt::NoFocus ); + layout->addWidget( btn ); + + connect( btn, SIGNAL(released()), SLOT(btnReleased()) ); + connect( btn, SIGNAL(clicked()), SLOT(btnClicked()) ); + + m_data->buttonDown[i] = btn; + } + + m_data->valueEdit = new QLineEdit( this ); + m_data->valueEdit->setReadOnly( false ); + m_data->valueEdit->setValidator( new QDoubleValidator( m_data->valueEdit ) ); + layout->addWidget( m_data->valueEdit ); + + connect( m_data->valueEdit, SIGNAL(editingFinished()), SLOT(textChanged()) ); + + layout->setStretchFactor( m_data->valueEdit, 10 ); + + for ( int i = 0; i < ButtonCnt; i++ ) + { + QwtArrowButton* btn = + new QwtArrowButton( i + 1, Qt::UpArrow, this ); + btn->setFocusPolicy( Qt::NoFocus ); + layout->addWidget( btn ); + + connect( btn, SIGNAL(released()), SLOT(btnReleased()) ); + connect( btn, SIGNAL(clicked()), SLOT(btnClicked()) ); + + m_data->buttonUp[i] = btn; + } + + setNumButtons( 2 ); + setRange( 0.0, 1.0 ); + setSingleStep( 0.001 ); + setValue( 0.0 ); + + setSizePolicy( + QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); + + setFocusProxy( m_data->valueEdit ); + setFocusPolicy( Qt::StrongFocus ); +} + +//! Destructor +QwtCounter::~QwtCounter() +{ + delete m_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 != m_data->isValid ) + { + m_data->isValid = on; + + updateButtons(); + + if ( m_data->isValid ) + { + showNumber( value() ); + Q_EMIT valueChanged( value() ); + } + else + { + m_data->valueEdit->setText( QString() ); + } + } +} + +/*! + \return True, if the value is valid + \sa setValid(), setValue() + */ +bool QwtCounter::isValid() const +{ + return m_data->isValid; +} + +/*! + \brief Allow/disallow the user to manually edit the value + + \param on True disable editing + \sa isReadOnly() + */ +void QwtCounter::setReadOnly( bool on ) +{ + m_data->valueEdit->setReadOnly( on ); +} + +/*! + \return True, when the line line edit is read only. (default is no) + \sa setReadOnly() + */ +bool QwtCounter::isReadOnly() const +{ + return m_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 = qwtMinF( m_data->minimum, m_data->maximum ); + const double vmax = qwtMaxF( m_data->minimum, m_data->maximum ); + + value = qBound( vmin, value, vmax ); + + if ( !m_data->isValid || value != m_data->value ) + { + m_data->isValid = true; + m_data->value = value; + + showNumber( value ); + updateButtons(); + + Q_EMIT valueChanged( value ); + } +} + +/*! + \return Current value of the counter + \sa setValue(), valueChanged() + */ +double QwtCounter::value() const +{ + return m_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 = qwtMaxF( min, max ); + + if ( m_data->maximum == max && m_data->minimum == min ) + return; + + m_data->minimum = min; + m_data->maximum = max; + + setSingleStep( singleStep() ); + + const double value = qBound( min, m_data->value, max ); + + if ( value != m_data->value ) + { + m_data->value = value; + + if ( m_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 m_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 m_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 ) +{ + m_data->singleStep = qwtMaxF( stepSize, 0.0 ); +} + +/*! + \return Single step size + \sa setSingleStep() + */ +double QwtCounter::singleStep() const +{ + return m_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 ) +{ + m_data->wrapping = on; +} + +/*! + \return True, when wrapping is set + \sa setWrapping() + */ +bool QwtCounter::wrapping() const +{ + return m_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 ) + { + m_data->buttonDown[i]->show(); + m_data->buttonUp[i]->show(); + } + else + { + m_data->buttonDown[i]->hide(); + m_data->buttonUp[i]->hide(); + } + } + + m_data->numButtons = numButtons; +} + +/*! + \return The number of buttons on each side of the widget. + \sa setNumButtons() + */ +int QwtCounter::numButtons() const +{ + return m_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 ) + m_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 m_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 = m_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 QFontMetrics fm = m_data->valueEdit->fontMetrics(); + + const int w = QwtPainter::horizontalAdvance( fm, "W" ) + 8; + for ( int i = 0; i < ButtonCnt; i++ ) + { + m_data->buttonDown[i]->setMinimumWidth( w ); + m_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( m_data->increment[0] ); + break; + } + case Qt::Key_Down: + { + incrementValue( -m_data->increment[0] ); + break; + } + case Qt::Key_PageUp: + case Qt::Key_PageDown: + { + int increment = m_data->increment[0]; + if ( m_data->numButtons >= 2 ) + increment = m_data->increment[1]; + if ( m_data->numButtons >= 3 ) + { + if ( event->modifiers() & Qt::ShiftModifier ) + increment = m_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 ( m_data->numButtons <= 0 ) + return; + + int increment = m_data->increment[0]; + if ( m_data->numButtons >= 2 ) + { + if ( event->modifiers() & Qt::ControlModifier ) + increment = m_data->increment[1]; + } + if ( m_data->numButtons >= 3 ) + { + if ( event->modifiers() & Qt::ShiftModifier ) + increment = m_data->increment[2]; + } + +#if QT_VERSION < 0x050e00 + const QPoint wheelPos = event->pos(); + const int wheelDelta = event->delta(); +#else + const QPoint wheelPos = event->position().toPoint(); + + const QPoint delta = event->angleDelta(); + const int wheelDelta = ( qAbs( delta.x() ) > qAbs( delta.y() ) ) + ? delta.x() : delta.y(); +#endif + + for ( int i = 0; i < m_data->numButtons; i++ ) + { + if ( m_data->buttonDown[i]->geometry().contains( wheelPos ) || + m_data->buttonUp[i]->geometry().contains( wheelPos ) ) + { + increment = m_data->increment[i]; + } + } + + incrementValue( wheelDelta / 120 * increment ); +} + +void QwtCounter::incrementValue( int numSteps ) +{ + const double min = m_data->minimum; + const double max = m_data->maximum; + double stepSize = m_data->singleStep; + + if ( !m_data->isValid || min >= max || stepSize <= 0.0 ) + return; + + +#if 1 + stepSize = qwtMaxF( stepSize, 1.0e-10 * ( max - min ) ); +#endif + + double value = m_data->value + numSteps * stepSize; + + if ( m_data->wrapping ) + { + const double range = max - min; + + if ( value < min ) + { + value += std::ceil( ( min - value ) / range ) * range; + } + else if ( value > max ) + { + value -= std::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 != m_data->value ) + { + m_data->value = value; + showNumber( m_data->value ); + updateButtons(); + + Q_EMIT valueChanged( m_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 ( m_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++ ) + { + m_data->buttonDown[i]->setEnabled( value() > minimum() ); + m_data->buttonUp[i]->setEnabled( value() < maximum() ); + } + } + else + { + for ( int i = 0; i < QwtCounter::ButtonCnt; i++ ) + { + m_data->buttonDown[i]->setEnabled( false ); + m_data->buttonUp[i]->setEnabled( false ); + } + } +} +/*! + Display number string + + \param number Number + */ +void QwtCounter::showNumber( double number ) +{ + QString text; + text.setNum( number ); + + const int cursorPos = m_data->valueEdit->cursorPosition(); + m_data->valueEdit->setText( text ); + m_data->valueEdit->setCursorPosition( cursorPos ); +} + +//! Button clicked +void QwtCounter::btnClicked() +{ + for ( int i = 0; i < ButtonCnt; i++ ) + { + if ( m_data->buttonUp[i] == sender() ) + incrementValue( m_data->increment[i] ); + + if ( m_data->buttonDown[i] == sender() ) + incrementValue( -m_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 ); + + w = QwtPainter::horizontalAdvance( m_data->valueEdit->fontMetrics(), tmp ) + 2; + + if ( m_data->valueEdit->hasFrame() ) + w += 2 * style()->pixelMetric( QStyle::PM_DefaultFrameWidth ); + + // Now we replace default sizeHint contribution of m_data->valueEdit by + // what we really need. + + w += QWidget::sizeHint().width() - m_data->valueEdit->sizeHint().width(); + + const int h = qMin( QWidget::sizeHint().height(), + m_data->valueEdit->minimumSizeHint().height() ); + + return QSize( w, h ); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_counter.cpp" +#endif diff --git a/libs/qwt/src/qwt_counter.h b/libs/qwt/src/qwt_counter.h new file mode 100644 index 00000000..4d0577f9 --- /dev/null +++ b/libs/qwt/src/qwt_counter.h @@ -0,0 +1,161 @@ +/****************************************************************************** + * 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 NOTIFY valueChanged USER true ) + 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 ); + int numButtons() const; + + void setIncSteps( QwtCounter::Button, int numSteps ); + int incSteps( QwtCounter::Button ) const; + + virtual QSize sizeHint() const QWT_OVERRIDE; + + double singleStep() const; + void setSingleStep( double stepSize ); + + void setRange( double min, double max ); + + double minimum() const; + void setMinimum( double ); + + double maximum() const; + void setMaximum( double ); + + 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* ) QWT_OVERRIDE; + virtual void wheelEvent( QWheelEvent* ) QWT_OVERRIDE; + virtual void keyPressEvent( QKeyEvent* ) QWT_OVERRIDE; + + private Q_SLOTS: + void btnReleased(); + void btnClicked(); + void textChanged(); + + private: + void incrementValue( int numSteps ); + void initCounter(); + void updateButtons(); + void showNumber( double ); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_curve_fitter.cpp b/libs/qwt/src/qwt_curve_fitter.cpp new file mode 100644 index 00000000..3e80f69f --- /dev/null +++ b/libs/qwt/src/qwt_curve_fitter.cpp @@ -0,0 +1,30 @@ +/****************************************************************************** + * 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" + +/*! + Constructor + \param mode Preferred fitting mode + */ +QwtCurveFitter::QwtCurveFitter( Mode mode ) + : m_mode( mode ) +{ +} + +//! Destructor +QwtCurveFitter::~QwtCurveFitter() +{ +} + +//! \return Preferred fitting mode +QwtCurveFitter::Mode QwtCurveFitter::mode() const +{ + return m_mode; +} diff --git a/libs/qwt/src/qwt_curve_fitter.h b/libs/qwt/src/qwt_curve_fitter.h new file mode 100644 index 00000000..1dba67f9 --- /dev/null +++ b/libs/qwt/src/qwt_curve_fitter.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * 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" + +class QPainterPath; +class QPolygonF; + +/*! + \brief Abstract base class for a curve fitter + */ +class QWT_EXPORT QwtCurveFitter +{ + public: + /*! + \brief Preferred mode of the fitting algorithm + + Even if a QPainterPath can always be created from a QPolygonF + the overhead of the conversion can be avoided by indicating + the preference of the implementation to the application + code. + */ + enum Mode + { + /*! + The fitting algorithm creates a polygon - the implementation + of fitCurvePath() simply wraps the polygon into a path. + + \sa QwtWeedingCurveFitter + */ + Polygon, + + /*! + The fitting algorithm creates a painter path - the implementation + of fitCurve() extracts a polygon from the path. + + \sa QwtSplineCurveFitter + */ + Path + }; + + virtual ~QwtCurveFitter(); + + Mode mode() const; + + /*! + Find a curve which has the best fit to a series of data points + + \param polygon Series of data points + \return Curve points + + \sa fitCurvePath() + */ + virtual QPolygonF fitCurve( const QPolygonF& polygon ) const = 0; + + /*! + Find a curve path which has the best fit to a series of data points + + \param polygon Series of data points + \return Curve path + + \sa fitCurve() + */ + virtual QPainterPath fitCurvePath( const QPolygonF& polygon ) const = 0; + + protected: + explicit QwtCurveFitter( Mode mode ); + + private: + Q_DISABLE_COPY(QwtCurveFitter) + + const Mode m_mode; +}; + +#endif diff --git a/libs/qwt/src/qwt_date.cpp b/libs/qwt/src/qwt_date.cpp new file mode 100644 index 00000000..9f086e2d --- /dev/null +++ b/libs/qwt/src/qwt_date.cpp @@ -0,0 +1,696 @@ +/****************************************************************************** + * 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_date.h" +#include "qwt_math.h" + +#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 +// there 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< int >::max(); + +#endif + +static QString qwtExpandedFormat( const QString& format, + const QDateTime& dateTime, QwtDate::Week0Type week0Type ) +{ + const int week = QwtDate::weekNumber( dateTime.date(), week0Type ); + + QString weekNo; + weekNo.setNum( week ); + + QString weekNoWW; + if ( weekNo.length() == 1 ) + weekNoWW += QLatin1Char( '0' ); + + weekNoWW += weekNo; + + QString fmt = format; + fmt.replace( QLatin1String( "ww" ), weekNoWW ); + fmt.replace( QLatin1Char( 'w' ), weekNo ); + + if ( week == 1 && dateTime.date().month() != 1 ) + { + // in case of week 1, we might need to increment the year + + QLatin1String s_yyyy( "yyyy" ); + QLatin1String s_yy( "yy" ); + + // week 1 might start in the previous year + + bool doReplaceYear = fmt.contains( s_yy ); + + if ( doReplaceYear ) + { + if ( fmt.contains( 'M' ) ) + { + // in case of also having 'M' we have a conflict about + // which year to show + + doReplaceYear = false; + } + else + { + // in case of also having 'd' or 'dd' we have a conflict about + // which year to show + + int numD = 0; + + for ( int i = 0; i < fmt.size(); i++ ) + { + if ( fmt[i] == 'd' ) + { + numD++; + } + else + { + if ( numD > 0 && numD <= 2 ) + break; + + numD = 0; + } + } + + if ( numD > 0 && numD <= 2 ) + doReplaceYear = false; + } + } + + if ( doReplaceYear ) + { + const QDate dt( dateTime.date().year() + 1, 1, 1 ); + const QString dtString = QLocale().toString( dt, s_yyyy ); + + if ( fmt.contains( s_yyyy ) ) + { + fmt.replace( s_yyyy, dtString ); + } + else + { + fmt.replace( s_yy, dtString ); + } + } + } + + return fmt; +} + +static inline Qt::DayOfWeek qwtFirstDayOfWeek() +{ + return QLocale().firstDayOfWeek(); +} + +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 >= std::numeric_limits< 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 ); +} + +#if 0 + +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 = std::floor( ( 4900.0 + year + m1 ) / 100 ); + + return std::floor( ( 1461.0 * ( year + 4800 + m1 ) ) / 4 ) + m2 + - std::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; +} + +#endif + +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 = std::floor( ( 4900.0 + year + m1 ) / 100 ); + + const double jd = std::floor( ( 1461.0 * ( year + 4800 + m1 ) ) / 4 ) + m2 + - std::floor( ( 3 * y1 ) / 4 ) + day - 32075; + + if ( jd > maxJulianDayD ) + { + qWarning() << "qwtToDate: overflow"; + return QDate(); + } + + return QDate::fromJulianDay( static_cast< QwtJulianDay >( 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< qint64 >( std::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< QwtJulianDay >( jd ) ); + + const int msecs = static_cast< int >( 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 = dt.addSecs( 1 ); + + break; + } + case QwtDate::Minute: + { + qwtFloorTime( QwtDate::Minute, dt ); + if ( dt < dateTime ) + dt = dt.addSecs( 60 ); + + break; + } + case QwtDate::Hour: + { + qwtFloorTime( QwtDate::Hour, dt ); + if ( dt < dateTime ) + dt = 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 = 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 ) + { + QDate day0; + + if ( date.month() == 12 && date.day() >= 24 ) + { + // week 1 usually starts in the previous years. + // and we have to check if we are already there + + day0 = dateOfWeek0( date.year() + 1, type ); + if ( day0.daysTo( date ) < 0 ) + day0 = dateOfWeek0( date.year(), type ); + } + else + { + 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.offsetFromUtc() + - 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: + { +#if QT_VERSION >= 0x050200 + seconds = dateTime.offsetFromUtc(); +#else + seconds = dateTime.utcOffset(); +#endif + break; + } + 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 ) + + As week 1 usually starts in the previous year a special rule + is applied for formats, where the year is expected to match the + week number - even if the date belongs to the previous year. + + \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 fmt = format; + if ( fmt.contains( 'w' ) ) + { + fmt = qwtExpandedFormat( fmt, dateTime, week0Type ); + } + + return QLocale().toString( dateTime, fmt ); +} diff --git a/libs/qwt/src/qwt_date.h b/libs/qwt/src/qwt_date.h new file mode 100644 index 00000000..49a1f0dd --- /dev/null +++ b/libs/qwt/src/qwt_date.h @@ -0,0 +1,128 @@ +/****************************************************************************** + * 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/libs/qwt/src/qwt_date_scale_draw.cpp b/libs/qwt/src/qwt_date_scale_draw.cpp new file mode 100644 index 00000000..57af0fdf --- /dev/null +++ b/libs/qwt/src/qwt_date_scale_draw.cpp @@ -0,0 +1,283 @@ +/****************************************************************************** + * 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_date_scale_draw.h" +#include "qwt_text.h" + +class QwtDateScaleDraw::PrivateData +{ + public: + explicit 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 ) +{ + m_data = new PrivateData( timeSpec ); +} + +//! Destructor +QwtDateScaleDraw::~QwtDateScaleDraw() +{ + delete m_data; +} + +/*! + Set the time specification used for the tick labels + + \param timeSpec Time specification + \sa timeSpec(), setUtcOffset(), toDateTime() + */ +void QwtDateScaleDraw::setTimeSpec( Qt::TimeSpec timeSpec ) +{ + m_data->timeSpec = timeSpec; +} + +/*! + \return Time specification used for the tick labels + \sa setTimeSpec(), utcOffset(), toDateTime() + */ +Qt::TimeSpec QwtDateScaleDraw::timeSpec() const +{ + return m_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 ) +{ + m_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 m_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 ) +{ + m_data->week0Type = week0Type; +} + +/*! + \return Setting how to identify the first week of a year. + \sa setWeek0Type() + */ +QwtDate::Week0Type QwtDateScaleDraw::week0Type() const +{ + return m_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 ) + { + m_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 m_data->dateFormats[ intervalType ]; + } + + return QString(); +} + +/*! + 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 m_data->dateFormats[ intervalType ]; + } + + return m_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, m_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< double > 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< QwtDate::IntervalType >( 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< QwtDate::IntervalType >( 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, m_data->timeSpec ); + if ( m_data->timeSpec == Qt::OffsetFromUTC ) + { + dt = dt.addSecs( m_data->utcOffset ); +#if QT_VERSION >= 0x050200 + dt.setOffsetFromUtc( m_data->utcOffset ); +#else + dt.setUtcOffset( m_data->utcOffset ); +#endif + } + + return dt; +} diff --git a/libs/qwt/src/qwt_date_scale_draw.h b/libs/qwt/src/qwt_date_scale_draw.h new file mode 100644 index 00000000..c49fd1e2 --- /dev/null +++ b/libs/qwt/src/qwt_date_scale_draw.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_DATE_SCALE_DRAW_H +#define QWT_DATE_SCALE_DRAW_H + +#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: + explicit 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 QWT_OVERRIDE; + + QDateTime toDateTime( double ) const; + + protected: + virtual QwtDate::IntervalType + intervalType( const QwtScaleDiv& ) const; + + virtual QString dateFormatOfDate( const QDateTime&, + QwtDate::IntervalType ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_date_scale_engine.cpp b/libs/qwt/src/qwt_date_scale_engine.cpp new file mode 100644 index 00000000..fce6f6fb --- /dev/null +++ b/libs/qwt/src/qwt_date_scale_engine.cpp @@ -0,0 +1,1321 @@ +/****************************************************************************** + * 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_date_scale_engine.h" +#include "qwt_math.h" +#include "qwt_interval.h" + +#include + +#include + +static inline double qwtMsecsForType( int 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< int >( 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 ? std::ceil( d ) : std::floor( d ); + + return static_cast< int >( d * stepSize ); +} + +static double qwtIntervalWidth( const QDateTime& minDate, + const QDateTime& maxDate, QwtDate::IntervalType intervalType ) +{ + switch( intervalType ) + { + case QwtDate::Millisecond: + { + return minDate.msecsTo( maxDate ); + } + case QwtDate::Second: + { + return minDate.secsTo( maxDate ); + } + case QwtDate::Minute: + { + const double secsTo = minDate.secsTo( maxDate ); + return std::floor( secsTo / 60 ); + } + case QwtDate::Hour: + { + const double secsTo = minDate.secsTo( maxDate ); + return std::floor( secsTo / 3600 ); + } + case QwtDate::Day: + { + return minDate.daysTo( maxDate ); + } + case QwtDate::Week: + { + return std::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 = std::floor( std::log( stepSize ) / std::log( double( base ) ) ); + const double fraction = std::pow( 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 = qwtCeil( 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 = std::ceil( v ); + else + stepSize = std::ceil( 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 = 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 = 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 = 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< int >( stepSize ); + + static int limits[] = { 1, 2, 3, 4, 6, 12 }; + + int numSteps = qwtStepCount( stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + if ( numSteps > 0 ) + minStepSize = 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 = stepSize / numSteps; + } + + break; + } + default: + break; + } + + if ( intervalType != QwtDate::Month + && minStepSize == 0.0 ) + { + minStepSize = 0.5 * stepSize; + } + + return minStepSize; +} + +static QList< double > qwtDstTicks( const QDateTime& dateTime, + int secondsMajor, int secondsMinor ) +{ + if ( secondsMinor <= 0 ) + QList< double >(); + + 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< double > ticks; + ticks.reserve( 3600 / secondsMinor); + + 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< int >( 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< double > majorTicks; + QList< double > mediumTicks; + QList< double > 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 = qwtFloor( 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; + else + minStepDays = 15; + } + else + { + minStepSize = qwtDivideMajorStep( + stepSize, maxMinSteps, QwtDate::Month ); + } + } + + QList< double > majorTicks; + QList< double > mediumTicks; + QList< double > 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< double > majorTicks; + QList< double > mediumTicks; + QList< double > minorTicks; + + double minStepSize = 0.0; + + if ( maxMinSteps > 1 ) + { + minStepSize = qwtDivideMajorStep( + stepSize, maxMinSteps, QwtDate::Year ); + } + + int numMinorSteps = 0; + if ( minStepSize > 0.0 ) + numMinorSteps = qwtFloor( 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 >= std::numeric_limits< 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: + explicit 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 ) +{ + m_data = new PrivateData( timeSpec ); +} + +//! Destructor +QwtDateScaleEngine::~QwtDateScaleEngine() +{ + delete m_data; +} + +/*! + Set the time specification used by the engine + + \param timeSpec Time specification + \sa timeSpec(), setUtcOffset(), toDateTime() + */ +void QwtDateScaleEngine::setTimeSpec( Qt::TimeSpec timeSpec ) +{ + m_data->timeSpec = timeSpec; +} + +/*! + \return Time specification used by the engine + \sa setTimeSpec(), utcOffset(), toDateTime() + */ +Qt::TimeSpec QwtDateScaleEngine::timeSpec() const +{ + return m_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 ) +{ + m_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 m_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 ) +{ + m_data->week0Type = week0Type; +} + +/*! + \return Setting how to identify the first week of a year. + \sa setWeek0Type(), maxWeeks() + */ +QwtDate::Week0Type QwtDateScaleEngine::week0Type() const +{ + return m_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 divided + into weeks [1-52] + \sa maxWeeks(), setWeek0Type() + */ +void QwtDateScaleEngine::setMaxWeeks( int weeks ) +{ + m_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 m_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 > m_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 = qwtMinF( x1, x2 ); + const double max = qwtMaxF( 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 = qwtCeil( ( 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 ) + { +#if QT_VERSION >= 0x050200 + dt.setOffsetFromUtc( 0 ); +#else + dt.setUtcOffset( 0 ); +#endif + } + + 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(), m_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 ) + { +#if QT_VERSION >= 0x050200 + dt.setOffsetFromUtc( dateTime.offsetFromUtc() ); +#else + dt.setUtcOffset( dateTime.utcOffset() ); +#endif + } + + 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, m_data->timeSpec ); + if ( !dt.isValid() ) + { + const QDate date = ( value <= 0.0 ) + ? QwtDate::minDate() : QwtDate::maxDate(); + + dt = QDateTime( date, QTime( 0, 0 ), m_data->timeSpec ); + } + + if ( m_data->timeSpec == Qt::OffsetFromUTC ) + { + dt = dt.addSecs( m_data->utcOffset ); +#if QT_VERSION >= 0x050200 + dt.setOffsetFromUtc( m_data->utcOffset ); +#else + dt.setUtcOffset( m_data->utcOffset ); +#endif + } + + return dt; +} + diff --git a/libs/qwt/src/qwt_date_scale_engine.h b/libs/qwt/src/qwt_date_scale_engine.h new file mode 100644 index 00000000..6e5f68b1 --- /dev/null +++ b/libs/qwt/src/qwt_date_scale_engine.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * 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_SCALE_ENGINE_H +#define QWT_DATE_SCALE_ENGINE_H + +#include "qwt_global.h" +#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: + explicit 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 QWT_OVERRIDE; + + virtual QwtScaleDiv divideScale( + double x1, double x2, + int maxMajorSteps, int maxMinorSteps, + double stepSize = 0.0 ) const QWT_OVERRIDE; + + 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* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_dial.cpp b/libs/qwt/src/qwt_dial.cpp new file mode 100644 index 00000000..5615d7ae --- /dev/null +++ b/libs/qwt/src/qwt_dial.cpp @@ -0,0 +1,878 @@ +/****************************************************************************** + * 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_map.h" +#include "qwt_round_scale_draw.h" +#include "qwt_painter.h" +#include "qwt.h" + +#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; + + 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 ) +{ + m_data = new PrivateData; + + setFocusPolicy( Qt::TabFocus ); + + QPalette p = palette(); + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + const QPalette::ColorGroup colorGroup = + static_cast< QPalette::ColorGroup >( 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 m_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 != m_data->frameShadow ) + { + invalidateCache(); + + m_data->frameShadow = shadow; + if ( lineWidth() > 0 ) + update(); + } +} + +/*! + \return Frame shadow + /sa setFrameShadow(), lineWidth(), QFrame::frameShadow() + */ +QwtDial::Shadow QwtDial::frameShadow() const +{ + return m_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 ( m_data->lineWidth != lineWidth ) + { + invalidateCache(); + + m_data->lineWidth = lineWidth; + update(); + } +} + +/*! + \return Line width of the frame + \sa setLineWidth(), frameShadow(), lineWidth() + */ +int QwtDial::lineWidth() const +{ + return m_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 int 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 = qwtCeil( 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 != m_data->mode ) + { + invalidateCache(); + + m_data->mode = mode; + sliderChange(); + } +} + +/*! + \return Mode of the dial. + \sa setMode(), origin(), setScaleArc(), value() + */ +QwtDial::Mode QwtDial::mode() const +{ + return m_data->mode; +} + +/*! + Invalidate the internal caches used to speed up repainting + */ +void QwtDial::invalidateCache() +{ + m_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.initFrom(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); + + if ( m_data->mode == QwtDial::RotateScale ) + { + painter.save(); + painter.setRenderHint( QPainter::Antialiasing, true ); + + drawContents( &painter ); + + painter.restore(); + } + + const QRect r = contentsRect(); + if ( r.size() != m_data->pixmapCache.size() ) + { + m_data->pixmapCache = QwtPainter::backingStore( this, r.size() ); + m_data->pixmapCache.fill( Qt::transparent ); + + QPainter p( &m_data->pixmapCache ); + p.setRenderHint( QPainter::Antialiasing, true ); + p.translate( -r.topLeft() ); + + if ( m_data->mode != QwtDial::RotateScale ) + drawContents( &p ); + + if ( lineWidth() > 0 ) + drawFrame( &p ); + + if ( m_data->mode != QwtDial::RotateNeedle ) + drawNeedle( &p ); + } + + painter.drawPixmap( r.topLeft(), m_data->pixmapCache ); + + if ( m_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(), m_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& center, + double radius, double direction, QPalette::ColorGroup colorGroup ) const +{ + if ( m_data->needle ) + { + direction = 360.0 - direction; // counter clockwise + m_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(), + scaleMap().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& center, double radius ) const +{ + QwtRoundScaleDraw* sd = const_cast< QwtRoundScaleDraw* >( 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->penWidthF() ) ); + + 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& center, 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 != m_data->needle ) + { + if ( m_data->needle ) + delete m_data->needle; + + m_data->needle = needle; + update(); + } +} + +/*! + \return needle + \sa setNeedle() + */ +const QwtDialNeedle* QwtDial::needle() const +{ + return m_data->needle; +} + +/*! + \return needle + \sa setNeedle() + */ +QwtDialNeedle* QwtDial::needle() +{ + return m_data->needle; +} + +//! \return the scale draw +QwtRoundScaleDraw* QwtDial::scaleDraw() +{ + return static_cast< QwtRoundScaleDraw* >( abstractScaleDraw() ); +} + +//! \return the scale draw +const QwtRoundScaleDraw* QwtDial::scaleDraw() const +{ + return static_cast< const QwtRoundScaleDraw* >( 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 = std::fmod( minArc, 360.0 ); + if ( maxArc != 360.0 && maxArc != -360.0 ) + maxArc = std::fmod( maxArc, 360.0 ); + + double minScaleArc = qwtMinF( minArc, maxArc ); + double maxScaleArc = qwtMaxF( minArc, maxArc ); + + if ( maxScaleArc - minScaleArc > 360.0 ) + maxScaleArc = minScaleArc + 360.0; + + if ( ( minScaleArc != m_data->minScaleArc ) || + ( maxScaleArc != m_data->maxScaleArc ) ) + { + m_data->minScaleArc = minScaleArc; + m_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, m_data->maxScaleArc ); +} + +/*! + \return Lower limit of the scale arc + \sa setScaleArc() + */ +double QwtDial::minScaleArc() const +{ + return m_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( m_data->minScaleArc, max ); +} + +/*! + \return Upper limit of the scale arc + \sa setScaleArc() + */ +double QwtDial::maxScaleArc() const +{ + return m_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(); + + m_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 m_data->origin; +} + +/*! + \return Size hint + \sa minimumSizeHint() + */ +QSize QwtDial::sizeHint() const +{ + int sh = 0; + if ( scaleDraw() ) + sh = qwtCeil( scaleDraw()->extent( font() ) ); + + const int d = 6 * sh + 2 * lineWidth(); + + QSize hint( d, d ); + if ( !isReadOnly() ) + hint = qwtExpandedToGlobalStrut( hint ); + + return hint; +} + +/*! + \return Minimum size hint + \sa sizeHint() + */ +QSize QwtDial::minimumSizeHint() const +{ + int sh = 0; + if ( scaleDraw() ) + sh = qwtCeil( 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 ( m_data->mode == QwtDial::RotateScale ) + angle = 360.0 - angle; + + double valueAngle = + qwtNormalizeDegrees( 90.0 - scaleMap().transform( value() ) ); + + m_data->mouseOffset = qwtNormalizeDegrees( angle - valueAngle ); + m_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 ( m_data->mode == QwtDial::RotateScale ) + { + angle += scaleMap().p1() - m_data->arcOffset; + angle = 360.0 - angle; + } + + angle = qwtNormalizeDegrees( angle - m_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 - scaleMap().transform( value() ); + if ( qAbs( arc ) > 180.0 ) + { + boundedAngle = ( arc > 0 ) + ? scaleMap().p1() : scaleMap().p2(); + } + + m_data->mouseOffset += ( boundedAngle - angle ); + + angle = boundedAngle; + } + } + else + { + const double boundedAngle = + qwtBoundedAngle( scaleMap().p1(), angle, scaleMap().p2() ); + + if ( !wrapping() ) + m_data->mouseOffset += ( boundedAngle - angle ); + + angle = boundedAngle; + } + + return scaleMap().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 ) +{ +#if QT_VERSION < 0x050e00 + const QPoint wheelPos = event->pos(); +#else + const QPoint wheelPos = event->position().toPoint(); +#endif + + const QRegion region( innerRect(), QRegion::Ellipse ); + if ( region.contains( wheelPos ) ) + QwtAbstractSlider::wheelEvent( event ); +} + +void QwtDial::setAngleRange( double angle, double span ) +{ + if ( QwtRoundScaleDraw* sd = scaleDraw() ) + { + 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( m_data->origin + m_data->minScaleArc, + m_data->maxScaleArc - m_data->minScaleArc ); + + if ( mode() == RotateScale ) + { + const double arc = scaleMap().transform( value() ) - scaleMap().p1(); + setAngleRange( m_data->origin - arc, + m_data->maxScaleArc - m_data->minScaleArc ); + } + + QwtAbstractSlider::sliderChange(); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_dial.cpp" +#endif diff --git a/libs/qwt/src/qwt_dial.h b/libs/qwt/src/qwt_dial.h new file mode 100644 index 00000000..e4aeff6b --- /dev/null +++ b/libs/qwt/src/qwt_dial.h @@ -0,0 +1,169 @@ +/****************************************************************************** + * 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 + +#include "qwt_global.h" +#include "qwt_abstract_slider.h" + +#include +#include + +class QwtDialNeedle; +class QwtRoundScaleDraw; +class QwtAbstractScaleDraw; + +/*! + \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 minArc, double maxArc ); + + void setMinScaleArc( double ); + double minScaleArc() const; + + void setMaxScaleArc( double ); + 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 QWT_OVERRIDE; + virtual QSize minimumSizeHint() const QWT_OVERRIDE; + + void setScaleDraw( QwtRoundScaleDraw* ); + + QwtRoundScaleDraw* scaleDraw(); + const QwtRoundScaleDraw* scaleDraw() const; + + protected: + virtual void wheelEvent( QWheelEvent* ) QWT_OVERRIDE; + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + virtual void changeEvent( QEvent* ) QWT_OVERRIDE; + + virtual void drawFrame( QPainter* ); + virtual void drawContents( QPainter* ) const; + virtual void drawFocusIndicator( QPainter* ) const; + + void invalidateCache(); + + virtual void drawScale( QPainter*, + const QPointF& center, double radius ) const; + + virtual void drawScaleContents( QPainter* painter, + const QPointF& center, double radius ) const; + + virtual void drawNeedle( QPainter*, const QPointF&, + double radius, double direction, QPalette::ColorGroup ) const; + + virtual double scrolledTo( const QPoint& ) const QWT_OVERRIDE; + virtual bool isScrollPosition( const QPoint& ) const QWT_OVERRIDE; + + virtual void sliderChange() QWT_OVERRIDE; + virtual void scaleChange() QWT_OVERRIDE; + + private: + void setAngleRange( double angle, double span ); + void drawNeedle( QPainter* ) const; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_dial_needle.cpp b/libs/qwt/src/qwt_dial_needle.cpp new file mode 100644 index 00000000..df8ad948 --- /dev/null +++ b/libs/qwt/src/qwt_dial_needle.cpp @@ -0,0 +1,434 @@ +/****************************************************************************** + * 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_math.h" + +#include +#include +#include +#include + +static void qwtDrawStyle1Needle( QPainter* painter, + const QPalette& palette, QPalette::ColorGroup colorGroup, qreal length ) +{ + const qreal r[] = { 0.4, 0.3, 1, 0.8, 1, 0.3, 0.4 }; + const qreal a[] = { -45, -20, -15, 0, 15, 20, 45 }; + + QPainterPath path; + for ( int i = 0; i < 7; i++ ) + { + const qreal angle = a[i] / 180.0 * M_PI; + const qreal radius = r[i] * length; + + const qreal x = radius * qFastCos( angle ); + const qreal 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, qreal length ) +{ + const qreal ratioX = 0.7; + const qreal 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, + qreal length, qreal width ) +{ + const qreal peak = qwtMaxF( length / 10.0, 5.0 ); + + const qreal 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, + qreal length, qreal width ) +{ + if ( width <= 0 ) + width = qwtMaxF( length * 0.06, 9.0 ); + + const qreal peak = qwtMaxF( 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, qreal length ) +{ + const qreal 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.lighter( 100 + colorOffset ); + color[1] = darkColor.darker( 100 + colorOffset ); + color[2] = lightColor.lighter( 100 + colorOffset ); + color[3] = lightColor.darker( 100 + colorOffset ); + + painter->setPen( Qt::NoPen ); + + for ( int i = 0; i < 4; i++ ) + { + painter->setBrush( color[i] ); + painter->drawPath( path[i] ); + } +} + +//! Constructor +QwtDialNeedle::QwtDialNeedle() + : m_palette( QApplication::palette() ) +{ +} + +//! Destructor +QwtDialNeedle::~QwtDialNeedle() +{ +} + +/*! + Sets the palette for the needle. + + \param palette New Palette + */ +void QwtDialNeedle::setPalette( const QPalette& palette ) +{ + m_palette = palette; +} + +/*! + \return the palette of the needle. + */ +const QPalette& QwtDialNeedle::palette() const +{ + return m_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& center, 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 ): + m_style( style ), + m_hasKnob( hasKnob ), + m_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 ) +{ + m_width = width; +} + +/*! + \return the width of the needle + \sa setWidth() + */ +double QwtDialSimpleNeedle::width() const +{ + return m_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 +{ + qreal knobWidth = 0.0; + qreal width = m_width; + + if ( m_style == Arrow ) + { + if ( width <= 0.0 ) + width = qwtMaxF( length * 0.06, 6.0 ); + + qwtDrawArrowNeedle( painter, + palette(), colorGroup, length, width ); + + knobWidth = qwtMinF( 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 = qwtMaxF( width * 3.0, 5.0 ); + } + + if ( m_hasKnob && knobWidth > 0.0 ) + { + drawKnob( painter, knobWidth, + palette().brush( colorGroup, QPalette::Base ), false ); + } +} + +//! Constructor +QwtCompassMagnetNeedle::QwtCompassMagnetNeedle( Style style, + const QColor& light, const QColor& dark ): + m_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 ( m_style == ThinStyle ) + { + const qreal width = qwtMaxF( 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.lighter( 100 + colorOffset ), + dark.darker( 100 + colorOffset ), + length, width ); + + painter->rotate( 180.0 ); + + qwtDrawShadedPointer( painter, + light.lighter( 100 + colorOffset ), + light.darker( 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 ): + m_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 ( m_style == Style1 ) + qwtDrawStyle1Needle( painter, palette(), colorGroup, length ); + else + qwtDrawStyle2Needle( painter, palette(), colorGroup, length ); +} diff --git a/libs/qwt/src/qwt_dial_needle.h b/libs/qwt/src/qwt_dial_needle.h new file mode 100644 index 00000000..3f2230d0 --- /dev/null +++ b/libs/qwt/src/qwt_dial_needle.h @@ -0,0 +1,188 @@ +/****************************************************************************** + * 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 + +#include "qwt_global.h" +#include + +class QPainter; + +/*! + \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*, const QPointF& center, + 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: + Q_DISABLE_COPY(QwtDialNeedle) + + QPalette m_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 QWT_OVERRIDE; + + private: + Style m_style; + bool m_hasKnob; + double m_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 QWT_OVERRIDE; + + private: + Style m_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 QWT_OVERRIDE; + + private: + Style m_style; +}; + +#endif diff --git a/libs/qwt/src/qwt_dyngrid_layout.cpp b/libs/qwt/src/qwt_dyngrid_layout.cpp new file mode 100644 index 00000000..89c73bb6 --- /dev/null +++ b/libs/qwt/src/qwt_dyngrid_layout.cpp @@ -0,0 +1,603 @@ +/****************************************************************************** + * 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 +#include + +class QwtDynGridLayout::PrivateData +{ + public: + PrivateData() + : isDirty( true ) + { + } + + void updateLayoutCache(); + + mutable QList< QLayoutItem* > itemList; + + uint maxColumns; + uint numRows; + uint numColumns; + + Qt::Orientations expanding; + + bool isDirty; + QVector< QSize > itemSizeHints; +}; + +void QwtDynGridLayout::PrivateData::updateLayoutCache() +{ + itemSizeHints.resize( itemList.count() ); + + int index = 0; + + for ( QList< QLayoutItem* >::const_iterator it = itemList.constBegin(); + it != itemList.constEnd(); ++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 ); + setContentsMargins( margin, margin, margin, margin ); +} + +/*! + \param spacing Spacing + */ + +QwtDynGridLayout::QwtDynGridLayout( int spacing ) +{ + init(); + setSpacing( spacing ); +} + +/*! + Initialize the layout with default values. + */ +void QwtDynGridLayout::init() +{ + m_data = new QwtDynGridLayout::PrivateData; + m_data->maxColumns = m_data->numRows = m_data->numColumns = 0; +} + +//! Destructor + +QwtDynGridLayout::~QwtDynGridLayout() +{ + qDeleteAll( m_data->itemList ); + delete m_data; +} + +//! Invalidate all internal caches +void QwtDynGridLayout::invalidate() +{ + m_data->isDirty = true; + QLayout::invalidate(); +} + +/*! + Limit the number of columns. + \param maxColumns upper limit, 0 means unlimited + \sa maxColumns() + */ +void QwtDynGridLayout::setMaxColumns( uint maxColumns ) +{ + m_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 m_data->maxColumns; +} + +/*! + \brief Add an item to the next free position. + \param item Layout item + */ +void QwtDynGridLayout::addItem( QLayoutItem* item ) +{ + m_data->itemList.append( item ); + invalidate(); +} + +/*! + \return true if this layout is empty. + */ +bool QwtDynGridLayout::isEmpty() const +{ + return m_data->itemList.isEmpty(); +} + +/*! + \return number of layout items + */ +uint QwtDynGridLayout::itemCount() const +{ + return m_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 >= m_data->itemList.count() ) + return NULL; + + return m_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 >= m_data->itemList.count() ) + return NULL; + + m_data->isDirty = true; + return m_data->itemList.takeAt( index ); +} + +//! \return Number of items in the layout +int QwtDynGridLayout::count() const +{ + return m_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 ) +{ + m_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 m_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; + + m_data->numColumns = columnsForWidth( rect.width() ); + m_data->numRows = itemCount() / m_data->numColumns; + if ( itemCount() % m_data->numColumns ) + m_data->numRows++; + + const QList< QRect > itemGeometries = layoutItems( rect, m_data->numColumns ); + + int index = 0; + for ( QList< QLayoutItem* >::const_iterator it = m_data->itemList.constBegin(); + it != m_data->itemList.constEnd(); ++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 ( m_data->maxColumns > 0 ) + maxColumns = qMin( m_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< int > colWidth( numColumns ); + for ( col = 0; col < numColumns; col++ ) + colWidth[col] = 0; + + if ( m_data->isDirty ) + m_data->updateLayoutCache(); + + for ( int index = 0; + index < m_data->itemSizeHints.count(); index++ ) + { + col = index % numColumns; + colWidth[col] = qMax( colWidth[col], + m_data->itemSizeHints[index].width() ); + } + + const QMargins m = contentsMargins(); + + int rowWidth = m.left() + m.right() + ( 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 ( m_data->isDirty ) + m_data->updateLayoutCache(); + + int w = 0; + for ( int i = 0; i < m_data->itemSizeHints.count(); i++ ) + { + const int itemW = m_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< QRect > QwtDynGridLayout::layoutItems( const QRect& rect, + uint numColumns ) const +{ + QList< QRect > itemGeometries; + if ( numColumns == 0 || isEmpty() ) + return itemGeometries; + + uint numRows = itemCount() / numColumns; + if ( numColumns % itemCount() ) + numRows++; + + if ( numRows == 0 ) + return itemGeometries; + + QVector< int > rowHeight( numRows ); + QVector< int > 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 = m_data->maxColumns; + m_data->maxColumns = numColumns; + const QRect alignedRect = alignmentRect( rect ); + m_data->maxColumns = maxColumns; + + const int xOffset = expandH ? 0 : alignedRect.x(); + const int yOffset = expandV ? 0 : alignedRect.y(); + + QVector< int > colX( numColumns ); + QVector< int > rowY( numRows ); + + const int xySpace = spacing(); + + const QMargins m = contentsMargins(); + + rowY[0] = yOffset + m.top(); + for ( uint r = 1; r < numRows; r++ ) + rowY[r] = rowY[r - 1] + rowHeight[r - 1] + xySpace; + + colX[0] = xOffset + m.left(); + for ( uint c = 1; c < numColumns; c++ ) + colX[c] = colX[c - 1] + colWidth[c - 1] + xySpace; + + const int itemCount = m_data->itemList.size(); + itemGeometries.reserve( itemCount ); + + for ( int i = 0; i < itemCount; i++ ) + { + const int row = i / numColumns; + const int col = i % numColumns; + + const 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< int >& rowHeight, QVector< int >& colWidth ) const +{ + if ( numColumns <= 0 ) + return; + + if ( m_data->isDirty ) + m_data->updateLayoutCache(); + + for ( int index = 0; index < m_data->itemSizeHints.count(); index++ ) + { + const int row = index / numColumns; + const int col = index % numColumns; + + const QSize& size = m_data->itemSizeHints[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< int > rowHeight( numRows ); + QVector< int > colWidth( numColumns ); + + layoutGrid( numColumns, rowHeight, colWidth ); + + const QMargins m = contentsMargins(); + + int h = m.top() + m.bottom() + ( 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< int >& rowHeight, QVector< int >& colWidth ) const +{ + if ( numColumns == 0 || isEmpty() ) + return; + + bool expandH, expandV; + expandH = expandingDirections() & Qt::Horizontal; + expandV = expandingDirections() & Qt::Vertical; + + const QMargins m = contentsMargins(); + + if ( expandH ) + { + int xDelta = rect.width() - m.left() - m.right() - ( 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() - m.top() - m.bottom() - ( 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 ( m_data->maxColumns > 0 ) + numColumns = qMin( m_data->maxColumns, numColumns ); + + uint numRows = itemCount() / numColumns; + if ( itemCount() % numColumns ) + numRows++; + + QVector< int > rowHeight( numRows ); + QVector< int > colWidth( numColumns ); + + layoutGrid( numColumns, rowHeight, colWidth ); + + const QMargins m = contentsMargins(); + + int h = m.top() + m.bottom() + ( numRows - 1 ) * spacing(); + for ( uint row = 0; row < numRows; row++ ) + h += rowHeight[row]; + + int w = m.left() + m.right() + ( 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 m_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 m_data->numColumns; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_dyngrid_layout.cpp" +#endif diff --git a/libs/qwt/src/qwt_dyngrid_layout.h b/libs/qwt/src/qwt_dyngrid_layout.h new file mode 100644 index 00000000..c1659e7f --- /dev/null +++ b/libs/qwt/src/qwt_dyngrid_layout.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * 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 + +template< typename T > class QList; + +/*! + \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 spacing = -1 ); + explicit QwtDynGridLayout( int spacing = -1 ); + + virtual ~QwtDynGridLayout(); + + virtual void invalidate() QWT_OVERRIDE; + + void setMaxColumns( uint maxColumns ); + uint maxColumns() const; + + uint numRows () const; + uint numColumns () const; + + virtual void addItem( QLayoutItem* ) QWT_OVERRIDE; + + virtual QLayoutItem* itemAt( int index ) const QWT_OVERRIDE; + virtual QLayoutItem* takeAt( int index ) QWT_OVERRIDE; + virtual int count() const QWT_OVERRIDE; + + void setExpandingDirections( Qt::Orientations ); + virtual Qt::Orientations expandingDirections() const QWT_OVERRIDE; + QList< QRect > layoutItems( const QRect&, uint numColumns ) const; + + virtual int maxItemWidth() const; + + virtual void setGeometry( const QRect& ) QWT_OVERRIDE; + + virtual bool hasHeightForWidth() const QWT_OVERRIDE; + virtual int heightForWidth( int ) const QWT_OVERRIDE; + + virtual QSize sizeHint() const QWT_OVERRIDE; + + virtual bool isEmpty() const QWT_OVERRIDE; + uint itemCount() const; + + virtual uint columnsForWidth( int width ) const; + + protected: + + void layoutGrid( uint numColumns, + QVector< int >& rowHeight, QVector< int >& colWidth ) const; + + void stretchGrid( const QRect& rect, uint numColumns, + QVector< int >& rowHeight, QVector< int >& colWidth ) const; + + private: + void init(); + int maxRowWidth( int numColumns ) const; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_event_pattern.cpp b/libs/qwt/src/qwt_event_pattern.cpp new file mode 100644 index 00000000..8d5e1fb5 --- /dev/null +++ b/libs/qwt/src/qwt_event_pattern.cpp @@ -0,0 +1,265 @@ +/****************************************************************************** + * 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() + : m_mousePattern( MousePatternCount ) + , m_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 ) +{ + m_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::MiddleButton ); + } + } + + setMousePattern( MouseSelect4, m_mousePattern[MouseSelect1].button, + m_mousePattern[MouseSelect1].modifiers | Qt::ShiftModifier ); + + setMousePattern( MouseSelect5, m_mousePattern[MouseSelect2].button, + m_mousePattern[MouseSelect2].modifiers | Qt::ShiftModifier ); + + setMousePattern( MouseSelect6, m_mousePattern[MouseSelect3].button, + m_mousePattern[MouseSelect3].modifiers | Qt::ShiftModifier ); +} + +/*! + Set default mouse patterns. + + \sa KeyPatternCode + */ +void QwtEventPattern::initKeyPattern() +{ + m_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 ) + { + m_mousePattern[ pattern ].button = button; + m_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 ) + { + m_keyPattern[ pattern ].key = key; + m_keyPattern[ pattern ].modifiers = modifiers; + } +} + +//! Change the mouse event patterns +void QwtEventPattern::setMousePattern( const QVector< MousePattern >& pattern ) +{ + m_mousePattern = pattern; +} + +//! Change the key event patterns +void QwtEventPattern::setKeyPattern( const QVector< KeyPattern >& pattern ) +{ + m_keyPattern = pattern; +} + +//! \return Mouse pattern +const QVector< QwtEventPattern::MousePattern >& +QwtEventPattern::mousePattern() const +{ + return m_mousePattern; +} + +//! \return Key pattern +const QVector< QwtEventPattern::KeyPattern >& +QwtEventPattern::keyPattern() const +{ + return m_keyPattern; +} + +//! \return Mouse pattern +QVector< QwtEventPattern::MousePattern >& QwtEventPattern::mousePattern() +{ + return m_mousePattern; +} + +//! \return Key pattern +QVector< QwtEventPattern::KeyPattern >& QwtEventPattern::keyPattern() +{ + return m_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( m_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( m_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/libs/qwt/src/qwt_event_pattern.h b/libs/qwt/src/qwt_event_pattern.h new file mode 100644 index 00000000..bcecc070 --- /dev/null +++ b/libs/qwt/src/qwt_event_pattern.h @@ -0,0 +1,241 @@ +/****************************************************************************** + * 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 + +#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 key, + Qt::KeyboardModifiers modifiers = Qt::NoModifier ); + + void setMousePattern( const QVector< MousePattern >& ); + void setKeyPattern( const QVector< KeyPattern >& ); + + const QVector< MousePattern >& mousePattern() const; + const QVector< KeyPattern >& keyPattern() const; + + QVector< MousePattern >& mousePattern(); + QVector< KeyPattern >& 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< MousePattern > m_mousePattern; + QVector< KeyPattern > m_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/libs/qwt/src/qwt_global.h b/libs/qwt/src/qwt_global.h new file mode 100644 index 00000000..3c5d6c63 --- /dev/null +++ b/libs/qwt/src/qwt_global.h @@ -0,0 +1,60 @@ +/****************************************************************************** + * 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 0x060200 +#define QWT_VERSION_STR "6.2.0" + +#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 + +#define QWT_CONSTEXPR Q_DECL_CONSTEXPR + +#if QT_VERSION >= 0x050000 +#define QWT_OVERRIDE Q_DECL_OVERRIDE +#define QWT_FINAL Q_DECL_FINAL +#endif + +#ifndef QWT_CONSTEXPR +#define QWT_CONSTEXPR +#endif + +#ifndef QWT_OVERRIDE +#define QWT_OVERRIDE +#endif + +#ifndef QWT_FINAL +#define QWT_FINAL +#endif + +#endif diff --git a/libs/qwt/src/qwt_graphic.cpp b/libs/qwt/src/qwt_graphic.cpp new file mode 100644 index 00000000..7f72efb7 --- /dev/null +++ b/libs/qwt/src/qwt_graphic.cpp @@ -0,0 +1,1152 @@ +/****************************************************************************** + * 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 "qwt_math.h" + +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= 0x050000 + +#include + +static inline qreal qwtDevicePixelRatio() +{ + return qGuiApp ? qGuiApp->devicePixelRatio() : 1.0; +} + +#endif + +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 QT_VERSION < 0x050000 + if ( !scalablePen && pen.widthF() == 0.0 ) + { + const QPainter::RenderHints hints = painter->renderHints(); + if ( hints.testFlag( QPainter::NonCosmeticDefaultPen ) ) + scalablePen = true; + } +#endif + } + + 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, + const QTransform* initialTransform ) +{ + switch( cmd.type() ) + { + case QwtPainterCommand::Path: + { + bool doMap = false; + + if ( painter->transform().isScaling() ) + { + bool isCosmetic = painter->pen().isCosmetic(); +#if QT_VERSION < 0x050000 + if ( isCosmetic && painter->pen().widthF() == 0.0 ) + { + QPainter::RenderHints hints = painter->renderHints(); + if ( hints.testFlag( QPainter::NonCosmeticDefaultPen ) ) + isCosmetic = false; + } +#endif + + if ( isCosmetic ) + { + // OpenGL2 seems to be buggy for cosmetic pens. + // It interpolates curves in too rough steps then + + doMap = painter->paintEngine()->type() == QPaintEngine::OpenGL2; + } + else + { + doMap = renderHints.testFlag( QwtGraphic::RenderPensUnscaled ); + } + } + + if ( doMap ) + { + const QTransform tr = painter->transform(); + + painter->resetTransform(); + + QPainterPath path = tr.map( *cmd.path() ); + if ( initialTransform ) + { + painter->setTransform( *initialTransform ); + path = initialTransform->inverted().map( path ); + } + + painter->drawPath( path ); + + painter->setTransform( tr ); + } + 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 ) + { + for ( int i = 0; i < 8; i++ ) + { + const QPainter::RenderHint hint = static_cast< QPainter::RenderHint >( 1 << i ); + painter->setRenderHint( hint, data->renderHints.testFlag( hint ) ); + } + } + + 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() + : m_scalablePen( false ) + { + // QVector needs a default constructor + } + + PathInfo( const QRectF& pointRect, + const QRectF& boundingRect, bool scalablePen ) + : m_pointRect( pointRect ) + , m_boundingRect( boundingRect ) + , m_scalablePen( scalablePen ) + { + } + + inline QRectF scaledBoundingRect( qreal sx, qreal sy, bool scalePens ) const + { + if ( sx == 1.0 && sy == 1.0 ) + return m_boundingRect; + + QTransform transform; + transform.scale( sx, sy ); + + QRectF rect; + if ( scalePens && m_scalablePen ) + { + rect = transform.mapRect( m_boundingRect ); + } + else + { + rect = transform.mapRect( m_pointRect ); + + const qreal l = qAbs( m_pointRect.left() - m_boundingRect.left() ); + const qreal r = qAbs( m_pointRect.right() - m_boundingRect.right() ); + const qreal t = qAbs( m_pointRect.top() - m_boundingRect.top() ); + const qreal b = qAbs( m_pointRect.bottom() - m_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 = m_pointRect.center(); + + const qreal l = qAbs( pathRect.left() - p0.x() ); + const qreal r = qAbs( pathRect.right() - p0.x() ); + + const double w = 2.0 * qwtMinF( l, r ) + * targetRect.width() / pathRect.width(); + + double sx; + if ( scalePens && m_scalablePen ) + { + sx = w / m_boundingRect.width(); + } + else + { + const qreal pw = qwtMaxF( + qAbs( m_boundingRect.left() - m_pointRect.left() ), + qAbs( m_boundingRect.right() - m_pointRect.right() ) ); + + sx = ( w - 2 * pw ) / m_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 = m_pointRect.center(); + + const qreal t = qAbs( pathRect.top() - p0.y() ); + const qreal b = qAbs( pathRect.bottom() - p0.y() ); + + const qreal h = 2.0 * qwtMinF( t, b ) + * targetRect.height() / pathRect.height(); + + double sy; + if ( scalePens && m_scalablePen ) + { + sy = h / m_boundingRect.height(); + } + else + { + const qreal pw = qwtMaxF( + qAbs( m_boundingRect.top() - m_pointRect.top() ), + qAbs( m_boundingRect.bottom() - m_pointRect.bottom() ) ); + + sy = ( h - 2 * pw ) / m_pointRect.height(); + } + + return sy; + } + + private: + QRectF m_pointRect; + QRectF m_boundingRect; + bool m_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< QwtPainterCommand > commands; + QVector< QwtGraphic::PathInfo > pathInfos; + + QRectF boundingRect; + QRectF pointRect; + + QwtGraphic::CommandTypes commandTypes; + QwtGraphic::RenderHints renderHints; +}; + +/*! + \brief Constructor + + Initializes a null graphic + \sa isNull() + */ +QwtGraphic::QwtGraphic() +{ + setMode( QwtNullPaintDevice::PathMode ); + m_data = new PrivateData; +} + +/*! + \brief Copy constructor + + \param other Source + \sa operator=() + */ +QwtGraphic::QwtGraphic( const QwtGraphic& other ) +{ + setMode( other.mode() ); + m_data = new PrivateData( *other.m_data ); +} + +//! Destructor +QwtGraphic::~QwtGraphic() +{ + delete m_data; +} + +/*! + \brief Assignment operator + + \param other Source + \return A reference of this object + */ +QwtGraphic& QwtGraphic::operator=( const QwtGraphic& other ) +{ + setMode( other.mode() ); + *m_data = *other.m_data; + + return *this; +} + +/*! + \brief Clear all stored commands + \sa isNull() + */ +void QwtGraphic::reset() +{ + m_data->commands.clear(); + m_data->pathInfos.clear(); + + m_data->commandTypes = CommandTypes(); + + m_data->boundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); + m_data->pointRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); + m_data->defaultSize = QSizeF(); +} + +/*! + \return True, when no painter commands have been stored + \sa isEmpty(), commands() + */ +bool QwtGraphic::isNull() const +{ + return m_data->commands.isEmpty(); +} + +/*! + \return True, when the bounding rectangle is empty + \sa boundingRect(), isNull() + */ +bool QwtGraphic::isEmpty() const +{ + return m_data->boundingRect.isEmpty(); +} + +/*! + \return Types of painter commands being used + */ +QwtGraphic::CommandTypes QwtGraphic::commandTypes() const +{ + return m_data->commandTypes; +} + +/*! + Toggle an render hint + + \param hint Render hint + \param on true/false + + \sa testRenderHint(), RenderHint + */ +void QwtGraphic::setRenderHint( RenderHint hint, bool on ) +{ + if ( on ) + m_data->renderHints |= hint; + else + m_data->renderHints &= ~hint; +} + +/*! + Test a render hint + + \param hint Render hint + \return true/false + \sa setRenderHint(), RenderHint + */ +bool QwtGraphic::testRenderHint( RenderHint hint ) const +{ + return m_data->renderHints.testFlag( hint ); +} + +//! \return Render hints +QwtGraphic::RenderHints QwtGraphic::renderHints() const +{ + return m_data->renderHints; +} + +/*! + 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 ( m_data->boundingRect.width() < 0 ) + return QRectF(); + + return m_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 ( m_data->pointRect.width() < 0 ) + return QRectF(); + + return m_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( qreal sx, qreal sy ) const +{ + if ( sx == 1.0 && sy == 1.0 ) + return m_data->boundingRect; + + const bool scalePens = !( m_data->renderHints & RenderPensUnscaled ); + + QTransform transform; + transform.scale( sx, sy ); + + QRectF rect = transform.mapRect( m_data->pointRect ); + + for ( int i = 0; i < m_data->pathInfos.size(); i++ ) + rect |= m_data->pathInfos[i].scaledBoundingRect( sx, sy, scalePens ); + + return rect; +} + +//! \return Ceiled defaultSize() +QSize QwtGraphic::sizeMetrics() const +{ + const QSizeF sz = defaultSize(); + return QSize( qwtCeil( sz.width() ), qwtCeil( 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 = qwtMaxF( 0.0, size.width() ); + const double h = qwtMaxF( 0.0, size.height() ); + + m_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 ( !m_data->defaultSize.isEmpty() ) + return m_data->defaultSize; + + return boundingRect().size(); +} + +/*! + Find the height for a given width + + The height is calculated using the aspect ratio of defaultSize(). + + \param width Width + + \return Calculated height + \sa defaultSize() + */ +qreal QwtGraphic::heightForWidth( qreal width ) const +{ + const QSizeF sz = defaultSize(); + if ( sz.isEmpty() ) + return 0.0; + + return sz.height() * width / sz.width(); +} + +/*! + Find the width for a given height + + The width is calculated using the aspect ratio of defaultSize(). + + \param height Height + + \return Calculated width + \sa defaultSize() + */ +qreal QwtGraphic::widthForHeight( qreal height ) const +{ + const QSizeF sz = defaultSize(); + if ( sz.isEmpty() ) + return 0.0; + + return sz.width() * height / sz.height(); +} + +/*! + \brief Replay all recorded painter commands + \param painter Qt painter + */ +void QwtGraphic::render( QPainter* painter ) const +{ + renderGraphic( painter, NULL ); +} + +void QwtGraphic::renderGraphic( QPainter* painter, QTransform* initialTransform ) const +{ + if ( isNull() ) + return; + + const int numCommands = m_data->commands.size(); + const QwtPainterCommand* commands = m_data->commands.constData(); + + const QTransform transform = painter->transform(); + + painter->save(); + + for ( int i = 0; i < numCommands; i++ ) + { + qwtExecCommand( painter, commands[i], + m_data->renderHints, transform, initialTransform ); + } + + 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 ( m_data->pointRect.width() > 0.0 ) + sx = rect.width() / m_data->pointRect.width(); + + if ( m_data->pointRect.height() > 0.0 ) + sy = rect.height() / m_data->pointRect.height(); + + const bool scalePens = !m_data->renderHints.testFlag( RenderPensUnscaled ); + + for ( int i = 0; i < m_data->pathInfos.size(); i++ ) + { + const PathInfo& info = m_data->pathInfos[i]; + + const double ssx = info.scaleFactorX( + m_data->pointRect, rect, scalePens ); + + if ( ssx > 0.0 ) + sx = qwtMinF( sx, ssx ); + + const double ssy = info.scaleFactorY( + m_data->pointRect, rect, scalePens ); + + if ( ssy > 0.0 ) + sy = qwtMinF( sy, ssy ); + } + + if ( aspectRatioMode == Qt::KeepAspectRatio ) + { + const qreal s = qwtMinF( sx, sy ); + sx = s; + sy = s; + } + else if ( aspectRatioMode == Qt::KeepAspectRatioByExpanding ) + { + const qreal s = qwtMaxF( sx, sy ); + sx = s; + sy = s; + } + + QTransform tr; + tr.translate( rect.center().x() - 0.5 * sx * m_data->pointRect.width(), + rect.center().y() - 0.5 * sy * m_data->pointRect.height() ); + tr.scale( sx, sy ); + tr.translate( -m_data->pointRect.x(), -m_data->pointRect.y() ); + + const QTransform transform = painter->transform(); + + painter->setTransform( tr, true ); + + if ( !scalePens && transform.isScaling() ) + { + // we don't want to scale pens according to sx/sy, + // but we want to apply the scaling from the + // painter transformation later + + QTransform initialTransform; + initialTransform.scale( transform.m11(), transform.m22() ); + + renderGraphic( painter, &initialTransform ); + } + else + { + renderGraphic( painter, NULL ); + } + + 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. + + \param devicePixelRatio Device pixel ratio for the pixmap. + If devicePixelRatio <= 0.0 the pixmap + is initialized with the system default. + + \return The graphic as pixmap in default size + \sa defaultSize(), toImage(), render() + */ +QPixmap QwtGraphic::toPixmap( qreal devicePixelRatio ) const +{ + if ( isNull() ) + return QPixmap(); + + const QSizeF sz = defaultSize(); + + const int w = qwtCeil( sz.width() ); + const int h = qwtCeil( sz.height() ); + + QPixmap pixmap( w, h ); + +#if QT_VERSION >= 0x050000 + if ( devicePixelRatio <= 0.0 ) + devicePixelRatio = qwtDevicePixelRatio(); + + pixmap.setDevicePixelRatio( devicePixelRatio ); +#else + Q_UNUSED( devicePixelRatio ) +#endif + + 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 + \param devicePixelRatio Device pixel ratio for the pixmap. + If devicePixelRatio <= 0.0 the pixmap + is initialized with the system default. + + \return The graphic as pixmap + \sa toImage(), render() + */ +QPixmap QwtGraphic::toPixmap( const QSize& size, + Qt::AspectRatioMode aspectRatioMode, qreal devicePixelRatio ) const +{ + QPixmap pixmap( size ); + +#if QT_VERSION >= 0x050000 + if ( devicePixelRatio <= 0.0 ) + devicePixelRatio = qwtDevicePixelRatio(); + + pixmap.setDevicePixelRatio( devicePixelRatio ); +#else + Q_UNUSED( devicePixelRatio ) +#endif + 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 ( will be multiplied by the devicePixelRatio ) + \param aspectRatioMode Aspect ratio how to scale the graphic + \param devicePixelRatio Device pixel ratio for the image. + If devicePixelRatio <= 0.0 the pixmap + is initialized with the system default. + + \return The graphic as image + \sa toPixmap(), render() + */ +QImage QwtGraphic::toImage( const QSize& size, + Qt::AspectRatioMode aspectRatioMode, qreal devicePixelRatio ) const +{ +#if QT_VERSION >= 0x050000 + if ( devicePixelRatio <= 0.0 ) + devicePixelRatio = qwtDevicePixelRatio(); + + QImage image( size* devicePixelRatio, QImage::Format_ARGB32_Premultiplied ); + image.setDevicePixelRatio( devicePixelRatio ); +#else + Q_UNUSED( devicePixelRatio ) + QImage image( size, QImage::Format_ARGB32_Premultiplied ); +#endif + + 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 multiplied by the devicePixelRatio. + + \param devicePixelRatio Device pixel ratio for the image. + If devicePixelRatio <= 0.0 the pixmap + is initialized with the system default. + + \return The graphic as image in default size + \sa defaultSize(), toPixmap(), render() + */ +QImage QwtGraphic::toImage( qreal devicePixelRatio ) const +{ + if ( isNull() ) + return QImage(); + + const QSizeF sz = defaultSize(); + + int w = qwtCeil( sz.width() ); + int h = qwtCeil( sz.height() ); + +#if QT_VERSION >= 0x050000 + if ( devicePixelRatio <= 0.0 ) + devicePixelRatio = qwtDevicePixelRatio(); + + w *= devicePixelRatio; + h *= devicePixelRatio; + + QImage image( w, h, QImage::Format_ARGB32 ); + image.setDevicePixelRatio( devicePixelRatio ); +#else + Q_UNUSED( devicePixelRatio ) + QImage image( w, h, QImage::Format_ARGB32 ); +#endif + + 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; + + m_data->commands += QwtPainterCommand( path ); + m_data->commandTypes |= QwtGraphic::VectorData; + + 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 ); + + m_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; + + m_data->commands += QwtPainterCommand( rect, pixmap, subRect ); + m_data->commandTypes |= QwtGraphic::RasterData; + + const QRectF r = painter->transform().mapRect( rect ); + updateControlPointRect( r ); + updateBoundingRect( r ); +} + +/*! + \brief Store a image command in the command list + + \param rect target 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; + + m_data->commands += QwtPainterCommand( rect, image, subRect, flags ); + m_data->commandTypes |= QwtGraphic::RasterData; + + 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 ) +{ + m_data->commands += QwtPainterCommand( state ); + + if ( state.state() & QPaintEngine::DirtyTransform ) + { + if ( !( m_data->commandTypes & QwtGraphic::Transformation ) ) + { + /* + QTransform::isScaling() returns true for all type + of transformations beside simple translations + even if it is f.e a rotation + */ + if ( state.transform().isScaling() ) + m_data->commandTypes |= QwtGraphic::Transformation; + } + } +} + +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( cr ); + + br &= cr; + } + + if ( m_data->boundingRect.width() < 0 ) + m_data->boundingRect = br; + else + m_data->boundingRect |= br; +} + +void QwtGraphic::updateControlPointRect( const QRectF& rect ) +{ + if ( m_data->pointRect.width() < 0.0 ) + m_data->pointRect = rect; + else + m_data->pointRect |= rect; +} + +/*! + \return List of recorded paint commands + \sa setCommands() + */ +const QVector< QwtPainterCommand >& QwtGraphic::commands() const +{ + return m_data->commands; +} + +/*! + \brief Append paint commands + + \param commands Paint commands + \sa commands() + */ +void QwtGraphic::setCommands( const 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(); + + const QTransform noTransform; + const RenderHints noRenderHints; + + QPainter painter( this ); + for ( int i = 0; i < numCommands; i++ ) + qwtExecCommand( &painter, cmds[i], noRenderHints, noTransform, NULL ); + + painter.end(); +} diff --git a/libs/qwt/src/qwt_graphic.h b/libs/qwt/src/qwt_graphic.h new file mode 100644 index 00000000..024cc679 --- /dev/null +++ b/libs/qwt/src/qwt_graphic.h @@ -0,0 +1,204 @@ +/****************************************************************************** + * 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 + +class QwtPainterCommand; +class QPixmap; +class QImage; + +/*! + \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 rendering a QwtGraphic a specific scaling between + the controlPointRect() and the coordinates of the target rectangle + is set up internally in render(). + + When RenderPensUnscaled is set this specific scaling is applied + for the control points only, but not for the pens. + All other painter transformations ( set up by application code ) + are supposed to work like usual. + + \sa render(); + */ + RenderPensUnscaled = 0x1 + }; + + Q_DECLARE_FLAGS( RenderHints, RenderHint ) + + /*! + Indicator if the graphic contains a specific type of painter command + \sa CommandTypes, commandTypes(); + */ + enum CommandType + { + //! The graphic contains scalable vector data + VectorData = 1 << 0, + + //! The graphic contains raster data ( QPixmap or QImage ) + RasterData = 1 << 1, + + //! The graphic contains transformations beyond simple translations + Transformation = 1 << 2 + }; + + Q_DECLARE_FLAGS( CommandTypes, CommandType ) + + QwtGraphic(); + QwtGraphic( const QwtGraphic& ); + + virtual ~QwtGraphic(); + + QwtGraphic& operator=( const QwtGraphic& ); + + void reset(); + + bool isNull() const; + bool isEmpty() const; + + CommandTypes commandTypes() const; + + void render( QPainter* ) const; + + void render( QPainter*, const QSizeF&, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio ) const; + + void render( QPainter*, const QPointF&, + Qt::Alignment = Qt::AlignTop | Qt::AlignLeft ) const; + + void render( QPainter*, const QRectF&, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio ) const; + + QPixmap toPixmap( qreal devicePixelRatio = 0.0 ) const; + + QPixmap toPixmap( const QSize&, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio, + qreal devicePixelRatio = 0.0 ) const; + + QImage toImage( qreal devicePixelRatio = 0.0 ) const; + + QImage toImage( const QSize&, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio, + qreal devicePixelRatio = 0.0 ) const; + + QRectF scaledBoundingRect( qreal sx, qreal sy ) const; + + QRectF boundingRect() const; + QRectF controlPointRect() const; + + const QVector< QwtPainterCommand >& commands() const; + void setCommands( const QVector< QwtPainterCommand >& ); + + void setDefaultSize( const QSizeF& ); + QSizeF defaultSize() const; + + qreal heightForWidth( qreal width ) const; + qreal widthForHeight( qreal height ) const; + + void setRenderHint( RenderHint, bool on = true ); + bool testRenderHint( RenderHint ) const; + + RenderHints renderHints() const; + + protected: + virtual QSize sizeMetrics() const QWT_OVERRIDE; + + virtual void drawPath( const QPainterPath& ) QWT_OVERRIDE; + + virtual void drawPixmap( const QRectF&, + const QPixmap&, const QRectF& ) QWT_OVERRIDE; + + virtual void drawImage( const QRectF&, const QImage&, + const QRectF&, Qt::ImageConversionFlags ) QWT_OVERRIDE; + + virtual void updateState( const QPaintEngineState& ) QWT_OVERRIDE; + + private: + void renderGraphic( QPainter*, QTransform* ) const; + + void updateBoundingRect( const QRectF& ); + void updateControlPointRect( const QRectF& ); + + class PathInfo; + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtGraphic::RenderHints ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtGraphic::CommandTypes ) +Q_DECLARE_METATYPE( QwtGraphic ) + +#endif diff --git a/libs/qwt/src/qwt_interval.cpp b/libs/qwt/src/qwt_interval.cpp new file mode 100644 index 00000000..644502da --- /dev/null +++ b/libs/qwt/src/qwt_interval.cpp @@ -0,0 +1,403 @@ +/****************************************************************************** + * 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" + +namespace +{ + static const struct RegisterQwtInterval + { + inline RegisterQwtInterval() { qRegisterMetaType< QwtInterval >(); } + + } qwtRegisterQwtInterval; +} + +/*! + \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 ( m_minValue > m_maxValue ) + { + return inverted(); + } + if ( m_minValue == m_maxValue && m_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 ( m_borderFlags & ExcludeMinimum ) + borderFlags |= ExcludeMaximum; + + if ( m_borderFlags & ExcludeMaximum ) + borderFlags |= ExcludeMinimum; + + return QwtInterval( m_maxValue, m_minValue, borderFlags ); +} + +/*! + Test if a value is inside an interval + + \param value Value + \return true, if value lies inside the boundaries + */ +bool QwtInterval::contains( double value ) const +{ + if ( !isValid() ) + return false; + + if ( ( value < m_minValue ) || ( value > m_maxValue ) ) + return false; + + if ( ( value == m_minValue ) && ( m_borderFlags & ExcludeMinimum ) ) + return false; + + if ( ( value == m_maxValue ) && ( m_borderFlags & ExcludeMaximum ) ) + return false; + + return true; +} + +/*! + Test if an interval is inside an interval + + \param interval Interval + \return true, if interval lies inside the boundaries + */ +bool QwtInterval::contains( const QwtInterval& interval ) const +{ + if ( !isValid() || !interval.isValid() ) + return false; + + if ( ( interval.m_minValue < m_minValue ) || ( interval.m_maxValue > m_maxValue ) ) + return false; + + if ( m_borderFlags ) + { + if ( interval.m_minValue == m_minValue ) + { + if ( ( m_borderFlags & ExcludeMinimum ) + && !( interval.m_borderFlags & ExcludeMinimum ) ) + { + return false; + } + } + + if ( interval.m_maxValue == m_maxValue ) + { + if ( ( m_borderFlags & ExcludeMaximum ) + && !( interval.m_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 ( m_minValue < other.minValue() ) + { + united.setMinValue( m_minValue ); + flags &= m_borderFlags & ExcludeMinimum; + } + else if ( other.minValue() < m_minValue ) + { + united.setMinValue( other.minValue() ); + flags &= other.borderFlags() & ExcludeMinimum; + } + else // m_minValue == other.minValue() + { + united.setMinValue( m_minValue ); + flags &= ( m_borderFlags & other.borderFlags() ) & ExcludeMinimum; + } + + // maximum + if ( m_maxValue > other.maxValue() ) + { + united.setMaxValue( m_maxValue ); + flags &= m_borderFlags & ExcludeMaximum; + } + else if ( other.maxValue() > m_maxValue ) + { + united.setMaxValue( other.maxValue() ); + flags &= other.borderFlags() & ExcludeMaximum; + } + else // m_maxValue == other.maxValue() ) + { + united.setMaxValue( m_maxValue ); + flags &= m_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 - m_maxValue ), qAbs( value - m_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( m_minValue, lowerBound ); + minValue = qMin( minValue, upperBound ); + + double maxValue = qMax( m_maxValue, lowerBound ); + maxValue = qMin( maxValue, upperBound ); + + return QwtInterval( minValue, maxValue, m_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, m_minValue ), + qMax( value, m_maxValue ), m_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 + +#include + +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/libs/qwt/src/qwt_interval.h b/libs/qwt/src/qwt_interval.h new file mode 100644 index 00000000..9d59fac7 --- /dev/null +++ b/libs/qwt/src/qwt_interval.h @@ -0,0 +1,335 @@ +/****************************************************************************** + * 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 + +/*! + \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 + Q_DECLARE_FLAGS( BorderFlags, BorderFlag ) + + 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 lowerBound, double upperBound ) 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; + long double widthL() const; + + void setMinValue( double ); + void setMaxValue( double ); + + bool contains( double value ) const; + bool contains( const QwtInterval& ) 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 m_minValue; + double m_maxValue; + BorderFlags m_borderFlags; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtInterval::BorderFlags ) +Q_DECLARE_METATYPE( QwtInterval ) +Q_DECLARE_TYPEINFO( QwtInterval, Q_MOVABLE_TYPE ); + +/*! + \brief Default Constructor + + Creates an invalid interval [0.0, -1.0] + \sa setInterval(), isValid() + */ +inline QwtInterval::QwtInterval() + : m_minValue( 0.0 ) + , m_maxValue( -1.0 ) + , m_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 ) + : m_minValue( minValue ) + , m_maxValue( maxValue ) + , m_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 ) +{ + m_minValue = minValue; + m_maxValue = maxValue; + m_borderFlags = borderFlags; +} + +/*! + Change the border flags + + \param borderFlags Or'd BorderMode flags + \sa borderFlags() + */ +inline void QwtInterval::setBorderFlags( BorderFlags borderFlags ) +{ + m_borderFlags = borderFlags; +} + +/*! + \return Border flags + \sa setBorderFlags() + */ +inline QwtInterval::BorderFlags QwtInterval::borderFlags() const +{ + return m_borderFlags; +} + +/*! + Assign the lower limit of the interval + + \param minValue Minimum value + */ +inline void QwtInterval::setMinValue( double minValue ) +{ + m_minValue = minValue; +} + +/*! + Assign the upper limit of the interval + + \param maxValue Maximum value + */ +inline void QwtInterval::setMaxValue( double maxValue ) +{ + m_maxValue = maxValue; +} + +//! \return Lower limit of the interval +inline double QwtInterval::minValue() const +{ + return m_minValue; +} + +//! \return Upper limit of the interval +inline double QwtInterval::maxValue() const +{ + return m_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 ( ( m_borderFlags & ExcludeBorders ) == 0 ) + return m_minValue <= m_maxValue; + else + return m_minValue < m_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() ? ( m_maxValue - m_minValue ) : 0.0; +} + +/*! + \brief Return the width of an interval as long double + + The width of invalid intervals is 0.0, otherwise the result is + maxValue() - minValue(). + + \return Interval width + \sa isValid() + */ +inline long double QwtInterval::widthL() const +{ + if ( !isValid() ) + return 0.0; + + return static_cast< long double >( m_maxValue ) + - static_cast< long double >( m_minValue ); +} + +/*! + \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 ( m_minValue == other.m_minValue ) && + ( m_maxValue == other.m_maxValue ) && + ( m_borderFlags == other.m_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() && m_minValue >= m_maxValue; +} + +/*! + Invalidate the interval + + The limits are set to interval [0.0, -1.0] + \sa isValid() + */ +inline void QwtInterval::invalidate() +{ + m_minValue = 0.0; + m_maxValue = -1.0; +} + +#ifndef QT_NO_DEBUG_STREAM +QWT_EXPORT QDebug operator<<( QDebug, const QwtInterval& ); +#endif + +#endif diff --git a/libs/qwt/src/qwt_interval_symbol.cpp b/libs/qwt/src/qwt_interval_symbol.cpp new file mode 100644 index 00000000..85f72ffc --- /dev/null +++ b/libs/qwt/src/qwt_interval_symbol.cpp @@ -0,0 +1,315 @@ +/****************************************************************************** + * 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 +#include + +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 ) +{ + m_data = new PrivateData(); + m_data->style = style; +} + +//! Copy constructor +QwtIntervalSymbol::QwtIntervalSymbol( const QwtIntervalSymbol& other ) +{ + m_data = new PrivateData(); + *m_data = *other.m_data; +} + +//! Destructor +QwtIntervalSymbol::~QwtIntervalSymbol() +{ + delete m_data; +} + +//! \brief Assignment operator +QwtIntervalSymbol& QwtIntervalSymbol::operator=( + const QwtIntervalSymbol& other ) +{ + *m_data = *other.m_data; + return *this; +} + +//! \brief Compare two symbols +bool QwtIntervalSymbol::operator==( + const QwtIntervalSymbol& other ) const +{ + return *m_data == *other.m_data; +} + +//! \brief Compare two symbols +bool QwtIntervalSymbol::operator!=( + const QwtIntervalSymbol& other ) const +{ + return !( *m_data == *other.m_data ); +} + +/*! + Specify the symbol style + + \param style Style + \sa style(), Style + */ +void QwtIntervalSymbol::setStyle( Style style ) +{ + m_data->style = style; +} + +/*! + \return Current symbol style + \sa setStyle() + */ +QwtIntervalSymbol::Style QwtIntervalSymbol::style() const +{ + return m_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 ) +{ + m_data->width = width; +} + +/*! + \return Width of the symbol. + \sa setWidth(), setStyle() + */ +int QwtIntervalSymbol::width() const +{ + return m_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 ) +{ + m_data->brush = brush; +} + +/*! + \return Brush + \sa setBrush() + */ +const QBrush& QwtIntervalSymbol::brush() const +{ + return m_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 ) +{ + m_data->pen = pen; +} + +/*! + \return Pen + \sa setPen(), brush() + */ +const QPen& QwtIntervalSymbol::pen() const +{ + return m_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 = QwtPainter::effectivePenWidth( painter->pen() ); + + QPointF p1 = from; + QPointF p2 = to; + if ( QwtPainter::roundingAlignment( painter ) ) + { + p1 = p1.toPoint(); + p2 = p2.toPoint(); + } + + switch ( m_data->style ) + { + case QwtIntervalSymbol::Bar: + { + QwtPainter::drawLine( painter, p1, p2 ); + if ( m_data->width > pw ) + { + if ( ( orientation == Qt::Horizontal ) + && ( p1.y() == p2.y() ) ) + { + const double sw = m_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 = m_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 = m_data->width; + + const double dx = p2.x() - p1.x(); + const double dy = p2.y() - p1.y(); + const double angle = std::atan2( 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 ( m_data->width <= pw ) + { + QwtPainter::drawLine( painter, p1, p2 ); + } + else + { + if ( ( orientation == Qt::Horizontal ) + && ( p1.y() == p2.y() ) ) + { + const double sw = m_data->width; + + const double y = p1.y() - m_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 = m_data->width; + + const double x = p1.x() - m_data->width / 2; + QwtPainter::drawRect( painter, + x, p1.y(), sw, p2.y() - p1.y() ); + } + else + { + const double sw = m_data->width; + + const double dx = p2.x() - p1.x(); + const double dy = p2.y() - p1.y(); + const double angle = std::atan2( 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/libs/qwt/src/qwt_interval_symbol.h b/libs/qwt/src/qwt_interval_symbol.h new file mode 100644 index 00000000..770cdd9f --- /dev/null +++ b/libs/qwt/src/qwt_interval_symbol.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * 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 + +class QPainter; +class QPen; +class QBrush; +class QPointF; +class QColor; + +/*! + \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: + explicit 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& ); + 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* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_knob.cpp b/libs/qwt/src/qwt_knob.cpp new file mode 100644 index 00000000..34cb0ff2 --- /dev/null +++ b/libs/qwt/src/qwt_knob.cpp @@ -0,0 +1,850 @@ +/****************************************************************************** + * 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_painter.h" +#include "qwt_scale_map.h" +#include "qwt_math.h" +#include "qwt.h" + +#include +#include +#include +#include +#include +#include +#include + +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 = qwtCeil( knob->scaleDraw()->extent( knob->font() ) ); + const int d = 2 * ( extent + 4 ) + knobWidth; + + const QMargins m = knob->contentsMargins(); + return QSize( d + m.left() + m.right(), d + m.top() + m.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 ) +{ + m_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 m_data; +} + +/*! + \brief Set the knob type + + \param knobStyle Knob type + \sa knobStyle(), setBorderWidth() + */ +void QwtKnob::setKnobStyle( KnobStyle knobStyle ) +{ + if ( m_data->knobStyle != knobStyle ) + { + m_data->knobStyle = knobStyle; + update(); + } +} + +/*! + \return Marker type of the knob + \sa setKnobStyle(), setBorderWidth() + */ +QwtKnob::KnobStyle QwtKnob::knobStyle() const +{ + return m_data->knobStyle; +} + +/*! + \brief Set the marker type of the knob + + \param markerStyle Marker type + \sa markerStyle(), setMarkerSize() + */ +void QwtKnob::setMarkerStyle( MarkerStyle markerStyle ) +{ + if ( m_data->markerStyle != markerStyle ) + { + m_data->markerStyle = markerStyle; + update(); + } +} + +/*! + \return Marker type of the knob + \sa setMarkerStyle(), setMarkerSize() + */ +QwtKnob::MarkerStyle QwtKnob::markerStyle() const +{ + return m_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 != m_data->totalAngle ) + { + m_data->totalAngle = angle; + + scaleDraw()->setAngleRange( -0.5 * m_data->totalAngle, + 0.5 * m_data->totalAngle ); + + updateGeometry(); + update(); + } +} + +/*! + \return the total angle + \sa setTotalAngle(), setNumTurns(), numTurns() + */ +double QwtKnob::totalAngle() const +{ + return m_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 && m_data->totalAngle <= 360.0 ) + return; + + const double angle = numTurns * 360.0; + if ( angle != m_data->totalAngle ) + { + m_data->totalAngle = angle; + + scaleDraw()->setAngleRange( -0.5 * m_data->totalAngle, + 0.5 * m_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 qwtCeil( m_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( m_data->totalAngle ); + + updateGeometry(); + update(); +} + +/*! + \return the scale draw of the knob + \sa setScaleDraw() + */ +const QwtRoundScaleDraw* QwtKnob::scaleDraw() const +{ + return static_cast< const QwtRoundScaleDraw* >( abstractScaleDraw() ); +} + +/*! + \return the scale draw of the knob + \sa setScaleDraw() + */ +QwtRoundScaleDraw* QwtKnob::scaleDraw() +{ + return static_cast< QwtRoundScaleDraw* >( 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 = qwtCeil( scaleDraw()->extent( font() ) ); + const int d = extent + m_data->scaleDist; + + int w = m_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 ( m_data->alignment & Qt::AlignLeft ) + { + r.moveLeft( cr.left() + d ); + } + else if ( m_data->alignment & Qt::AlignRight ) + { + r.moveRight( cr.right() - d ); + } + else + { + r.moveCenter( QPoint( cr.center().x(), r.center().y() ) ); + } + + if ( m_data->alignment & Qt::AlignTop ) + { + r.moveTop( cr.top() + d ); + } + else if ( m_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( scaleMap().transform( value() ) ); + + m_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 - m_data->mouseOffset ); + + if ( scaleMap().pDist() > 360.0 ) + { + angle = qwtToDegrees( angle ); + + const double v = scaleMap().transform( value() ); + + int numTurns = qwtFloor( ( 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() ); + + m_data->mouseOffset += ( boundedAngle - angle ); + angle = boundedAngle; + } + } + else + { + angle = qwtToScaleAngle( angle ); + + double boundedAngle = qBound( scaleMap().p1(), angle, scaleMap().p2() ); + + if ( !wrapping() ) + { + const double currentAngle = scaleMap().transform( value() ); + + if ( ( currentAngle > 90.0 ) && ( boundedAngle < -90.0 ) ) + boundedAngle = scaleMap().p2(); + else if ( ( currentAngle < -90.0 ) && ( boundedAngle > 90.0 ) ) + boundedAngle = scaleMap().p1(); + + m_data->mouseOffset += ( boundedAngle - angle ); + } + + angle = boundedAngle; + } + + return scaleMap().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.initFrom(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() + m_data->scaleDist ); + scaleDraw()->moveCenter( knobRect.center() ); + + scaleDraw()->draw( &painter, palette() ); + } + + drawKnob( &painter, knobRect ); + + drawMarker( &painter, knobRect, + qwtNormalizeDegrees( scaleMap().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 -= m_data->borderWidth * 0.5; + + QRectF aRect( 0, 0, dim, dim ); + aRect.moveCenter( knobRect.center() ); + + QPen pen( Qt::NoPen ); + if ( m_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, m_data->borderWidth ); + } + + QBrush brush; + switch( m_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( 0.5, c); + gradient.setColorAt( 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 ( m_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() - m_data->borderWidth ) - margin; + if ( radius < 1.0 ) + radius = 1.0; + + double markerSize = m_data->markerSize; + if ( markerSize <= 0 ) + markerSize = qRound( 0.4 * radius ); + + switch ( m_data->markerStyle ) + { + case Notch: + case Nub: + { + const double dotWidth = qwtMinF( 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 ( m_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 = qwtMinF( 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 = qwtMaxF( 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 = qwtMaxF( 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 = m_data->knobWidth; + if ( w <= 0 ) + { + w = qMin( cr.width(), cr.height() ); + } + else + { + const int extent = qCeil( scaleDraw()->extent( font() ) ); + w += 2 * ( extent + m_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 ( m_data->alignment != alignment ) + { + m_data->alignment = alignment; + update(); + } +} + +/*! + \return Alignment of the knob inside of contentsRect() + \sa setAlignment(), knobWidth(), knobRect() + */ +Qt::Alignment QwtKnob::alignment() const +{ + return m_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 != m_data->knobWidth ) + { + QSizePolicy::Policy policy; + if ( width > 0 ) + policy = QSizePolicy::Minimum; + else + policy = QSizePolicy::MinimumExpanding; + + setSizePolicy( policy, policy ); + + m_data->knobWidth = width; + + updateGeometry(); + update(); + } +} + +//! Return the width of the knob +int QwtKnob::knobWidth() const +{ + return m_data->knobWidth; +} + +/*! + \brief Set the knob's border width + \param borderWidth new border width + */ +void QwtKnob::setBorderWidth( int borderWidth ) +{ + m_data->borderWidth = qMax( borderWidth, 0 ); + + updateGeometry(); + update(); +} + +//! Return the border width +int QwtKnob::borderWidth() const +{ + return m_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 ( m_data->markerSize != size ) + { + m_data->markerSize = size; + update(); + } +} + +/*! + \return Marker size + \sa setMarkerSize() + */ +int QwtKnob::markerSize() const +{ + return m_data->markerSize; +} + +/*! + \return sizeHint() + */ +QSize QwtKnob::sizeHint() const +{ + const QSize hint = qwtKnobSizeHint( this, 50 ); + return qwtExpandedToGlobalStrut( hint ); +} + +/*! + \return Minimum size hint + \sa sizeHint() + */ +QSize QwtKnob::minimumSizeHint() const +{ + return qwtKnobSizeHint( this, 20 ); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_knob.cpp" +#endif diff --git a/libs/qwt/src/qwt_knob.h b/libs/qwt/src/qwt_knob.h new file mode 100644 index 00000000..9d8c7a65 --- /dev/null +++ b/libs/qwt/src/qwt_knob.h @@ -0,0 +1,178 @@ +/****************************************************************************** + * 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 ); + int borderWidth() const; + + void setMarkerStyle( MarkerStyle ); + MarkerStyle markerStyle() const; + + void setMarkerSize( int ); + int markerSize() const; + + virtual QSize sizeHint() const QWT_OVERRIDE; + virtual QSize minimumSizeHint() const QWT_OVERRIDE; + + void setScaleDraw( QwtRoundScaleDraw* ); + + const QwtRoundScaleDraw* scaleDraw() const; + QwtRoundScaleDraw* scaleDraw(); + + QRect knobRect() const; + + protected: + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + virtual void changeEvent( QEvent* ) QWT_OVERRIDE; + + virtual void drawKnob( QPainter*, const QRectF& ) const; + + virtual void drawFocusIndicator( QPainter* ) const; + + virtual void drawMarker( QPainter*, + const QRectF&, double angle ) const; + + virtual double scrolledTo( const QPoint& ) const QWT_OVERRIDE; + virtual bool isScrollPosition( const QPoint& ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_legend.cpp b/libs/qwt/src/qwt_legend.cpp new file mode 100644 index 00000000..990abc28 --- /dev/null +++ b/libs/qwt/src/qwt_legend.cpp @@ -0,0 +1,835 @@ +/****************************************************************************** + * 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_painter.h" +#include "qwt_graphic.h" + +#include +#include +#include +#include +#include + +namespace +{ + class LegendMap + { + public: + inline bool isEmpty() const { return m_entries.isEmpty(); } + + void insert( const QVariant&, const QList< QWidget* >& ); + void remove( const QVariant& ); + + void removeWidget( const QWidget* ); + + QList< QWidget* > 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< QWidget* > widgets; + }; + + QList< Entry > m_entries; + }; + + void LegendMap::insert( const QVariant& itemInfo, + const QList< QWidget* >& widgets ) + { + for ( int i = 0; i < m_entries.size(); i++ ) + { + Entry& entry = m_entries[i]; + if ( entry.itemInfo == itemInfo ) + { + entry.widgets = widgets; + return; + } + } + + Entry newEntry; + newEntry.itemInfo = itemInfo; + newEntry.widgets = widgets; + + m_entries += newEntry; + } + + void LegendMap::remove( const QVariant& itemInfo ) + { + for ( int i = 0; i < m_entries.size(); i++ ) + { + Entry& entry = m_entries[i]; + if ( entry.itemInfo == itemInfo ) + { + m_entries.removeAt( i ); + return; + } + } + } + + void LegendMap::removeWidget( const QWidget* widget ) + { + QWidget* w = const_cast< QWidget* >( widget ); + + for ( int i = 0; i < m_entries.size(); i++ ) + m_entries[ i ].widgets.removeAll( w ); + } + + QVariant LegendMap::itemInfo( const QWidget* widget ) const + { + if ( widget != NULL ) + { + QWidget* w = const_cast< QWidget* >( widget ); + + for ( int i = 0; i < m_entries.size(); i++ ) + { + const Entry& entry = m_entries[i]; + if ( entry.widgets.indexOf( w ) >= 0 ) + return entry.itemInfo; + } + } + + return QVariant(); + } + + QList< QWidget* > LegendMap::legendWidgets( const QVariant& itemInfo ) const + { + if ( itemInfo.isValid() ) + { + for ( int i = 0; i < m_entries.size(); i++ ) + { + const Entry& entry = m_entries[i]; + if ( entry.itemInfo == itemInfo ) + return entry.widgets; + } + } + + return QList< QWidget* >(); + } +} + +class QwtLegend::PrivateData +{ + public: + PrivateData() + : itemMode( QwtLegendData::ReadOnly ) + , view( NULL ) + { + } + + QwtLegendData::Mode itemMode; + LegendMap itemMap; + + class LegendView; + LegendView* view; +}; + +class QwtLegend::PrivateData::LegendView QWT_FINAL : public QScrollArea +{ + public: + explicit 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 ) QWT_OVERRIDE + { + 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 ) QWT_OVERRIDE + { + 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< QwtDynGridLayout* >( + contentsWidget->layout() ); + if ( tl == NULL ) + return; + + const QSize visibleSize = viewport()->contentsRect().size(); + + const QMargins m = tl->contentsMargins(); + const int minW = tl->maxItemWidth() + m.left() + m.right(); + + 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 ); + + m_data = new QwtLegend::PrivateData; + + m_data->view = new QwtLegend::PrivateData::LegendView( this ); + m_data->view->setObjectName( "QwtLegendView" ); + m_data->view->setFrameStyle( NoFrame ); + + QwtDynGridLayout* gridLayout = new QwtDynGridLayout( + m_data->view->contentsWidget ); + gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop ); + + m_data->view->contentsWidget->installEventFilter( this ); + + QVBoxLayout* layout = new QVBoxLayout( this ); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->addWidget( m_data->view ); +} + +//! Destructor +QwtLegend::~QwtLegend() +{ + delete m_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< QwtDynGridLayout* >( + m_data->view->contentsWidget->layout() ); + if ( tl ) + tl->setMaxColumns( numColums ); + + updateGeometry(); +} + +/*! + \return Maximum number of entries in a row + \sa setMaxColumns(), QwtDynGridLayout::maxColumns() + */ +uint QwtLegend::maxColumns() const +{ + uint maxCols = 0; + + const QwtDynGridLayout* tl = qobject_cast< const QwtDynGridLayout* >( + m_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 ) +{ + m_data->itemMode = mode; +} + +/*! + \return Default item mode + \sa setDefaultItemMode() + */ +QwtLegendData::Mode QwtLegend::defaultItemMode() const +{ + return m_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 m_data->view->contentsWidget; +} + +/*! + \return Horizontal scrollbar + \sa verticalScrollBar() + */ +QScrollBar* QwtLegend::horizontalScrollBar() const +{ + return m_data->view->horizontalScrollBar(); +} + +/*! + \return Vertical scrollbar + \sa horizontalScrollBar() + */ +QScrollBar* QwtLegend::verticalScrollBar() const +{ + return m_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 m_data->view->contentsWidget; +} + +/*! + \brief Update the entries for an item + + \param itemInfo Info for an item + \param legendData List of legend entry attributes for the item + */ +void QwtLegend::updateLegend( const QVariant& itemInfo, + const QList< QwtLegendData >& legendData ) +{ + QList< QWidget* > widgetList = legendWidgets( itemInfo ); + + if ( widgetList.size() != legendData.size() ) + { + QLayout* contentsLayout = m_data->view->contentsWidget->layout(); + + while ( widgetList.size() > legendData.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(); + } + + widgetList.reserve( legendData.size() ); + + for ( int i = widgetList.size(); i < legendData.size(); i++ ) + { + QWidget* widget = createWidget( legendData[i] ); + + if ( contentsLayout ) + contentsLayout->addWidget( widget ); + + if ( isVisible() ) + { + // QLayout does a delayed show, with the effect, that + // the size hint will be wrong, when applications + // call replot() right after changing the list + // of plot items. So we better do the show now. + + widget->setVisible( true ); + } + + widgetList += widget; + } + + if ( widgetList.isEmpty() ) + { + m_data->itemMap.remove( itemInfo ); + } + else + { + m_data->itemMap.insert( itemInfo, widgetList ); + } + + updateTabOrder(); + } + + for ( int i = 0; i < legendData.size(); i++ ) + updateWidget( widgetList[i], legendData[i] ); +} + +/*! + \brief Create a widget to be inserted into the legend + + The default implementation returns a QwtLegendLabel. + + \param legendData 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& legendData ) const +{ + Q_UNUSED( legendData ); + + 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 legendData Attributes to be displayed + + \sa createWidget() + \note When widget is no QwtLegendLabel updateWidget() does nothing. + */ +void QwtLegend::updateWidget( QWidget* widget, const QwtLegendData& legendData ) +{ + QwtLegendLabel* label = qobject_cast< QwtLegendLabel* >( widget ); + if ( label ) + { + label->setData( legendData ); + if ( !legendData.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 = m_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 = m_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 = m_data->view->contentsWidget->heightForWidth( width ); + if ( h >= 0 ) + h += 2 * frameWidth(); + + return h; +} + + +/*! + Handle QEvent::ChildRemoved and QEvent::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 == m_data->view->contentsWidget ) + { + switch ( event->type() ) + { + case QEvent::ChildRemoved: + { + const QChildEvent* ce = + static_cast< const QChildEvent* >( event ); + + if ( ce->child()->isWidgetType() ) + { + /* + We are called from the ~QObject and ce->child() is + no widget anymore. But all we need is the address + to remove it from the map. + */ + QWidget* w = reinterpret_cast< QWidget* >( ce->child() ); + m_data->itemMap.removeWidget( w ); + } + break; + } + case QEvent::LayoutRequest: + { + m_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< QWidget* >( sender() ); + if ( w ) + { + const QVariant itemInfo = m_data->itemMap.itemInfo( w ); + if ( itemInfo.isValid() ) + { + const QList< QWidget* > widgetList = + m_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< QWidget* >( sender() ); + if ( w ) + { + const QVariant itemInfo = m_data->itemMap.itemInfo( w ); + if ( itemInfo.isValid() ) + { + const QList< QWidget* > widgetList = + m_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 ( m_data->itemMap.isEmpty() ) + return; + + if ( fillBackground ) + { + if ( autoFillBackground() || + testAttribute( Qt::WA_StyledBackground ) ) + { + QwtPainter::drawBackgound( painter, rect, this ); + } + } + + const QwtDynGridLayout* legendLayout = + qobject_cast< QwtDynGridLayout* >( contentsWidget()->layout() ); + if ( legendLayout == NULL ) + return; + + const QMargins m = contentsMargins(); + + QRect layoutRect; + layoutRect.setLeft( qwtCeil( rect.left() ) + m.left() ); + layoutRect.setTop( qwtCeil( rect.top() ) + m.top() ); + layoutRect.setRight( qwtFloor( rect.right() ) - m.right() ); + layoutRect.setBottom( qwtFloor( rect.bottom() ) - m.bottom() ); + + uint numCols = legendLayout->columnsForWidth( layoutRect.width() ); + const QList< QRect > 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], Qt::IntersectClip ); + 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< const QwtLegendLabel* >( 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() ); + + QFont labelFont = label->font(); +#if QT_VERSION >= 0x060000 + labelFont.setResolveMask( QFont::AllPropertiesResolved ); +#else + labelFont.resolve( QFont::AllPropertiesResolved ); +#endif + + painter->setFont( labelFont ); + 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< QWidget* > QwtLegend::legendWidgets( const QVariant& itemInfo ) const +{ + return m_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< QWidget* > list = m_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 m_data->itemMap.itemInfo( widget ); +} + +//! \return True, when no item is inserted +bool QwtLegend::isEmpty() const +{ + return m_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; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_legend.cpp" +#endif diff --git a/libs/qwt/src/qwt_legend.h b/libs/qwt/src/qwt_legend.h new file mode 100644 index 00000000..b67c6cfd --- /dev/null +++ b/libs/qwt/src/qwt_legend.h @@ -0,0 +1,119 @@ +/****************************************************************************** + * 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 "qwt_legend_data.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< QWidget* > legendWidgets( const QVariant& ) const; + + QVariant itemInfo( const QWidget* ) const; + + virtual bool eventFilter( QObject*, QEvent* ) QWT_OVERRIDE; + + virtual QSize sizeHint() const QWT_OVERRIDE; + virtual int heightForWidth( int w ) const QWT_OVERRIDE; + + QScrollBar* horizontalScrollBar() const; + QScrollBar* verticalScrollBar() const; + + virtual void renderLegend( QPainter*, + const QRectF&, bool fillBackground ) const QWT_OVERRIDE; + + virtual void renderItem( QPainter*, + const QWidget*, const QRectF&, bool fillBackground ) const; + + virtual bool isEmpty() const QWT_OVERRIDE; + virtual int scrollExtent( Qt::Orientation ) const QWT_OVERRIDE; + + 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< QwtLegendData >& ) QWT_OVERRIDE; + + protected Q_SLOTS: + void itemClicked(); + void itemChecked( bool ); + + protected: + virtual QWidget* createWidget( const QwtLegendData& ) const; + virtual void updateWidget( QWidget*, const QwtLegendData& ); + + private: + void updateTabOrder(); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_legend_data.cpp b/libs/qwt/src/qwt_legend_data.cpp new file mode 100644 index 00000000..036f0cec --- /dev/null +++ b/libs/qwt/src/qwt_legend_data.cpp @@ -0,0 +1,131 @@ +/****************************************************************************** + * 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" +#include "qwt_text.h" +#include "qwt_graphic.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< int, QVariant >& map ) +{ + m_map = map; +} + +/*! + \return Legend attributes + \sa setValues() + */ +const QMap< int, QVariant >& QwtLegendData::values() const +{ + return m_map; +} + +/*! + \param role Attribute role + \return True, when the internal map has an entry for role + */ +bool QwtLegendData::hasRole( int role ) const +{ + return m_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 ) +{ + m_map[role] = data; +} + +/*! + \param role Attribute role + \return Attribute value for a specific role + */ +QVariant QwtLegendData::value( int role ) const +{ + if ( !m_map.contains( role ) ) + return QVariant(); + + return m_map[role]; +} + +//! \return True, when the internal map is empty +bool QwtLegendData::isValid() const +{ + return !m_map.isEmpty(); +} + +//! \return Value of the TitleRole attribute +QwtText QwtLegendData::title() const +{ + QwtText text; + + const QVariant titleValue = value( QwtLegendData::TitleRole ); + if ( titleValue.canConvert< QwtText >() ) + { + text = qvariant_cast< QwtText >( titleValue ); + } + else if ( titleValue.canConvert< QString >() ) + { + text.setText( qvariant_cast< QString >( titleValue ) ); + } + + return text; +} + +//! \return Value of the IconRole attribute +QwtGraphic QwtLegendData::icon() const +{ + const QVariant iconValue = value( QwtLegendData::IconRole ); + + QwtGraphic graphic; + if ( iconValue.canConvert< QwtGraphic >() ) + { + graphic = qvariant_cast< QwtGraphic >( iconValue ); + } + + return graphic; +} + +//! \return Value of the ModeRole attribute +QwtLegendData::Mode QwtLegendData::mode() const +{ + const QVariant modeValue = value( QwtLegendData::ModeRole ); + if ( modeValue.canConvert< int >() ) + { + const int mode = qvariant_cast< int >( modeValue ); + return static_cast< QwtLegendData::Mode >( mode ); + } + + return QwtLegendData::ReadOnly; +} + diff --git a/libs/qwt/src/qwt_legend_data.h b/libs/qwt/src/qwt_legend_data.h new file mode 100644 index 00000000..68550cf7 --- /dev/null +++ b/libs/qwt/src/qwt_legend_data.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * 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 +#include + +class QwtText; +class QwtGraphic; + +/*! + \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 interpret 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< int, QVariant >& ); + const QMap< int, QVariant >& 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< int, QVariant > m_map; +}; + +#endif diff --git a/libs/qwt/src/qwt_legend_label.cpp b/libs/qwt/src/qwt_legend_label.cpp new file mode 100644 index 00000000..c5370891 --- /dev/null +++ b/libs/qwt/src/qwt_legend_label.cpp @@ -0,0 +1,420 @@ +/****************************************************************************** + * 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_graphic.h" +#include "qwt.h" + +#include +#include +#include +#include +#include + +static const int ButtonFrame = 2; +static const int Margin = 2; + +static QSize buttonShift( const QwtLegendLabel* w ) +{ + QStyleOption option; + option.initFrom( 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 ) +{ + m_data->legendData = legendData; + + const bool doUpdate = updatesEnabled(); + if ( doUpdate ) + setUpdatesEnabled( false ); + + setText( legendData.title() ); + setIcon( legendData.icon().toPixmap() ); + + if ( legendData.hasRole( QwtLegendData::ModeRole ) ) + setItemMode( legendData.mode() ); + + if ( doUpdate ) + setUpdatesEnabled( true ); +} + +/*! + \return Attributes of the label + \sa setData(), QwtPlotItem::legendData() + */ +const QwtLegendData& QwtLegendLabel::data() const +{ + return m_data->legendData; +} + +/*! + \param parent Parent widget + */ +QwtLegendLabel::QwtLegendLabel( QWidget* parent ) + : QwtTextLabel( parent ) +{ + m_data = new PrivateData; + setMargin( Margin ); + setIndent( Margin ); +} + +//! Destructor +QwtLegendLabel::~QwtLegendLabel() +{ + delete m_data; + m_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 != m_data->itemMode ) + { + m_data->itemMode = mode; + m_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 m_data->itemMode; +} + +/*! + Assign the icon + + \param icon Pixmap representing a plot item + + \sa icon(), QwtPlotItem::legendIcon() + */ +void QwtLegendLabel::setIcon( const QPixmap& icon ) +{ + m_data->icon = icon; + + int indent = margin() + m_data->spacing; + if ( icon.width() > 0 ) + indent += icon.width() + m_data->spacing; + + setIndent( indent ); +} + +/*! + \return Pixmap representing a plot item + \sa setIcon() + */ +QPixmap QwtLegendLabel::icon() const +{ + return m_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 != m_data->spacing ) + { + m_data->spacing = spacing; + + int indent = margin() + m_data->spacing; + if ( m_data->icon.width() > 0 ) + indent += m_data->icon.width() + m_data->spacing; + + setIndent( indent ); + } +} + +/*! + \return Spacing between icon and text + \sa setSpacing(), QwtTextLabel::margin() + */ +int QwtLegendLabel::spacing() const +{ + return m_data->spacing; +} + +/*! + Check/Uncheck a the item + + \param on check/uncheck + \sa setItemMode() + */ +void QwtLegendLabel::setChecked( bool on ) +{ + if ( m_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 m_data->itemMode == QwtLegendData::Checkable && isDown(); +} + +//! Set the item being down +void QwtLegendLabel::setDown( bool down ) +{ + if ( down == m_data->isDown ) + return; + + m_data->isDown = down; + update(); + + if ( m_data->itemMode == QwtLegendData::Clickable ) + { + if ( m_data->isDown ) + Q_EMIT pressed(); + else + { + Q_EMIT released(); + Q_EMIT clicked(); + } + } + + if ( m_data->itemMode == QwtLegendData::Checkable ) + Q_EMIT checked( m_data->isDown ); +} + +//! Return true, if the item is down +bool QwtLegendLabel::isDown() const +{ + return m_data->isDown; +} + +//! Return a size hint +QSize QwtLegendLabel::sizeHint() const +{ + QSize sz = QwtTextLabel::sizeHint(); + sz.setHeight( qMax( sz.height(), m_data->icon.height() + 4 ) ); + + if ( m_data->itemMode != QwtLegendData::ReadOnly ) + { + sz += buttonShift( this ); + sz = qwtExpandedToGlobalStrut( sz ); + } + + return sz; +} + +//! Paint event +void QwtLegendLabel::paintEvent( QPaintEvent* e ) +{ + const QRect cr = contentsRect(); + + QPainter painter( this ); + painter.setClipRegion( e->region() ); + + if ( m_data->isDown ) + { + qDrawWinButton( &painter, 0, 0, width(), height(), + palette(), true ); + } + + painter.save(); + + if ( m_data->isDown ) + { + const QSize shiftSize = buttonShift( this ); + painter.translate( shiftSize.width(), shiftSize.height() ); + } + + painter.setClipRect( cr ); + + drawContents( &painter ); + + if ( !m_data->icon.isNull() ) + { + QRect iconRect = cr; + iconRect.setX( iconRect.x() + margin() ); + if ( m_data->itemMode != QwtLegendData::ReadOnly ) + iconRect.setX( iconRect.x() + ButtonFrame ); + + iconRect.setSize( m_data->icon.size() ); + iconRect.moveCenter( QPoint( iconRect.center().x(), cr.center().y() ) ); + + painter.drawPixmap( iconRect, m_data->icon ); + } + + painter.restore(); +} + +//! Handle mouse press events +void QwtLegendLabel::mousePressEvent( QMouseEvent* e ) +{ + if ( e->button() == Qt::LeftButton ) + { + switch ( m_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 ( m_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 ( m_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 ( m_data->itemMode ) + { + case QwtLegendData::Clickable: + { + if ( !e->isAutoRepeat() ) + setDown( false ); + return; + } + case QwtLegendData::Checkable: + { + return; // do nothing, but accept + } + default:; + } + } + + QwtTextLabel::keyReleaseEvent( e ); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_legend_label.cpp" +#endif diff --git a/libs/qwt/src/qwt_legend_label.h b/libs/qwt/src/qwt_legend_label.h new file mode 100644 index 00000000..d75f2b01 --- /dev/null +++ b/libs/qwt/src/qwt_legend_label.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_LEGEND_LABEL_H +#define QWT_LEGEND_LABEL_H + +#include "qwt_global.h" +#include "qwt_text_label.h" +#include "qwt_legend_data.h" + +class QwtText; + +/*! + \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& ) QWT_OVERRIDE; + + void setIcon( const QPixmap& ); + QPixmap icon() const; + + virtual QSize sizeHint() const QWT_OVERRIDE; + + 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* ) QWT_OVERRIDE; + virtual void mousePressEvent( QMouseEvent* ) QWT_OVERRIDE; + virtual void mouseReleaseEvent( QMouseEvent* ) QWT_OVERRIDE; + virtual void keyPressEvent( QKeyEvent* ) QWT_OVERRIDE; + virtual void keyReleaseEvent( QKeyEvent* ) QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_magnifier.cpp b/libs/qwt/src/qwt_magnifier.cpp new file mode 100644 index 00000000..5a0bf7ff --- /dev/null +++ b/libs/qwt/src/qwt_magnifier.cpp @@ -0,0 +1,512 @@ +/****************************************************************************** + * 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 ) + , hasMouseTracking( 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 ) +{ + m_data = new PrivateData(); + + if ( parent ) + { + if ( parent->focusPolicy() == Qt::NoFocus ) + parent->setFocusPolicy( Qt::WheelFocus ); + } + + setEnabled( true ); +} + +//! Destructor +QwtMagnifier::~QwtMagnifier() +{ + delete m_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 ( m_data->isEnabled != on ) + { + m_data->isEnabled = on; + + QObject* o = parent(); + if ( o ) + { + if ( m_data->isEnabled ) + o->installEventFilter( this ); + else + o->removeEventFilter( this ); + } + } +} + +/*! + \return true when enabled, false otherwise + \sa setEnabled(), eventFilter() + */ +bool QwtMagnifier::isEnabled() const +{ + return m_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 ) +{ + m_data->wheelFactor = factor; +} + +/*! + \return Wheel factor + \sa setWheelFactor() + */ +double QwtMagnifier::wheelFactor() const +{ + return m_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 ) +{ + m_data->wheelModifiers = modifiers; +} + +/*! + \return Wheel modifiers + \sa setWheelModifiers() + */ +Qt::KeyboardModifiers QwtMagnifier::wheelModifiers() const +{ + return m_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 ) +{ + m_data->mouseFactor = factor; +} + +/*! + \return Mouse factor + \sa setMouseFactor() + */ +double QwtMagnifier::mouseFactor() const +{ + return m_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 ) +{ + m_data->mouseButton = button; + m_data->mouseButtonModifiers = modifiers; +} + +//! \sa setMouseButton() +void QwtMagnifier::getMouseButton( + Qt::MouseButton& button, Qt::KeyboardModifiers& modifiers ) const +{ + button = m_data->mouseButton; + modifiers = m_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 ) +{ + m_data->keyFactor = factor; +} + +/*! + \return Key factor + \sa setKeyFactor() + */ +double QwtMagnifier::keyFactor() const +{ + return m_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 ) +{ + m_data->zoomInKey = key; + m_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 = m_data->zoomInKey; + modifiers = m_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 ) +{ + m_data->zoomOutKey = key; + m_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 = m_data->zoomOutKey; + modifiers = m_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< QMouseEvent* >( event ) ); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent( static_cast< QMouseEvent* >( event ) ); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent( static_cast< QMouseEvent* >( event ) ); + break; + } + case QEvent::Wheel: + { + widgetWheelEvent( static_cast< QWheelEvent* >( event ) ); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent( static_cast< QKeyEvent* >( event ) ); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent( static_cast< QKeyEvent* >( 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() != m_data->mouseButton ) || + ( mouseEvent->modifiers() != m_data->mouseButtonModifiers ) ) + { + return; + } + + m_data->hasMouseTracking = parentWidget()->hasMouseTracking(); + + parentWidget()->setMouseTracking( true ); + m_data->mousePos = mouseEvent->pos(); + m_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 ( m_data->mousePressed && parentWidget() ) + { + m_data->mousePressed = false; + parentWidget()->setMouseTracking( m_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 ( !m_data->mousePressed ) + return; + + const int dy = mouseEvent->pos().y() - m_data->mousePos.y(); + if ( dy != 0 ) + { + double f = m_data->mouseFactor; + if ( dy < 0 ) + f = 1 / f; + + rescale( f ); + } + + m_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() != m_data->wheelModifiers ) + { + return; + } + + if ( m_data->wheelFactor != 0.0 ) + { +#if QT_VERSION < 0x050000 + const int wheelDelta = wheelEvent->delta(); +#else + const QPoint delta = wheelEvent->angleDelta(); + const int wheelDelta = ( qAbs( delta.x() ) > qAbs( delta.y() ) ) + ? delta.x() : delta.y(); +#endif + + /* + 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 = std::pow( m_data->wheelFactor, + qAbs( wheelDelta / 120.0 ) ); + + if ( wheelDelta > 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() == m_data->zoomInKey && + keyEvent->modifiers() == m_data->zoomInKeyModifiers ) + { + rescale( m_data->keyFactor ); + } + else if ( keyEvent->key() == m_data->zoomOutKey && + keyEvent->modifiers() == m_data->zoomOutKeyModifiers ) + { + rescale( 1.0 / m_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< QWidget* >( parent() ); +} + +//! \return Parent widget, where the rescaling happens +const QWidget* QwtMagnifier::parentWidget() const +{ + return qobject_cast< const QWidget* >( parent() ); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_magnifier.cpp" +#endif diff --git a/libs/qwt/src/qwt_magnifier.h b/libs/qwt/src/qwt_magnifier.h new file mode 100644 index 00000000..fa744c6b --- /dev/null +++ b/libs/qwt/src/qwt_magnifier.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_MAGNIFIER_H +#define QWT_MAGNIFIER_H + +#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* ) QWT_OVERRIDE; + + 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* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_math.cpp b/libs/qwt/src/qwt_math.cpp new file mode 100644 index 00000000..9ef11b85 --- /dev/null +++ b/libs/qwt/src/qwt_math.cpp @@ -0,0 +1,55 @@ +/****************************************************************************** + * 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" +#if QT_VERSION >= 0x050a00 +#include +#endif + +/*! + \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 = std::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 = std::fmod( degrees, 360.0 ); + if ( a < 0.0 ) + a += 360.0; + + return a; +} + +/*! + \brief Uses QRandomGenerator for Qt >= 5.10 and qRand() otherwise + \return A 32-bit random quantity + */ +quint32 qwtRand() +{ +#if QT_VERSION >= 0x050a00 + return QRandomGenerator::global()->generate(); +#else + return static_cast< quint32 >( qrand() ); // [0, RAND_MAX ] +#endif +} diff --git a/libs/qwt/src/qwt_math.h b/libs/qwt/src/qwt_math.h new file mode 100644 index 00000000..71344fc5 --- /dev/null +++ b/libs/qwt/src/qwt_math.h @@ -0,0 +1,281 @@ +/****************************************************************************** + * 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" + +/* + 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. + */ + +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#define undef_USE_MATH_DEFINES +#endif + +#include + +#ifdef undef_USE_MATH_DEFINES +#undef _USE_MATH_DEFINES +#undef undef_USE_MATH_DEFINES +#endif + +#ifndef M_E +#define M_E ( 2.7182818284590452354 ) +#endif + +#ifndef M_LOG2E +#define M_LOG2E ( 1.4426950408889634074 ) +#endif + +#ifndef M_LOG10E +#define M_LOG10E ( 0.43429448190325182765 ) +#endif + +#ifndef M_LN2 +#define M_LN2 ( 0.69314718055994530942 ) +#endif + +#ifndef M_LN10 +#define M_LN10 ( 2.30258509299404568402 ) +#endif + +#ifndef M_PI +#define M_PI ( 3.14159265358979323846 ) +#endif + +#ifndef M_PI_2 +#define M_PI_2 ( 1.57079632679489661923 ) +#endif + +#ifndef M_PI_4 +#define M_PI_4 ( 0.78539816339744830962 ) +#endif + +#ifndef M_1_PI +#define M_1_PI ( 0.31830988618379067154 ) +#endif + +#ifndef M_2_PI +#define M_2_PI ( 0.63661977236758134308 ) +#endif + +#ifndef M_2_SQRTPI +#define M_2_SQRTPI ( 1.12837916709551257390 ) +#endif + +#ifndef M_SQRT2 +#define M_SQRT2 ( 1.41421356237309504880 ) +#endif + +#ifndef M_SQRT1_2 +#define M_SQRT1_2 ( 0.70710678118654752440 ) +#endif + +#if defined( QT_WARNING_PUSH ) + /* + early Qt versions not having QT_WARNING_PUSH is full of warnings + so that we do not care of suppressing those from below + */ + QT_WARNING_PUSH + QT_WARNING_DISABLE_CLANG("-Wdouble-promotion") + QT_WARNING_DISABLE_GCC("-Wdouble-promotion") +#endif + +/* + On systems, where qreal is a float you often run into + compiler issues with qMin/qMax. + */ + +//! \return Minimum of a and b. +QWT_CONSTEXPR inline float qwtMinF( float a, float b ) +{ + return ( a < b ) ? a : b; +} + +//! \return Minimum of a and b. +QWT_CONSTEXPR inline double qwtMinF( double a, double b ) +{ + return ( a < b ) ? a : b; +} + +//! \return Minimum of a and b. +QWT_CONSTEXPR inline qreal qwtMinF( float a, double b ) +{ + return ( a < b ) ? a : b; +} + +//! \return Minimum of a and b. +QWT_CONSTEXPR inline qreal qwtMinF( double a, float b ) +{ + return ( a < b ) ? a : b; +} + +//! \return Maximum of a and b. +QWT_CONSTEXPR inline float qwtMaxF( float a, float b ) +{ + return ( a < b ) ? b : a; +} + +//! \return Maximum of a and b. +QWT_CONSTEXPR inline double qwtMaxF( double a, double b ) +{ + return ( a < b ) ? b : a; +} + +//! \return Maximum of a and b. +QWT_CONSTEXPR inline qreal qwtMaxF( float a, double b ) +{ + return ( a < b ) ? b : a; +} + +//! \return Maximum of a and b. +QWT_CONSTEXPR inline qreal qwtMaxF( double a, float b ) +{ + return ( a < b ) ? b : a; +} + +#if defined( QT_WARNING_POP ) + QT_WARNING_POP +#endif + +QWT_EXPORT double qwtNormalizeRadians( double radians ); +QWT_EXPORT double qwtNormalizeDegrees( double degrees ); +QWT_EXPORT quint32 qwtRand(); + +/*! + \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; +} + +//! 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; +} + +/* ! + \brief Calculate a value of a cubic polynomial + + \param x Value + \param a Cubic coefficient + \param b Quadratic coefficient + \param c Linear coefficient + \param d Constant offset + + \return Value of the polyonom for x + */ +inline double qwtCubicPolynomial( double x, + double a, double b, double c, double d ) +{ + return ( ( ( a * x ) + b ) * x + c ) * x + d; +} + +//! 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; +} + +/*! + The same as qCeil, but avoids including qmath.h + \return Ceiling of value. + */ +inline int qwtCeil( qreal value ) +{ + using std::ceil; + return int( ceil( value ) ); +} +/*! + The same as qFloor, but avoids including qmath.h + \return Floor of value. + */ +inline int qwtFloor( qreal value ) +{ + using std::floor; + return int( floor( value ) ); +} + +#endif diff --git a/libs/qwt/src/qwt_matrix_raster_data.cpp b/libs/qwt/src/qwt_matrix_raster_data.cpp new file mode 100644 index 00000000..40800264 --- /dev/null +++ b/libs/qwt/src/qwt_matrix_raster_data.cpp @@ -0,0 +1,417 @@ +/****************************************************************************** + * 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 "qwt_interval.h" + +#include +#include +#include + +static inline double qwtHermiteInterpolate( + double A, double B, double C, double D, double t ) +{ + const double t2 = t * t; + const double t3 = t2 * t; + + const double a = -A / 2.0 + ( 3.0 * B ) / 2.0 - ( 3.0 * C ) / 2.0 + D / 2.0; + const double b = A - ( 5.0 * B ) / 2.0 + 2.0 * C - D / 2.0; + const double c = -A / 2.0 + C / 2.0; + const double d = B; + + return a * t3 + b * t2 + c * t + d; +} + +static inline double qwtBicubicInterpolate( + double v00, double v10, double v20, double v30, + double v01, double v11, double v21, double v31, + double v02, double v12, double v22, double v32, + double v03, double v13, double v23, double v33, + double dx, double dy ) +{ + const double v0 = qwtHermiteInterpolate( v00, v10, v20, v30, dx ); + const double v1 = qwtHermiteInterpolate( v01, v11, v21, v31, dx ); + const double v2 = qwtHermiteInterpolate( v02, v12, v22, v32, dx ); + const double v3 = qwtHermiteInterpolate( v03, v13, v23, v33, dx ); + + return qwtHermiteInterpolate( v0, v1, v2, v3, dy ); +} + +class QwtMatrixRasterData::PrivateData +{ + public: + PrivateData() + : resampleMode( QwtMatrixRasterData::NearestNeighbour ) + , numColumns(0) + { + } + + inline double value(int row, int col) const + { + return values.data()[ row * numColumns + col ]; + } + + QwtInterval intervals[3]; + QwtMatrixRasterData::ResampleMode resampleMode; + + QVector< double > values; + int numColumns; + int numRows; + + double dx; + double dy; +}; + +//! Constructor +QwtMatrixRasterData::QwtMatrixRasterData() +{ + m_data = new PrivateData(); + update(); +} + +//! Destructor +QwtMatrixRasterData::~QwtMatrixRasterData() +{ + delete m_data; +} + +/*! + \brief Set the resampling algorithm + + \param mode Resampling mode + \sa resampleMode(), value() + */ +void QwtMatrixRasterData::setResampleMode( ResampleMode mode ) +{ + m_data->resampleMode = mode; +} + +/*! + \return resampling algorithm + \sa setResampleMode(), value() + */ +QwtMatrixRasterData::ResampleMode QwtMatrixRasterData::resampleMode() const +{ + return m_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 ) +{ + if ( axis >= 0 && axis <= 2 ) + { + m_data->intervals[axis] = interval; + update(); + } +} + +/*! + \return Bounding interval for an axis + \sa setInterval + */ +QwtInterval QwtMatrixRasterData::interval( Qt::Axis axis ) const +{ + if ( axis >= 0 && axis <= 2 ) + return m_data->intervals[ axis ]; + + return QwtInterval(); +} + +/*! + \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< double >& values, int numColumns ) +{ + m_data->values = values; + m_data->numColumns = qMax( numColumns, 0 ); + update(); +} + +/*! + \return Value matrix + \sa setValueMatrix(), numColumns(), numRows(), setInterval() + */ +const QVector< double > QwtMatrixRasterData::valueMatrix() const +{ + return m_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 < m_data->numRows && + col >= 0 && col < m_data->numColumns ) + { + const int index = row * m_data->numColumns + col; + m_data->values.data()[ index ] = value; + } +} + +/*! + \return Number of columns of the value matrix + \sa valueMatrix(), numRows(), setValueMatrix() + */ +int QwtMatrixRasterData::numColumns() const +{ + return m_data->numColumns; +} + +/*! + \return Number of rows of the value matrix + \sa valueMatrix(), numColumns(), setValueMatrix() + */ +int QwtMatrixRasterData::numRows() const +{ + return m_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 ( m_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(), + m_data->dx, m_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( m_data->resampleMode ) + { + case BicubicInterpolation: + { + const double colF = ( x - xInterval.minValue() ) / m_data->dx; + const double rowF = ( y - yInterval.minValue() ) / m_data->dy; + + const int col = qRound( colF ); + const int row = qRound( rowF ); + + int col0 = col - 2; + int col1 = col - 1; + int col2 = col; + int col3 = col + 1; + + if ( col1 < 0 ) + col1 = col2; + + if ( col0 < 0 ) + col0 = col1; + + if ( col2 >= m_data->numColumns ) + col2 = col1; + + if ( col3 >= m_data->numColumns ) + col3 = col2; + + int row0 = row - 2; + int row1 = row - 1; + int row2 = row; + int row3 = row + 1; + + if ( row1 < 0 ) + row1 = row2; + + if ( row0 < 0 ) + row0 = row1; + + if ( row2 >= m_data->numRows ) + row2 = row1; + + if ( row3 >= m_data->numRows ) + row3 = row2; + + // First row + const double v00 = m_data->value( row0, col0 ); + const double v10 = m_data->value( row0, col1 ); + const double v20 = m_data->value( row0, col2 ); + const double v30 = m_data->value( row0, col3 ); + + // Second row + const double v01 = m_data->value( row1, col0 ); + const double v11 = m_data->value( row1, col1 ); + const double v21 = m_data->value( row1, col2 ); + const double v31 = m_data->value( row1, col3 ); + + // Third row + const double v02 = m_data->value( row2, col0 ); + const double v12 = m_data->value( row2, col1 ); + const double v22 = m_data->value( row2, col2 ); + const double v32 = m_data->value( row2, col3 ); + + // Fourth row + const double v03 = m_data->value( row3, col0 ); + const double v13 = m_data->value( row3, col1 ); + const double v23 = m_data->value( row3, col2 ); + const double v33 = m_data->value( row3, col3 ); + + value = qwtBicubicInterpolate( + v00, v10, v20, v30, v01, v11, v21, v31, + v02, v12, v22, v32, v03, v13, v23, v33, + colF - col + 0.5, rowF - row + 0.5 ); + + break; + } + case BilinearInterpolation: + { + int col1 = qRound( ( x - xInterval.minValue() ) / m_data->dx ) - 1; + int row1 = qRound( ( y - yInterval.minValue() ) / m_data->dy ) - 1; + int col2 = col1 + 1; + int row2 = row1 + 1; + + if ( col1 < 0 ) + col1 = col2; + else if ( col2 >= m_data->numColumns ) + col2 = col1; + + if ( row1 < 0 ) + row1 = row2; + else if ( row2 >= m_data->numRows ) + row2 = row1; + + const double v11 = m_data->value( row1, col1 ); + const double v21 = m_data->value( row1, col2 ); + const double v12 = m_data->value( row2, col1 ); + const double v22 = m_data->value( row2, col2 ); + + const double x2 = xInterval.minValue() + ( col2 + 0.5 ) * m_data->dx; + const double y2 = yInterval.minValue() + ( row2 + 0.5 ) * m_data->dy; + + const double rx = ( x2 - x ) / m_data->dx; + const double ry = ( y2 - y ) / m_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() ) / m_data->dy ); + int col = int( ( x - xInterval.minValue() ) / m_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 >= m_data->numRows ) + row = m_data->numRows - 1; + + if ( col >= m_data->numColumns ) + col = m_data->numColumns - 1; + + value = m_data->value( row, col ); + } + } + + return value; +} + +void QwtMatrixRasterData::update() +{ + m_data->numRows = 0; + m_data->dx = 0.0; + m_data->dy = 0.0; + + if ( m_data->numColumns > 0 ) + { + m_data->numRows = m_data->values.size() / m_data->numColumns; + + const QwtInterval xInterval = interval( Qt::XAxis ); + const QwtInterval yInterval = interval( Qt::YAxis ); + if ( xInterval.isValid() ) + m_data->dx = xInterval.width() / m_data->numColumns; + if ( yInterval.isValid() ) + m_data->dy = yInterval.width() / m_data->numRows; + } +} diff --git a/libs/qwt/src/qwt_matrix_raster_data.h b/libs/qwt/src/qwt_matrix_raster_data.h new file mode 100644 index 00000000..96f625d5 --- /dev/null +++ b/libs/qwt/src/qwt_matrix_raster_data.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * 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 + +#include "qwt_global.h" +#include "qwt_raster_data.h" + +#if QT_VERSION < 0x060000 +template< typename T > class QVector; +#endif + +/*! + \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, + + /*! + Interpolate the value from the 16 surrounding values in the + matrix using hermite bicubic interpolation + */ + BicubicInterpolation + }; + + QwtMatrixRasterData(); + virtual ~QwtMatrixRasterData(); + + void setResampleMode(ResampleMode mode); + ResampleMode resampleMode() const; + + void setInterval( Qt::Axis, const QwtInterval& ); + virtual QwtInterval interval( Qt::Axis axis) const QWT_OVERRIDE QWT_FINAL; + + void setValueMatrix( const QVector< double >& values, int numColumns ); + const QVector< double > valueMatrix() const; + + void setValue( int row, int col, double value ); + + int numColumns() const; + int numRows() const; + + virtual QRectF pixelHint( const QRectF& ) const QWT_OVERRIDE; + + virtual double value( double x, double y ) const QWT_OVERRIDE; + + private: + void update(); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_null_paintdevice.cpp b/libs/qwt/src/qwt_null_paintdevice.cpp new file mode 100644 index 00000000..922b32da --- /dev/null +++ b/libs/qwt/src/qwt_null_paintdevice.cpp @@ -0,0 +1,602 @@ +/****************************************************************************** + * 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 QWT_FINAL : public QPaintEngine +{ + public: + PaintEngine(); + + virtual bool begin( QPaintDevice* ) QWT_OVERRIDE; + virtual bool end() QWT_OVERRIDE; + + virtual Type type () const QWT_OVERRIDE; + virtual void updateState(const QPaintEngineState&) QWT_OVERRIDE; + + virtual void drawRects(const QRect*, int ) QWT_OVERRIDE; + virtual void drawRects(const QRectF*, int ) QWT_OVERRIDE; + + virtual void drawLines(const QLine*, int ) QWT_OVERRIDE; + virtual void drawLines(const QLineF*, int ) QWT_OVERRIDE; + + virtual void drawEllipse(const QRectF&) QWT_OVERRIDE; + virtual void drawEllipse(const QRect&) QWT_OVERRIDE; + + virtual void drawPath(const QPainterPath&) QWT_OVERRIDE; + + virtual void drawPoints(const QPointF*, int ) QWT_OVERRIDE; + virtual void drawPoints(const QPoint*, int ) QWT_OVERRIDE; + + virtual void drawPolygon( + const QPointF*, int, PolygonDrawMode ) QWT_OVERRIDE; + + virtual void drawPolygon( + const QPoint*, int, PolygonDrawMode ) QWT_OVERRIDE; + + virtual void drawPixmap(const QRectF&, + const QPixmap&, const QRectF&) QWT_OVERRIDE; + + virtual void drawTextItem( + const QPointF&, const QTextItem&) QWT_OVERRIDE; + + virtual void drawTiledPixmap(const QRectF&, + const QPixmap&, const QPointF& s) QWT_OVERRIDE; + + virtual void drawImage(const QRectF&, const QImage&, + const QRectF&, Qt::ImageConversionFlags ) QWT_OVERRIDE; + + 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 +{ + /* + How to avoid conflicts with other 3rd party pain engines ? + At least we don't use QPaintEngine::User what is known to + be the value of some print engines + */ + return static_cast< QPaintEngine::Type >( QPaintEngine::MaxUser - 2 ); +} + +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& engineState) +{ + QwtNullPaintDevice* device = nullDevice(); + if ( device == NULL ) + return; + + device->updateState( engineState ); +} + +inline QwtNullPaintDevice* QwtNullPaintDevice::PaintEngine::nullDevice() +{ + if ( !isActive() ) + return NULL; + + return static_cast< QwtNullPaintDevice* >( paintDevice() ); +} + +//! Constructor +QwtNullPaintDevice::QwtNullPaintDevice(): + m_engine( NULL ) +{ + m_data = new PrivateData; +} + +//! Destructor +QwtNullPaintDevice::~QwtNullPaintDevice() +{ + delete m_engine; + delete m_data; +} + +/*! + Set the render mode + + \param mode New mode + \sa mode() + */ +void QwtNullPaintDevice::setMode( Mode mode ) +{ + m_data->mode = mode; +} + +/*! + \return Render mode + \sa setMode() + */ +QwtNullPaintDevice::Mode QwtNullPaintDevice::mode() const +{ + return m_data->mode; +} + +//! See QPaintDevice::paintEngine() +QPaintEngine* QwtNullPaintDevice::paintEngine() const +{ + if ( m_engine == NULL ) + { + QwtNullPaintDevice* that = + const_cast< QwtNullPaintDevice* >( this ); + + that->m_engine = new PaintEngine(); + } + + return m_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/libs/qwt/src/qwt_null_paintdevice.h b/libs/qwt/src/qwt_null_paintdevice.h new file mode 100644 index 00000000..76118970 --- /dev/null +++ b/libs/qwt/src/qwt_null_paintdevice.h @@ -0,0 +1,127 @@ +/****************************************************************************** + * 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 + +#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 QWT_OVERRIDE; + + virtual int metric( PaintDeviceMetric ) const QWT_OVERRIDE; + + 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& ); + + virtual void drawImage(const QRectF&, const QImage&, + const QRectF&, Qt::ImageConversionFlags ); + + virtual void updateState( const QPaintEngineState& ); + + protected: + //! \return Size needed to implement metric() + virtual QSize sizeMetrics() const = 0; + + private: + class PaintEngine; + PaintEngine* m_engine; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_painter.cpp b/libs/qwt/src/qwt_painter.cpp new file mode 100644 index 00000000..f24486ea --- /dev/null +++ b/libs/qwt/src/qwt_painter.cpp @@ -0,0 +1,1549 @@ +/****************************************************************************** + * 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 + +#if QT_VERSION >= 0x060000 +#include +#else +#include +#endif + +#if QT_VERSION < 0x050000 + +#ifdef Q_WS_X11 +#include +#endif + +#endif + +#include + +bool QwtPainter::m_polylineSplitting = true; +bool QwtPainter::m_roundingAlignment = true; + +static inline bool qwtIsRasterPaintEngineBuggy() +{ +#if 0 + static int isBuggy = -1; + if ( isBuggy < 0 ) + { + // auto detect bug of the raster paint engine, + // fixed with: https://codereview.qt-project.org/#/c/99456/ + + QImage image( 2, 3, QImage::Format_ARGB32 ); + image.fill( 0u ); + + QPolygonF p; + p += QPointF(0, 1); + p += QPointF(0, 0); + p += QPointF(1, 0 ); + p += QPointF(1, 2 ); + + QPainter painter( &image ); + painter.drawPolyline( p ); + painter.end(); + + isBuggy = ( image.pixel( 1, 1 ) == 0 ) ? 1 : 0; + } + + return isBuggy == 1; +#endif + +#if QT_VERSION < 0x050000 + return true; +#elif QT_VERSION < 0x050100 + return false; +#elif QT_VERSION < 0x050400 + return true; +#else + return false; +#endif +} + +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< class T > +static inline void qwtDrawPolyline( QPainter* painter, + const T* points, int pointCount, bool polylineSplitting ) +{ + bool doSplit = false; + if ( polylineSplitting && pointCount > 3 ) + { + const QPaintEngine* pe = painter->paintEngine(); + if ( pe && pe->type() == QPaintEngine::Raster ) + { + if ( painter->pen().width() <= 1 ) + { + // work around a bug with short lines below 2 pixels difference + // in height and width + + doSplit = qwtIsRasterPaintEngineBuggy(); + } + else + { + /* + Raster paint engine is much faster when splitting + the polygon, but of course we might see some issues where + the pieces are joining + */ + doSplit = true; + } + } + } + + if ( doSplit ) + { + QPen pen = painter->pen(); + + const int splitSize = 6; + + if ( pen.width() <= 1 && pen.isSolid() && qwtIsRasterPaintEngineBuggy() + && !( painter->renderHints() & QPainter::Antialiasing ) ) + { + int k = 0; + + for ( int i = k + 1; i < pointCount; i++ ) + { + const QPointF& p1 = points[i - 1]; + const QPointF& p2 = points[i]; + + const bool isBad = ( qAbs( p2.y() - p1.y() ) <= 1 ) + && qAbs( p2.x() - p1.x() ) <= 1; + + if ( isBad || ( i - k >= splitSize ) ) + { + painter->drawPolyline( points + k, i - k + 1 ); + k = i; + } + } + + painter->drawPolyline( points + k, pointCount - k ); + } + else + { + 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 QSize qwtScreenResolution() +{ + static QSize screenResolution; + if ( !screenResolution.isValid() ) + { + /* + We might have screens with different resolutions. TODO ... + */ +#if QT_VERSION >= 0x060000 + QScreen* screen = QGuiApplication::primaryScreen(); + if ( screen ) + { + screenResolution.setWidth( screen->logicalDotsPerInchX() ); + screenResolution.setHeight( screen->logicalDotsPerInchY() ); + } +#else + QDesktopWidget* desktop = QApplication::desktop(); + if ( desktop ) + { + screenResolution.setWidth( desktop->logicalDpiX() ); + screenResolution.setHeight( desktop->logicalDpiY() ); + } +#endif + } + + return screenResolution; +} + +static inline void qwtUnscaleFont( QPainter* painter ) +{ + if ( painter->font().pixelSize() >= 0 ) + return; + + const QSize screenResolution = qwtScreenResolution(); + + const QPaintDevice* pd = painter->device(); + if ( pd->logicalDpiX() != screenResolution.width() || + pd->logicalDpiY() != screenResolution.height() ) + { + QFont pixelFont = QwtPainter::scaledFont( painter->font() ); + 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() +{ + /* + The X11 paint engine has been removed with Qt 5.0, but + reintroduced with Qt 5.10. It can be enabled with + "export QT_XCB_NATIVE_PAINTING=1". + */ + + 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( const QPainter* painter ) +{ + if ( painter && painter->isActive() ) + { + const QPaintEngine::Type type = + painter->paintEngine()->type(); + + if ( type >= QPaintEngine::User ) + { + // we have no idea - better don't align + return false; + } + + switch ( type ) + { + case QPaintEngine::Pdf: + case QPaintEngine::SVG: +#if 0 + case QPaintEngine::MacPrinter: +#endif + return false; + + default: + break; + } + + 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 + ( 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 ) +{ + m_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. + + Also the raster paint engine has a nasty bug in many versions ( Qt 4.8 - ... ) + for short lines ( https://codereview.qt-project.org/#/c/99456 ), that is worked + around in this mode. + + The default setting is true. + + \sa polylineSplitting() + */ +void QwtPainter::setPolylineSplitting( bool enable ) +{ + m_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, qreal x, qreal y, qreal w, qreal 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, + qreal x, qreal 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, + qreal x, qreal y, qreal w, qreal 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 Target 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(); + + QRectF unscaledRect = rect; + + if ( painter->font().pixelSize() < 0 ) + { + const QSize res = qwtScreenResolution(); + + const QPaintDevice* pd = painter->device(); + if ( pd->logicalDpiX() != res.width() || + pd->logicalDpiY() != res.height() ) + { + QTransform transform; + transform.scale( res.width() / qreal( pd->logicalDpiX() ), + res.height() / qreal( pd->logicalDpiY() ) ); + + painter->setWorldTransform( transform, true ); + unscaledRect = transform.inverted().mapRect(rect); + } + } + + txt->setDefaultFont( painter->font() ); + txt->setPageSize( QSizeF( unscaledRect.width(), QWIDGETSIZE_MAX ) ); + + QAbstractTextDocumentLayout* layout = txt->documentLayout(); + + const qreal height = layout->documentSize().height(); + qreal y = unscaledRect.y(); + if ( flags & Qt::AlignBottom ) + y += ( unscaledRect.height() - height ); + else if ( flags & Qt::AlignVCenter ) + y += ( unscaledRect.height() - height ) / 2; + + QAbstractTextDocumentLayout::PaintContext context; + context.palette.setColor( QPalette::Text, painter->pen().color() ); + + painter->translate( unscaledRect.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 ); + + if ( deviceClipping ) + { + painter->drawPolygon( + QwtClipper::clippedPolygonF( clipRect, polygon, true ) ); + } + else + { + painter->drawPolygon( polygon ); + } +} + +//! Wrapper for QPainter::drawPolyline() +void QwtPainter::drawPolyline( QPainter* painter, const QPolygonF& polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + const QPolygonF cpa = QwtClipper::clippedPolygonF( clipRect, polygon ); + + qwtDrawPolyline< QPointF >( painter, + cpa.constData(), cpa.size(), m_polylineSplitting ); + } + else + { + qwtDrawPolyline< QPointF >( painter, + polygon.constData(), polygon.size(), m_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 ); + std::memcpy( polygon.data(), points, pointCount * sizeof( QPointF ) ); + + QwtClipper::clipPolygonF( clipRect, polygon ); + qwtDrawPolyline< QPointF >( painter, + polygon.constData(), polygon.size(), m_polylineSplitting ); + } + else + { + qwtDrawPolyline< QPointF >( painter, points, pointCount, m_polylineSplitting ); + } +} + +//! Wrapper for QPainter::drawPolygon() +void QwtPainter::drawPolygon( QPainter* painter, const QPolygon& polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + painter->drawPolygon( + QwtClipper::clippedPolygon( clipRect, polygon, true ) ); + } + else + { + painter->drawPolygon( polygon ); + } +} + +//! Wrapper for QPainter::drawPolyline() +void QwtPainter::drawPolyline( QPainter* painter, const QPolygon& polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + const QPolygon cpa = QwtClipper::clippedPolygon( clipRect, polygon ); + + qwtDrawPolyline< QPoint >( painter, + cpa.constData(), cpa.size(), m_polylineSplitting ); + } + else + { + qwtDrawPolyline< QPoint >( painter, + polygon.constData(), polygon.size(), m_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 ); + std::memcpy( polygon.data(), points, pointCount * sizeof( QPoint ) ); + + QwtClipper::clipPolygon( clipRect, polygon ); + qwtDrawPolyline< QPoint >( painter, + polygon.constData(), polygon.size(), m_polylineSplitting ); + } + else + { + qwtDrawPolyline< QPoint >( painter, points, pointCount, m_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 = qwtCeil( clipRect.left() ); + const int maxX = qwtFloor( clipRect.right() ); + const int minY = qwtCeil( clipRect.top() ); + const int maxY = qwtFloor( 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 = qwtCeil( clipRect.left() ); + const int maxX = qwtFloor( clipRect.right() ); + const int minY = qwtCeil( clipRect.top() ); + const int maxY = qwtFloor( 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.initFrom( widget ); + opt.rect = rect; + opt.state |= QStyle::State_HasFocus; + opt.backgroundColor = widget->palette().color( widget->backgroundRole() ); + + 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 qreal 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 ); + } + 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, qreal xRadius, qreal yRadius, + const QPalette& palette, int lineWidth, int frameStyle ) +{ + painter->save(); + painter->setRenderHint( QPainter::Antialiasing, true ); + painter->setBrush( Qt::NoBrush ); + + qreal lw2 = lineWidth * 0.5; + QRectF innerRect = rect.adjusted( lw2, lw2, -lw2, -lw2 ); + + QPainterPath path; + path.addRoundedRect( innerRect, 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++ ) + { + const 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 Target rectangle + */ +void QwtPainter::drawColorBar( QPainter* painter, + const QwtColorMap& colorMap, const QwtInterval& interval, + const QwtScaleMap& scaleMap, Qt::Orientation orientation, + const QRectF& rect ) +{ + QVector< QRgb > colorTable; + if ( colorMap.format() == QwtColorMap::Indexed ) + colorTable = colorMap.colorTable256(); + + 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() ); + pixmap.fill( Qt::transparent ); + + 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( 256, 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.setRgba( colorMap.rgb( interval, value ) ); + else + c = colorTable[colorMap.colorIndex( 256, 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 ); + } +} + +/*! + Distance appropriate for drawing a subsequent character after text. + + \param fontMetrics Font metrics + \param text Text + \return horizontal advance in pixels + */ +int QwtPainter::horizontalAdvance( + const QFontMetrics& fontMetrics, const QString& text ) +{ +#if QT_VERSION >= 0x050b00 + return fontMetrics.horizontalAdvance( text ); +#else + return fontMetrics.width( text ); +#endif + +} + +/*! + Distance appropriate for drawing a subsequent character after text. + + \param fontMetrics Font metrics + \param text Text + \return horizontal advance in pixels + */ +qreal QwtPainter::horizontalAdvance( + const QFontMetricsF& fontMetrics, const QString& text ) +{ +#if QT_VERSION >= 0x050b00 + return fontMetrics.horizontalAdvance( text ); +#else + return fontMetrics.width( text ); +#endif +} + +/*! + Distance appropriate for drawing a subsequent character after ch. + + \param fontMetrics Font metrics + \param ch Character + \return horizontal advance in pixels + */ +int QwtPainter::horizontalAdvance( + const QFontMetrics& fontMetrics, QChar ch ) +{ +#if QT_VERSION >= 0x050b00 + return fontMetrics.horizontalAdvance( ch ); +#else + return fontMetrics.width( ch ); +#endif +} + +/*! + Distance appropriate for drawing a subsequent character after ch. + + \param fontMetrics Font metrics + \param ch Character + \return horizontal advance in pixels + */ +qreal QwtPainter::horizontalAdvance( + const QFontMetricsF& fontMetrics, QChar ch ) +{ +#if QT_VERSION >= 0x050b00 + return fontMetrics.horizontalAdvance( ch ); +#else + return fontMetrics.width( ch ); +#endif +} + +/*! + Adjust the DPI value of font according to the DPI value of the paint device + + \param font Unscaled font + \param paintDevice Paint device providing a DPI value. If paintDevice == null + the DPI value of the primary screen will be used + + \return Font being adjusted to the DPI value of the paint device + */ +QFont QwtPainter::scaledFont( const QFont& font, const QPaintDevice* paintDevice ) +{ + if ( paintDevice == nullptr ) + { +#if QT_VERSION < 0x060000 + paintDevice = QApplication::desktop(); +#else + class PaintDevice : public QPaintDevice + { + virtual QPaintEngine* paintEngine() const QWT_OVERRIDE + { + return nullptr; + } + + virtual int metric( PaintDeviceMetric metric ) const QWT_OVERRIDE + { + if ( metric == PdmDpiY ) + { + QScreen* screen = QGuiApplication::primaryScreen(); + if ( screen ) + { + return screen->logicalDotsPerInchY(); + } + } + + return QPaintDevice::metric( metric ); + } + }; + + static PaintDevice dummyPaintDevice; + paintDevice = &dummyPaintDevice; +#endif + } + + return QFont( font, const_cast< QPaintDevice* >( paintDevice ) ); +} + +/*! + \return Pixel ratio for a paint device + \param paintDevice Paint device + */ +qreal QwtPainter::devicePixelRatio( const QPaintDevice* paintDevice ) +{ + qreal pixelRatio = 0.0; + +#if QT_VERSION >= 0x050100 + if ( paintDevice ) + { +#if QT_VERSION >= 0x050600 + pixelRatio = paintDevice->devicePixelRatioF(); +#else + pixelRatio = paintDevice->devicePixelRatio(); +#endif + } +#else + Q_UNUSED( paintDevice ) +#endif + +#if QT_VERSION >= 0x050000 + if ( pixelRatio == 0.0 && qApp ) + pixelRatio = qApp->devicePixelRatio(); +#endif + + if ( pixelRatio == 0.0 ) + pixelRatio = 1.0; + + return pixelRatio; +} + +/*! + \return A pixmap that can be used as backing store + + \param widget Widget, for which the backingstore is intended + \param size Size of the pixmap + */ +QPixmap QwtPainter::backingStore( QWidget* widget, const QSize& size ) +{ + QPixmap pm; + +#if QT_VERSION >= 0x050000 + const qreal pixelRatio = QwtPainter::devicePixelRatio( widget ); + + pm = QPixmap( size * pixelRatio ); + pm.setDevicePixelRatio( pixelRatio ); +#else + pm = QPixmap( size ); +#endif + +#ifdef Q_WS_X11 + if ( widget && isX11GraphicsSystem() ) + { + if ( pm.x11Info().screen() != widget->x11Info().screen() ) + pm.x11SetScreen( widget->x11Info().screen() ); + } +#else + Q_UNUSED( widget ) +#endif + + return pm; +} diff --git a/libs/qwt/src/qwt_painter.h b/libs/qwt/src/qwt_painter.h new file mode 100644 index 00000000..dd47ce35 --- /dev/null +++ b/libs/qwt/src/qwt_painter.h @@ -0,0 +1,207 @@ +/****************************************************************************** + * 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 + +class QwtScaleMap; +class QwtColorMap; +class QwtInterval; + +class QPainter; +class QBrush; +class QWidget; +class QImage; +class QPixmap; +class QFontMetrics; +class QFontMetricsF; +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( const QPainter* ); + + static void drawText( QPainter*, qreal x, qreal y, const QString& ); + static void drawText( QPainter*, const QPointF&, const QString& ); + static void drawText( QPainter*, qreal x, qreal y, qreal w, qreal 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*, qreal x, qreal y, qreal w, qreal 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*, qreal x1, qreal y1, qreal x2, qreal 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*, qreal x, qreal 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&, qreal xRadius, qreal 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*, + const QwtColorMap&, const QwtInterval&, + const QwtScaleMap&, Qt::Orientation, const QRectF& ); + + static bool isAligning( const QPainter*); + static bool isX11GraphicsSystem(); + + static void fillPixmap( const QWidget*, + QPixmap&, const QPoint& offset = QPoint() ); + + static void drawBackgound( QPainter*, + const QRectF&, const QWidget* ); + + static QPixmap backingStore( QWidget*, const QSize& ); + static qreal devicePixelRatio( const QPaintDevice* ); + + static qreal effectivePenWidth( const QPen& ); + + static int horizontalAdvance( const QFontMetrics&, const QString& ); + static qreal horizontalAdvance( const QFontMetricsF&, const QString& ); + + static int horizontalAdvance( const QFontMetrics&, QChar ); + static qreal horizontalAdvance( const QFontMetricsF&, QChar ); + + static QFont scaledFont( const QFont&, const QPaintDevice* = nullptr ); + + private: + static bool m_polylineSplitting; + static bool m_roundingAlignment; +}; + +//! Wrapper for QPainter::drawPoint() +inline void QwtPainter::drawPoint( QPainter* painter, qreal x, qreal 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, + qreal x1, qreal y1, qreal x2, qreal 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 m_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 m_roundingAlignment; +} + +/*! + \return roundingAlignment() && isAligning(painter); + \param painter Painter + */ +inline bool QwtPainter::roundingAlignment( const QPainter* painter ) +{ + return m_roundingAlignment && isAligning(painter); +} + +/*! + \return pen.widthF() expanded to at least 1.0 + \param pen Pen + */ +inline qreal QwtPainter::effectivePenWidth( const QPen& pen ) +{ + const qreal width = pen.widthF(); + return ( width < 1.0 ) ? 1.0 : width; +} + +#endif diff --git a/libs/qwt/src/qwt_painter_command.cpp b/libs/qwt/src/qwt_painter_command.cpp new file mode 100644 index 00000000..81a9f00b --- /dev/null +++ b/libs/qwt/src/qwt_painter_command.cpp @@ -0,0 +1,237 @@ +/****************************************************************************** + * 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() + : m_type( Invalid ) +{ +} + +//! Copy constructor +QwtPainterCommand::QwtPainterCommand( const QPainterPath& path ) + : m_type( Path ) +{ + m_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 ) + : m_type( Pixmap ) +{ + m_pixmapData = new PixmapData(); + m_pixmapData->rect = rect; + m_pixmapData->pixmap = pixmap; + m_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 ) + : m_type( Image ) +{ + m_imageData = new ImageData(); + m_imageData->rect = rect; + m_imageData->image = image; + m_imageData->subRect = subRect; + m_imageData->flags = flags; +} + +/*! + Constructor for State paint operation + \param state Paint engine state + */ +QwtPainterCommand::QwtPainterCommand( const QPaintEngineState& state ) + : m_type( State ) +{ + m_stateData = new StateData(); + + m_stateData->flags = state.state(); + + if ( m_stateData->flags & QPaintEngine::DirtyPen ) + m_stateData->pen = state.pen(); + + if ( m_stateData->flags & QPaintEngine::DirtyBrush ) + m_stateData->brush = state.brush(); + + if ( m_stateData->flags & QPaintEngine::DirtyBrushOrigin ) + m_stateData->brushOrigin = state.brushOrigin(); + + if ( m_stateData->flags & QPaintEngine::DirtyFont ) + m_stateData->font = state.font(); + + if ( m_stateData->flags & QPaintEngine::DirtyBackground ) + { + m_stateData->backgroundMode = state.backgroundMode(); + m_stateData->backgroundBrush = state.backgroundBrush(); + } + + if ( m_stateData->flags & QPaintEngine::DirtyTransform ) + m_stateData->transform = state.transform(); + + if ( m_stateData->flags & QPaintEngine::DirtyClipEnabled ) + m_stateData->isClipEnabled = state.isClipEnabled(); + + if ( m_stateData->flags & QPaintEngine::DirtyClipRegion ) + { + m_stateData->clipRegion = state.clipRegion(); + m_stateData->clipOperation = state.clipOperation(); + } + + if ( m_stateData->flags & QPaintEngine::DirtyClipPath ) + { + m_stateData->clipPath = state.clipPath(); + m_stateData->clipOperation = state.clipOperation(); + } + + if ( m_stateData->flags & QPaintEngine::DirtyHints ) + m_stateData->renderHints = state.renderHints(); + + if ( m_stateData->flags & QPaintEngine::DirtyCompositionMode ) + m_stateData->compositionMode = state.compositionMode(); + + if ( m_stateData->flags & QPaintEngine::DirtyOpacity ) + m_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 ) +{ + m_type = other.m_type; + + switch( other.m_type ) + { + case Path: + { + m_path = new QPainterPath( *other.m_path ); + break; + } + case Pixmap: + { + m_pixmapData = new PixmapData( *other.m_pixmapData ); + break; + } + case Image: + { + m_imageData = new ImageData( *other.m_imageData ); + break; + } + case State: + { + m_stateData = new StateData( *other.m_stateData ); + break; + } + default: + break; + } +} + +void QwtPainterCommand::reset() +{ + switch( m_type ) + { + case Path: + { + delete m_path; + break; + } + case Pixmap: + { + delete m_pixmapData; + break; + } + case Image: + { + delete m_imageData; + break; + } + case State: + { + delete m_stateData; + break; + } + default: + break; + } + + m_type = Invalid; +} + +//! \return Painter path to be painted +QPainterPath* QwtPainterCommand::path() +{ + return m_path; +} + +//! \return Attributes how to paint a QPixmap +QwtPainterCommand::PixmapData* QwtPainterCommand::pixmapData() +{ + return m_pixmapData; +} + +//! \return Attributes how to paint a QImage +QwtPainterCommand::ImageData* QwtPainterCommand::imageData() +{ + return m_imageData; +} + +//! \return Attributes of a state change +QwtPainterCommand::StateData* QwtPainterCommand::stateData() +{ + return m_stateData; +} diff --git a/libs/qwt/src/qwt_painter_command.h b/libs/qwt/src/qwt_painter_command.h new file mode 100644 index 00000000..87af9612 --- /dev/null +++ b/libs/qwt/src/qwt_painter_command.h @@ -0,0 +1,174 @@ +/****************************************************************************** + * 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 +#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; + QTransform transform; + + Qt::ClipOperation clipOperation; + QRegion clipRegion; + QPainterPath clipPath; + bool isClipEnabled; + + QPainter::RenderHints renderHints; + QPainter::CompositionMode compositionMode; + qreal opacity; + }; + + QwtPainterCommand(); + QwtPainterCommand(const QwtPainterCommand&); + + explicit QwtPainterCommand( const QPainterPath& ); + + QwtPainterCommand( const QRectF& rect, + const QPixmap&, const QRectF& subRect ); + + QwtPainterCommand( const QRectF& rect, + const QImage&, const QRectF& subRect, + Qt::ImageConversionFlags ); + + explicit 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 m_type; + + union + { + QPainterPath* m_path; + PixmapData* m_pixmapData; + ImageData* m_imageData; + StateData* m_stateData; + }; +}; + +//! \return Type of the command +inline QwtPainterCommand::Type QwtPainterCommand::type() const +{ + return m_type; +} + +//! \return Painter path to be painted +inline const QPainterPath* QwtPainterCommand::path() const +{ + return m_path; +} + +//! \return Attributes how to paint a QPixmap +inline const QwtPainterCommand::PixmapData* +QwtPainterCommand::pixmapData() const +{ + return m_pixmapData; +} + +//! \return Attributes how to paint a QImage +inline const QwtPainterCommand::ImageData* +QwtPainterCommand::imageData() const +{ + return m_imageData; +} + +//! \return Attributes of a state change +inline const QwtPainterCommand::StateData* +QwtPainterCommand::stateData() const +{ + return m_stateData; +} + +#endif diff --git a/libs/qwt/src/qwt_panner.cpp b/libs/qwt/src/qwt_panner.cpp new file mode 100644 index 00000000..e56a72d3 --- /dev/null +++ b/libs/qwt/src/qwt_panner.cpp @@ -0,0 +1,544 @@ +/****************************************************************************** + * 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< QwtPicker* > qwtActivePickers( QWidget* w ) +{ + QVector< QwtPicker* > pickers; + + QObjectList children = w->children(); + for ( int i = 0; i < children.size(); i++ ) + { + QwtPicker* picker = qobject_cast< QwtPicker* >( 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 ) +{ + m_data = new PrivateData(); + + setAttribute( Qt::WA_TransparentForMouseEvents ); + setAttribute( Qt::WA_NoSystemBackground ); + setFocusPolicy( Qt::NoFocus ); + hide(); + + setEnabled( true ); +} + +//! Destructor +QwtPanner::~QwtPanner() +{ + delete m_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 ) +{ + m_data->button = button; + m_data->buttonModifiers = modifiers; +} + +//! Get mouse button and modifiers used for panning +void QwtPanner::getMouseButton( Qt::MouseButton& button, + Qt::KeyboardModifiers& modifiers ) const +{ + button = m_data->button; + modifiers = m_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 ) +{ + m_data->abortKey = key; + m_data->abortKeyModifiers = modifiers; +} + +//! Get the abort key and modifiers +void QwtPanner::getAbortKey( int& key, + Qt::KeyboardModifiers& modifiers ) const +{ + key = m_data->abortKey; + modifiers = m_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 ) +{ + m_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 ( m_data->cursor ) + return *m_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 ( m_data->isEnabled != on ) + { + m_data->isEnabled = on; + + QWidget* w = parentWidget(); + if ( w ) + { + if ( m_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 ) +{ + m_data->orientations = o; +} + +//! Return the orientation, where panning is enabled +Qt::Orientations QwtPanner::orientations() const +{ + return m_data->orientations; +} + +/*! + \return True if an orientation is enabled + \sa orientations(), setOrientations() + */ +bool QwtPanner::isOrientationEnabled( Qt::Orientation o ) const +{ + return m_data->orientations & o; +} + +/*! + \return true when enabled, false otherwise + \sa setEnabled, eventFilter() + */ +bool QwtPanner::isEnabled() const +{ + return m_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 event Paint event + */ +void QwtPanner::paintEvent( QPaintEvent* event ) +{ + int dx = m_data->pos.x() - m_data->initialPos.x(); + int dy = m_data->pos.y() - m_data->initialPos.y(); + + QRectF r; + r.setSize( m_data->pixmap.size() / QwtPainter::devicePixelRatio( &m_data->pixmap ) ); + r.moveCenter( QPointF( r.center().x() + dx, r.center().y() + dy ) ); + + QPixmap pm = QwtPainter::backingStore( this, size() ); + QwtPainter::fillPixmap( parentWidget(), pm ); + + QPainter painter( &pm ); + + if ( !m_data->contentsMask.isNull() ) + { + QPixmap masked = m_data->pixmap; + masked.setMask( m_data->contentsMask ); + painter.drawPixmap( r.toRect(), masked ); + } + else + { + painter.drawPixmap( r.toRect(), m_data->pixmap ); + } + + painter.end(); + + if ( !m_data->contentsMask.isNull() ) + pm.setMask( m_data->contentsMask ); + + painter.begin( this ); + painter.setClipRegion( event->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< QMouseEvent* >( event ) ); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent( static_cast< QMouseEvent* >( event ) ); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent( static_cast< QMouseEvent* >( event ) ); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent( static_cast< QKeyEvent* >( event ) ); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent( static_cast< QKeyEvent* >( 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() != m_data->button ) + || ( mouseEvent->modifiers() != m_data->buttonModifiers ) ) + { + return; + } + + QWidget* w = parentWidget(); + if ( w == NULL ) + return; + +#ifndef QT_NO_CURSOR + showCursor( true ); +#endif + + m_data->initialPos = m_data->pos = mouseEvent->pos(); + + setGeometry( parentWidget()->rect() ); + + // We don't want to grab the picker ! + QVector< QwtPicker* > pickers = qwtActivePickers( parentWidget() ); + for ( int i = 0; i < pickers.size(); i++ ) + pickers[i]->setEnabled( false ); + + m_data->pixmap = grab(); + m_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( m_data->initialPos.x() ); + if ( !isOrientationEnabled( Qt::Vertical ) ) + pos.setY( m_data->initialPos.y() ); + + if ( pos != m_data->pos && rect().contains( pos ) ) + { + m_data->pos = pos; + update(); + + Q_EMIT moved( m_data->pos.x() - m_data->initialPos.x(), + m_data->pos.y() - m_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( m_data->initialPos.x() ); + if ( !isOrientationEnabled( Qt::Vertical ) ) + pos.setY( m_data->initialPos.y() ); + + m_data->pixmap = QPixmap(); + m_data->contentsMask = QBitmap(); + m_data->pos = pos; + + if ( m_data->pos != m_data->initialPos ) + { + Q_EMIT panned( m_data->pos.x() - m_data->initialPos.x(), + m_data->pos.y() - m_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() == m_data->abortKey ) + && ( keyEvent->modifiers() == m_data->abortKeyModifiers ) ) + { + hide(); + +#ifndef QT_NO_CURSOR + showCursor( false ); +#endif + m_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 == m_data->hasCursor ) + return; + + QWidget* w = parentWidget(); + if ( w == NULL || m_data->cursor == NULL ) + return; + + m_data->hasCursor = on; + + if ( on ) + { + if ( w->testAttribute( Qt::WA_SetCursor ) ) + { + delete m_data->restoreCursor; + m_data->restoreCursor = new QCursor( w->cursor() ); + } + w->setCursor( *m_data->cursor ); + } + else + { + if ( m_data->restoreCursor ) + { + w->setCursor( *m_data->restoreCursor ); + delete m_data->restoreCursor; + m_data->restoreCursor = NULL; + } + else + w->unsetCursor(); + } +} +#endif + +#if QWT_MOC_INCLUDE +#include "moc_qwt_panner.cpp" +#endif diff --git a/libs/qwt/src/qwt_panner.h b/libs/qwt/src/qwt_panner.h new file mode 100644 index 00000000..a640e659 --- /dev/null +++ b/libs/qwt/src/qwt_panner.h @@ -0,0 +1,103 @@ +/****************************************************************************** + * 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 + +#include "qwt_global.h" +#include + +class QCursor; +class QPixmap; + +/*! + \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: + explicit 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* ) QWT_OVERRIDE; + + 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* ) QWT_OVERRIDE; + + virtual QBitmap contentsMask() const; + virtual QPixmap grab() const; + + private: +#ifndef QT_NO_CURSOR + void showCursor( bool ); +#endif + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_picker.cpp b/libs/qwt/src/qwt_picker.cpp new file mode 100644 index 00000000..4a44b515 --- /dev/null +++ b/libs/qwt/src/qwt_picker.cpp @@ -0,0 +1,1594 @@ +/****************************************************************************** + * 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 "qwt_text.h" + +#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; +} + +namespace +{ + class Rubberband QWT_FINAL : public QwtWidgetOverlay + { + public: + Rubberband( QwtPicker* picker, QWidget* parent ) + : QwtWidgetOverlay( parent ) + , m_picker( picker ) + { + setMaskMode( QwtWidgetOverlay::MaskHint ); + } + + protected: + virtual void drawOverlay( QPainter* painter ) const QWT_OVERRIDE + { + painter->setPen( m_picker->rubberBandPen() ); + m_picker->drawRubberBand( painter ); + } + + virtual QRegion maskHint() const QWT_OVERRIDE + { + return m_picker->rubberBandMask(); + } + + QwtPicker* m_picker; + }; + + class Tracker QWT_FINAL : public QwtWidgetOverlay + { + public: + Tracker( QwtPicker* picker, QWidget* parent ) + : QwtWidgetOverlay( parent ) + , m_picker( picker ) + { + setMaskMode( QwtWidgetOverlay::MaskHint ); + } + + protected: + virtual void drawOverlay( QPainter* painter ) const QWT_OVERRIDE + { + painter->setPen( m_picker->trackerPen() ); + m_picker->drawTracker( painter ); + } + + virtual QRegion maskHint() const QWT_OVERRIDE + { + return m_picker->trackerMask(); + } + + QwtPicker* m_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< Rubberband > rubberBandOverlay; + QPointer< Tracker > trackerOverlay; + + bool openGL; +}; + +/*! + 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 m_data->stateMachine; + delete m_data->rubberBandOverlay; + delete m_data->trackerOverlay; + + delete m_data; +} + +//! Initialize the picker - used by the constructors +void QwtPicker::init( QWidget* parent, + RubberBand rubberBand, DisplayMode trackerMode ) +{ + m_data = new PrivateData; + + m_data->rubberBand = rubberBand; + + if ( parent ) + { + if ( parent->focusPolicy() == Qt::NoFocus ) + parent->setFocusPolicy( Qt::WheelFocus ); + + m_data->openGL = parent->inherits( "QGLWidget" ); + m_data->trackerFont = parent->font(); + m_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 ( m_data->stateMachine != stateMachine ) + { + reset(); + + delete m_data->stateMachine; + m_data->stateMachine = stateMachine; + + if ( m_data->stateMachine ) + m_data->stateMachine->reset(); + } +} + +/*! + \return Assigned state machine + \sa setStateMachine() + */ +QwtPickerMachine* QwtPicker::stateMachine() +{ + return m_data->stateMachine; +} + +/*! + \return Assigned state machine + \sa setStateMachine() + */ +const QwtPickerMachine* QwtPicker::stateMachine() const +{ + return m_data->stateMachine; +} + +//! Return the parent widget, where the selection happens +QWidget* QwtPicker::parentWidget() +{ + QObject* obj = parent(); + if ( obj && obj->isWidgetType() ) + return static_cast< QWidget* >( 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 ) +{ + m_data->rubberBand = rubberBand; +} + +/*! + \return Rubber band style + \sa setRubberBand(), RubberBand, rubberBandPen() + */ +QwtPicker::RubberBand QwtPicker::rubberBand() const +{ + return m_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 ( m_data->trackerMode != mode ) + { + m_data->trackerMode = mode; + setMouseTracking( m_data->trackerMode == AlwaysOn ); + } +} + +/*! + \return Tracker display mode + \sa setTrackerMode(), DisplayMode + */ +QwtPicker::DisplayMode QwtPicker::trackerMode() const +{ + return m_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 ) +{ + m_data->resizeMode = mode; +} + +/*! + \return Resize mode + \sa setResizeMode(), ResizeMode + */ + +QwtPicker::ResizeMode QwtPicker::resizeMode() const +{ + return m_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 ( m_data->enabled != enabled ) + { + m_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 m_data->enabled; +} + +/*! + Set the font for the tracker + + \param font Tracker font + \sa trackerFont(), setTrackerMode(), setTrackerPen() + */ +void QwtPicker::setTrackerFont( const QFont& font ) +{ + if ( font != m_data->trackerFont ) + { + m_data->trackerFont = font; + updateDisplay(); + } +} + +/*! + \return Tracker font + \sa setTrackerFont(), trackerMode(), trackerPen() + */ + +QFont QwtPicker::trackerFont() const +{ + return m_data->trackerFont; +} + +/*! + Set the pen for the tracker + + \param pen Tracker pen + \sa trackerPen(), setTrackerMode(), setTrackerFont() + */ +void QwtPicker::setTrackerPen( const QPen& pen ) +{ + if ( pen != m_data->trackerPen ) + { + m_data->trackerPen = pen; + updateDisplay(); + } +} + +/*! + \return Tracker pen + \sa setTrackerPen(), trackerMode(), trackerFont() + */ +QPen QwtPicker::trackerPen() const +{ + return m_data->trackerPen; +} + +/*! + Set the pen for the rubberband + + \param pen Rubber band pen + \sa rubberBandPen(), setRubberBand() + */ +void QwtPicker::setRubberBandPen( const QPen& pen ) +{ + if ( pen != m_data->rubberBandPen ) + { + m_data->rubberBandPen = pen; + updateDisplay(); + } +} + +/*! + \return Rubber band pen + \sa setRubberBandPen(), rubberBand() + */ +QPen QwtPicker::rubberBandPen() const +{ + return m_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 = QString::number( pos.y() ); + break; + case VLineRubberBand: + label = QString::number( pos.x() ); + break; + default: + label = QString::number( pos.x() ) + ", " + QString::number( pos.y() ); + } + return label; +} + +/*! + Calculate the mask for the tracker overlay + + \return Region with one rectangle: trackerRect( trackerFont() ); + \sa QWidget::setMask(), trackerRect() + */ +QRegion QwtPicker::trackerMask() const +{ + return trackerRect( m_data->trackerFont ); +} + +/*! + 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( m_data->pickedPoints ); + + QwtPickerMachine::SelectionType selectionType = + QwtPickerMachine::NoSelection; + + if ( m_data->stateMachine ) + selectionType = m_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( m_data->pickedPoints ); + + QwtPickerMachine::SelectionType selectionType = + QwtPickerMachine::NoSelection; + + if ( m_data->stateMachine ) + selectionType = m_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( m_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. + - A selection could accept non intersecting polygons only. + - ... + + The example below is for a rectangular selection, where the first + point is the center of the selected rectangle. + + \par Example + \code + 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; + } + \endcode + + \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( m_data->pickedPoints ); +} + +//! \return Current position of the tracker +QPoint QwtPicker::trackerPosition() const +{ + return m_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 ( m_data->trackerPosition.x() < 0 || m_data->trackerPosition.y() < 0 ) + return QRect(); + + QwtText text = trackerText( m_data->trackerPosition ); + if ( text.isEmpty() ) + return QRect(); + + const QSizeF textSize = text.textSize( font ); + QRect textRect( 0, 0, qwtCeil( textSize.width() ), qwtCeil( textSize.height() ) ); + + const QPoint& pos = m_data->trackerPosition; + + int alignment = 0; + if ( isActive() && m_data->pickedPoints.count() > 1 + && rubberBand() != NoRubberBand ) + { + const QPoint last = + m_data->pickedPoints[ m_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< QResizeEvent* >( event ); + + /* + Adding/deleting additional event filters inside of an event filter + is not safe dues to the implementation in Qt ( changing a list while iterating ). + So we create the overlays in a way, that they don't install en event filter + ( parent set to NULL ) and do the resizing here. + */ + if ( m_data->trackerOverlay ) + m_data->trackerOverlay->resize( re->size() ); + + if ( m_data->rubberBandOverlay ) + m_data->rubberBandOverlay->resize( re->size() ); + + if ( m_data->resizeMode == Stretch ) + stretchSelection( re->oldSize(), re->size() ); + + updateDisplay(); + break; + } + case QEvent::Enter: + { + widgetEnterEvent( event ); + break; + } + case QEvent::Leave: + { + widgetLeaveEvent( event ); + break; + } + case QEvent::MouseButtonPress: + { + widgetMousePressEvent( static_cast< QMouseEvent* >( event ) ); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent( static_cast< QMouseEvent* >( event ) ); + break; + } + case QEvent::MouseButtonDblClick: + { + widgetMouseDoubleClickEvent( static_cast< QMouseEvent* >( event ) ); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent( static_cast< QMouseEvent* >( event ) ); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent( static_cast< QKeyEvent* >( event ) ); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent( static_cast< QKeyEvent* >( event ) ); + break; + } + case QEvent::Wheel: + { + widgetWheelEvent( static_cast< QWheelEvent* >( 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() ) ) + m_data->trackerPosition = mouseEvent->pos(); + else + m_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 ); + + m_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 QT_VERSION < 0x050e00 + const QPoint wheelPos = wheelEvent->pos(); +#else + const QPoint wheelPos = wheelEvent->position().toPoint(); +#endif + if ( pickArea().contains( wheelPos ) ) + m_data->trackerPosition = wheelPos; + else + m_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 ( !m_data->stateMachine ) + return; + + const QList< QwtPickerMachine::Command > commandList = + m_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 ( m_data->isActive ) + return; + + m_data->pickedPoints.clear(); + m_data->isActive = true; + Q_EMIT activated( true ); + + if ( trackerMode() != AlwaysOff ) + { + if ( m_data->trackerPosition.x() < 0 || m_data->trackerPosition.y() < 0 ) + { + QWidget* w = parentWidget(); + if ( w ) + m_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 ( m_data->isActive ) + { + setMouseTracking( false ); + + m_data->isActive = false; + Q_EMIT activated( false ); + + if ( trackerMode() == ActiveOnly ) + m_data->trackerPosition = QPoint( -1, -1 ); + + if ( ok ) + ok = accept( m_data->pickedPoints ); + + if ( ok ) + Q_EMIT selected( m_data->pickedPoints ); + else + m_data->pickedPoints.clear(); + + updateDisplay(); + } + else + ok = false; + + return ok; +} + +/*! + Reset the state machine and terminate ( end(false) ) the selection + */ +void QwtPicker::reset() +{ + if ( m_data->stateMachine ) + m_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 ( m_data->isActive ) + { + m_data->pickedPoints += 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 ( m_data->isActive && !m_data->pickedPoints.isEmpty() ) + { + QPoint& point = m_data->pickedPoints.last(); + if ( point != pos ) + { + point = 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 ( m_data->isActive && !m_data->pickedPoints.isEmpty() ) + { +#if QT_VERSION >= 0x050100 + const QPoint pos = m_data->pickedPoints.takeLast(); +#else + const QPoint pos = m_data->pickedPoints.last(); + m_data->pickedPoints.resize( m_data->pickedPoints.count() - 1 ); +#endif + + 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 m_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 m_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 < m_data->pickedPoints.count(); i++ ) + { + QPoint& p = m_data->pickedPoints[i]; + p.setX( qRound( p.x() * xRatio ) ); + p.setY( qRound( p.y() * yRatio ) ); + + Q_EMIT changed( m_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 ) + { + m_data->mouseTracking = widget->hasMouseTracking(); + widget->setMouseTracking( true ); + } + else + { + widget->setMouseTracking( m_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() && m_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< Rubberband >& rw = m_data->rubberBandOverlay; + if ( showRubberband ) + { + if ( rw.isNull() ) + { + rw = new Rubberband( this, NULL ); // NULL -> no extra event filter + rw->setObjectName( "PickerRubberBand" ); + rw->setParent( w ); + rw->resize( w->size() ); + } + + if ( m_data->rubberBand <= RectRubberBand ) + rw->setMaskMode( QwtWidgetOverlay::MaskHint ); + else + rw->setMaskMode( QwtWidgetOverlay::AlphaMask ); + + rw->updateOverlay(); + } + else + { + if ( m_data->openGL ) + { + // Qt 4.8 crashes for a delete + if ( !rw.isNull() ) + { + rw->hide(); + rw->deleteLater(); + rw = NULL; + } + } + else + { + delete rw; + } + } + + QPointer< Tracker >& tw = m_data->trackerOverlay; + if ( showTracker ) + { + if ( tw.isNull() ) + { + tw = new Tracker( this, NULL ); // NULL -> no extra event filter + tw->setObjectName( "PickerTracker" ); + tw->setParent( w ); + tw->resize( w->size() ); + } + tw->setFont( m_data->trackerFont ); + tw->updateOverlay(); + } + else + { + if ( m_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 m_data->rubberBandOverlay; +} + +//! \return Overlay displaying the tracker text +const QwtWidgetOverlay* QwtPicker::trackerOverlay() const +{ + return m_data->trackerOverlay; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_picker.cpp" +#endif diff --git a/libs/qwt/src/qwt_picker.h b/libs/qwt/src/qwt_picker.h new file mode 100644 index 00000000..84e67f94 --- /dev/null +++ b/libs/qwt/src/qwt_picker.h @@ -0,0 +1,338 @@ +/****************************************************************************** + * 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 + +#include "qwt_global.h" +#include "qwt_event_pattern.h" + +#include + +class QwtPickerMachine; +class QwtWidgetOverlay; +class QwtText; +class QWidget; +class QMouseEvent; +class QWheelEvent; +class QKeyEvent; +class QPainter; +class QPen; +class QFont; +class QRegion; +class QPainterPath; +class QPoint; +class QRect; +class QSize; +class QPolygon; + +/*! + \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 + \code + #include + #include + + QwtPicker *picker = new QwtPicker(widget); + picker->setStateMachine(new QwtPickerDragRectMachine); + picker->setTrackerMode(QwtPicker::ActiveOnly); + picker->setRubberBand(QwtPicker::RectRubberBand); + \endcode + + 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* ) QWT_OVERRIDE; + + QWidget* parentWidget(); + const QWidget* parentWidget() const; + + virtual QPainterPath pickArea() const; + + virtual void drawRubberBand( QPainter* ) const; + virtual void drawTracker( QPainter* ) const; + + virtual QRegion trackerMask() 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* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_picker_machine.cpp b/libs/qwt/src/qwt_picker_machine.cpp new file mode 100644 index 00000000..2baa84cd --- /dev/null +++ b/libs/qwt/src/qwt_picker_machine.cpp @@ -0,0 +1,542 @@ +/****************************************************************************** + * 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 ) + : m_selectionType( type ) + , m_state( 0 ) +{ +} + +//! Destructor +QwtPickerMachine::~QwtPickerMachine() +{ +} + +//! Return the selection type +QwtPickerMachine::SelectionType QwtPickerMachine::selectionType() const +{ + return m_selectionType; +} + +//! Return the current state +int QwtPickerMachine::state() const +{ + return m_state; +} + +//! Change the current state +void QwtPickerMachine::setState( int state ) +{ + m_state = state; +} + +//! Set the current state to 0. +void QwtPickerMachine::reset() +{ + setState( 0 ); +} + +//! Constructor +QwtPickerTrackerMachine::QwtPickerTrackerMachine(): + QwtPickerMachine( NoSelection ) +{ +} + +//! Transition +QList< QwtPickerMachine::Command > QwtPickerTrackerMachine::transition( + const QwtEventPattern&, const QEvent* e ) +{ + QList< QwtPickerMachine::Command > 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< QwtPickerMachine::Command > QwtPickerClickPointMachine::transition( + const QwtEventPattern& eventPattern, const QEvent* event ) +{ + QList< QwtPickerMachine::Command > cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast< const QMouseEvent* >( event ) ) ) + { + cmdList += Begin; + cmdList += Append; + cmdList += End; + } + break; + } + case QEvent::KeyPress: + { + const QKeyEvent* keyEvent = static_cast< const QKeyEvent* > ( event ); + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, keyEvent ) ) + { + if ( !keyEvent->isAutoRepeat() ) + { + cmdList += Begin; + cmdList += Append; + cmdList += End; + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerDragPointMachine::QwtPickerDragPointMachine(): + QwtPickerMachine( PointSelection ) +{ +} + +//! Transition +QList< QwtPickerMachine::Command > QwtPickerDragPointMachine::transition( + const QwtEventPattern& eventPattern, const QEvent* event ) +{ + QList< QwtPickerMachine::Command > cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast< const QMouseEvent* >( 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: + { + const QKeyEvent* keyEvent = static_cast< const QKeyEvent* > ( event ); + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, keyEvent ) ) + { + if ( !keyEvent->isAutoRepeat() ) + { + 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< QwtPickerMachine::Command > QwtPickerClickRectMachine::transition( + const QwtEventPattern& eventPattern, const QEvent* event ) +{ + QList< QwtPickerMachine::Command > cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast< const QMouseEvent* >( 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 ); + } + } + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::MouseButtonRelease: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast< const QMouseEvent* >( event ) ) ) + { + if ( state() == 1 ) + { + cmdList += Append; + setState( 2 ); + } + } + break; + } + case QEvent::KeyPress: + { + const QKeyEvent* keyEvent = static_cast< const QKeyEvent* > ( event ); + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, keyEvent ) ) + { + if ( !keyEvent->isAutoRepeat() ) + { + 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< QwtPickerMachine::Command > QwtPickerDragRectMachine::transition( + const QwtEventPattern& eventPattern, const QEvent* event ) +{ + QList< QwtPickerMachine::Command > cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast< const QMouseEvent* >( 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< const QKeyEvent* > ( 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< QwtPickerMachine::Command > QwtPickerPolygonMachine::transition( + const QwtEventPattern& eventPattern, const QEvent* event ) +{ + QList< QwtPickerMachine::Command > cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast< const QMouseEvent* >( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += Append; + } + } + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect2, + static_cast< const QMouseEvent* >( event ) ) ) + { + if ( state() == 1 ) + { + cmdList += End; + setState( 0 ); + } + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::KeyPress: + { + const QKeyEvent* keyEvent = static_cast< const QKeyEvent* > ( event ); + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, keyEvent ) ) + { + if ( !keyEvent->isAutoRepeat() ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += Append; + } + } + } + else if ( eventPattern.keyMatch( QwtEventPattern::KeySelect2, keyEvent ) ) + { + if ( !keyEvent->isAutoRepeat() ) + { + if ( state() == 1 ) + { + cmdList += End; + setState( 0 ); + } + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerDragLineMachine::QwtPickerDragLineMachine(): + QwtPickerMachine( PolygonSelection ) +{ +} + +//! Transition +QList< QwtPickerMachine::Command > QwtPickerDragLineMachine::transition( + const QwtEventPattern& eventPattern, const QEvent* event ) +{ + QList< QwtPickerMachine::Command > cmdList; + + switch( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast< const QMouseEvent* >( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast< const QKeyEvent* > ( 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/libs/qwt/src/qwt_picker_machine.h b/libs/qwt/src/qwt_picker_machine.h new file mode 100644 index 00000000..3feccb99 --- /dev/null +++ b/libs/qwt/src/qwt_picker_machine.h @@ -0,0 +1,214 @@ +/****************************************************************************** + * 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 + +#include "qwt_global.h" + +class QwtEventPattern; +class QEvent; +template< typename T > class QList; + +/*! + \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 + }; + + explicit QwtPickerMachine( SelectionType ); + virtual ~QwtPickerMachine(); + + //! Transition + virtual QList< Command > transition( + const QwtEventPattern&, const QEvent* ) = 0; + void reset(); + + int state() const; + void setState( int ); + + SelectionType selectionType() const; + + private: + const SelectionType m_selectionType; + int m_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< Command > transition( + const QwtEventPattern&, const QEvent* ) QWT_OVERRIDE; +}; + +/*! + \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< Command > transition( + const QwtEventPattern&, const QEvent* ) QWT_OVERRIDE; +}; + +/*! + \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< Command > transition( + const QwtEventPattern&, const QEvent* ) QWT_OVERRIDE; +}; + +/*! + \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< Command > transition( + const QwtEventPattern&, const QEvent* ) QWT_OVERRIDE; +}; + +/*! + \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< Command > transition( + const QwtEventPattern&, const QEvent* ) QWT_OVERRIDE; +}; + +/*! + \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< Command > transition( + const QwtEventPattern&, const QEvent* ) QWT_OVERRIDE; +}; + +/*! + \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< Command > transition( + const QwtEventPattern&, const QEvent* ) QWT_OVERRIDE; +}; + +#endif diff --git a/libs/qwt/src/qwt_pixel_matrix.cpp b/libs/qwt/src/qwt_pixel_matrix.cpp new file mode 100644 index 00000000..e9e4c453 --- /dev/null +++ b/libs/qwt/src/qwt_pixel_matrix.cpp @@ -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 "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 ) ) + , m_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 != m_rect ) + { + m_rect = rect; + const int sz = qMax( rect.width() * rect.height(), 0 ); + resize( sz ); + } + + fill( false ); +} + +//! \return Bounding rectangle +QRect QwtPixelMatrix::rect() const +{ + return m_rect; +} diff --git a/libs/qwt/src/qwt_pixel_matrix.h b/libs/qwt/src/qwt_pixel_matrix.h new file mode 100644 index 00000000..a6e2a6ff --- /dev/null +++ b/libs/qwt/src/qwt_pixel_matrix.h @@ -0,0 +1,99 @@ +/****************************************************************************** + * 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_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: + explicit 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 m_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 - m_rect.x(); + if ( dx < 0 || dx >= m_rect.width() ) + return -1; + + const int dy = y - m_rect.y(); + if ( dy < 0 || dy >= m_rect.height() ) + return -1; + + return dy * m_rect.width() + dx; +} + +#endif diff --git a/libs/qwt/src/qwt_plot.cpp b/libs/qwt/src/qwt_plot.cpp new file mode 100644 index 00000000..3b0f278b --- /dev/null +++ b/libs/qwt/src/qwt_plot.cpp @@ -0,0 +1,1169 @@ +/****************************************************************************** + * 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_scale_map.h" +#include "qwt_text_label.h" +#include "qwt_legend.h" +#include "qwt_legend_data.h" +#include "qwt_plot_canvas.h" +#include "qwt_math.h" + +#include +#include +#include +#include + +static inline void qwtEnableLegendItems( QwtPlot* plot, bool on ) +{ + // gcc seems to have problems with const char sig[] in combination with certain options + const char* sig = SIGNAL(legendDataChanged(QVariant,QList)); + const char* slot = SLOT(updateLegendItems(QVariant,QList)); + + if ( on ) + QObject::connect( plot, sig, plot, slot ); + else + QObject::disconnect( plot, sig, plot, slot ); +} + +static void qwtSetTabOrder( + QWidget* first, QWidget* second, bool withChildren ) +{ + QList< QWidget* > tabChain; + tabChain += first; + tabChain += second; + + if ( withChildren ) + { + QList< QWidget* > children = second->findChildren< QWidget* >(); + + 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 ); + } +} + +class QwtPlot::PrivateData +{ + public: + QPointer< QwtTextLabel > titleLabel; + QPointer< QwtTextLabel > footerLabel; + QPointer< QWidget > canvas; + QPointer< QwtAbstractLegend > 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() +{ + setAutoReplot( false ); + detachItems( QwtPlotItem::Rtti_PlotItem, autoDelete() ); + + delete m_data->layout; + deleteAxesData(); + delete m_data; +} + +/*! + \brief Initializes a QwtPlot instance + \param title Title text + */ +void QwtPlot::initPlot( const QwtText& title ) +{ + m_data = new PrivateData; + + m_data->layout = new QwtPlotLayout; + m_data->autoReplot = false; + + // title + m_data->titleLabel = new QwtTextLabel( this ); + m_data->titleLabel->setObjectName( "QwtPlotTitle" ); + m_data->titleLabel->setFont( QFont( fontInfo().family(), 14, QFont::Bold ) ); + + QwtText text( title ); + text.setRenderFlags( Qt::AlignCenter | Qt::TextWordWrap ); + m_data->titleLabel->setText( text ); + + // footer + m_data->footerLabel = new QwtTextLabel( this ); + m_data->footerLabel->setObjectName( "QwtPlotFooter" ); + + QwtText footer; + footer.setRenderFlags( Qt::AlignCenter | Qt::TextWordWrap ); + m_data->footerLabel->setText( footer ); + + // legend + m_data->legend = NULL; + + // axes + initAxesData(); + + // canvas + m_data->canvas = new QwtPlotCanvas( this ); + m_data->canvas->setObjectName( "QwtPlotCanvas" ); + m_data->canvas->installEventFilter( this ); + + setSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding ); + + resize( 200, 200 ); + + using namespace QwtAxis; + + QList< QWidget* > focusChain; + focusChain << this << m_data->titleLabel << axisWidget( XTop ) + << axisWidget( YLeft ) << m_data->canvas + << axisWidget( YRight ) << axisWidget( XBottom ) + << m_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 == m_data->canvas ) + return; + + delete m_data->canvas; + m_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 == m_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 ( m_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 ) +{ + m_data->autoReplot = tf; +} + +/*! + \return true if the autoReplot option is set. + \sa setAutoReplot() + */ +bool QwtPlot::autoReplot() const +{ + return m_data->autoReplot; +} + +/*! + Change the plot's title + \param title New title + */ +void QwtPlot::setTitle( const QString& title ) +{ + if ( title != m_data->titleLabel->text().text() ) + { + m_data->titleLabel->setText( title ); + updateLayout(); + } +} + +/*! + Change the plot's title + \param title New title + */ +void QwtPlot::setTitle( const QwtText& title ) +{ + if ( title != m_data->titleLabel->text() ) + { + m_data->titleLabel->setText( title ); + updateLayout(); + } +} + +//! \return Title of the plot +QwtText QwtPlot::title() const +{ + return m_data->titleLabel->text(); +} + +//! \return Title label widget. +QwtTextLabel* QwtPlot::titleLabel() +{ + return m_data->titleLabel; +} + +//! \return Title label widget. +const QwtTextLabel* QwtPlot::titleLabel() const +{ + return m_data->titleLabel; +} + +/*! + Change the text the footer + \param text New text of the footer + */ +void QwtPlot::setFooter( const QString& text ) +{ + if ( text != m_data->footerLabel->text().text() ) + { + m_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 != m_data->footerLabel->text() ) + { + m_data->footerLabel->setText( text ); + updateLayout(); + } +} + +//! \return Text of the footer +QwtText QwtPlot::footer() const +{ + return m_data->footerLabel->text(); +} + +//! \return Footer label widget. +QwtTextLabel* QwtPlot::footerLabel() +{ + return m_data->footerLabel; +} + +//! \return Footer label widget. +const QwtTextLabel* QwtPlot::footerLabel() const +{ + return m_data->footerLabel; +} + +/*! + \brief Assign a new plot layout + + \param layout Layout() + \sa plotLayout() + */ +void QwtPlot::setPlotLayout( QwtPlotLayout* layout ) +{ + if ( layout != m_data->layout ) + { + delete m_data->layout; + m_data->layout = layout; + + updateLayout(); + } +} + +//! \return the plot's layout +QwtPlotLayout* QwtPlot::plotLayout() +{ + return m_data->layout; +} + +//! \return the plot's layout +const QwtPlotLayout* QwtPlot::plotLayout() const +{ + return m_data->layout; +} + +/*! + \return the plot's legend + \sa insertLegend() + */ +QwtAbstractLegend* QwtPlot::legend() +{ + return m_data->legend; +} + +/*! + \return the plot's legend + \sa insertLegend() + */ +const QwtAbstractLegend* QwtPlot::legend() const +{ + return m_data->legend; +} + + +/*! + \return the plot's canvas + */ +QWidget* QwtPlot::canvas() +{ + return m_data->canvas; +} + +/*! + \return the plot's canvas + */ +const QWidget* QwtPlot::canvas() const +{ + return m_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::AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + 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 = m_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 ( m_data->canvas ) + { + const bool ok = QMetaObject::invokeMethod( + m_data->canvas, "replot", Qt::DirectConnection ); + if ( !ok ) + { + // fallback, when canvas has no a replot method + m_data->canvas->update( m_data->canvas->contentsRect() ); + } + } + + setAutoReplot( doAutoReplot ); +} + +/*! + \brief Adjust plot content to its current size. + \sa resizeEvent() + */ +void QwtPlot::updateLayout() +{ + QwtPlotLayout* layout = m_data->layout; + layout->activate( this, contentsRect() ); + + const QRect titleRect = layout->titleRect().toRect(); + const QRect footerRect = layout->footerRect().toRect(); + const QRect legendRect = layout->legendRect().toRect(); + const QRect canvasRect = layout->canvasRect().toRect(); + + // resize and show the visible widgets + + if ( !m_data->titleLabel->text().isEmpty() ) + { + m_data->titleLabel->setGeometry( titleRect ); + if ( !m_data->titleLabel->isVisibleTo( this ) ) + m_data->titleLabel->show(); + } + else + m_data->titleLabel->hide(); + + if ( !m_data->footerLabel->text().isEmpty() ) + { + m_data->footerLabel->setGeometry( footerRect ); + if ( !m_data->footerLabel->isVisibleTo( this ) ) + m_data->footerLabel->show(); + } + else + { + m_data->footerLabel->hide(); + } + + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + QwtScaleWidget* scaleWidget = axisWidget( axisId ); + + if ( isAxisVisible( axisId ) ) + { + const QRect scaleRect = layout->scaleRect( axisId ).toRect(); + + if ( scaleRect != scaleWidget->geometry() ) + { + scaleWidget->setGeometry( scaleRect ); + + int startDist, endDist; + scaleWidget->getBorderDistHint( startDist, endDist ); + scaleWidget->setBorderDist( startDist, endDist ); + } + + if ( !scaleWidget->isVisibleTo( this ) ) + scaleWidget->show(); + } + else + { + scaleWidget->hide(); + } + } + } + + if ( m_data->legend ) + { + if ( m_data->legend->isEmpty() ) + { + m_data->legend->hide(); + } + else + { + m_data->legend->setGeometry( legendRect ); + m_data->legend->show(); + } + } + + m_data->canvas->setGeometry( canvasRect ); +} + +/*! + \brief Calculate the canvas margins + + \param maps QwtAxis::AxisCount 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 QwtScaleMap maps[], 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 ) ) + { + using namespace QwtAxis; + + double m[ AxisPositions ]; + item->getCanvasMarginHint( + maps[ item->xAxis() ], maps[ item->yAxis() ], + canvasRect, m[YLeft], m[XTop], m[YRight], m[XBottom] ); + + left = qwtMaxF( left, m[YLeft] ); + top = qwtMaxF( top, m[XTop] ); + right = qwtMaxF( right, m[YRight] ); + bottom = qwtMaxF( bottom, m[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() +{ + using namespace QwtAxis; + + QwtScaleMap maps[ AxisPositions ]; + for ( int axisId = 0; axisId < AxisPositions; axisId++ ) + maps[axisId] = canvasMap( axisId ); + + double margins[AxisPositions]; + getCanvasMarginsHint( maps, canvas()->contentsRect(), + margins[YLeft], margins[XTop], margins[YRight], margins[XBottom] ); + + bool doUpdate = false; + for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ ) + { + if ( margins[axisPos] >= 0.0 ) + { + const int m = qwtCeil( 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 ) +{ + QwtScaleMap maps[ QwtAxis::AxisPositions ]; + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + maps[axisPos] = canvasMap( axisPos ); + + drawItems( painter, m_data->canvas->contentsRect(), maps ); +} + +/*! + Redraw the canvas items. + + \param painter Painter used for drawing + \param canvasRect Bounding rectangle where to paint + \param maps QwtAxis::AxisCount 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 QwtScaleMap maps[ QwtAxis::AxisPositions ] ) const +{ + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPlotItem* item = *it; + if ( item && item->isVisible() ) + { + const QwtAxisId xAxis = item->xAxis(); + const QwtAxisId yAxis = item->yAxis(); + + painter->save(); + + painter->setRenderHint( QPainter::Antialiasing, + item->testRenderHint( QwtPlotItem::RenderAntialiased ) ); + +#if QT_VERSION < 0x050100 + painter->setRenderHint( QPainter::HighQualityAntialiasing, + item->testRenderHint( QwtPlotItem::RenderAntialiased ) ); +#endif + + item->draw( painter, maps[xAxis], maps[yAxis], canvasRect ); + + painter->restore(); + } + } +} + +/*! + \param axisId 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 ( !m_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 ) ) + { + double y = s->y() + s->startBorderDist() - m_data->canvas->y(); + double h = s->height() - s->startBorderDist() - s->endBorderDist(); + map.setPaintInterval( y + h, y ); + } + else + { + double x = s->x() + s->startBorderDist() - m_data->canvas->x(); + double w = s->width() - s->startBorderDist() - s->endBorderDist(); + map.setPaintInterval( x, x + w ); + } + } + else + { + using namespace QwtAxis; + + const QRect& canvasRect = m_data->canvas->contentsRect(); + if ( isYAxis( axisId ) ) + { + int top = 0; + if ( !plotLayout()->alignCanvasToScale( XTop ) ) + top = plotLayout()->canvasMargin( XTop ); + + int bottom = 0; + if ( !plotLayout()->alignCanvasToScale( XBottom ) ) + bottom = plotLayout()->canvasMargin( XBottom ); + + map.setPaintInterval( canvasRect.bottom() - bottom, + canvasRect.top() + top ); + } + else + { + int left = 0; + if ( !plotLayout()->alignCanvasToScale( YLeft ) ) + left = plotLayout()->canvasMargin( YLeft ); + + int right = 0; + if ( !plotLayout()->alignCanvasToScale( YRight ) ) + right = plotLayout()->canvasMargin( YRight ); + + map.setPaintInterval( canvasRect.left() + left, + canvasRect.right() - right ); + } + } + + 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 = m_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 ) +{ + m_data->layout->setLegendPosition( pos, ratio ); + + if ( legend != m_data->legend ) + { + if ( m_data->legend && m_data->legend->parent() == this ) + delete m_data->legend; + + m_data->legend = legend; + + if ( m_data->legend ) + { + connect( + this, SIGNAL(legendDataChanged(QVariant,QList)), + m_data->legend, SLOT(updateLegend(QVariant,QList)) + ); + + if ( m_data->legend->parent() != this ) + m_data->legend->setParent( this ); + + qwtEnableLegendItems( this, false ); + updateLegend(); + qwtEnableLegendItems( this, true ); + + QwtLegend* lgd = qobject_cast< QwtLegend* >( legend ); + if ( lgd ) + { + switch ( m_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 ( m_data->layout->legendPosition() ) + { + case LeftLegend: + { + const QwtAxisId axisId( QwtAxis::XTop ); + previousInChain = axisWidget( axisId ); + break; + } + case TopLegend: + { + previousInChain = this; + break; + } + case RightLegend: + { + const QwtAxisId axisId( QwtAxis::YRight ); + previousInChain = axisWidget( axisId ); + 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< QwtLegendData > 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< QwtLegendData >& 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< QwtLegendData > 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< QwtLegendData >() ); + } + } + + autoRefresh(); +} + +/*! + \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. + + \param plotItem Plot item + \return Plot item embedded in a QVariant + \sa infoToItem() + */ +QVariant QwtPlot::itemToInfo( QwtPlotItem* plotItem ) const +{ + return QVariant::fromValue( plotItem ); +} + +/*! + \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< QwtPlotItem* >() ) + return qvariant_cast< QwtPlotItem* >( itemInfo ); + + return NULL; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_plot.cpp" +#endif diff --git a/libs/qwt/src/qwt_plot.h b/libs/qwt/src/qwt_plot.h new file mode 100644 index 00000000..2343628d --- /dev/null +++ b/libs/qwt/src/qwt_plot.h @@ -0,0 +1,310 @@ +/****************************************************************************** + * 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_plot_dict.h" + +#include + +class QwtPlotLayout; +class QwtAbstractLegend; +class QwtScaleWidget; +class QwtScaleEngine; +class QwtScaleDiv; +class QwtScaleMap; +class QwtScaleDraw; +class QwtTextLabel; +class QwtInterval; +class QwtText; +template< typename T > class QList; + +// 6.1 compatibility definitions +#define QWT_AXIS_COMPAT 1 + +/*! + \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. + \code + #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(); + \endcode + */ + +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 ) + + 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 setAutoReplot( bool = true ); + bool autoReplot() const; + + // Layout + + void setPlotLayout( QwtPlotLayout* ); + + QwtPlotLayout* plotLayout(); + const QwtPlotLayout* plotLayout() const; + + // Title + + void setTitle( const QString& ); + void setTitle( const QwtText& ); + QwtText title() const; + + QwtTextLabel* titleLabel(); + const QwtTextLabel* titleLabel() const; + + // Footer + + void setFooter( const QString& ); + void setFooter( const QwtText& ); + 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 pos ) const; + double transform( QwtAxisId, double value ) const; + + // Axes + + bool isAxisValid( QwtAxisId ) const; + + void setAxisVisible( QwtAxisId, bool on = true ); + bool isAxisVisible( QwtAxisId ) const; + + // Axes data + + QwtScaleEngine* axisScaleEngine( QwtAxisId ); + const QwtScaleEngine* axisScaleEngine( QwtAxisId ) const; + void setAxisScaleEngine( QwtAxisId, QwtScaleEngine* ); + + void setAxisAutoScale( QwtAxisId, bool on = true ); + bool axisAutoScale( QwtAxisId ) const; + + void setAxisFont( QwtAxisId, const QFont& ); + QFont axisFont( QwtAxisId ) const; + + void setAxisScale( QwtAxisId, double min, double max, double stepSize = 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 QWT_OVERRIDE; + virtual QSize minimumSizeHint() const QWT_OVERRIDE; + + virtual void updateLayout(); + virtual void drawCanvas( QPainter* ); + + void updateAxes(); + void updateCanvasMargins(); + + virtual void getCanvasMarginsHint( + const QwtScaleMap maps[], const QRectF& canvasRect, + double& left, double& top, double& right, double& bottom) const; + + virtual bool event( QEvent* ) QWT_OVERRIDE; + virtual bool eventFilter( QObject*, QEvent* ) QWT_OVERRIDE; + + virtual void drawItems( QPainter*, const QRectF&, + const QwtScaleMap maps[ QwtAxis::AxisPositions ] ) const; + + virtual QVariant itemToInfo( QwtPlotItem* ) const; + virtual QwtPlotItem* infoToItem( const QVariant& ) const; + +#if QWT_AXIS_COMPAT + enum Axis + { + yLeft = QwtAxis::YLeft, + yRight = QwtAxis::YRight, + xBottom = QwtAxis::XBottom, + xTop = QwtAxis::XTop, + + axisCnt = QwtAxis::AxisPositions + }; + + 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< QwtLegendData >& data ); + + public Q_SLOTS: + virtual void replot(); + void autoRefresh(); + + protected: + + virtual void resizeEvent( QResizeEvent* ) QWT_OVERRIDE; + + private Q_SLOTS: + void updateLegendItems( const QVariant& itemInfo, + const QList< QwtLegendData >& legendData ); + + private: + friend class QwtPlotItem; + void attachItem( QwtPlotItem*, bool ); + + void initAxesData(); + void deleteAxesData(); + void updateScaleDiv(); + + void initPlot( const QwtText& title ); + + class ScaleData; + ScaleData* m_scaleData; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_abstract_barchart.cpp b/libs/qwt/src/qwt_plot_abstract_barchart.cpp new file mode 100644 index 00000000..f77199f0 --- /dev/null +++ b/libs/qwt/src/qwt_plot_abstract_barchart.cpp @@ -0,0 +1,369 @@ +/****************************************************************************** + * 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" +#include "qwt_math.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 ) +{ + m_data = new PrivateData; + + setItemAttribute( QwtPlotItem::Legend, true ); + setItemAttribute( QwtPlotItem::AutoScale, true ); + setItemAttribute( QwtPlotItem::Margins, true ); + setZ( 19.0 ); +} + +//! Destructor +QwtPlotAbstractBarChart::~QwtPlotAbstractBarChart() +{ + delete m_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 != m_data->layoutPolicy ) + { + m_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 m_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 = qwtMaxF( 0.0, hint ); + if ( hint != m_data->layoutHint ) + { + m_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 m_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 != m_data->spacing ) + { + m_data->spacing = spacing; + itemChanged(); + } +} + +/*! + \return Spacing between 2 samples ( bars or groups of bars ) + \sa setSpacing(), margin() + */ +int QwtPlotAbstractBarChart::spacing() const +{ + return m_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 != m_data->margin ) + { + m_data->margin = margin; + itemChanged(); + } +} + +/*! + \return Margin between the outmost bars and the contentsRect() + of the canvas. + + \sa setMargin(), spacing() + */ +int QwtPlotAbstractBarChart::margin() const +{ + return m_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 != m_data->baseline ) + { + m_data->baseline = value; + itemChanged(); + } +} + +/*! + \return Value for the origin of the bar chart + \sa setBaseline(), QwtPlotSeriesItem::orientation() + */ +double QwtPlotAbstractBarChart::baseline() const +{ + return m_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( m_data->layoutPolicy ) + { + case ScaleSamplesToAxes: + { + width = qwtTransformWidth( map, value, m_data->layoutHint ); + break; + } + case ScaleSampleToCanvas: + { + width = canvasSize * m_data->layoutHint; + break; + } + case FixedSampleSize: + { + width = m_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 -= m_data->spacing; + width = qwtMaxF( width, m_data->layoutHint ); + } + } + + 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() * m_data->layoutHint; + else + hint = 0.5 * canvasRect.height() * m_data->layoutHint; + + break; + } + case FixedSampleSize: + { + hint = 0.5 * m_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 = qwtMaxF( m_data->layoutHint, 0.0 ); + } + else + { + spacing = m_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 * ( numSamples - 1 ) ) + * sampleWidthS / ( ds + sampleWidthS ); + + hint = 0.5 * sampleWidthP; + hint += qMax( m_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/libs/qwt/src/qwt_plot_abstract_barchart.h b/libs/qwt/src/qwt_plot_abstract_barchart.h new file mode 100644 index 00000000..9d2b1651 --- /dev/null +++ b/libs/qwt/src/qwt_plot_abstract_barchart.h @@ -0,0 +1,96 @@ +/****************************************************************************** + * 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" + +/*! + \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. The layoutHint() is used as a minimum width + in paint device coordinates. + + \sa boundingRectangle() + */ + 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 QWT_OVERRIDE; + + + protected: + double sampleWidth( const QwtScaleMap& map, + double canvasSize, double boundingSize, + double value ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_abstract_canvas.cpp b/libs/qwt/src/qwt_plot_abstract_canvas.cpp new file mode 100644 index 00000000..d52312fe --- /dev/null +++ b/libs/qwt/src/qwt_plot_abstract_canvas.cpp @@ -0,0 +1,1129 @@ +/****************************************************************************** + * 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_canvas.h" +#include "qwt_plot.h" +#include "qwt_painter.h" +#include "qwt_null_paintdevice.h" +#include "qwt_math.h" + +#include +#include +#include +#include + +namespace +{ + class QwtStyleSheetRecorder QWT_FINAL : public QwtNullPaintDevice + { + public: + explicit QwtStyleSheetRecorder( const QSize& size ) + : m_size( size ) + { + } + + virtual void updateState( const QPaintEngineState& state ) QWT_OVERRIDE + { + if ( state.state() & QPaintEngine::DirtyPen ) + { + m_pen = state.pen(); + } + if ( state.state() & QPaintEngine::DirtyBrush ) + { + m_brush = state.brush(); + } + if ( state.state() & QPaintEngine::DirtyBrushOrigin ) + { + m_origin = state.brushOrigin(); + } + } + + virtual void drawRects(const QRectF* rects, int count ) QWT_OVERRIDE + { + for ( int i = 0; i < count; i++ ) + border.rectList += rects[i]; + } + + virtual void drawRects(const QRect* rects, int count ) QWT_OVERRIDE + { + for ( int i = 0; i < count; i++ ) + border.rectList += rects[i]; + } + + virtual void drawPath( const QPainterPath& path ) QWT_OVERRIDE + { + const QRectF rect( QPointF( 0.0, 0.0 ), m_size ); + if ( path.controlPointRect().contains( rect.center() ) ) + { + setCornerRects( path ); + alignCornerRects( rect ); + + background.path = path; + background.brush = m_brush; + background.origin = m_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( + qwtMinF( r.left(), el.x ), + qwtMinF( r.top(), el.y ), + qwtMaxF( r.right(), el.x ), + qwtMaxF( r.bottom(), el.y ) + ); + clipRects.last() = r.normalized(); + } + break; + } + } + } + } + + protected: + virtual QSize sizeMetrics() const QWT_OVERRIDE + { + return m_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< QRectF > clipRects; + + struct Border + { + QList< QPainterPath > pathList; + QList< QRectF > rectList; + QRegion clipRegion; + } border; + + struct Background + { + QPainterPath path; + QBrush brush; + QPointF origin; + } background; + + private: + const QSize m_size; + + QPen m_pen; + QBrush m_brush; + QPointF m_origin; + }; +} + +static void qwtUpdateContentsRect( int fw, QWidget* canvas ) +{ + canvas->setContentsMargins( fw, fw, fw, fw ); +} + +static void qwtFillRegion( QPainter* painter, const QRegion& region ) +{ +#if QT_VERSION >= 0x050800 + for ( QRegion::const_iterator it = region.cbegin(); + it != region.cend(); ++it ) + { + painter->drawRect( *it ); + } +#else + painter->drawRects( region.rects() ); +#endif +} + +static void qwtDrawBackground( QPainter* painter, QWidget* canvas ) +{ + painter->save(); + + QPainterPath borderClip; + + ( void )QMetaObject::invokeMethod( + canvas, "borderPath", Qt::DirectConnection, + Q_RETURN_ARG( QPainterPath, borderClip ), Q_ARG( QRect, 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() ) + { + const bool fillClipRegion = + brush.gradient()->coordinateMode() != QGradient::ObjectBoundingMode; + + painter->setPen( Qt::NoPen ); + painter->setBrush( brush ); + + if ( fillClipRegion ) + qwtFillRegion( painter, painter->clipRegion() ); + else + painter->drawRect( canvas->rect() ); + } + else + { + painter->setPen( Qt::NoPen ); + painter->setBrush( brush ); + qwtFillRegion( painter, painter->clipRegion() ); + } + + painter->restore(); +} + +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< QRectF >& 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, QWidget* canvas ) +{ + QVector< QRectF > 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 double borderRadius = canvas->property( "borderRadius" ).toDouble(); + if ( borderRadius > 0.0 ) + { + QSizeF sz( borderRadius, borderRadius ); + + const QRectF r = canvas->rect(); + rects += QRectF( r.topLeft(), sz ); + rects += QRectF( r.topRight() - QPointF( borderRadius, 0 ), sz ); + rects += QRectF( r.bottomRight() - QPointF( borderRadius, borderRadius ), sz ); + rects += QRectF( r.bottomLeft() - QPointF( 0, borderRadius ), sz ); + } + } + + qwtFillBackground( painter, canvas, rects); +} + +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< QPainterPath >& 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 QPainterPath qwtBorderPath( const QWidget* canvas, const QRect& rect ) +{ + if ( canvas->testAttribute(Qt::WA_StyledBackground ) ) + { + QwtStyleSheetRecorder recorder( rect.size() ); + + QPainter painter( &recorder ); + + QStyleOption opt; + opt.initFrom( canvas ); + opt.rect = rect; + canvas->style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, canvas ); + + painter.end(); + + if ( !recorder.background.path.isEmpty() ) + return recorder.background.path; + + if ( !recorder.border.rectList.isEmpty() ) + return qwtCombinePathList( rect, recorder.border.pathList ); + } + else + { + const double borderRadius = canvas->property( "borderRadius" ).toDouble(); + + if ( borderRadius > 0.0 ) + { + double fw2 = canvas->property( "frameWidth" ).toInt() * 0.5; + QRectF r = QRectF(rect).adjusted( fw2, fw2, -fw2, -fw2 ); + + QPainterPath path; + path.addRoundedRect( r, borderRadius, borderRadius ); + return path; + } + } + + return QPainterPath(); +} + +class QwtPlotAbstractCanvas::PrivateData +{ + public: + PrivateData() + : focusIndicator( NoFocusIndicator ) + , borderRadius( 0 ) + { + styleSheet.hasBorder = false; + } + + FocusIndicator focusIndicator; + double borderRadius; + + struct StyleSheet + { + bool hasBorder; + QPainterPath borderPath; + QVector< QRectF > cornerRects; + + struct StyleSheetBackground + { + QBrush brush; + QPointF origin; + } background; + + } styleSheet; + + QWidget* canvasWidget; +}; + +/*! + \brief Constructor + \param canvasWidget plot canvas widget + */ +QwtPlotAbstractCanvas::QwtPlotAbstractCanvas( QWidget* canvasWidget ) +{ + m_data = new PrivateData; + m_data->canvasWidget = canvasWidget; + +#ifndef QT_NO_CURSOR + canvasWidget->setCursor( Qt::CrossCursor ); +#endif + canvasWidget->setAutoFillBackground( true ); +} + +//! Destructor +QwtPlotAbstractCanvas::~QwtPlotAbstractCanvas() +{ + delete m_data; +} + +//! Return parent plot widget +QwtPlot* QwtPlotAbstractCanvas::plot() +{ + return qobject_cast< QwtPlot* >( m_data->canvasWidget->parent() ); +} + +//! Return parent plot widget +const QwtPlot* QwtPlotAbstractCanvas::plot() const +{ + return qobject_cast< const QwtPlot* >( m_data->canvasWidget->parent() ); +} + +/*! + Set the focus indicator + + \sa FocusIndicator, focusIndicator() + */ +void QwtPlotAbstractCanvas::setFocusIndicator( FocusIndicator focusIndicator ) +{ + m_data->focusIndicator = focusIndicator; +} + +/*! + \return Focus indicator + + \sa FocusIndicator, setFocusIndicator() + */ +QwtPlotAbstractCanvas::FocusIndicator QwtPlotAbstractCanvas::focusIndicator() const +{ + return m_data->focusIndicator; +} + +/*! + Draw the focus indication + \param painter Painter + */ +void QwtPlotAbstractCanvas::drawFocusIndicator( QPainter* painter ) +{ + const int margin = 1; + + QRect focusRect = m_data->canvasWidget->contentsRect(); + focusRect.setRect( focusRect.x() + margin, focusRect.y() + margin, + focusRect.width() - 2 * margin, focusRect.height() - 2 * margin ); + + QwtPainter::drawFocusRect( painter, m_data->canvasWidget, focusRect ); +} + +/*! + Set the radius for the corners of the border frame + + \param radius Radius of a rounded corner + \sa borderRadius() + */ +void QwtPlotAbstractCanvas::setBorderRadius( double radius ) +{ + m_data->borderRadius = qwtMaxF( 0.0, radius ); +} + +/*! + \return Radius for the corners of the border frame + \sa setBorderRadius() + */ +double QwtPlotAbstractCanvas::borderRadius() const +{ + return m_data->borderRadius; +} + +//! \return Path for the canvas border +QPainterPath QwtPlotAbstractCanvas::canvasBorderPath( const QRect& rect ) const +{ + return qwtBorderPath( canvasWidget(), rect ); +} + +/*! + Draw the border of the canvas + \param painter Painter + */ +void QwtPlotAbstractCanvas::drawBorder( QPainter* painter ) +{ + const QWidget* w = canvasWidget(); + + if ( m_data->borderRadius > 0 ) + { + const int frameWidth = w->property( "frameWidth" ).toInt(); + if ( frameWidth > 0 ) + { + const int frameShape = w->property( "frameShape" ).toInt(); + const int frameShadow = w->property( "frameShadow" ).toInt(); + + const QRectF frameRect = w->property( "frameRect" ).toRect(); + + QwtPainter::drawRoundedFrame( painter, frameRect, + m_data->borderRadius, m_data->borderRadius, + w->palette(), frameWidth, frameShape | frameShadow ); + } + } + else + { + const int frameShape = w->property( "frameShape" ).toInt(); + const int frameShadow = w->property( "frameShadow" ).toInt(); + +#if QT_VERSION < 0x050000 + QStyleOptionFrameV3 opt; +#else + QStyleOptionFrame opt; +#endif + opt.initFrom( w ); + + opt.frameShape = QFrame::Shape( int( opt.frameShape ) | frameShape ); + + switch (frameShape) + { + case QFrame::Box: + case QFrame::HLine: + case QFrame::VLine: + case QFrame::StyledPanel: + case QFrame::Panel: + { + opt.lineWidth = w->property( "lineWidth" ).toInt(); + opt.midLineWidth = w->property( "midLineWidth" ).toInt(); + break; + } + default: + { + opt.lineWidth = w->property( "frameWidth" ).toInt(); + break; + } + } + + if ( frameShadow == QFrame::Sunken ) + opt.state |= QStyle::State_Sunken; + else if ( frameShadow == QFrame::Raised ) + opt.state |= QStyle::State_Raised; + + w->style()->drawControl(QStyle::CE_ShapedFrame, &opt, painter, w ); + } +} + +//! Helper function for the derived plot canvas +void QwtPlotAbstractCanvas::drawBackground( QPainter* painter ) +{ + qwtDrawBackground( painter, canvasWidget() ); +} + +//! Helper function for the derived plot canvas +void QwtPlotAbstractCanvas::fillBackground( QPainter* painter ) +{ + qwtFillBackground( painter, canvasWidget() ); +} + +//! Helper function for the derived plot canvas +void QwtPlotAbstractCanvas::drawUnstyled( QPainter* painter ) +{ + fillBackground( painter ); + + QWidget* w = canvasWidget(); + + if ( w->autoFillBackground() ) + { + const QRect canvasRect = w->rect(); + + painter->save(); + + painter->setPen( Qt::NoPen ); + painter->setBrush( w->palette().brush( w->backgroundRole() ) ); + + const QRect frameRect = w->property( "frameRect" ).toRect(); + if ( borderRadius() > 0.0 && ( canvasRect == frameRect ) ) + { + const int frameWidth = w->property( "frameWidth" ).toInt(); + if ( frameWidth > 0 ) + { + painter->setClipPath( canvasBorderPath( canvasRect ) ); + painter->drawRect( canvasRect ); + } + else + { + painter->setRenderHint( QPainter::Antialiasing, true ); + painter->drawPath( canvasBorderPath( canvasRect ) ); + } + } + else + { + painter->drawRect( canvasRect ); + } + + painter->restore(); + } + + drawCanvas( painter ); +} + +//! Helper function for the derived plot canvas +void QwtPlotAbstractCanvas::drawStyled( QPainter* painter, bool hackStyledBackground ) +{ + fillBackground( painter ); + + if ( 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 antialiased 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 ( !m_data->styleSheet.hasBorder || + m_data->styleSheet.borderPath.isEmpty() ) + { + // We have no border with at least one rounded corner + hackStyledBackground = false; + } + } + + QWidget* w = canvasWidget(); + + if ( hackStyledBackground ) + { + painter->save(); + + // paint background without border + painter->setPen( Qt::NoPen ); + painter->setBrush( m_data->styleSheet.background.brush ); + painter->setBrushOrigin( m_data->styleSheet.background.origin ); + painter->setClipPath( m_data->styleSheet.borderPath ); + painter->drawRect( w->contentsRect() ); + + painter->restore(); + + drawCanvas( painter ); + + // Now paint the border on top + QStyleOptionFrame opt; + opt.initFrom( w ); + w->style()->drawPrimitive( QStyle::PE_Frame, &opt, painter, w); + } + else + { + QStyleOption opt; + opt.initFrom( w ); + w->style()->drawPrimitive( QStyle::PE_Widget, &opt, painter, w ); + + drawCanvas( painter ); + } +} + +//! \brief Draw the plot to the canvas +void QwtPlotAbstractCanvas::drawCanvas( QPainter* painter ) +{ + QWidget* w = canvasWidget(); + + painter->save(); + + if ( !m_data->styleSheet.borderPath.isEmpty() ) + { + painter->setClipPath( + m_data->styleSheet.borderPath, Qt::IntersectClip ); + } + else + { + if ( borderRadius() > 0.0 ) + { + const QRect frameRect = w->property( "frameRect" ).toRect(); + painter->setClipPath( canvasBorderPath( frameRect ), Qt::IntersectClip ); + } + else + { + painter->setClipRect( w->contentsRect(), Qt::IntersectClip ); + } + } + + QwtPlot* plot = qobject_cast< QwtPlot* >( w->parent() ); + if ( plot ) + plot->drawCanvas( painter ); + + painter->restore(); +} + +//! Update the cached information about the current style sheet +void QwtPlotAbstractCanvas::updateStyleSheetInfo() +{ + QWidget* w = canvasWidget(); + + if ( !w->testAttribute( Qt::WA_StyledBackground ) ) + return; + + QwtStyleSheetRecorder recorder( w->size() ); + + QPainter painter( &recorder ); + + QStyleOption opt; + opt.initFrom(w); + w->style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, w); + + painter.end(); + + m_data->styleSheet.hasBorder = !recorder.border.rectList.isEmpty(); + m_data->styleSheet.cornerRects = recorder.clipRects; + + if ( recorder.background.path.isEmpty() ) + { + if ( !recorder.border.rectList.isEmpty() ) + { + m_data->styleSheet.borderPath = + qwtCombinePathList( w->rect(), recorder.border.pathList ); + } + } + else + { + m_data->styleSheet.borderPath = recorder.background.path; + m_data->styleSheet.background.brush = recorder.background.brush; + m_data->styleSheet.background.origin = recorder.background.origin; + } +} + +//! \return canvas widget +QWidget* QwtPlotAbstractCanvas::canvasWidget() +{ + return m_data->canvasWidget; +} + +//! \return canvas widget +const QWidget* QwtPlotAbstractCanvas::canvasWidget() const +{ + return m_data->canvasWidget; +} + +class QwtPlotAbstractGLCanvas::PrivateData +{ + public: + PrivateData(): + frameStyle( QFrame::Panel | QFrame::Sunken), + lineWidth( 2 ), + midLineWidth( 0 ) + { + } + + QwtPlotAbstractGLCanvas::PaintAttributes paintAttributes; + + int frameStyle; + int lineWidth; + int midLineWidth; +}; + +/*! + \brief Constructor + \param canvasWidget plot canvas widget + */ +QwtPlotAbstractGLCanvas::QwtPlotAbstractGLCanvas( QWidget* canvasWidget ): + QwtPlotAbstractCanvas( canvasWidget ) +{ + m_data = new PrivateData; + + qwtUpdateContentsRect( frameWidth(), canvasWidget ); + m_data->paintAttributes = QwtPlotAbstractGLCanvas::BackingStore; +} + +//! Destructor +QwtPlotAbstractGLCanvas::~QwtPlotAbstractGLCanvas() +{ + delete m_data; +} + +/*! + \brief Changing the paint attributes + + \param attribute Paint attribute + \param on On/Off + + \sa testPaintAttribute() + */ +void QwtPlotAbstractGLCanvas::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( bool( m_data->paintAttributes & attribute ) == on ) + return; + + if ( on ) + { + m_data->paintAttributes |= attribute; + } + else + { + m_data->paintAttributes &= ~attribute; + + if ( attribute == BackingStore ) + clearBackingStore(); + } +} + +/*! + Test whether a paint attribute is enabled + + \param attribute Paint attribute + \return true, when attribute is enabled + \sa setPaintAttribute() + */ +bool QwtPlotAbstractGLCanvas::testPaintAttribute( PaintAttribute attribute ) const +{ + return m_data->paintAttributes & attribute; +} + +/*! + Set the frame style + + \param style The bitwise OR between a shape and a shadow. + + \sa frameStyle(), QFrame::setFrameStyle(), + setFrameShadow(), setFrameShape() + */ +void QwtPlotAbstractGLCanvas::setFrameStyle( int style ) +{ + if ( style != m_data->frameStyle ) + { + m_data->frameStyle = style; + qwtUpdateContentsRect( frameWidth(), canvasWidget() ); + + canvasWidget()->update(); + } +} + +/*! + \return The bitwise OR between a frameShape() and a frameShadow() + \sa setFrameStyle(), QFrame::frameStyle() + */ +int QwtPlotAbstractGLCanvas::frameStyle() const +{ + return m_data->frameStyle; +} + +/*! + Set the frame shadow + + \param shadow Frame shadow + \sa frameShadow(), setFrameShape(), QFrame::setFrameShadow() + */ +void QwtPlotAbstractGLCanvas::setFrameShadow( QFrame::Shadow shadow ) +{ + setFrameStyle( ( m_data->frameStyle & QFrame::Shape_Mask ) | shadow ); +} + +/*! + \return Frame shadow + \sa setFrameShadow(), QFrame::setFrameShadow() + */ +QFrame::Shadow QwtPlotAbstractGLCanvas::frameShadow() const +{ + return (QFrame::Shadow) ( m_data->frameStyle & QFrame::Shadow_Mask ); +} + +/*! + Set the frame shape + + \param shape Frame shape + \sa frameShape(), setFrameShadow(), QFrame::frameShape() + */ +void QwtPlotAbstractGLCanvas::setFrameShape( QFrame::Shape shape ) +{ + setFrameStyle( ( m_data->frameStyle & QFrame::Shadow_Mask ) | shape ); +} + +/*! + \return Frame shape + \sa setFrameShape(), QFrame::frameShape() + */ +QFrame::Shape QwtPlotAbstractGLCanvas::frameShape() const +{ + return (QFrame::Shape) ( m_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 QwtPlotAbstractGLCanvas::setLineWidth( int width ) +{ + width = qMax( width, 0 ); + if ( width != m_data->lineWidth ) + { + m_data->lineWidth = qMax( width, 0 ); + qwtUpdateContentsRect( frameWidth(), canvasWidget() ); + canvasWidget()->update(); + } +} + +/*! + \return Line width of the frame + \sa setLineWidth(), midLineWidth() + */ +int QwtPlotAbstractGLCanvas::lineWidth() const +{ + return m_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 QwtPlotAbstractGLCanvas::setMidLineWidth( int width ) +{ + width = qMax( width, 0 ); + if ( width != m_data->midLineWidth ) + { + m_data->midLineWidth = width; + qwtUpdateContentsRect( frameWidth(), canvasWidget() ); + canvasWidget()->update(); + } +} + +/*! + \return Midline width of the frame + \sa setMidLineWidth(), lineWidth() + */ +int QwtPlotAbstractGLCanvas::midLineWidth() const +{ + return m_data->midLineWidth; +} + +/*! + \return Frame width depending on the style, line width and midline width. + */ +int QwtPlotAbstractGLCanvas::frameWidth() const +{ + return ( frameStyle() != QFrame::NoFrame ) ? m_data->lineWidth : 0; +} + +/*! + Invalidate the paint cache and repaint the canvas + \sa invalidatePaintCache() + */ +void QwtPlotAbstractGLCanvas::replot() +{ + invalidateBackingStore(); + + QWidget* w = canvasWidget(); + if ( testPaintAttribute( QwtPlotAbstractGLCanvas::ImmediatePaint ) ) + w->repaint( w->contentsRect() ); + else + w->update( w->contentsRect() ); +} + +//! \return The rectangle where the frame is drawn in. +QRect QwtPlotAbstractGLCanvas::frameRect() const +{ + const int fw = frameWidth(); + return canvasWidget()->contentsRect().adjusted( -fw, -fw, fw, fw ); +} + +//! Helper function for the derived plot canvas +void QwtPlotAbstractGLCanvas::draw( QPainter* painter ) +{ +#if FIX_GL_TRANSLATION + if ( painter->paintEngine()->type() == QPaintEngine::OpenGL2 ) + { + // work around a translation bug of QPaintEngine::OpenGL2 + painter->translate( 1, 1 ); + } +#endif + + if ( canvasWidget()->testAttribute( Qt::WA_StyledBackground ) ) + drawStyled( painter, true ); + else + drawUnstyled( painter ); + + if ( frameWidth() > 0 ) + drawBorder( painter ); +} diff --git a/libs/qwt/src/qwt_plot_abstract_canvas.h b/libs/qwt/src/qwt_plot_abstract_canvas.h new file mode 100644 index 00000000..58514c4c --- /dev/null +++ b/libs/qwt/src/qwt_plot_abstract_canvas.h @@ -0,0 +1,164 @@ +/****************************************************************************** + * 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_CANVAS_H +#define QWT_PLOT_ABSTRACT_CANVAS_H + +#include "qwt_global.h" +#include + +class QwtPlot; + +/*! + \brief Base class for all type of plot canvases + */ +class QWT_EXPORT QwtPlotAbstractCanvas +{ + public: + /*! + \brief Focus indicator + The default setting is NoFocusIndicator + \sa setFocusIndicator(), focusIndicator(), drawFocusIndicator() + */ + + enum FocusIndicator + { + //! Don't paint a focus indicator + NoFocusIndicator, + + /*! + The focus is related to the complete canvas. + Paint the focus indicator using drawFocusIndicator() + */ + 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 QwtPlotAbstractCanvas( QWidget* canvasWidget ); + virtual ~QwtPlotAbstractCanvas(); + + QwtPlot* plot(); + const QwtPlot* plot() const; + + void setFocusIndicator( FocusIndicator ); + FocusIndicator focusIndicator() const; + + void setBorderRadius( double ); + double borderRadius() const; + + protected: + QWidget* canvasWidget(); + const QWidget* canvasWidget() const; + + virtual void drawFocusIndicator( QPainter* ); + virtual void drawBorder( QPainter* ); + virtual void drawBackground( QPainter* ); + + void fillBackground( QPainter* ); + void drawCanvas( QPainter* ); + void drawStyled( QPainter*, bool ); + void drawUnstyled( QPainter* ); + + QPainterPath canvasBorderPath( const QRect& rect ) const; + void updateStyleSheetInfo(); + + private: + Q_DISABLE_COPY(QwtPlotAbstractCanvas) + + class PrivateData; + PrivateData* m_data; +}; + +/*! + \brief Base class of QwtPlotOpenGLCanvas and QwtPlotGLCanvas + */ +class QWT_EXPORT QwtPlotAbstractGLCanvas : public QwtPlotAbstractCanvas +{ + 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, + + /*! + When ImmediatePaint is set replot() calls repaint() + instead of update(). + + \sa replot(), QWidget::repaint(), QWidget::update() + */ + ImmediatePaint = 8, + }; + + //! Paint attributes + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + explicit QwtPlotAbstractGLCanvas( QWidget* canvasWidget ); + virtual ~QwtPlotAbstractGLCanvas(); + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setFrameStyle( int style ); + int frameStyle() const; + + void setFrameShadow( QFrame::Shadow ); + QFrame::Shadow frameShadow() const; + + void setFrameShape( QFrame::Shape ); + QFrame::Shape frameShape() const; + + void setLineWidth( int ); + int lineWidth() const; + + void setMidLineWidth( int ); + int midLineWidth() const; + + int frameWidth() const; + QRect frameRect() const; + + //! Invalidate the internal backing store + virtual void invalidateBackingStore() = 0; + + protected: + void replot(); + void draw( QPainter* ); + + private: + virtual void clearBackingStore() = 0; + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotAbstractGLCanvas::PaintAttributes ) + +#endif diff --git a/libs/qwt/src/qwt_plot_axis.cpp b/libs/qwt/src/qwt_plot_axis.cpp new file mode 100644 index 00000000..c14c072e --- /dev/null +++ b/libs/qwt/src/qwt_plot_axis.cpp @@ -0,0 +1,749 @@ +/****************************************************************************** + * 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_scale_widget.h" +#include "qwt_scale_map.h" +#include "qwt_scale_div.h" +#include "qwt_scale_engine.h" +#include "qwt_interval.h" + +namespace +{ + class AxisData + { + public: + AxisData() + : isVisible( true ) + , doAutoScale( true ) + , minValue( 0.0 ) + , maxValue( 1000.0 ) + , stepSize( 0.0 ) + , maxMajor( 8 ) + , maxMinor( 5 ) + , isValid( false ) + , scaleEngine( new QwtLinearScaleEngine() ) + , scaleWidget( NULL ) + { + } + + ~AxisData() + { + delete scaleEngine; + } + + 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 ) + { + using namespace QwtAxis; + + m_axisData[YLeft].initWidget( QwtScaleDraw::LeftScale, "QwtPlotAxisYLeft", plot ); + m_axisData[YRight].initWidget( QwtScaleDraw::RightScale, "QwtPlotAxisYRight", plot ); + m_axisData[XTop].initWidget( QwtScaleDraw::TopScale, "QwtPlotAxisXTop", plot ); + m_axisData[XBottom].initWidget( QwtScaleDraw::BottomScale, "QwtPlotAxisXBottom", plot ); + } + + inline AxisData& axisData( QwtAxisId axisId ) + { + return m_axisData[ axisId ]; + } + + inline const AxisData& axisData( QwtAxisId axisId ) const + { + return m_axisData[ axisId ]; + } + + private: + AxisData m_axisData[ QwtAxis::AxisPositions ]; +}; + +void QwtPlot::initAxesData() +{ + m_scaleData = new ScaleData( this ); + + m_scaleData->axisData( QwtAxis::YRight ).isVisible = false; + m_scaleData->axisData( QwtAxis::XTop ).isVisible = false; +} + +void QwtPlot::deleteAxesData() +{ + delete m_scaleData; + m_scaleData = NULL; +} + +/*! + Checks if an axis is valid + + \param axisId axis + \return \c true if the specified axis exists, otherwise \c false + + \note This method is equivalent to QwtAxis::isValid( axisId ) and simply checks + if axisId is one of the values of QwtAxis::Position. It is a placeholder + for future releases, where it will be possible to have a customizable number + of axes ( multiaxes branch ) at each side. + */ +bool QwtPlot::isAxisValid( QwtAxisId axisId ) const +{ + return QwtAxis::isValid( axisId ); +} + +/*! + \return Scale widget of the specified axis, or NULL if axisId is invalid. + \param axisId Axis + */ +const QwtScaleWidget* QwtPlot::axisWidget( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return m_scaleData->axisData( axisId ).scaleWidget; + + return NULL; +} + +/*! + \return Scale widget of the specified axis, or NULL if axisId is invalid. + \param axisId Axis + */ +QwtScaleWidget* QwtPlot::axisWidget( QwtAxisId axisId ) +{ + if ( isAxisValid( axisId ) ) + return m_scaleData->axisData( axisId ).scaleWidget; + + return NULL; +} + +/*! + Change the scale engine for an axis + + \param axisId Axis + \param scaleEngine Scale engine + + \sa axisScaleEngine() + */ +void QwtPlot::setAxisScaleEngine( QwtAxisId axisId, QwtScaleEngine* scaleEngine ) +{ + if ( isAxisValid( axisId ) && scaleEngine != NULL ) + { + AxisData& d = m_scaleData->axisData( axisId ); + + delete d.scaleEngine; + d.scaleEngine = scaleEngine; + + d.scaleWidget->setTransformation( scaleEngine->transformation() ); + + d.isValid = false; + + autoRefresh(); + } +} + +/*! + \param axisId Axis + \return Scale engine for a specific axis + */ +QwtScaleEngine* QwtPlot::axisScaleEngine( QwtAxisId axisId ) +{ + if ( isAxisValid( axisId ) ) + return m_scaleData->axisData( axisId ).scaleEngine; + else + return NULL; +} + +/*! + \param axisId Axis + \return Scale engine for a specific axis + */ +const QwtScaleEngine* QwtPlot::axisScaleEngine( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return m_scaleData->axisData( axisId ).scaleEngine; + else + return NULL; +} +/*! + \return \c True, if autoscaling is enabled + \param axisId Axis + */ +bool QwtPlot::axisAutoScale( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return m_scaleData->axisData( axisId ).doAutoScale; + else + return false; +} + +/*! + \return \c True, if a specified axis is visible + \param axisId Axis + */ +bool QwtPlot::isAxisVisible( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return m_scaleData->axisData( axisId ).isVisible; + else + return false; +} + +/*! + \return The font of the scale labels for a specified axis + \param axisId Axis + */ +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 axisId Axis + \sa setAxisMaxMajor(), QwtScaleEngine::divideScale() + */ +int QwtPlot::axisMaxMajor( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return m_scaleData->axisData( axisId ).maxMajor; + else + return 0; +} + +/*! + \return the maximum number of minor ticks for a specified axis + \param axisId Axis + \sa setAxisMaxMinor(), QwtScaleEngine::divideScale() + */ +int QwtPlot::axisMaxMinor( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return m_scaleData->axisData( axisId ).maxMinor; + else + return 0; +} + +/*! + \brief Return the scale division of a specified axis + + axisScaleDiv(axisId).lowerBound(), axisScaleDiv(axisId).upperBound() + are the current limits of the axis scale. + + \param axisId Axis + \return Scale division + + \sa QwtScaleDiv, setAxisScaleDiv(), QwtScaleEngine::divideScale() + */ +const QwtScaleDiv& QwtPlot::axisScaleDiv( QwtAxisId axisId ) const +{ + return m_scaleData->axisData( axisId ).scaleDiv; +} + +/*! + \brief Return the scale draw of a specified axis + + \param axisId Axis + \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 axisId Axis + \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 axisId Axis + \return step size parameter value + + \sa setAxisScale(), QwtScaleEngine::divideScale() + */ +double QwtPlot::axisStepSize( QwtAxisId axisId ) const +{ + if ( !isAxisValid( axisId ) ) + return 0; + + return m_scaleData->axisData( axisId ).stepSize; +} + +/*! + \brief Return the current interval of the specified axis + + This is only a convenience function for axisScaleDiv( axisId )->interval(); + + \param axisId Axis + \return Scale interval + + \sa QwtScaleDiv, axisScaleDiv() + */ +QwtInterval QwtPlot::axisInterval( QwtAxisId axisId ) const +{ + if ( !isAxisValid( axisId ) ) + return QwtInterval(); + + return m_scaleData->axisData( axisId ).scaleDiv.interval(); +} + +/*! + \return Title of a specified axis + \param axisId Axis + */ +QwtText QwtPlot::axisTitle( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return axisWidget( axisId )->title(); + else + return QwtText(); +} + +/*! + \brief Hide or show a specified axis + + Curves, markers and other items can be attached + to hidden axes, and transformation of screen coordinates + into values works as normal. + + Only QwtAxis::XBottom and QwtAxis::YLeft are enabled by default. + + \param axisId Axis + \param on \c true (visible) or \c false (hidden) + */ +void QwtPlot::setAxisVisible( QwtAxisId axisId, bool on ) +{ + if ( isAxisValid( axisId ) && on != m_scaleData->axisData( axisId ).isVisible ) + { + m_scaleData->axisData( axisId ).isVisible = on; + updateLayout(); + } +} + +/*! + Transform the x or y coordinate of a position in the + drawing region into a value. + + \param axisId Axis + \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 axisId Axis + \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 axisId Axis + \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 axisId Axis + \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 ) && ( m_scaleData->axisData( axisId ).doAutoScale != on ) ) + { + m_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 axisId Axis + \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 ) ) + { + AxisData& d = m_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 axisId Axis + \param scaleDiv Scale division + + \sa setAxisScale(), setAxisAutoScale() + */ +void QwtPlot::setAxisScaleDiv( QwtAxisId axisId, const QwtScaleDiv& scaleDiv ) +{ + if ( isAxisValid( axisId ) ) + { + AxisData& d = m_scaleData->axisData( axisId ); + + d.doAutoScale = false; + d.scaleDiv = scaleDiv; + d.isValid = true; + + autoRefresh(); + } +} + +/*! + \brief Set a scale draw + + \param axisId Axis + \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 axisId Axis + \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 axisId Axis + \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 axisId Axis + \param maxMinor Maximum number of minor steps + + \sa axisMaxMinor() + */ +void QwtPlot::setAxisMaxMinor( QwtAxisId axisId, int maxMinor ) +{ + if ( isAxisValid( axisId ) ) + { + maxMinor = qBound( 0, maxMinor, 100 ); + + AxisData& d = m_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 axisId Axis + \param maxMajor Maximum number of major steps + + \sa axisMaxMajor() + */ +void QwtPlot::setAxisMaxMajor( QwtAxisId axisId, int maxMajor ) +{ + if ( isAxisValid( axisId ) ) + { + maxMajor = qBound( 1, maxMajor, 10000 ); + + AxisData& d = m_scaleData->axisData( axisId ); + if ( maxMajor != d.maxMajor ) + { + d.maxMajor = maxMajor; + d.isValid = false; + autoRefresh(); + } + } +} + +/*! + \brief Change the title of a specified axis + + \param axisId Axis + \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 axisId Axis + \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 boundingIntervals[QwtAxis::AxisPositions]; + + 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 ) + boundingIntervals[item->xAxis()] |= QwtInterval( rect.left(), rect.right() ); + + if ( rect.height() >= 0.0 ) + boundingIntervals[item->yAxis()] |= QwtInterval( rect.top(), rect.bottom() ); + } + } + + // Adjust scales + + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + AxisData& d = m_scaleData->axisData( axisId ); + + double minValue = d.minValue; + double maxValue = d.maxValue; + double stepSize = d.stepSize; + + const QwtInterval& interval = boundingIntervals[axisId]; + + if ( d.doAutoScale && interval.isValid() ) + { + d.isValid = false; + + minValue = interval.minValue(); + maxValue = interval.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/libs/qwt/src/qwt_plot_barchart.cpp b/libs/qwt/src/qwt_plot_barchart.cpp new file mode 100644 index 00000000..b5f0ffee --- /dev/null +++ b/libs/qwt/src/qwt_plot_barchart.cpp @@ -0,0 +1,484 @@ +/****************************************************************************** + * 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_text.h" +#include "qwt_graphic.h" +#include "qwt_legend_data.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 m_data; +} + +void QwtPlotBarChart::init() +{ + m_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< QPointF >& 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< double >& samples ) +{ + QVector< QPointF > points; + points.reserve( samples.size() ); + + 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< QPointF >* 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 != m_data->symbol ) + { + delete m_data->symbol; + m_data->symbol = symbol; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() + */ +const QwtColumnSymbol* QwtPlotBarChart::symbol() const +{ + return m_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 != m_data->legendMode ) + { + m_data->legendMode = mode; + legendChanged(); + } +} + +/*! + \return Legend mode + \sa setLegendMode() + */ +QwtPlotBarChart::LegendMode QwtPlotBarChart::legendMode() const +{ + return m_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(); + + QRectF rect = QwtPlotSeriesItem::boundingRect(); + if ( rect.height() >= 0 ) + { + const double baseLine = baseline(); + + if ( rect.bottom() < baseLine ) + rect.setBottom( baseLine ); + + if ( rect.top() > baseLine ) + rect.setTop( baseLine ); + } + + if ( 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(); +} + +/*! + Calculate the geometry of a bar in widget coordinates + + \param xMap x map + \param yMap y map + \param canvasRect Contents rect of the canvas + \param boundingInterval Bounding interval of sample values + \param sample Value of the sample + + \return Geometry of the column + */ +QwtColumnRect QwtPlotBarChart::columnRect( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, const QwtInterval& boundingInterval, + 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(); + } + + return barRect; +} + +/*! + 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 +{ + const QwtColumnRect barRect = columnRect( xMap, yMap, + canvasRect, boundingInterval, sample ); + + 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 = m_data->symbol; + + if ( sym ) + { + sym->draw( painter, rect ); + } + else + { + // we build a temporary default symbol + QwtColumnSymbol columnSymbol( QwtColumnSymbol::Box ); + columnSymbol.setLineWidth( 1 ); + columnSymbol.setFrameStyle( QwtColumnSymbol::Plain ); + columnSymbol.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< QwtLegendData > QwtPlotBarChart::legendData() const +{ + QList< QwtLegendData > list; + + if ( m_data->legendMode == LegendBarTitles ) + { + const size_t numSamples = dataSize(); + list.reserve( numSamples ); + + for ( size_t i = 0; i < numSamples; i++ ) + { + QwtLegendData data; + + data.setValue( QwtLegendData::TitleRole, + QVariant::fromValue( barTitle( i ) ) ); + + if ( !legendIconSize().isEmpty() ) + { + data.setValue( QwtLegendData::IconRole, + QVariant::fromValue( legendIcon( i, legendIconSize() ) ) ); + } + + 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 ( m_data->legendMode == QwtPlotBarChart::LegendBarTitles ) + barIndex = index; + + drawBar( &painter, barIndex, QPointF(), column ); + + return icon; +} diff --git a/libs/qwt/src/qwt_plot_barchart.h b/libs/qwt/src/qwt_plot_barchart.h new file mode 100644 index 00000000..1ca9a005 --- /dev/null +++ b/libs/qwt/src/qwt_plot_barchart.h @@ -0,0 +1,124 @@ +/****************************************************************************** + * 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" + +class QwtColumnRect; +class QwtColumnSymbol; +template< typename T > class QwtSeriesData; + +/*! + \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< QPointF > +{ + 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() ); + explicit QwtPlotBarChart( const QwtText& title ); + + virtual ~QwtPlotBarChart(); + + virtual int rtti() const QWT_OVERRIDE; + + void setSamples( const QVector< QPointF >& ); + void setSamples( const QVector< double >& ); + void setSamples( QwtSeriesData< QPointF >* ); + + void setSymbol( QwtColumnSymbol* ); + const QwtColumnSymbol* symbol() const; + + void setLegendMode( LegendMode ); + LegendMode legendMode() const; + + virtual void drawSeries( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const QWT_OVERRIDE; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + 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& sample, + const QwtColumnRect& ) const; + + QwtColumnRect columnRect( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, const QwtInterval& boundingInterval, + const QPointF& sample ) const; + + QList< QwtLegendData > legendData() const QWT_OVERRIDE; + QwtGraphic legendIcon( int index, const QSizeF& ) const QWT_OVERRIDE; + + private: + void init(); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_canvas.cpp b/libs/qwt/src/qwt_plot_canvas.cpp new file mode 100644 index 00000000..64422bfb --- /dev/null +++ b/libs/qwt/src/qwt_plot_canvas.cpp @@ -0,0 +1,327 @@ +/****************************************************************************** + * 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_plot.h" + +#include +#include +#include + +class QwtPlotCanvas::PrivateData +{ + public: + PrivateData() + : backingStore( NULL ) + { + } + + ~PrivateData() + { + delete backingStore; + } + + QwtPlotCanvas::PaintAttributes paintAttributes; + QPixmap* backingStore; +}; + +/*! + \brief Constructor + + \param plot Parent plot widget + \sa QwtPlot::setCanvas() + */ +QwtPlotCanvas::QwtPlotCanvas( QwtPlot* plot ) + : QFrame( plot ) + , QwtPlotAbstractCanvas( this ) +{ + m_data = new PrivateData; + + setPaintAttribute( QwtPlotCanvas::BackingStore, true ); + setPaintAttribute( QwtPlotCanvas::Opaque, true ); + setPaintAttribute( QwtPlotCanvas::HackStyledBackground, true ); + + setLineWidth( 2 ); + setFrameShadow( QFrame::Sunken ); + setFrameShape( QFrame::Panel ); +} + +//! Destructor +QwtPlotCanvas::~QwtPlotCanvas() +{ + delete m_data; +} + +/*! + \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( m_data->paintAttributes & attribute ) == on ) + return; + + if ( on ) + m_data->paintAttributes |= attribute; + else + m_data->paintAttributes &= ~attribute; + + switch ( attribute ) + { + case BackingStore: + { + if ( on ) + { + if ( m_data->backingStore == NULL ) + m_data->backingStore = new QPixmap(); + + if ( isVisible() ) + { +#if QT_VERSION >= 0x050000 + *m_data->backingStore = grab( rect() ); +#else + *m_data->backingStore = + QPixmap::grabWidget( this, rect() ); +#endif + } + } + else + { + delete m_data->backingStore; + m_data->backingStore = NULL; + } + break; + } + case Opaque: + { + if ( on ) + setAttribute( Qt::WA_OpaquePaintEvent, true ); + + break; + } + default: + { + 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 m_data->paintAttributes & attribute; +} + +//! \return Backing store, might be null +const QPixmap* QwtPlotCanvas::backingStore() const +{ + return m_data->backingStore; +} + +//! Invalidate the internal backing store +void QwtPlotCanvas::invalidateBackingStore() +{ + if ( m_data->backingStore ) + *m_data->backingStore = QPixmap(); +} + +/*! + 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 ) && + m_data->backingStore != NULL ) + { + QPixmap& bs = *m_data->backingStore; + if ( bs.size() != size() * QwtPainter::devicePixelRatio( &bs ) ) + { + bs = QwtPainter::backingStore( this, size() ); + + if ( testAttribute(Qt::WA_StyledBackground) ) + { + QPainter p( &bs ); + drawStyled( &p, testPaintAttribute( HackStyledBackground ) ); + } + else + { + QPainter p; + if ( borderRadius() <= 0.0 ) + { + QwtPainter::fillPixmap( this, bs ); + p.begin( &bs ); + drawCanvas( &p ); + } + else + { + p.begin( &bs ); + drawUnstyled( &p ); + } + + if ( frameWidth() > 0 ) + drawBorder( &p ); + } + } + + painter.drawPixmap( 0, 0, *m_data->backingStore ); + } + else + { + if ( testAttribute(Qt::WA_StyledBackground ) ) + { + if ( testAttribute( Qt::WA_OpaquePaintEvent ) ) + { + drawStyled( &painter, testPaintAttribute( HackStyledBackground ) ); + } + else + { + drawCanvas( &painter ); + } + } + else + { + if ( testAttribute( Qt::WA_OpaquePaintEvent ) ) + { + if ( autoFillBackground() ) + { + fillBackground( &painter ); + drawBackground( &painter ); + } + } + else + { + if ( borderRadius() > 0.0 ) + { + QPainterPath clipPath; + clipPath.addRect( rect() ); + clipPath = clipPath.subtracted( borderPath( rect() ) ); + + painter.save(); + + painter.setClipPath( clipPath, Qt::IntersectClip ); + fillBackground( &painter ); + drawBackground( &painter ); + + painter.restore(); + } + } + + drawCanvas( &painter ); + + if ( frameWidth() > 0 ) + drawBorder( &painter ); + } + } + + if ( hasFocus() && focusIndicator() == CanvasFocusIndicator ) + drawFocusIndicator( &painter ); +} + +/*! + Draw the border of the plot canvas + + \param painter Painter + \sa setBorderRadius() + */ +void QwtPlotCanvas::drawBorder( QPainter* painter ) +{ + if ( borderRadius() <= 0 ) + { + drawFrame( painter ); + return; + } + + QwtPlotAbstractCanvas::drawBorder( painter ); +} + +/*! + Resize event + \param event Resize event + */ +void QwtPlotCanvas::resizeEvent( QResizeEvent* event ) +{ + QFrame::resizeEvent( event ); + updateStyleSheetInfo(); +} + +/*! + Invalidate the paint cache and repaint the canvas + \sa invalidatePaintCache() + */ +void QwtPlotCanvas::replot() +{ + invalidateBackingStore(); + + if ( testPaintAttribute( QwtPlotCanvas::ImmediatePaint ) ) + repaint( contentsRect() ); + else + update( contentsRect() ); +} + +/*! + 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 +{ + return canvasBorderPath( rect ); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_plot_canvas.cpp" +#endif diff --git a/libs/qwt/src/qwt_plot_canvas.h b/libs/qwt/src/qwt_plot_canvas.h new file mode 100644 index 00000000..b13336dc --- /dev/null +++ b/libs/qwt/src/qwt_plot_canvas.h @@ -0,0 +1,133 @@ +/****************************************************************************** + * 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 "qwt_plot_abstract_canvas.h" + +#include + +class QwtPlot; +class QPixmap; +class QPainterPath; + +/*! + \brief Canvas of a QwtPlot. + + Canvas is the widget where all plot items are displayed + + \sa QwtPlot::setCanvas(), QwtPlotGLCanvas, QwtPlotOpenGLCanvas + */ +class QWT_EXPORT QwtPlotCanvas : public QFrame, public QwtPlotAbstractCanvas +{ + 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 + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + explicit QwtPlotCanvas( QwtPlot* = NULL ); + virtual ~QwtPlotCanvas(); + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + const QPixmap* backingStore() const; + Q_INVOKABLE void invalidateBackingStore(); + + virtual bool event( QEvent* ) QWT_OVERRIDE; + + Q_INVOKABLE QPainterPath borderPath( const QRect& ) const; + + public Q_SLOTS: + void replot(); + + protected: + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + virtual void resizeEvent( QResizeEvent* ) QWT_OVERRIDE; + + virtual void drawBorder( QPainter* ) QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCanvas::PaintAttributes ) + +#endif diff --git a/libs/qwt/src/qwt_plot_curve.cpp b/libs/qwt/src/qwt_plot_curve.cpp new file mode 100644 index 00000000..7abd4e4b --- /dev/null +++ b/libs/qwt/src/qwt_plot_curve.cpp @@ -0,0 +1,1393 @@ +/****************************************************************************** + * 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_spline_curve_fitter.h" +#include "qwt_symbol.h" +#include "qwt_point_mapper.h" +#include "qwt_text.h" +#include "qwt_graphic.h" + +#include +#include + +static inline QRectF qwtIntersectedClipRect( const QRectF& rect, QPainter* painter ) +{ + QRectF clipRect = rect; + if ( painter->hasClipping() ) + clipRect &= painter->clipBoundingRect(); + + return clipRect; +} + +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 = qwtCeil( 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 ) + , pen( Qt::black ) + , paintAttributes( QwtPlotCurve::ClipPolygons | QwtPlotCurve::FilterPoints ) + { + 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 m_data; +} + +//! Initialize internal members +void QwtPlotCurve::init() +{ + setItemAttribute( QwtPlotItem::Legend ); + setItemAttribute( QwtPlotItem::AutoScale ); + + m_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 ) + m_data->paintAttributes |= attribute; + else + m_data->paintAttributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa setPaintAttribute() + */ +bool QwtPlotCurve::testPaintAttribute( PaintAttribute attribute ) const +{ + return ( m_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 ) + m_data->legendAttributes |= attribute; + else + m_data->legendAttributes &= ~attribute; + + qwtUpdateLegendIconSize( this ); + legendChanged(); + } +} + +/*! + \return True, when attribute is enabled + \sa setLegendAttribute() + */ +bool QwtPlotCurve::testLegendAttribute( LegendAttribute attribute ) const +{ + return ( m_data->legendAttributes & attribute ); +} + +/*! + Specify the attributes how to draw the legend icon + + \param attributes Attributes + /sa setLegendAttribute(). legendIcon() + */ +void QwtPlotCurve::setLegendAttributes( LegendAttributes attributes ) +{ + if ( attributes != m_data->legendAttributes ) + { + m_data->legendAttributes = attributes; + + qwtUpdateLegendIconSize( this ); + legendChanged(); + } +} + +/*! + \return Attributes how to draw the legend icon + \sa setLegendAttributes(), testLegendAttribute() + */ +QwtPlotCurve::LegendAttributes QwtPlotCurve::legendAttributes() const +{ + return m_data->legendAttributes; +} + +/*! + Set the curve's drawing style + + \param style Curve style + \sa style() + */ +void QwtPlotCurve::setStyle( CurveStyle style ) +{ + if ( style != m_data->style ) + { + m_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Style of the curve + \sa setStyle() + */ +QwtPlotCurve::CurveStyle QwtPlotCurve::style() const +{ + return m_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 != m_data->symbol ) + { + delete m_data->symbol; + m_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 m_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 != m_data->pen ) + { + m_data->pen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Pen used to draw the lines + \sa setPen(), brush() + */ +const QPen& QwtPlotCurve::pen() const +{ + return m_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 != m_data->brush ) + { + m_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 m_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( m_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, m_data->style, xMap, yMap, canvasRect, from, to ); + painter->restore(); + + if ( m_data->symbol && + ( m_data->symbol->style() != QwtSymbol::NoSymbol ) ) + { + painter->save(); + drawSymbols( painter, *m_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 doFit = ( m_data->attributes & Fitted ) && m_data->curveFitter; + const bool doAlign = !doFit && QwtPainter::roundingAlignment( painter ); + const bool doFill = ( m_data->brush.style() != Qt::NoBrush ) + && ( m_data->brush.color().alpha() > 0 ); + + QRectF clipRect; + if ( m_data->paintAttributes & ClipPolygons ) + { + clipRect = qwtIntersectedClipRect( canvasRect, painter ); + + const qreal pw = QwtPainter::effectivePenWidth( painter->pen() ); + clipRect = clipRect.adjusted(-pw, -pw, pw, pw); + } + + QwtPointMapper mapper; + + if ( doAlign ) + { + mapper.setFlag( QwtPointMapper::RoundPoints, true ); + mapper.setFlag( QwtPointMapper::WeedOutIntermediatePoints, + testPaintAttribute( FilterPointsAggressive ) ); + } + + mapper.setFlag( QwtPointMapper::WeedOutPoints, + testPaintAttribute( FilterPoints ) || + testPaintAttribute( FilterPointsAggressive ) ); + + mapper.setBoundingRect( canvasRect ); + + QPolygonF polyline = mapper.toPolygonF( xMap, yMap, data(), from, to ); + + if ( doFill ) + { + if ( doFit ) + { + // it might be better to extend and draw the curvePath, but for + // the moment we keep an implementation, where we translate the + // path back to a polyline. + + polyline = m_data->curveFitter->fitCurve( polyline ); + } + + if ( painter->pen().style() != Qt::NoPen ) + { + // here we are wasting memory for the filled copy, + // do polygon clipping twice etc .. TODO + + QPolygonF filled = polyline; + fillCurve( painter, xMap, yMap, canvasRect, filled ); + filled.clear(); + + if ( m_data->paintAttributes & ClipPolygons ) + QwtClipper::clipPolygonF( clipRect, polyline, false ); + + QwtPainter::drawPolyline( painter, polyline ); + } + else + { + fillCurve( painter, xMap, yMap, canvasRect, polyline ); + } + } + else + { + if ( testPaintAttribute( ClipPolygons ) ) + { + QwtClipper::clipPolygonF( clipRect, polyline, false ); + } + + if ( doFit ) + { + if ( m_data->curveFitter->mode() == QwtCurveFitter::Path ) + { + const QPainterPath curvePath = + m_data->curveFitter->fitCurvePath( polyline ); + + painter->drawPath( curvePath ); + } + else + { + polyline = m_data->curveFitter->fitCurve( polyline ); + QwtPainter::drawPolyline( painter, polyline ); + } + } + else + { + QwtPainter::drawPolyline( painter, 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& canvasRect, int from, int to ) const +{ + Q_UNUSED( canvasRect ) + + painter->save(); + painter->setRenderHint( QPainter::Antialiasing, false ); + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + double x0 = xMap.transform( m_data->baseline ); + double y0 = yMap.transform( m_data->baseline ); + if ( doAlign ) + { + x0 = qRound( x0 ); + y0 = qRound( y0 ); + } + + const Qt::Orientation o = orientation(); + + const QwtSeriesData< QPointF >* 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 = ( m_data->brush.style() != Qt::NoBrush ) + && ( m_data->brush.color().alpha() > 0 ); + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + QwtPointMapper mapper; + mapper.setBoundingRect( canvasRect ); + mapper.setFlag( QwtPointMapper::RoundPoints, doAlign ); + + if ( m_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.toPolygonF( + xMap, yMap, data(), from, to ); + + QwtPainter::drawPoints( painter, points ); + fillCurve( painter, xMap, yMap, canvasRect, points ); + } + else if ( m_data->paintAttributes & ImageBuffer ) + { + const QImage image = mapper.toImage( xMap, yMap, + data(), from, to, m_data->pen, + painter->testRenderHint( QPainter::Antialiasing ), + renderThreadCount() ); + + painter->drawImage( canvasRect.toAlignedRect(), image ); + } + else if ( m_data->paintAttributes & MinimizeMemory ) + { + const QwtSeriesData< QPointF >* 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 ( m_data->attributes & Inverted ) + inverted = !inverted; + + const QwtSeriesData< QPointF >* 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 ( m_data->paintAttributes & ClipPolygons ) + { + QRectF clipRect = qwtIntersectedClipRect( canvasRect, painter ); + + const qreal pw = QwtPainter::effectivePenWidth( painter->pen() ); + clipRect = clipRect.adjusted(-pw, -pw, pw, pw); + + const QPolygonF clipped = QwtClipper::clippedPolygonF( + clipRect, polygon, false ); + + QwtPainter::drawPolyline( painter, clipped ); + } + else + { + QwtPainter::drawPolyline( painter, polygon ); + } + + if ( m_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( m_data->attributes & attribute ) == on ) + return; + + if ( on ) + m_data->attributes |= attribute; + else + m_data->attributes &= ~attribute; + + itemChanged(); +} + +/*! + \return true, if attribute is enabled + \sa setCurveAttribute() + */ +bool QwtPlotCurve::testCurveAttribute( CurveAttribute attribute ) const +{ + return m_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 m_data->curveFitter; + m_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 m_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 ( m_data->brush.style() == Qt::NoBrush ) + return; + + closePolyline( painter, xMap, yMap, polygon ); + if ( polygon.count() <= 2 ) // a line can't be filled + return; + + QBrush brush = m_data->brush; + if ( !brush.color().isValid() ) + brush.setColor( m_data->pen.color() ); + + if ( m_data->paintAttributes & ClipPolygons ) + { + const QRectF clipRect = qwtIntersectedClipRect( canvasRect, painter ); + QwtClipper::clipPolygonF( clipRect, 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 = m_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 ) ); + + const QRectF clipRect = qwtIntersectedClipRect( canvasRect, painter ); + mapper.setBoundingRect( clipRect ); + + 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::Vertical, the baseline is interpreted as a horizontal line + at y = baseline(), with Qt::Horizontal, 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 ( m_data->baseline != value ) + { + m_data->baseline = value; + itemChanged(); + } +} + +/*! + \return Value of the baseline + \sa setBaseline() + */ +double QwtPlotCurve::baseline() const +{ + return m_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 QPointF& pos, double* dist ) const +{ + const size_t numSamples = dataSize(); + + if ( plot() == NULL || numSamples <= 0 ) + return -1; + + const QwtSeriesData< QPointF >* 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 = std::sqrt( 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 ( m_data->legendAttributes == 0 || + m_data->legendAttributes & QwtPlotCurve::LegendShowBrush ) + { + QBrush brush = m_data->brush; + + if ( brush.style() == Qt::NoBrush && + m_data->legendAttributes == 0 ) + { + if ( style() != QwtPlotCurve::NoCurve ) + { + brush = QBrush( pen().color() ); + } + else if ( m_data->symbol && + ( m_data->symbol->style() != QwtSymbol::NoSymbol ) ) + { + brush = QBrush( m_data->symbol->pen().color() ); + } + } + + if ( brush.style() != Qt::NoBrush ) + { + QRectF r( 0, 0, size.width(), size.height() ); + painter.fillRect( r, brush ); + } + } + + if ( m_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 ( m_data->legendAttributes & QwtPlotCurve::LegendShowSymbol ) + { + if ( m_data->symbol ) + { + QRectF r( 0, 0, size.width(), size.height() ); + m_data->symbol->drawSymbol( &painter, r ); + } + } + + return graphic; +} + +/*! + 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< QPointF >* data ) +{ + setData( data ); +} + +/*! + 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< QPointF >& samples ) +{ + setData( new QwtPointSeriesData( samples ) ); +} + +/*! + \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< double >( xData, yData, size ) ); +} + +/*! + \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 float* xData, const float* yData, int size ) +{ + setData( new QwtCPointerData< float >( xData, yData, size ) ); +} + +/*! + \brief Initialize the data by pointing to a memory block which + is not managed by QwtPlotCurve. + + The memory contains the y coordinates, while the index is + interpreted as x coordinate. + + setRawSamples() is provided for efficiency. It is important to + keep the pointers during the lifetime of the underlying + QwtCPointerValueData class. + + \param yData pointer to y data + \param size size of x and y + + \sa QwtCPointerData + */ +void QwtPlotCurve::setRawSamples( const double* yData, int size ) +{ + setData( new QwtCPointerValueData< double >( yData, size ) ); +} + +/*! + \brief Initialize the data by pointing to memory blocks which + are not managed by QwtPlotCurve. + + The memory contains the y coordinates, while the index is + interpreted as x coordinate. + + setRawSamples() is provided for efficiency. It is important to + keep the pointers during the lifetime of the underlying + QwtCPointerValueData class. + + \param yData pointer to y data + \param size size of x and y + + \sa QwtCPointerData + */ +void QwtPlotCurve::setRawSamples( const float* yData, int size ) +{ + setData( new QwtCPointerValueData< float >( 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< double >( 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 float* xData, const float* yData, int size ) +{ + setData( new QwtPointArrayData< float >( 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< double >& xData, + const QVector< double >& yData ) +{ + setData( new QwtPointArrayData< double >( xData, yData ) ); +} + +/*! + \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< float >& xData, + const QVector< float >& yData ) +{ + setData( new QwtPointArrayData< float >( xData, yData ) ); +} + +/*! + Set data by copying y-values from a specified memory block. + + The memory contains the y coordinates, while the index is + interpreted as x coordinate. + + \param yData y data + \param size size of yData + + \sa QwtValuePointData + */ +void QwtPlotCurve::setSamples( const double* yData, int size ) +{ + setData( new QwtValuePointData< double >( yData, size ) ); +} + +/*! + Set data by copying y-values from a specified memory block. + + The vector contains the y coordinates, while the index is + interpreted as x coordinate. + + \param yData y data + \param size size of yData + + \sa QwtValuePointData + */ +void QwtPlotCurve::setSamples( const float* yData, int size ) +{ + setData( new QwtValuePointData< float >( yData, size ) ); +} + +/*! + Initialize data with an array of y values (explicitly shared) + + The vector contains the y coordinates, while the index is + the x coordinate. + + \param yData y data + + \sa QwtValuePointData + */ +void QwtPlotCurve::setSamples( const QVector< double >& yData ) +{ + setData( new QwtValuePointData< double >( yData ) ); +} + +/*! + Initialize data with an array of y values (explicitly shared) + + The vector contains the y coordinates, while the index is + the x coordinate. + + \param yData y data + + \sa QwtValuePointData + */ +void QwtPlotCurve::setSamples( const QVector< float >& yData ) +{ + setData( new QwtValuePointData< float >( yData ) ); +} diff --git a/libs/qwt/src/qwt_plot_curve.h b/libs/qwt/src/qwt_plot_curve.h new file mode 100644 index 00000000..d9e47283 --- /dev/null +++ b/libs/qwt/src/qwt_plot_curve.h @@ -0,0 +1,376 @@ +/****************************************************************************** + * 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 + +class QwtScaleMap; +class QwtSymbol; +class QwtCurveFitter; +template< typename T > class QwtSeriesData; +class QwtText; +class QPainter; +class QPolygonF; +class QPen; + +/*! + \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< QPointF > +{ + 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 + }; + + Q_DECLARE_FLAGS( CurveAttributes, CurveAttribute ) + + /*! + 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 + }; + + Q_DECLARE_FLAGS( LegendAttributes, LegendAttribute ) + + /*! + 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, + + /*! + More aggressive point filtering trying to filter out + intermediate points, accepting minor visual differences. + + Has only an effect, when drawing the curve to a paint device + in integer coordinates ( f.e. all widgets on screen ) using the fact, + that consecutive points are often mapped to the same x or y coordinate. + Each chunk of samples mapped to the same coordinate can be reduced to + 4 points ( first, min, max last ). + + In the worst case the polygon to be rendered will be 4 times the width + of the plot canvas. + + The algorithm is very fast and effective for huge datasets, and can be used + inside a replot cycle. + + \note Implemented for QwtPlotCurve::Lines only + \note As this algo replaces many small lines by a long one + a nasty bug of the raster paint engine ( Qt 4.8, Qt 5.1 - 5.3 ) + becomes more dominant. For these versions the bug can be + worked around by enabling the QwtPainter::polylineSplitting() mode. + */ + FilterPointsAggressive = 0x10, + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + explicit QwtPlotCurve( const QString& title = QString() ); + explicit QwtPlotCurve( const QwtText& title ); + + virtual ~QwtPlotCurve(); + + virtual int rtti() const QWT_OVERRIDE; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setLegendAttribute( LegendAttribute, bool on = true ); + bool testLegendAttribute( LegendAttribute ) const; + + void setLegendAttributes( LegendAttributes ); + LegendAttributes legendAttributes() const; + + void setRawSamples( const double* xData, const double* yData, int size ); + void setRawSamples( const float* xData, const float* yData, int size ); + + void setRawSamples( const double* yData, int size ); + void setRawSamples( const float* yData, int size ); + + void setSamples( const double* xData, const double* yData, int size ); + void setSamples( const float* xData, const float* yData, int size ); + + void setSamples( const double* yData, int size ); + void setSamples( const float* yData, int size ); + + void setSamples( const QVector< double >& yData ); + void setSamples( const QVector< float >& yData ); + + void setSamples( const QVector< double >& xData, const QVector< double >& yData ); + void setSamples( const QVector< float >& xData, const QVector< float >& yData ); + + void setSamples( const QVector< QPointF >& ); + void setSamples( QwtSeriesData< QPointF >* ); + + virtual int closestPoint( const QPointF& 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 QWT_OVERRIDE; + + virtual QwtGraphic legendIcon( int index, const QSizeF& ) const QWT_OVERRIDE; + + protected: + + void init(); + + virtual void drawCurve( QPainter*, int style, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const; + + virtual void drawSymbols( QPainter*, const QwtSymbol&, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const; + + virtual void drawLines( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const; + + virtual void drawSticks( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const; + + virtual void drawDots( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const; + + virtual void drawSteps( QPainter*, + 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* m_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/libs/qwt/src/qwt_plot_dict.cpp b/libs/qwt/src/qwt_plot_dict.cpp new file mode 100644 index 00000000..a4de2b7a --- /dev/null +++ b/libs/qwt/src/qwt_plot_dict.cpp @@ -0,0 +1,192 @@ +/****************************************************************************** + * 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" +#include + +class QwtPlotDict::PrivateData +{ + public: + + class ItemList : public QList< QwtPlotItem* > + { + public: + void insertItem( QwtPlotItem* item ) + { + if ( item == NULL ) + return; + + QList< QwtPlotItem* >::iterator it = + std::upper_bound( begin(), end(), item, LessZThan() ); + insert( it, item ); + } + + void removeItem( QwtPlotItem* item ) + { + if ( item == NULL ) + return; + + QList< QwtPlotItem* >::iterator it = + std::lower_bound( 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() +{ + m_data = new QwtPlotDict::PrivateData; + m_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, m_data->autoDelete ); + delete m_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 ) +{ + m_data->autoDelete = autoDelete; +} + +/*! + \return true if auto deletion is enabled + \sa setAutoDelete(), insertItem() + */ +bool QwtPlotDict::autoDelete() const +{ + return m_data->autoDelete; +} + +/*! + Insert a plot item + + \param item PlotItem + \sa removeItem() + */ +void QwtPlotDict::insertItem( QwtPlotItem* item ) +{ + m_data->itemList.insertItem( item ); +} + +/*! + Remove a plot item + + \param item PlotItem + \sa insertItem() + */ +void QwtPlotDict::removeItem( QwtPlotItem* item ) +{ + m_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 = m_data->itemList; + QwtPlotItemIterator it = list.constBegin(); + while ( it != list.constEnd() ) + { + 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 m_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 m_data->itemList; + + QwtPlotItemList items; + + PrivateData::ItemList list = m_data->itemList; + for ( QwtPlotItemIterator it = list.constBegin(); it != list.constEnd(); ++it ) + { + QwtPlotItem* item = *it; + if ( item->rtti() == rtti ) + items += item; + } + + return items; +} diff --git a/libs/qwt/src/qwt_plot_dict.h b/libs/qwt/src/qwt_plot_dict.h new file mode 100644 index 00000000..689903cc --- /dev/null +++ b/libs/qwt/src/qwt_plot_dict.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * 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_DICT +#define QWT_PLOT_DICT + +#include "qwt_global.h" +#include "qwt_plot_item.h" + +#include + +typedef QList< QwtPlotItem* > QwtPlotItemList; +typedef QList< QwtPlotItem* >::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* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_directpainter.cpp b/libs/qwt/src/qwt_plot_directpainter.cpp new file mode 100644 index 00000000..6b2c87f4 --- /dev/null +++ b/libs/qwt/src/qwt_plot_directpainter.cpp @@ -0,0 +1,320 @@ +/****************************************************************************** + * 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 + +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() + : hasClipping( false ) + , seriesItem( NULL ) + , from( 0 ) + , to( 0 ) + { + } + + QwtPlotDirectPainter::Attributes attributes; + + bool hasClipping; + QRegion clipRegion; + + QPainter painter; + + QwtPlotSeriesItem* seriesItem; + int from; + int to; +}; + +//! Constructor +QwtPlotDirectPainter::QwtPlotDirectPainter( QObject* parent ) + : QObject( parent ) +{ + m_data = new PrivateData; +} + +//! Destructor +QwtPlotDirectPainter::~QwtPlotDirectPainter() +{ + delete m_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( m_data->attributes & attribute ) != on ) + { + if ( on ) + m_data->attributes |= attribute; + else + m_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 m_data->attributes & attribute; +} + +/*! + En/Disables clipping + + \param enable Enables clipping is true, disable it otherwise + \sa hasClipping(), clipRegion(), setClipRegion() + */ +void QwtPlotDirectPainter::setClipping( bool enable ) +{ + m_data->hasClipping = enable; +} + +/*! + \return true, when clipping is enabled + \sa setClipping(), clipRegion(), setClipRegion() + */ +bool QwtPlotDirectPainter::hasClipping() const +{ + return m_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& region ) +{ + m_data->clipRegion = region; + m_data->hasClipping = true; +} + +/*! + \return Currently set clip region. + \sa setClipRegion(), setClipping(), hasClipping() + */ +QRegion QwtPlotDirectPainter::clipRegion() const +{ + return m_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< QwtPlotCanvas* >( canvas ); + + if ( plotCanvas && qwtHasBackingStore( plotCanvas ) ) + { + QPainter painter( const_cast< QPixmap* >( plotCanvas->backingStore() ) ); + + if ( m_data->hasClipping ) + painter.setClipRegion( m_data->clipRegion ); + + qwtRenderItem( &painter, canvasRect, seriesItem, from, to ); + + painter.end(); + + 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 ( !m_data->painter.isActive() ) + { + reset(); + + m_data->painter.begin( canvas ); + canvas->installEventFilter( this ); + } + + if ( m_data->hasClipping ) + { + m_data->painter.setClipRegion( + QRegion( canvasRect ) & m_data->clipRegion ); + } + else + { + if ( !m_data->painter.hasClipping() ) + m_data->painter.setClipRect( canvasRect ); + } + + qwtRenderItem( &m_data->painter, canvasRect, seriesItem, from, to ); + + if ( m_data->attributes & QwtPlotDirectPainter::AtomicPainter ) + { + reset(); + } + else + { + if ( m_data->hasClipping ) + m_data->painter.setClipping( false ); + } + } + else + { + reset(); + + m_data->seriesItem = seriesItem; + m_data->from = from; + m_data->to = to; + + QRegion clipRegion = canvasRect; + if ( m_data->hasClipping ) + clipRegion &= m_data->clipRegion; + + canvas->installEventFilter( this ); + canvas->repaint(clipRegion); + canvas->removeEventFilter( this ); + + m_data->seriesItem = NULL; + } +} + +//! Close the internal QPainter +void QwtPlotDirectPainter::reset() +{ + if ( m_data->painter.isActive() ) + { + QWidget* w = static_cast< QWidget* >( m_data->painter.device() ); + if ( w ) + w->removeEventFilter( this ); + + m_data->painter.end(); + } +} + +//! Event filter +bool QwtPlotDirectPainter::eventFilter( QObject*, QEvent* event ) +{ + if ( event->type() == QEvent::Paint ) + { + reset(); + + if ( m_data->seriesItem ) + { + const QPaintEvent* pe = static_cast< QPaintEvent* >( event ); + + QWidget* canvas = m_data->seriesItem->plot()->canvas(); + + QPainter painter( canvas ); + painter.setClipRegion( pe->region() ); + + bool doCopyCache = testAttribute( CopyBackingStore ); + + if ( doCopyCache ) + { + QwtPlotCanvas* plotCanvas = + qobject_cast< QwtPlotCanvas* >( canvas ); + if ( plotCanvas ) + { + doCopyCache = qwtHasBackingStore( plotCanvas ); + if ( doCopyCache ) + { + painter.drawPixmap( plotCanvas->rect().topLeft(), + *plotCanvas->backingStore() ); + } + } + } + + if ( !doCopyCache ) + { + qwtRenderItem( &painter, canvas->contentsRect(), + m_data->seriesItem, m_data->from, m_data->to ); + } + + return true; // don't call QwtPlotCanvas::paintEvent() + } + } + + return false; +} diff --git a/libs/qwt/src/qwt_plot_directpainter.h b/libs/qwt/src/qwt_plot_directpainter.h new file mode 100644 index 00000000..3746749b --- /dev/null +++ b/libs/qwt/src/qwt_plot_directpainter.h @@ -0,0 +1,99 @@ +/****************************************************************************** + * 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 + }; + + Q_DECLARE_FLAGS( Attributes, Attribute ) + + explicit 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* ) QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotDirectPainter::Attributes ) + +#endif diff --git a/libs/qwt/src/qwt_plot_glcanvas.cpp b/libs/qwt/src/qwt_plot_glcanvas.cpp new file mode 100644 index 00000000..451e634f --- /dev/null +++ b/libs/qwt/src/qwt_plot_glcanvas.cpp @@ -0,0 +1,240 @@ +/****************************************************************************** + * 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 "qwt_painter.h" + +#include +#include +#include +#include + +namespace +{ + class QwtPlotGLCanvasFormat : public QGLFormat + { + public: + QwtPlotGLCanvasFormat() + : QGLFormat( QGLFormat::defaultFormat() ) + { + setSampleBuffers( true ); + } + }; +} + +class QwtPlotGLCanvas::PrivateData +{ + public: + PrivateData() + : fboDirty( true ) + , fbo( NULL ) + { + } + + ~PrivateData() + { + delete fbo; + } + + bool fboDirty; + QGLFramebufferObject* fbo; +}; + +/*! + \brief Constructor + + \param plot Parent plot widget + \sa QwtPlot::setCanvas() + */ +QwtPlotGLCanvas::QwtPlotGLCanvas( QwtPlot* plot ) + : QGLWidget( QwtPlotGLCanvasFormat(), plot ) + , QwtPlotAbstractGLCanvas( this ) +{ + init(); +} +/*! + \brief Constructor + + \param format OpenGL rendering options + \param plot Parent plot widget + \sa QwtPlot::setCanvas() + */ +QwtPlotGLCanvas::QwtPlotGLCanvas( const QGLFormat& format, QwtPlot* plot ) + : QGLWidget( format, plot ) + , QwtPlotAbstractGLCanvas( this ) +{ + init(); +} + +//! Destructor +QwtPlotGLCanvas::~QwtPlotGLCanvas() +{ + delete m_data; +} + +void QwtPlotGLCanvas::init() +{ + m_data = new PrivateData; + +#if 1 + setAttribute( Qt::WA_OpaquePaintEvent, true ); +#endif + setLineWidth( 2 ); + setFrameShadow( QFrame::Sunken ); + setFrameShape( QFrame::Panel ); +} + +/*! + Paint event + + \param event Paint event + \sa QwtPlot::drawCanvas() + */ +void QwtPlotGLCanvas::paintEvent( QPaintEvent* event ) +{ + QGLWidget::paintEvent( event ); +} + +/*! + 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; +} + +/*! + Invalidate the paint cache and repaint the canvas + \sa invalidatePaintCache() + */ +void QwtPlotGLCanvas::replot() +{ + QwtPlotAbstractGLCanvas::replot(); +} + +//! Invalidate the internal backing store +void QwtPlotGLCanvas::invalidateBackingStore() +{ + m_data->fboDirty = true; +} + +void QwtPlotGLCanvas::clearBackingStore() +{ + delete m_data->fbo; + m_data->fbo = NULL; +} + +/*! + 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 QwtPlotGLCanvas::borderPath( const QRect& rect ) const +{ + return canvasBorderPath( rect ); +} + +//! No operation - reserved for some potential use in the future +void QwtPlotGLCanvas::initializeGL() +{ +} + +//! Paint the plot +void QwtPlotGLCanvas::paintGL() +{ + const bool hasFocusIndicator = + hasFocus() && focusIndicator() == CanvasFocusIndicator; + + QPainter painter; + + if ( testPaintAttribute( QwtPlotGLCanvas::BackingStore ) ) + { + const qreal pixelRatio = QwtPainter::devicePixelRatio( NULL ); + const QRect rect( 0, 0, width() * pixelRatio, height() * pixelRatio ); + + if ( hasFocusIndicator ) + painter.begin( this ); + + if ( m_data->fbo ) + { + if ( m_data->fbo->size() != rect.size() ) + { + delete m_data->fbo; + m_data->fbo = NULL; + } + } + + if ( m_data->fbo == NULL ) + { + QGLFramebufferObjectFormat format; + format.setSamples( 4 ); + format.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + + m_data->fbo = new QGLFramebufferObject( rect.size(), format ); + m_data->fboDirty = true; + } + + if ( m_data->fboDirty ) + { + QPainter fboPainter( m_data->fbo ); + fboPainter.scale( pixelRatio, pixelRatio ); + draw( &fboPainter ); + fboPainter.end(); + + m_data->fboDirty = false; + } + + /* + Why do we have this strange translation - but, anyway + QwtPlotGLCanvas in combination with scaling factor + is not very likely to happen as using QwtPlotOpenGLCanvas + usually makes more sense then. + */ + + QGLFramebufferObject::blitFramebuffer( NULL, + rect.translated( 0, height() - rect.height() ), m_data->fbo, rect ); + } + else + { + painter.begin( this ); + draw( &painter ); + } + + if ( hasFocusIndicator ) + drawFocusIndicator( &painter ); +} + +//! No operation - reserved for some potential use in the future +void QwtPlotGLCanvas::resizeGL( int, int ) +{ + // nothing to do +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_plot_glcanvas.cpp" +#endif diff --git a/libs/qwt/src/qwt_plot_glcanvas.h b/libs/qwt/src/qwt_plot_glcanvas.h new file mode 100644 index 00000000..8796a508 --- /dev/null +++ b/libs/qwt/src/qwt_plot_glcanvas.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_PLOT_GLCANVAS_H +#define QWT_PLOT_GLCANVAS_H + +#include "qwt_global.h" +#include "qwt_plot_abstract_canvas.h" + +#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. + + Since Qt 5.4 QOpenGLWidget is available, that is used by QwtPlotOpenGLCanvas. + + \sa QwtPlot::setCanvas(), QwtPlotCanvas, QwtPlotOpenGLCanvas + + \note With Qt4 you might want to use the QPaintEngine::OpenGL paint engine + ( see QGL::setPreferredPaintEngine() ). On a Linux test system + QPaintEngine::OpenGL2 shows very basic problems like translated + geometries. + + \note Another way for getting hardware accelerated graphics is using + an OpenGL offscreen buffer ( QwtPlotCanvas::OpenGLBuffer ) with QwtPlotCanvas. + Performance is worse, than rendering straight to a QGLWidget, but is usually + better integrated into a desktop application. + */ +class QWT_EXPORT QwtPlotGLCanvas : public QGLWidget, public QwtPlotAbstractGLCanvas +{ + Q_OBJECT + + Q_PROPERTY( QFrame::Shadow frameShadow READ frameShadow WRITE setFrameShadow ) + Q_PROPERTY( QFrame::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 ) + + Q_PROPERTY( double borderRadius READ borderRadius WRITE setBorderRadius ) + + public: + explicit QwtPlotGLCanvas( QwtPlot* = NULL ); + explicit QwtPlotGLCanvas( const QGLFormat&, QwtPlot* = NULL ); + virtual ~QwtPlotGLCanvas(); + + Q_INVOKABLE virtual void invalidateBackingStore() QWT_OVERRIDE; + Q_INVOKABLE QPainterPath borderPath( const QRect& ) const; + + virtual bool event( QEvent* ) QWT_OVERRIDE; + + public Q_SLOTS: + void replot(); + + protected: + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + + virtual void initializeGL() QWT_OVERRIDE; + virtual void paintGL() QWT_OVERRIDE; + virtual void resizeGL( int width, int height ) QWT_OVERRIDE; + + private: + void init(); + virtual void clearBackingStore() QWT_OVERRIDE; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_graphicitem.cpp b/libs/qwt/src/qwt_plot_graphicitem.cpp new file mode 100644 index 00000000..1a310857 --- /dev/null +++ b/libs/qwt/src/qwt_plot_graphicitem.cpp @@ -0,0 +1,136 @@ +/****************************************************************************** + * 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_graphicitem.h" +#include "qwt_scale_map.h" +#include "qwt_painter.h" +#include "qwt_text.h" +#include "qwt_graphic.h" + +class QwtPlotGraphicItem::PrivateData +{ + public: + QRectF boundingRect; + QwtGraphic graphic; +}; + +/*! + \brief Constructor + + Sets the following item attributes: + - QwtPlotItem::AutoScale: true + - QwtPlotItem::Legend: false + + \param title Title + */ +QwtPlotGraphicItem::QwtPlotGraphicItem( const QString& title ) + : QwtPlotItem( QwtText( title ) ) +{ + init(); +} + +/*! + \brief Constructor + + Sets the following item attributes: + - QwtPlotItem::AutoScale: true + - QwtPlotItem::Legend: false + + \param title Title + */ +QwtPlotGraphicItem::QwtPlotGraphicItem( const QwtText& title ) + : QwtPlotItem( title ) +{ + init(); +} + +//! Destructor +QwtPlotGraphicItem::~QwtPlotGraphicItem() +{ + delete m_data; +} + +void QwtPlotGraphicItem::init() +{ + m_data = new PrivateData(); + m_data->boundingRect = QwtPlotItem::boundingRect(); + + setItemAttribute( QwtPlotItem::AutoScale, true ); + setItemAttribute( QwtPlotItem::Legend, false ); + + setZ( 8.0 ); +} + +//! \return QwtPlotItem::Rtti_PlotGraphic +int QwtPlotGraphicItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotGraphic; +} + +/*! + Set the graphic to be displayed + + \param rect Rectangle in plot coordinates + \param graphic Recorded sequence of painter commands + */ +void QwtPlotGraphicItem::setGraphic( + const QRectF& rect, const QwtGraphic& graphic ) +{ + m_data->boundingRect = rect; + m_data->graphic = graphic; + + legendChanged(); + itemChanged(); +} + +/*! + \return Recorded sequence of painter commands + \sa setGraphic() + */ +QwtGraphic QwtPlotGraphicItem::graphic() const +{ + return m_data->graphic; +} + +//! Bounding rectangle of the item +QRectF QwtPlotGraphicItem::boundingRect() const +{ + return m_data->boundingRect; +} + +/*! + Draw the item + + \param painter Painter + \param xMap X-Scale Map + \param yMap Y-Scale Map + \param canvasRect Contents rect of the plot canvas + */ +void QwtPlotGraphicItem::draw( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + if ( m_data->graphic.isEmpty() ) + return; + + QRectF r = QwtScaleMap::transform( xMap, yMap, boundingRect() ); + + if ( !r.intersects( canvasRect ) ) + return; + + if ( QwtPainter::roundingAlignment( painter ) ) + { + r.setLeft ( qRound( r.left() ) ); + r.setRight ( qRound( r.right() ) ); + r.setTop ( qRound( r.top() ) ); + r.setBottom ( qRound( r.bottom() ) ); + } + + m_data->graphic.render( painter, r ); +} diff --git a/libs/qwt/src/qwt_plot_graphicitem.h b/libs/qwt/src/qwt_plot_graphicitem.h new file mode 100644 index 00000000..b8441036 --- /dev/null +++ b/libs/qwt/src/qwt_plot_graphicitem.h @@ -0,0 +1,55 @@ +/****************************************************************************** + * 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_GRAPHIC_ITEM_H +#define QWT_PLOT_GRAPHIC_ITEM_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" + +#include + +/*! + \brief A plot item, which displays + a recorded sequence of QPainter commands + + QwtPlotGraphicItem renders a sequence of recorded painter commands + into a specific plot area. Recording of painter commands can be + done manually by QPainter or e.g. QSvgRenderer. + + \sa QwtPlotShapeItem, QwtPlotSvgItem + */ + +class QWT_EXPORT QwtPlotGraphicItem : public QwtPlotItem +{ + public: + explicit QwtPlotGraphicItem( const QString& title = QString() ); + explicit QwtPlotGraphicItem( const QwtText& title ); + + virtual ~QwtPlotGraphicItem(); + + void setGraphic( const QRectF& rect, const QwtGraphic& ); + QwtGraphic graphic() const; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + virtual void draw( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + virtual int rtti() const QWT_OVERRIDE; + + private: + void init(); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_grid.cpp b/libs/qwt/src/qwt_plot_grid.cpp new file mode 100644 index 00000000..30b6b7d5 --- /dev/null +++ b/libs/qwt/src/qwt_plot_grid.cpp @@ -0,0 +1,443 @@ +/****************************************************************************** + * 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 +#include + +static inline bool qwtFuzzyGreaterOrEqual( double d1, double d2 ) +{ + return ( d1 >= d2 ) || qFuzzyCompare( d1, d2 ); +} + +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" ) ) +{ + m_data = new PrivateData; + + setItemInterest( QwtPlotItem::ScaleInterest, true ); + setZ( 10.0 ); +} + +//! Destructor +QwtPlotGrid::~QwtPlotGrid() +{ + delete m_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 ( m_data->xEnabled != on ) + { + m_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 ( m_data->yEnabled != on ) + { + m_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 ( m_data->xMinEnabled != on ) + { + m_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 ( m_data->yMinEnabled != on ) + { + m_data->yMinEnabled = on; + + legendChanged(); + itemChanged(); + } +} + +/*! + Assign an x axis scale division + + \param scaleDiv Scale division + */ +void QwtPlotGrid::setXDiv( const QwtScaleDiv& scaleDiv ) +{ + if ( m_data->xScaleDiv != scaleDiv ) + { + m_data->xScaleDiv = scaleDiv; + itemChanged(); + } +} + +/*! + Assign a y axis division + + \param scaleDiv Scale division + */ +void QwtPlotGrid::setYDiv( const QwtScaleDiv& scaleDiv ) +{ + if ( m_data->yScaleDiv != scaleDiv ) + { + m_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 ( m_data->majorPen != pen || m_data->minorPen != pen ) + { + m_data->majorPen = pen; + m_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 ( m_data->majorPen != pen ) + { + m_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 ( m_data->minorPen != pen ) + { + m_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 = m_data->minorPen; + minorPen.setCapStyle( Qt::FlatCap ); + + painter->setPen( minorPen ); + + if ( m_data->xEnabled && m_data->xMinEnabled ) + { + drawLines( painter, canvasRect, Qt::Vertical, xMap, + m_data->xScaleDiv.ticks( QwtScaleDiv::MinorTick ) ); + drawLines( painter, canvasRect, Qt::Vertical, xMap, + m_data->xScaleDiv.ticks( QwtScaleDiv::MediumTick ) ); + } + + if ( m_data->yEnabled && m_data->yMinEnabled ) + { + drawLines( painter, canvasRect, Qt::Horizontal, yMap, + m_data->yScaleDiv.ticks( QwtScaleDiv::MinorTick ) ); + drawLines( painter, canvasRect, Qt::Horizontal, yMap, + m_data->yScaleDiv.ticks( QwtScaleDiv::MediumTick ) ); + } + + // draw major grid lines + QPen majorPen = m_data->majorPen; + majorPen.setCapStyle( Qt::FlatCap ); + + painter->setPen( majorPen ); + + if ( m_data->xEnabled ) + { + drawLines( painter, canvasRect, Qt::Vertical, xMap, + m_data->xScaleDiv.ticks( QwtScaleDiv::MajorTick ) ); + } + + if ( m_data->yEnabled ) + { + drawLines( painter, canvasRect, Qt::Horizontal, yMap, + m_data->yScaleDiv.ticks( QwtScaleDiv::MajorTick ) ); + } +} + +void QwtPlotGrid::drawLines( QPainter* painter, const QRectF& canvasRect, + Qt::Orientation orientation, const QwtScaleMap& scaleMap, + const QList< double >& 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 ) && + qwtFuzzyGreaterOrEqual( y2, value ) ) + { + QwtPainter::drawLine( painter, x1, value, x2, value ); + } + } + else + { + if ( qwtFuzzyGreaterOrEqual( value, x1 ) && + qwtFuzzyGreaterOrEqual( x2, value ) ) + { + 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 m_data->majorPen; +} + +/*! + \return the pen for the minor grid lines + \sa setMinorPen(), setMajorPen(), setPen() + */ +const QPen& QwtPlotGrid::minorPen() const +{ + return m_data->minorPen; +} + +/*! + \return true if vertical grid lines are enabled + \sa enableX() + */ +bool QwtPlotGrid::xEnabled() const +{ + return m_data->xEnabled; +} + +/*! + \return true if minor vertical grid lines are enabled + \sa enableXMin() + */ +bool QwtPlotGrid::xMinEnabled() const +{ + return m_data->xMinEnabled; +} + +/*! + \return true if horizontal grid lines are enabled + \sa enableY() + */ +bool QwtPlotGrid::yEnabled() const +{ + return m_data->yEnabled; +} + +/*! + \return true if minor horizontal grid lines are enabled + \sa enableYMin() + */ +bool QwtPlotGrid::yMinEnabled() const +{ + return m_data->yMinEnabled; +} + + +/*! \return the scale division of the x axis */ +const QwtScaleDiv& QwtPlotGrid::xScaleDiv() const +{ + return m_data->xScaleDiv; +} + +/*! \return the scale division of the y axis */ +const QwtScaleDiv& QwtPlotGrid::yScaleDiv() const +{ + return m_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/libs/qwt/src/qwt_plot_grid.h b/libs/qwt/src/qwt_plot_grid.h new file mode 100644 index 00000000..a742fc27 --- /dev/null +++ b/libs/qwt/src/qwt_plot_grid.h @@ -0,0 +1,90 @@ +/****************************************************************************** + * 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" + +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 QWT_OVERRIDE; + + void enableX( bool ); + bool xEnabled() const; + + void enableY( bool ); + bool yEnabled() const; + + void enableXMin( bool ); + bool xMinEnabled() const; + + void enableYMin( bool ); + bool yMinEnabled() const; + + void setXDiv( const QwtScaleDiv& ); + const QwtScaleDiv& xScaleDiv() const; + + void setYDiv( const QwtScaleDiv& ); + 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& ); + const QPen& minorPen() const; + + virtual void draw( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + virtual void updateScaleDiv( + const QwtScaleDiv& xScaleDiv, const QwtScaleDiv& yScaleDiv ) QWT_OVERRIDE; + + private: + void drawLines( QPainter*, const QRectF&, + Qt::Orientation, const QwtScaleMap&, + const QList< double >& ) const; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_histogram.cpp b/libs/qwt/src/qwt_plot_histogram.cpp new file mode 100644 index 00000000..ed3ee8be --- /dev/null +++ b/libs/qwt/src/qwt_plot_histogram.cpp @@ -0,0 +1,692 @@ +/****************************************************************************** + * 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_painter.h" +#include "qwt_column_symbol.h" +#include "qwt_scale_map.h" +#include "qwt_graphic.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 m_data; +} + +//! Initialize data members +void QwtPlotHistogram::init() +{ + m_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 != m_data->style ) + { + m_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Style of the histogram + \sa HistogramStyle, setStyle() + */ +QwtPlotHistogram::HistogramStyle QwtPlotHistogram::style() const +{ + return m_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 != m_data->pen ) + { + m_data->pen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Pen used in a style() depending way. + \sa setPen(), brush() + */ +const QPen& QwtPlotHistogram::pen() const +{ + return m_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 != m_data->brush ) + { + m_data->brush = brush; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Brush used in a style() depending way. + \sa setPen(), brush() + */ +const QBrush& QwtPlotHistogram::brush() const +{ + return m_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 != m_data->symbol ) + { + delete m_data->symbol; + m_data->symbol = symbol; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() + */ +const QwtColumnSymbol* QwtPlotHistogram::symbol() const +{ + return m_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 ( m_data->baseline != value ) + { + m_data->baseline = value; + itemChanged(); + } +} + +/*! + \return Value of the baseline + \sa setBaseline() + */ +double QwtPlotHistogram::baseline() const +{ + return m_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() > m_data->baseline ) + rect.setLeft( m_data->baseline ); + else if ( rect.right() < m_data->baseline ) + rect.setRight( m_data->baseline ); + } + else + { + if ( rect.bottom() < m_data->baseline ) + rect.setBottom( m_data->baseline ); + else if ( rect.top() > m_data->baseline ) + rect.setTop( m_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< QwtIntervalSample >& 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< QwtIntervalSample >* 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& canvasRect, int from, int to ) const +{ + Q_UNUSED( canvasRect ) + + if ( !painter || dataSize() <= 0 ) + return; + + if ( to < 0 ) + to = dataSize() - 1; + + switch ( m_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( m_data->pen ); + painter->setBrush( m_data->brush ); + + const QwtSeriesData< QwtIntervalSample >* 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( m_data->pen ); + painter->setBrush( Qt::NoBrush ); + + const QwtSeriesData< QwtIntervalSample >* 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 ( m_data->brush.style() != Qt::NoBrush ) + { + painter->setPen( Qt::NoPen ); + painter->setBrush( m_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 ( m_data->pen.style() != Qt::NoPen ) + { + painter->setBrush( Qt::NoBrush ); + painter->setPen( m_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 ( m_data->symbol && + ( m_data->symbol->style() != QwtColumnSymbol::NoStyle ) ) + { + m_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( m_data->brush, size ); +} diff --git a/libs/qwt/src/qwt_plot_histogram.h b/libs/qwt/src/qwt_plot_histogram.h new file mode 100644 index 00000000..6fdd3455 --- /dev/null +++ b/libs/qwt/src/qwt_plot_histogram.h @@ -0,0 +1,145 @@ +/****************************************************************************** + * 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" + +class QwtColumnSymbol; +class QwtColumnRect; +class QColor; +class QPolygonF; + +#if QT_VERSION < 0x060000 +template< typename T > class QVector; +#endif + +/*! + \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< QwtIntervalSample > +{ + 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() ); + explicit QwtPlotHistogram( const QwtText& title ); + virtual ~QwtPlotHistogram(); + + virtual int rtti() const QWT_OVERRIDE; + + 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< QwtIntervalSample >& ); + void setSamples( QwtSeriesData< QwtIntervalSample >* ); + + void setBaseline( double ); + double baseline() const; + + void setStyle( HistogramStyle style ); + HistogramStyle style() const; + + void setSymbol( const QwtColumnSymbol* ); + const QwtColumnSymbol* symbol() const; + + virtual void drawSeries( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const QWT_OVERRIDE; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + virtual QwtGraphic legendIcon( + int index, const QSizeF& ) const QWT_OVERRIDE; + + 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* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_intervalcurve.cpp b/libs/qwt/src/qwt_plot_intervalcurve.cpp new file mode 100644 index 00000000..33beffeb --- /dev/null +++ b/libs/qwt/src/qwt_plot_intervalcurve.cpp @@ -0,0 +1,603 @@ +/****************************************************************************** + * 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 "qwt_graphic.h" +#include "qwt_text.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 m_data; +} + +//! Initialize internal members +void QwtPlotIntervalCurve::init() +{ + setItemAttribute( QwtPlotItem::Legend, true ); + setItemAttribute( QwtPlotItem::AutoScale, true ); + + m_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 ) + m_data->paintAttributes |= attribute; + else + m_data->paintAttributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa PaintAttribute, setPaintAttribute() + */ +bool QwtPlotIntervalCurve::testPaintAttribute( + PaintAttribute attribute ) const +{ + return ( m_data->paintAttributes & attribute ); +} + +/*! + Initialize data with an array of samples. + \param samples Vector of samples + */ +void QwtPlotIntervalCurve::setSamples( + const QVector< QwtIntervalSample >& 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< QwtIntervalSample >* data ) +{ + setData( data ); +} + +/*! + Set the curve's drawing style + + \param style Curve style + \sa CurveStyle, style() + */ +void QwtPlotIntervalCurve::setStyle( CurveStyle style ) +{ + if ( style != m_data->style ) + { + m_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Style of the curve + \sa setStyle() + */ +QwtPlotIntervalCurve::CurveStyle QwtPlotIntervalCurve::style() const +{ + return m_data->style; +} + +/*! + Assign a symbol. + + \param symbol Symbol + \sa symbol() + */ +void QwtPlotIntervalCurve::setSymbol( const QwtIntervalSymbol* symbol ) +{ + if ( symbol != m_data->symbol ) + { + delete m_data->symbol; + m_data->symbol = symbol; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() + */ +const QwtIntervalSymbol* QwtPlotIntervalCurve::symbol() const +{ + return m_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 != m_data->pen ) + { + m_data->pen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Pen used to draw the lines + \sa setPen(), brush() + */ +const QPen& QwtPlotIntervalCurve::pen() const +{ + return m_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 != m_data->brush ) + { + m_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 m_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 ( 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 ( m_data->style ) + { + case Tube: + drawTube( painter, xMap, yMap, canvasRect, from, to ); + break; + + case NoCurve: + default: + break; + } + + if ( m_data->symbol && + ( m_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) ) + { + drawSymbols( painter, *m_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 ( m_data->brush.style() != Qt::NoBrush ) + { + painter->setPen( QPen( Qt::NoPen ) ); + painter->setBrush( m_data->brush ); + + if ( m_data->paintAttributes & ClipPolygons ) + { + const qreal m = 1.0; + const QPolygonF p = QwtClipper::clippedPolygonF( + canvasRect.adjusted( -m, -m, m, m ), polygon, true ); + + QwtPainter::drawPolygon( painter, p ); + } + else + { + QwtPainter::drawPolygon( painter, polygon ); + } + } + + if ( m_data->pen.style() != Qt::NoPen ) + { + painter->setPen( m_data->pen ); + painter->setBrush( Qt::NoBrush ); + + if ( m_data->paintAttributes & ClipPolygons ) + { + qreal pw = QwtPainter::effectivePenWidth( painter->pen() ); + const QRectF clipRect = canvasRect.adjusted( -pw, -pw, pw, pw ); + + QPolygonF p( size ); + + std::memcpy( p.data(), points, size * sizeof( QPointF ) ); + QwtPainter::drawPolyline( painter, + QwtClipper::clippedPolygonF( clipRect, p ) ); + + std::memcpy( p.data(), points + size, size * sizeof( QPointF ) ); + QwtPainter::drawPolyline( painter, + QwtClipper::clippedPolygonF( clipRect, 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 = m_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 ( m_data->style == Tube ) + { + QRectF r( 0, 0, size.width(), size.height() ); + painter.fillRect( r, m_data->brush ); + } + + if ( m_data->symbol && + ( m_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) ) + { + QPen pen = m_data->symbol->pen(); + pen.setWidthF( pen.widthF() ); + pen.setCapStyle( Qt::FlatCap ); + + painter.setPen( pen ); + painter.setBrush( m_data->symbol->brush() ); + + if ( orientation() == Qt::Vertical ) + { + const double x = 0.5 * size.width(); + + m_data->symbol->draw( &painter, orientation(), + QPointF( x, 0 ), QPointF( x, size.height() - 1.0 ) ); + } + else + { + const double y = 0.5 * size.height(); + + m_data->symbol->draw( &painter, orientation(), + QPointF( 0.0, y ), QPointF( size.width() - 1.0, y ) ); + } + } + + return icon; +} diff --git a/libs/qwt/src/qwt_plot_intervalcurve.h b/libs/qwt/src/qwt_plot_intervalcurve.h new file mode 100644 index 00000000..db3a8450 --- /dev/null +++ b/libs/qwt/src/qwt_plot_intervalcurve.h @@ -0,0 +1,135 @@ +/****************************************************************************** + * 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" + +class QwtIntervalSymbol; +template< typename T > class QwtSeriesData; + +/*! + \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< QwtIntervalSample > +{ + 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 + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + explicit QwtPlotIntervalCurve( const QString& title = QString() ); + explicit QwtPlotIntervalCurve( const QwtText& title ); + + virtual ~QwtPlotIntervalCurve(); + + virtual int rtti() const QWT_OVERRIDE; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setSamples( const QVector< QwtIntervalSample >& ); + void setSamples( QwtSeriesData< QwtIntervalSample >* ); + + 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*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const QWT_OVERRIDE; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + virtual QwtGraphic legendIcon( + int index, const QSizeF& ) const QWT_OVERRIDE; + + 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* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotIntervalCurve::PaintAttributes ) + +#endif diff --git a/libs/qwt/src/qwt_plot_item.cpp b/libs/qwt/src/qwt_plot_item.cpp new file mode 100644 index 00000000..5b9e6358 --- /dev/null +++ b/libs/qwt/src/qwt_plot_item.cpp @@ -0,0 +1,727 @@ +/****************************************************************************** + * 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_map.h" +#include "qwt_graphic.h" + +#include + +class QwtPlotItem::PrivateData +{ + public: + PrivateData() + : plot( NULL ) + , isVisible( true ) + , renderThreadCount( 1 ) + , z( 0.0 ) + , xAxisId( QwtAxis::XBottom ) + , yAxisId( QwtAxis::YLeft ) + , legendIconSize( 8, 8 ) + { + } + + mutable QwtPlot* plot; + + bool isVisible; + + QwtPlotItem::ItemAttributes attributes; + QwtPlotItem::ItemInterests interests; + + QwtPlotItem::RenderHints renderHints; + uint renderThreadCount; + + double z; + + QwtAxisId xAxisId; + QwtAxisId yAxisId; + + QwtText title; + QSize legendIconSize; +}; + +/*! + Constructor + */ +QwtPlotItem::QwtPlotItem() +{ + m_data = new PrivateData; +} + +/*! + Constructor + \param title Title of the item + */ +QwtPlotItem::QwtPlotItem( const QString& title ) +{ + m_data = new PrivateData; + m_data->title = title; +} + +/*! + Constructor + \param title Title of the item + */ +QwtPlotItem::QwtPlotItem( const QwtText& title ) +{ + m_data = new PrivateData; + m_data->title = title; +} + +//! Destroy the QwtPlotItem +QwtPlotItem::~QwtPlotItem() +{ + attach( NULL ); + delete m_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 == m_data->plot ) + return; + + if ( m_data->plot ) + m_data->plot->attachItem( this, false ); + + m_data->plot = plot; + + if ( m_data->plot ) + m_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 m_data->plot; +} + +/*! + Plot items are painted in increasing z-order. + + \return setZ(), QwtPlotDict::itemList() + */ +double QwtPlotItem::z() const +{ + return m_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 ( m_data->z != z ) + { + if ( m_data->plot ) // update the z order + m_data->plot->attachItem( this, false ); + + m_data->z = z; + + if ( m_data->plot ) + m_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 ( m_data->title != title ) + { + m_data->title = title; + + legendChanged(); +#if 0 + itemChanged(); +#endif + } +} + +/*! + \return Title of the item + \sa setTitle() + */ +const QwtText& QwtPlotItem::title() const +{ + return m_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 ( m_data->attributes.testFlag( attribute ) != on ) + { + if ( on ) + m_data->attributes |= attribute; + else + m_data->attributes &= ~attribute; + + if ( attribute == QwtPlotItem::Legend ) + { + if ( on ) + { + legendChanged(); + } + else + { + /* + In the special case of taking an item from + the legend we can't use legendChanged() as + it depends on QwtPlotItem::Legend being enabled + */ + if ( m_data->plot ) + m_data->plot->updateLegend( this ); + } + } + + itemChanged(); + } +} + +/*! + Test an item attribute + + \param attribute Attribute type + \return true/false + \sa setItemAttribute(), ItemInterest + */ +bool QwtPlotItem::testItemAttribute( ItemAttribute attribute ) const +{ + return m_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 ( m_data->interests.testFlag( interest ) != on ) + { + if ( on ) + m_data->interests |= interest; + else + m_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 m_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 ( m_data->renderHints.testFlag( hint ) != on ) + { + if ( on ) + m_data->renderHints |= hint; + else + m_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 m_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 ) +{ + m_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 m_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 ( m_data->legendIconSize != size ) + { + m_data->legendIconSize = size; + legendChanged(); + } +} + +/*! + \return Legend icon size + \sa setLegendIconSize(), legendIcon() + */ +QSize QwtPlotItem::legendIconSize() const +{ + return m_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 != m_data->isVisible ) + { + m_data->isVisible = on; + itemChanged(); + } +} + +/*! + \return true if visible + \sa setVisible(), show(), hide() + */ +bool QwtPlotItem::isVisible() const +{ + return m_data->isVisible; +} + +/*! + Update the legend and call QwtPlot::autoRefresh() for the + parent plot. + + \sa QwtPlot::legendChanged(), QwtPlot::autoRefresh() + */ +void QwtPlotItem::itemChanged() +{ + if ( m_data->plot ) + m_data->plot->autoRefresh(); +} + +/*! + Update the legend of the parent plot. + \sa QwtPlot::updateLegend(), itemChanged() + */ +void QwtPlotItem::legendChanged() +{ + if ( testItemAttribute( QwtPlotItem::Legend ) && m_data->plot ) + m_data->plot->updateLegend( this ); +} + +/*! + Set X and Y axis + + The item will painted according to the coordinates of its Axes. + + \param xAxisId X Axis + \param yAxisId Y Axis + + \sa setXAxis(), setYAxis(), xAxis(), yAxis() + */ +void QwtPlotItem::setAxes( QwtAxisId xAxisId, QwtAxisId yAxisId ) +{ + if ( QwtAxis::isXAxis( xAxisId ) ) + m_data->xAxisId = xAxisId; + + if ( QwtAxis::isYAxis( yAxisId ) ) + m_data->yAxisId = yAxisId; + + itemChanged(); +} + +/*! + Set the X axis + + The item will painted according to the coordinates its Axes. + + \param axisId X Axis + \sa setAxes(), setYAxis(), xAxis() + */ +void QwtPlotItem::setXAxis( QwtAxisId axisId ) +{ + if ( QwtAxis::isXAxis( axisId ) ) + { + m_data->xAxisId = axisId; + itemChanged(); + } +} + +/*! + Set the Y axis + + The item will painted according to the coordinates its Axes. + + \param axisId Y Axis + \sa setAxes(), setXAxis(), yAxis() + */ +void QwtPlotItem::setYAxis( QwtAxisId axisId ) +{ + if ( QwtAxis::isYAxis( axisId ) ) + { + m_data->yAxisId = axisId; + itemChanged(); + } +} + +//! Return xAxis +QwtAxisId QwtPlotItem::xAxis() const +{ + return m_data->xAxisId; +} + +//! Return yAxis +QwtAxisId QwtPlotItem::yAxis() const +{ + return m_data->yAxisId; +} + +/*! + \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 + + 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< QwtLegendData > QwtPlotItem::legendData() const +{ + QwtLegendData data; + + QwtText label = title(); + label.setRenderFlags( label.renderFlags() & Qt::AlignLeft ); + + data.setValue( QwtLegendData::TitleRole, + QVariant::fromValue( label ) ); + + const QwtGraphic graphic = legendIcon( 0, legendIconSize() ); + if ( !graphic.isNull() ) + { + data.setValue( QwtLegendData::IconRole, + QVariant::fromValue( graphic ) ); + } + + QList< QwtLegendData > 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< QwtLegendData >& 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/libs/qwt/src/qwt_plot_item.h b/libs/qwt/src/qwt_plot_item.h new file mode 100644 index 00000000..baeb5fed --- /dev/null +++ b/libs/qwt/src/qwt_plot_item.h @@ -0,0 +1,310 @@ +/****************************************************************************** + * 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_axis_id.h" +#include + +class QwtScaleMap; +class QwtScaleDiv; +class QwtPlot; +class QwtText; +class QwtGraphic; +class QwtLegendData; +class QRectF; +class QPainter; +class QString; +template< typename T > class QList; + +/*! + \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 QwtPlotGraphicItem, QwtPlotSvgItem + Rtti_PlotGraphic, + + //! For QwtPlotTradingCurve + Rtti_PlotTradingCurve, + + //! For QwtPlotBarChart + Rtti_PlotBarChart, + + //! For QwtPlotMultiBarChart + Rtti_PlotMultiBarChart, + + //! For QwtPlotShapeItem + Rtti_PlotShape, + + //! For QwtPlotTextLabel + Rtti_PlotTextLabel, + + //! For QwtPlotZoneItem + Rtti_PlotZone, + + //! For QwtPlotVectorField + Rtti_PlotVectorField, + + /*! + 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 + }; + + Q_DECLARE_FLAGS( ItemAttributes, ItemAttribute ) + + /*! + \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 + }; + + Q_DECLARE_FLAGS( ItemInterests, ItemInterest ) + + //! Render hints + enum RenderHint + { + //! Enable antialiasing + RenderAntialiased = 0x1 + }; + + Q_DECLARE_FLAGS( RenderHints, RenderHint ) + + explicit QwtPlotItem(); + explicit QwtPlotItem( const QString& title ); + explicit QwtPlotItem( const QwtText& title ); + + 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& canvasRect, + double& left, double& top, double& right, double& bottom) const; + + virtual void updateScaleDiv( + const QwtScaleDiv&, const QwtScaleDiv& ); + + virtual void updateLegend( const QwtPlotItem*, + const QList< QwtLegendData >& ); + + QRectF scaleRect( const QwtScaleMap&, const QwtScaleMap& ) const; + QRectF paintRect( const QwtScaleMap&, const QwtScaleMap& ) const; + + virtual QList< QwtLegendData > legendData() const; + + virtual QwtGraphic legendIcon( int index, const QSizeF& ) const; + + protected: + QwtGraphic defaultIcon( const QBrush&, const QSizeF& ) const; + + private: + Q_DISABLE_COPY(QwtPlotItem) + + class PrivateData; + PrivateData* m_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/libs/qwt/src/qwt_plot_layout.cpp b/libs/qwt/src/qwt_plot_layout.cpp new file mode 100644 index 00000000..093af88e --- /dev/null +++ b/libs/qwt/src/qwt_plot_layout.cpp @@ -0,0 +1,1706 @@ +/****************************************************************************** + * 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 "qwt_math.h" + +#include + +namespace +{ + class LayoutData + { + 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 + { + const int w = qMin( hint.width(), qwtFloor( rect.width() ) ); + + int h = legend->heightForWidth( w ); + if ( h <= 0 ) + h = hint.height(); + + 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 ) + { + isVisible = true; + + scaleWidget = axisWidget; + scaleFont = axisWidget->font(); + + start = axisWidget->startBorderDist(); + end = axisWidget->endBorderDist(); + + baseLineOffset = axisWidget->margin(); + + tickOffset = axisWidget->margin(); + if ( axisWidget->scaleDraw()->hasComponent( QwtAbstractScaleDraw::Ticks ) ) + tickOffset += axisWidget->scaleDraw()->maxTickLength(); + + dimWithoutTitle = axisWidget->dimForLength( QWIDGETSIZE_MAX, scaleFont ); + if ( !axisWidget->title().isEmpty() ) + dimWithoutTitle -= axisWidget->titleHeightForWidth( QWIDGETSIZE_MAX ); + } + + void reset() + { + isVisible = false; + start = 0; + end = 0; + baseLineOffset = 0; + tickOffset = 0.0; + dimWithoutTitle = 0; + } + + bool isVisible; + const QwtScaleWidget* scaleWidget; + QFont scaleFont; + int start; + int end; + int baseLineOffset; + double tickOffset; + int dimWithoutTitle; + }; + + struct CanvasData + { + void init( const QWidget* canvas ) + { + const QMargins m = canvas->contentsMargins(); + + contentsMargins[ QwtAxis::YLeft ] = m.left(); + contentsMargins[ QwtAxis::XTop ] = m.top(); + contentsMargins[ QwtAxis::YRight ] = m.right(); + contentsMargins[ QwtAxis::XBottom ] = m.bottom(); + } + + int contentsMargins[ QwtAxis::AxisPositions ]; + }; + + public: + enum Label + { + Title, + Footer, + + NumLabels + }; + + LayoutData( const QwtPlot* ); + bool hasSymmetricYAxes() const; + + inline ScaleData& axisData( QwtAxisId axisId ) + { + return m_scaleData[ axisId ]; + } + + inline const ScaleData& axisData( QwtAxisId axisId ) const + { + return m_scaleData[ axisId ]; + } + + inline double tickOffset( int axisPos ) const + { + return axisData( axisPos ).tickOffset; + } + + LegendData legendData; + LabelData labelData[ NumLabels ]; + CanvasData canvasData; + + private: + ScaleData m_scaleData[ QwtAxis::AxisPositions ]; + }; + + /* + Extract all layout relevant data from the plot components + */ + LayoutData::LayoutData( const QwtPlot* plot ) + { + legendData.init( plot->legend() ); + labelData[ Title ].init( plot->titleLabel() ); + labelData[ Footer ].init( plot->footerLabel() ); + + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + ScaleData& scaleData = axisData( axisId ); + + if ( plot->isAxisVisible( axisId ) ) + { + const QwtScaleWidget* scaleWidget = plot->axisWidget( axisId ); + scaleData.init( scaleWidget ); + } + else + { + scaleData.reset(); + } + } + } + + canvasData.init( plot->canvas() ); + } + + bool LayoutData::hasSymmetricYAxes() const + { + using namespace QwtAxis; + return m_scaleData[ YLeft ].isVisible == m_scaleData[ YRight ].isVisible; + } +} + +namespace +{ + class LayoutHintData + { + public: + LayoutHintData( const QwtPlot* plot ); + + int alignedSize( const QwtAxisId ) const; + + inline int yAxesWidth() const + { + using namespace QwtAxis; + return axesWidth( YLeft ) + axesWidth( YRight ); + } + + inline int yAxesHeight() const + { + using namespace QwtAxis; + return qMax( axesHeight( YLeft ), axesHeight( YRight ) ); + } + + inline int xAxesHeight() const + { + using namespace QwtAxis; + return axesHeight( XTop ) + axesHeight( XBottom ); + } + + inline int xAxesWidth() const + { + using namespace QwtAxis; + return qMax( axesWidth( XTop ), axesWidth( XBottom ) ); + } + + private: + + struct ScaleData + { + ScaleData() + { + w = h = minLeft = minRight = tickOffset = 0; + } + + int w; + int h; + int minLeft; + int minRight; + int tickOffset; + + }; + + const ScaleData& axisData( QwtAxisId axisId ) const + { + return m_scaleData[ axisId ]; + } + + ScaleData& axisData( QwtAxisId axisId ) + { + return m_scaleData[ axisId ]; + } + + inline int axesWidth( int axisPos ) const + { + return m_scaleData[axisPos].w; + } + + inline int axesHeight( int axisPos ) const + { + return m_scaleData[axisPos].h; + } + + int m_canvasBorder[QwtAxis::AxisPositions]; + ScaleData m_scaleData[QwtAxis::AxisPositions]; + }; + + LayoutHintData::LayoutHintData( const QwtPlot* plot ) + { + using namespace QwtAxis; + + const QMargins m = plot->canvas()->contentsMargins(); + + int contentsMargins[ 4 ]; + contentsMargins[ YLeft ] = m.left(); + contentsMargins[ XTop ] = m.top(); + contentsMargins[ YRight ] = m.right(); + contentsMargins[ XBottom ] = m.bottom(); + + for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ ) + { + m_canvasBorder[axisPos] = contentsMargins[axisPos] + + plot->plotLayout()->canvasMargin( axisPos ) + 1; + { + const QwtAxisId axisId( axisPos ); + + 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 ); + + { + sd.tickOffset = scl->margin(); + if ( scl->scaleDraw()->hasComponent( QwtAbstractScaleDraw::Ticks ) ) + sd.tickOffset += qwtCeil( scl->scaleDraw()->maxTickLength() ); + } + } + } + } + + for ( int axis = 0; axis < AxisPositions; axis++ ) + { + const int sz = alignedSize( axis ); + + ScaleData& sd = axisData( axis ); + if ( isXAxis( axis ) ) + sd.w = sz; + else + sd.h = sz; + } + } + + int LayoutHintData::alignedSize( const QwtAxisId axisId ) const + { + using namespace QwtAxis; + + const ScaleData& sd = axisData( axisId ); + + if ( sd.w && isXAxis( axisId ) ) + { + int w = sd.w; + + if ( const int leftW = axesWidth( YLeft ) ) + { + const int shiftLeft = sd.minLeft - m_canvasBorder[YLeft]; + if ( shiftLeft > 0 ) + w -= qMin( shiftLeft, leftW ); + } + + if ( const int rightW = axesWidth( YRight ) ) + { + const int shiftRight = sd.minRight - m_canvasBorder[YRight]; + if ( shiftRight > 0 ) + w -= qMin( shiftRight, rightW ); + } + + return w; + } + + if ( sd.h && isYAxis( axisId ) ) + { + int h = sd.h; + + if ( axesHeight( XBottom ) ) + { + const int shiftBottom = sd.minLeft - m_canvasBorder[XBottom]; + if ( shiftBottom > 0 ) + h -= qMin( shiftBottom, axisData( XBottom ).tickOffset ); + } + + if ( axesHeight( XTop ) ) + { + const int shiftTop = sd.minRight - m_canvasBorder[XTop]; + if ( shiftTop > 0 ) + h -= qMin( shiftTop, axisData( XTop ).tickOffset ); + } + + return h; + } + + return 0; + } +} + +namespace +{ + class LayoutEngine + { + public: + struct Dimensions + { + Dimensions() + { + dimTitle = dimFooter = 0; + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + m_dimAxes[axisPos] = 0; + } + + inline int dimAxis( QwtAxisId axisId ) const + { + return m_dimAxes[ axisId ]; + } + + void setDimAxis( QwtAxisId axisId, int dim ) + { + m_dimAxes[ axisId ] = dim; + } + + inline int dimAxes( int axisPos ) const + { + return m_dimAxes[ axisPos ]; + } + + 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().y() ); + r.setHeight( 0 ); + } + + return r; + } + + int dimTitle; + int dimFooter; + + private: + int m_dimAxes[QwtAxis::AxisPositions]; + }; + + LayoutEngine() + : m_legendPos( QwtPlot::BottomLegend ) + , m_legendRatio( 1.0 ) + , m_spacing( 5 ) + { + } + + QRectF layoutLegend( QwtPlotLayout::Options, + const LayoutData::LegendData&, const QRectF&, const QSize& ) const; + + QRectF alignLegend( const QSize& legendHint, + const QRectF& canvasRect, const QRectF& legendRect ) const; + + void alignScales( QwtPlotLayout::Options, + const LayoutData&, QRectF& canvasRect, + QRectF scaleRect[QwtAxis::AxisPositions] ) const; + + Dimensions layoutDimensions( QwtPlotLayout::Options, + const LayoutData&, const QRectF& ) const; + + inline void setSpacing( unsigned int spacing ) { m_spacing = spacing; } + inline unsigned int spacing() const { return m_spacing; } + + inline void setAlignCanvas( int axisPos, bool on ) { m_alignCanvas[ axisPos ] = on; } + inline bool alignCanvas( int axisPos ) const { return m_alignCanvas[ axisPos ]; } + + inline void setCanvasMargin( int axisPos, int margin ) { m_canvasMargin[ axisPos ] = margin; } + inline int canvasMargin( int axisPos ) const { return m_canvasMargin[ axisPos ]; } + + inline void setLegendPos( QwtPlot::LegendPosition pos ) { m_legendPos = pos; } + inline QwtPlot::LegendPosition legendPos() const { return m_legendPos; } + + inline void setLegendRatio( double ratio ) { m_legendRatio = ratio; } + inline double legendRatio() const { return m_legendRatio; } + + private: + int heightForWidth( LayoutData::Label, const LayoutData&, + QwtPlotLayout::Options, double width, int axesWidth ) const; + + QwtPlot::LegendPosition m_legendPos; + double m_legendRatio; + + unsigned int m_canvasMargin[QwtAxis::AxisPositions]; + bool m_alignCanvas[QwtAxis::AxisPositions]; + + unsigned int m_spacing; + }; +} + +QRectF LayoutEngine::layoutLegend( QwtPlotLayout::Options options, + const LayoutData::LegendData& legendData, + const QRectF& rect, const QSize& legendHint ) const +{ + int dim; + if ( m_legendPos == QwtPlot::LeftLegend + || m_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() * m_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() * m_legendRatio ) ); + dim = qMax( dim, legendData.vScrollExtent ); + } + + QRectF legendRect = rect; + switch ( m_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 LayoutEngine::alignLegend( const QSize& legendHint, + const QRectF& canvasRect, const QRectF& legendRect ) const +{ + QRectF alignedRect = legendRect; + + if ( m_legendPos == QwtPlot::BottomLegend + || m_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 LayoutEngine::heightForWidth( + LayoutData::Label labelType, const LayoutData& layoutData, + QwtPlotLayout::Options options, + double width, int axesWidth ) const +{ + const LayoutData::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 = qwtCeil( labelData.text.heightForWidth( w ) ); + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + d += 2 * labelData.frameWidth; + + return d; +} + +LayoutEngine::Dimensions LayoutEngine::layoutDimensions( QwtPlotLayout::Options options, + const LayoutData& layoutData, const QRectF& rect ) const +{ + using namespace QwtAxis; + + Dimensions dimensions; + + int backboneOffset[AxisPositions]; + for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ ) + { + backboneOffset[axisPos] = 0; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + backboneOffset[axisPos] += layoutData.canvasData.contentsMargins[axisPos]; + + if ( !m_alignCanvas[axisPos] ) + backboneOffset[axisPos] += m_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( + LayoutData::Title, layoutData, options, + rect.width(), dimensions.dimYAxes() ); + + if ( d > dimensions.dimTitle ) + { + dimensions.dimTitle = d; + done = false; + } + } + + if ( !( options & QwtPlotLayout::IgnoreFooter ) ) + { + const int d = heightForWidth( + LayoutData::Footer, layoutData, options, + rect.width(), dimensions.dimYAxes() ); + + if ( d > dimensions.dimFooter ) + { + dimensions.dimFooter = d; + done = false; + } + } + + for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + const LayoutData::ScaleData& scaleData = layoutData.axisData( axisId ); + + if ( scaleData.isVisible ) + { + double length; + if ( isXAxis( axisPos ) ) + { + length = rect.width() - dimensions.dimYAxes(); + length -= scaleData.start + scaleData.end; + + if ( dimensions.dimAxes( YRight ) > 0 ) + length -= 1; + + length += qMin( dimensions.dimAxes( YLeft ), + scaleData.start - backboneOffset[YLeft] ); + + length += qMin( dimensions.dimAxes( YRight ), + scaleData.end - backboneOffset[YRight] ); + } + else // y axis + { + length = rect.height() - dimensions.dimXAxes(); + length -= scaleData.start + scaleData.end; + length -= 1; + + if ( dimensions.dimAxes( XBottom ) <= 0 ) + length -= 1; + + if ( dimensions.dimAxes( 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 the labels don't overlap. + */ + if ( dimensions.dimAxes( XBottom ) > 0 ) + { + length += qMin( + layoutData.tickOffset( XBottom ), + double( scaleData.start - backboneOffset[XBottom] ) ); + } + + if ( dimensions.dimAxes( XTop ) > 0 ) + { + length += qMin( + layoutData.tickOffset( XTop ), + double( scaleData.end - backboneOffset[XTop] ) ); + } + + if ( dimensions.dimTitle > 0 ) + length -= dimensions.dimTitle + m_spacing; + } + + int d = scaleData.dimWithoutTitle; + if ( !scaleData.scaleWidget->title().isEmpty() ) + { + d += scaleData.scaleWidget->titleHeightForWidth( qwtFloor( length ) ); + } + + + if ( d > dimensions.dimAxis( axisId ) ) + { + dimensions.setDimAxis( axisId, d ); + done = false; + } + } + } + } + } + + return dimensions; +} + +void LayoutEngine::alignScales( QwtPlotLayout::Options options, + const LayoutData& layoutData, QRectF& canvasRect, + QRectF scaleRect[QwtAxis::AxisPositions] ) const +{ + using namespace QwtAxis; + + int backboneOffset[AxisPositions]; + for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ ) + { + backboneOffset[axisPos] = 0; + + if ( !m_alignCanvas[axisPos] ) + { + backboneOffset[axisPos] += m_canvasMargin[axisPos]; + } + + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + { + backboneOffset[axisPos] += + layoutData.canvasData.contentsMargins[axisPos]; + } + } + + for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ ) + { + { + QRectF& axisRect = scaleRect[axisPos]; + if ( !axisRect.isValid() ) + continue; + + const QwtAxisId axisId( axisPos ); + + const int startDist = layoutData.axisData( axisId ).start; + const int endDist = layoutData.axisData( axisId ).end; + + if ( isXAxis( axisPos ) ) + { + const QRectF& leftScaleRect = scaleRect[YLeft]; + const int leftOffset = backboneOffset[YLeft] - startDist; + + if ( leftScaleRect.isValid() ) + { + const double dx = leftOffset + leftScaleRect.width(); + if ( m_alignCanvas[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( qwtMaxF( cLeft, axisRect.left() - dx ) ); + } + else + { + const double minLeft = leftScaleRect.left(); + const double left = axisRect.left() + leftOffset; + axisRect.setLeft( qwtMaxF( left, minLeft ) ); + } + } + else + { + if ( m_alignCanvas[YLeft] && leftOffset < 0 ) + { + canvasRect.setLeft( qwtMaxF( canvasRect.left(), + axisRect.left() - leftOffset ) ); + } + else + { + if ( leftOffset > 0 ) + axisRect.setLeft( axisRect.left() + leftOffset ); + } + } + + const QRectF& rightScaleRect = scaleRect[YRight]; + const int rightOffset = backboneOffset[YRight] - endDist + 1; + + if ( rightScaleRect.isValid() ) + { + const double dx = rightOffset + rightScaleRect.width(); + if ( m_alignCanvas[YRight] && dx < 0 ) + { + /* + The axis needs more space than the width + of the right scale. + */ + const double cRight = canvasRect.right(); // qreal -> double + canvasRect.setRight( qwtMinF( cRight, axisRect.right() + dx ) ); + } + + const double maxRight = rightScaleRect.right(); + const double right = axisRect.right() - rightOffset; + axisRect.setRight( qwtMinF( right, maxRight ) ); + } + else + { + if ( m_alignCanvas[YRight] && rightOffset < 0 ) + { + canvasRect.setRight( qwtMinF( canvasRect.right(), + axisRect.right() + rightOffset ) ); + } + else + { + if ( rightOffset > 0 ) + axisRect.setRight( axisRect.right() - rightOffset ); + } + } + } + else // y axes + { + const QRectF& bottomScaleRect = scaleRect[XBottom]; + const int bottomOffset = backboneOffset[XBottom] - endDist + 1; + + if ( bottomScaleRect.isValid() ) + { + const double dy = bottomOffset + bottomScaleRect.height(); + if ( m_alignCanvas[XBottom] && dy < 0 ) + { + /* + The axis needs more space than the height + of the bottom scale. + */ + const double cBottom = canvasRect.bottom(); // qreal -> double + canvasRect.setBottom( qwtMinF( cBottom, axisRect.bottom() + dy ) ); + } + else + { + const double maxBottom = bottomScaleRect.top() + + layoutData.tickOffset( XBottom ); + const double bottom = axisRect.bottom() - bottomOffset; + axisRect.setBottom( qwtMinF( bottom, maxBottom ) ); + } + } + else + { + if ( m_alignCanvas[XBottom] && bottomOffset < 0 ) + { + canvasRect.setBottom( qwtMinF( canvasRect.bottom(), + axisRect.bottom() + bottomOffset ) ); + } + else + { + if ( bottomOffset > 0 ) + axisRect.setBottom( axisRect.bottom() - bottomOffset ); + } + } + + const QRectF& topScaleRect = scaleRect[XTop]; + const int topOffset = backboneOffset[XTop] - startDist; + + if ( topScaleRect.isValid() ) + { + const double dy = topOffset + topScaleRect.height(); + if ( m_alignCanvas[XTop] && dy < 0 ) + { + /* + The axis needs more space than the height + of the top scale. + */ + const double cTop = canvasRect.top(); // qreal -> double + canvasRect.setTop( qwtMaxF( cTop, axisRect.top() - dy ) ); + } + else + { + const double minTop = topScaleRect.bottom() - + layoutData.tickOffset( XTop ); + + const double top = axisRect.top() + topOffset; + axisRect.setTop( qwtMaxF( top, minTop ) ); + } + } + else + { + if ( m_alignCanvas[XTop] && topOffset < 0 ) + { + canvasRect.setTop( qwtMaxF( 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 < AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + QRectF& sRect = scaleRect[axisPos]; + const LayoutData::ScaleData& axisData = layoutData.axisData( axisId ); + + if ( !sRect.isValid() ) + continue; + + if ( isXAxis( axisPos ) ) + { + if ( m_alignCanvas[YLeft] ) + { + double y = canvasRect.left() - axisData.start; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + y += layoutData.canvasData.contentsMargins[YLeft]; + + sRect.setLeft( y ); + } + + if ( m_alignCanvas[YRight] ) + { + double y = canvasRect.right() - 1 + axisData.end; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + y -= layoutData.canvasData.contentsMargins[YRight]; + + sRect.setRight( y ); + } + + if ( m_alignCanvas[axisPos] ) + { + if ( axisPos == XTop ) + sRect.setBottom( canvasRect.top() ); + else + sRect.setTop( canvasRect.bottom() ); + } + } + else + { + if ( m_alignCanvas[XTop] ) + { + double x = canvasRect.top() - axisData.start; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + x += layoutData.canvasData.contentsMargins[XTop]; + + sRect.setTop( x ); + } + + if ( m_alignCanvas[XBottom] ) + { + double x = canvasRect.bottom() - 1 + axisData.end; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + x -= layoutData.canvasData.contentsMargins[XBottom]; + + sRect.setBottom( x ); + } + + if ( m_alignCanvas[axisPos] ) + { + if ( axisPos == YLeft ) + sRect.setRight( canvasRect.left() ); + else + sRect.setLeft( canvasRect.right() ); + } + } + } + } +} + +class QwtPlotLayout::PrivateData +{ + public: + QRectF titleRect; + QRectF footerRect; + QRectF legendRect; + QRectF scaleRects[QwtAxis::AxisPositions]; + QRectF canvasRect; + + LayoutEngine engine; +}; + +/*! + \brief Constructor + */ + +QwtPlotLayout::QwtPlotLayout() +{ + m_data = new PrivateData; + + setLegendPosition( QwtPlot::BottomLegend ); + setCanvasMargin( 4 ); + setAlignCanvasToScales( false ); + + invalidate(); +} + +//! Destructor +QwtPlotLayout::~QwtPlotLayout() +{ + delete m_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; + + LayoutEngine& engine = m_data->engine; + + if ( axisPos == -1 ) + { + for ( axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + engine.setCanvasMargin( axisPos, margin ); + } + else if ( QwtAxis::isValid( axisPos ) ) + { + engine.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 m_data->engine.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::AxisPositions; axisPos++ ) + m_data->engine.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 ) ) + m_data->engine.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 axisPos Axis position + \return align-canvas-to-axis-scales setting + \sa setAlignCanvasToScale(), setAlignCanvasToScale(), setCanvasMargin() + */ +bool QwtPlotLayout::alignCanvasToScale( int axisPos ) const +{ + if ( !QwtAxis::isValid( axisPos ) ) + return false; + + return m_data->engine.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 ) +{ + m_data->engine.setSpacing( qMax( 0, spacing ) ); +} + +/*! + \return Spacing + \sa margin(), setSpacing() + */ +int QwtPlotLayout::spacing() const +{ + return m_data->engine.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; + + LayoutEngine& engine = m_data->engine; + + switch ( pos ) + { + case QwtPlot::TopLegend: + case QwtPlot::BottomLegend: + { + if ( ratio <= 0.0 ) + ratio = 0.33; + + engine.setLegendRatio( ratio ); + engine.setLegendPos( pos ); + + break; + } + case QwtPlot::LeftLegend: + case QwtPlot::RightLegend: + { + if ( ratio <= 0.0 ) + ratio = 0.5; + + engine.setLegendRatio( ratio ); + engine.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 m_data->engine.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 m_data->engine.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 ) +{ + m_data->titleRect = rect; +} + +/*! + \return Geometry for the title + \sa activate(), invalidate() + */ +QRectF QwtPlotLayout::titleRect() const +{ + return m_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 ) +{ + m_data->footerRect = rect; +} + +/*! + \return Geometry for the footer + \sa activate(), invalidate() + */ +QRectF QwtPlotLayout::footerRect() const +{ + return m_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 ) +{ + m_data->legendRect = rect; +} + +/*! + \return Geometry for the legend + \sa activate(), invalidate() + */ +QRectF QwtPlotLayout::legendRect() const +{ + return m_data->legendRect; +} + +/*! + \brief Set the geometry for an axis + + This method is intended to be used from derived layouts + overloading activate() + + \param axisId Axis + \param rect Rectangle for the scale + + \sa scaleRect(), activate() + */ +void QwtPlotLayout::setScaleRect( QwtAxisId axisId, const QRectF& rect ) +{ + if ( QwtAxis::isValid( axisId ) ) + m_data->scaleRects[axisId] = rect; +} + +/*! + \param axisId Axis + \return Geometry for the scale + \sa activate(), invalidate() + */ +QRectF QwtPlotLayout::scaleRect( QwtAxisId axisId ) const +{ + if ( QwtAxis::isValid( axisId ) ) + return m_data->scaleRects[axisId]; + + 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 ) +{ + m_data->canvasRect = rect; +} + +/*! + \return Geometry for the canvas + \sa activate(), invalidate() + */ +QRectF QwtPlotLayout::canvasRect() const +{ + return m_data->canvasRect; +} + +/*! + Invalidate the geometry of all components. + \sa activate() + */ +void QwtPlotLayout::invalidate() +{ + m_data->titleRect = m_data->footerRect = + m_data->legendRect = m_data->canvasRect = QRectF(); + + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + m_data->scaleRects[axisPos] = QRect(); +} + +/*! + \return Minimum size hint + \param plot Plot widget + + \sa QwtPlot::minimumSizeHint() + */ +QSize QwtPlotLayout::minimumSizeHint( const QwtPlot* plot ) const +{ + LayoutHintData hintData( plot ); + + const int xAxesWidth = hintData.xAxesWidth(); + const int yAxesHeight = hintData.yAxesHeight(); + + const QWidget* canvas = plot->canvas(); + + const QMargins m = canvas->contentsMargins(); + const QSize minCanvasSize = canvas->minimumSize(); + + int w = hintData.yAxesWidth(); + int cw = xAxesWidth + m.left() + 1 + m.right() + 1; + w += qMax( cw, minCanvasSize.width() ); + + int h = hintData.xAxesHeight(); + int ch = yAxesHeight + m.top() + 1 + m.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 = !( plot->isAxisVisible( QwtAxis::YLeft ) + && plot->isAxisVisible( QwtAxis::YRight ) ); + + 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() ) + { + const LayoutEngine& engine = m_data->engine; + + if ( engine.legendPos() == QwtPlot::LeftLegend + || engine.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 ( engine.legendRatio() < 1.0 ) + legendW = qMin( legendW, int( w / ( 1.0 - engine.legendRatio() ) ) ); + + w += legendW + spacing(); + } + else + { + int legendW = qMin( legend->sizeHint().width(), w ); + int legendH = legend->heightForWidth( legendW ); + + if ( legend->frameWidth() > 0 ) + h += spacing(); + + if ( engine.legendRatio() < 1.0 ) + legendH = qMin( legendH, int( h / ( 1.0 - engine.legendRatio() ) ) ); + + h += legendH + spacing(); + } + } + + return QSize( w, h ); +} + +/*! + \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 ) +{ + invalidate(); + + QRectF rect( plotRect ); // undistributed rest of the plot rect + + // We extract all layout relevant parameters from the widgets, + // and save them to m_data->layoutData. + + LayoutData layoutData( plot ); + + QSize legendHint; + + if ( !( options & IgnoreLegend ) + && plot->legend() && !plot->legend()->isEmpty() ) + { + legendHint = layoutData.legendData.legendHint( plot->legend(), rect ); + + m_data->legendRect = m_data->engine.layoutLegend( + options, layoutData.legendData, rect, legendHint ); + + // subtract m_data->legendRect from rect + + const QRegion region( rect.toRect() ); + rect = region.subtracted( m_data->legendRect.toRect() ).boundingRect(); + + switch ( m_data->engine.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. + + using namespace QwtAxis; + + const LayoutEngine::Dimensions dimensions = + m_data->engine.layoutDimensions( options, layoutData, rect ); + + if ( dimensions.dimTitle > 0 ) + { + QRectF& labelRect = m_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 = m_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 ); + } + } + + m_data->canvasRect = dimensions.innerRect( rect ); + + for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ ) + { + // set the rects for the axes + + const int pos = 0; + { + const QwtAxisId axisId( axisPos ); + + if ( dimensions.dimAxis( axisId ) ) + { + const int dim = dimensions.dimAxis( axisId ); + + const QRectF& canvasRect = m_data->canvasRect; + + QRectF& scaleRect = m_data->scaleRects[axisId]; + scaleRect = canvasRect; + + switch ( axisPos ) + { + case YLeft: + { + scaleRect.setX( canvasRect.left() - pos - dim ); + scaleRect.setWidth( dim ); + break; + } + case YRight: + { + scaleRect.setX( canvasRect.right() + pos ); + scaleRect.setWidth( dim ); + break; + } + case XBottom: + { + scaleRect.setY( canvasRect.bottom() + pos ); + scaleRect.setHeight( dim ); + break; + } + case XTop: + { + scaleRect.setY( canvasRect.top() - pos - dim ); + scaleRect.setHeight( dim ); + break; + } + } + scaleRect = scaleRect.normalized(); + } + } + } + + // +---+-----------+---+ + // | <- 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. + + m_data->engine.alignScales( options, layoutData, + m_data->canvasRect, m_data->scaleRects ); + + if ( !m_data->legendRect.isEmpty() ) + { + // We prefer to align the legend to the canvas - not to + // the complete plot - if possible. + + m_data->legendRect = m_data->engine.alignLegend( + legendHint, m_data->canvasRect, m_data->legendRect ); + } +} diff --git a/libs/qwt/src/qwt_plot_layout.h b/libs/qwt/src/qwt_plot_layout.h new file mode 100644 index 00000000..08902751 --- /dev/null +++ b/libs/qwt/src/qwt_plot_layout.h @@ -0,0 +1,111 @@ +/****************************************************************************** + * 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 activate(), 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 + }; + + Q_DECLARE_FLAGS( Options, Option ) + + explicit QwtPlotLayout(); + virtual ~QwtPlotLayout(); + + void setCanvasMargin( int margin, int axis = -1 ); + int canvasMargin( int axisId ) const; + + void setAlignCanvasToScales( bool ); + + void setAlignCanvasToScale( int axisId, bool ); + bool alignCanvasToScale( int axisId ) const; + + void setSpacing( int ); + int spacing() const; + + void setLegendPosition( QwtPlot::LegendPosition pos, double ratio ); + void setLegendPosition( QwtPlot::LegendPosition pos ); + QwtPlot::LegendPosition legendPosition() const; + + void setLegendRatio( double ratio ); + double legendRatio() const; + + virtual QSize minimumSizeHint( const QwtPlot* ) const; + + virtual void activate( const QwtPlot*, + const QRectF& plotRect, Options options = Options() ); + + virtual void invalidate(); + + QRectF titleRect() const; + QRectF footerRect() const; + QRectF legendRect() const; + QRectF scaleRect( QwtAxisId ) const; + QRectF canvasRect() const; + + protected: + + void setTitleRect( const QRectF& ); + void setFooterRect( const QRectF& ); + void setLegendRect( const QRectF& ); + void setScaleRect( QwtAxisId, const QRectF& ); + void setCanvasRect( const QRectF& ); + + private: + Q_DISABLE_COPY(QwtPlotLayout) + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotLayout::Options ) + +#endif diff --git a/libs/qwt/src/qwt_plot_legenditem.cpp b/libs/qwt/src/qwt_plot_legenditem.cpp new file mode 100644 index 00000000..a4ac65b2 --- /dev/null +++ b/libs/qwt/src/qwt_plot_legenditem.cpp @@ -0,0 +1,912 @@ +/****************************************************************************** + * 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_legenditem.h" +#include "qwt_dyngrid_layout.h" +#include "qwt_text.h" +#include "qwt_graphic.h" +#include "qwt_legend_data.h" +#include "qwt_math.h" + +#include +#include +#include +#include + +namespace +{ + class LayoutItem QWT_FINAL : public QLayoutItem + { + public: + LayoutItem( const QwtPlotLegendItem*, const QwtPlotItem* ); + virtual ~LayoutItem(); + + const QwtPlotItem* plotItem() const; + + void setData( const QwtLegendData& ); + const QwtLegendData& data() const; + + virtual Qt::Orientations expandingDirections() const QWT_OVERRIDE; + virtual QRect geometry() const QWT_OVERRIDE; + virtual bool hasHeightForWidth() const QWT_OVERRIDE; + virtual int heightForWidth( int ) const QWT_OVERRIDE; + virtual bool isEmpty() const QWT_OVERRIDE; + virtual QSize maximumSize() const QWT_OVERRIDE; + virtual int minimumHeightForWidth( int ) const QWT_OVERRIDE; + virtual QSize minimumSize() const QWT_OVERRIDE; + virtual void setGeometry( const QRect& ) QWT_OVERRIDE; + virtual QSize sizeHint() const QWT_OVERRIDE; + + private: + + const QwtPlotLegendItem* m_legendItem; + const QwtPlotItem* m_plotItem; + QwtLegendData m_data; + + QRect m_rect; + }; + + LayoutItem::LayoutItem( + const QwtPlotLegendItem* legendItem, const QwtPlotItem* plotItem ) + : m_legendItem( legendItem ) + , m_plotItem( plotItem) + { + } + + LayoutItem::~LayoutItem() + { + } + + const QwtPlotItem* LayoutItem::plotItem() const + { + return m_plotItem; + } + + void LayoutItem::setData( const QwtLegendData& data ) + { + m_data = data; + } + + const QwtLegendData& LayoutItem::data() const + { + return m_data; + } + + Qt::Orientations LayoutItem::expandingDirections() const + { + return Qt::Horizontal; + } + + bool LayoutItem::hasHeightForWidth() const + { + return !m_data.title().isEmpty(); + } + + int LayoutItem::minimumHeightForWidth( int w ) const + { + return m_legendItem->heightForWidth( m_data, w ); + } + + int LayoutItem::heightForWidth( int w ) const + { + return m_legendItem->heightForWidth( m_data, w ); + } + + bool LayoutItem::isEmpty() const + { + return false; + } + + QSize LayoutItem::maximumSize() const + { + return QSize( QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX ); + } + + QSize LayoutItem::minimumSize() const + { + return m_legendItem->minimumSize( m_data ); + } + + QSize LayoutItem::sizeHint() const + { + return minimumSize(); + } + + void LayoutItem::setGeometry( const QRect& rect ) + { + m_rect = rect; + } + + QRect LayoutItem::geometry() const + { + return m_rect; + } +} + +class QwtPlotLegendItem::PrivateData +{ + public: + PrivateData() + : itemMargin( 4 ) + , itemSpacing( 4 ) + , borderRadius( 0.0 ) + , borderPen( Qt::NoPen ) + , backgroundBrush( Qt::NoBrush ) + , backgroundMode( QwtPlotLegendItem::LegendBackground ) + , canvasAlignment( Qt::AlignRight | Qt::AlignBottom ) + { + canvasOffset[ 0 ] = canvasOffset[1] = 10; + layout = new QwtDynGridLayout(); + layout->setMaxColumns( 2 ); + + layout->setSpacing( 0 ); + layout->setContentsMargins( 0, 0, 0, 0 ); + } + + ~PrivateData() + { + delete layout; + } + + QFont font; + QPen textPen; + int itemMargin; + int itemSpacing; + + double borderRadius; + QPen borderPen; + QBrush backgroundBrush; + QwtPlotLegendItem::BackgroundMode backgroundMode; + + int canvasOffset[2]; + Qt::Alignment canvasAlignment; + + QMap< const QwtPlotItem*, QList< LayoutItem* > > map; + QwtDynGridLayout* layout; +}; + +//! Constructor +QwtPlotLegendItem::QwtPlotLegendItem() + : QwtPlotItem( QwtText( "Legend" ) ) +{ + m_data = new PrivateData; + + setItemInterest( QwtPlotItem::LegendInterest, true ); + setZ( 100.0 ); +} + +//! Destructor +QwtPlotLegendItem::~QwtPlotLegendItem() +{ + clearLegend(); + delete m_data; +} + +//! \return QwtPlotItem::Rtti_PlotLegend +int QwtPlotLegendItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotLegend; +} + +/*! + \brief Set the alignmnet + + Alignment means the position of the legend relative + to the geometry of the plot canvas. + + \param alignment Alignment flags + + \sa alignmentInCanvas(), setMaxColumns() + + \note To align a legend with many items horizontally + the number of columns need to be limited + */ +void QwtPlotLegendItem::setAlignmentInCanvas( Qt::Alignment alignment ) +{ + if ( m_data->canvasAlignment != alignment ) + { + m_data->canvasAlignment = alignment; + itemChanged(); + } +} + +/*! + \return Alignment flags + \sa setAlignmentInCanvas() + */ +Qt::Alignment QwtPlotLegendItem::alignmentInCanvas() const +{ + return m_data->canvasAlignment; +} + +/*! + \brief Limit the number of columns + + When aligning the legend horizontally ( Qt::AlignLeft, Qt::AlignRight ) + the number of columns needs to be limited to avoid, that + the width of the legend grows with an increasing number of entries. + + \param maxColumns Maximum number of columns. 0 means unlimited. + \sa maxColumns(), QwtDynGridLayout::setMaxColumns() + */ +void QwtPlotLegendItem::setMaxColumns( uint maxColumns ) +{ + if ( maxColumns != m_data->layout->maxColumns() ) + { + m_data->layout->setMaxColumns( maxColumns ); + itemChanged(); + } +} + +/*! + \return Maximum number of columns + \sa maxColumns(), QwtDynGridLayout::maxColumns() + */ +uint QwtPlotLegendItem::maxColumns() const +{ + return m_data->layout->maxColumns(); +} + +/*! + \brief Set the margin around legend items + + The default setting for the margin is 0. + + \param margin Margin in pixels + \sa margin(), setSpacing(), setItemMargin(), setItemSpacing + */ +void QwtPlotLegendItem::setMargin( int margin ) +{ + margin = qMax( margin, 0 ); + if ( margin != this->margin() ) + { + m_data->layout->setContentsMargins( + margin, margin, margin, margin ); + + itemChanged(); + } +} + +/*! + \return Margin around the legend items + \sa setMargin(), spacing(), itemMargin(), itemSpacing() + */ +int QwtPlotLegendItem::margin() const +{ + int left; + m_data->layout->getContentsMargins( &left, NULL, NULL, NULL ); + + return left; +} + +/*! + \brief Set the spacing between the legend items + + \param spacing Spacing in pixels + \sa spacing(), setMargin() + */ +void QwtPlotLegendItem::setSpacing( int spacing ) +{ + spacing = qMax( spacing, 0 ); + if ( spacing != m_data->layout->spacing() ) + { + m_data->layout->setSpacing( spacing ); + itemChanged(); + } +} + +/*! + \return Spacing between the legend items + \sa setSpacing(), margin(), itemSpacing(), itemMargin() + */ +int QwtPlotLegendItem::spacing() const +{ + return m_data->layout->spacing(); +} + +/*! + Set the margin around each item + + \param margin Margin + \sa itemMargin(), setItemSpacing(), setMargin(), setSpacing() + */ +void QwtPlotLegendItem::setItemMargin( int margin ) +{ + margin = qMax( margin, 0 ); + if ( margin != m_data->itemMargin ) + { + m_data->itemMargin = margin; + + m_data->layout->invalidate(); + itemChanged(); + } +} + +/*! + \return Margin around each item + \sa setItemMargin(), itemSpacing(), margin(), spacing() + */ +int QwtPlotLegendItem::itemMargin() const +{ + return m_data->itemMargin; +} + +/*! + Set the spacing inside of each item + + \param spacing Spacing + \sa itemSpacing(), setItemMargin(), setMargin(), setSpacing() + */ +void QwtPlotLegendItem::setItemSpacing( int spacing ) +{ + spacing = qMax( spacing, 0 ); + if ( spacing != m_data->itemSpacing ) + { + m_data->itemSpacing = spacing; + + m_data->layout->invalidate(); + itemChanged(); + } + +} + +/*! + \return Spacing inside of each item + \sa setItemSpacing(), itemMargin(), margin(), spacing() + */ +int QwtPlotLegendItem::itemSpacing() const +{ + return m_data->itemSpacing; +} + +/*! + Change the font used for drawing the text label + + \param font Legend font + \sa font() + */ +void QwtPlotLegendItem::setFont( const QFont& font ) +{ + if ( font != m_data->font ) + { + m_data->font = font; + + m_data->layout->invalidate(); + itemChanged(); + } +} + +/*! + \return Font used for drawing the text label + \sa setFont() + */ +QFont QwtPlotLegendItem::font() const +{ + return m_data->font; +} + +/*! + \brief Set the distance between the legend and the canvas border + + The default setting is 10 pixels. + + \param orientations Qt::Horizontal is for the left/right, + Qt::Vertical for the top/bottom offset. + + \param numPixels Distance in pixels + \sa setMargin() + */ +void QwtPlotLegendItem::setOffsetInCanvas( + Qt::Orientations orientations, int numPixels ) +{ + if ( numPixels < 0 ) + numPixels = -1; + + bool isChanged = false; + + int* offset = m_data->canvasOffset; + + if ( orientations & Qt::Horizontal ) + { + if ( numPixels != offset[0] ) + { + offset[0] = numPixels; + isChanged = true; + } + } + + if ( orientations & Qt::Vertical ) + { + if ( numPixels != offset[1] ) + { + offset[1] = numPixels; + isChanged = true; + } + } + + if ( isChanged ) + itemChanged(); +} + +/*! + \param orientation Qt::Horizontal is for the left/right, + Qt::Vertical for the top/bottom padding. + + \return Distance between the legend and the canvas border + \sa setOffsetInCanvas() + */ +int QwtPlotLegendItem::offsetInCanvas( + Qt::Orientation orientation ) const +{ + const int index = ( orientation == Qt::Vertical ) ? 1 : 0; + return m_data->canvasOffset[index]; +} + +/*! + Set the radius for the border + + \param radius A value <= 0 defines a rectangular border + \sa borderRadius(), setBorderPen() + */ +void QwtPlotLegendItem::setBorderRadius( double radius ) +{ + radius = qwtMaxF( 0.0, radius ); + + if ( radius != m_data->borderRadius ) + { + m_data->borderRadius = radius; + itemChanged(); + } +} + +/*! + \return Radius of the border + \sa setBorderRadius(), setBorderPen() + */ +double QwtPlotLegendItem::borderRadius() const +{ + return m_data->borderRadius; +} + +/*! + Set the pen for drawing the border + + \param pen Border pen + \sa borderPen(), setBackgroundBrush() + */ +void QwtPlotLegendItem::setBorderPen( const QPen& pen ) +{ + if ( m_data->borderPen != pen ) + { + m_data->borderPen = pen; + itemChanged(); + } +} + +/*! + \return Pen for drawing the border + \sa setBorderPen(), backgroundBrush() + */ +QPen QwtPlotLegendItem::borderPen() const +{ + return m_data->borderPen; +} + +/*! + \brief Set the background brush + + The brush is used to fill the background + + \param brush Brush + \sa backgroundBrush(), setBackgroundMode(), drawBackground() + */ +void QwtPlotLegendItem::setBackgroundBrush( const QBrush& brush ) +{ + if ( m_data->backgroundBrush != brush ) + { + m_data->backgroundBrush = brush; + itemChanged(); + } +} + +/*! + \return Brush is used to fill the background + \sa setBackgroundBrush(), backgroundMode(), drawBackground() + */ +QBrush QwtPlotLegendItem::backgroundBrush() const +{ + return m_data->backgroundBrush; +} + +/*! + \brief Set the background mode + + Depending on the mode the complete legend or each item + might have an background. + + The default setting is LegendBackground. + + \sa backgroundMode(), setBackgroundBrush(), drawBackground() + */ +void QwtPlotLegendItem::setBackgroundMode( BackgroundMode mode ) +{ + if ( mode != m_data->backgroundMode ) + { + m_data->backgroundMode = mode; + itemChanged(); + } +} + +/*! + \return backgroundMode + \sa setBackgroundMode(), backgroundBrush(), drawBackground() + */ +QwtPlotLegendItem::BackgroundMode QwtPlotLegendItem::backgroundMode() const +{ + return m_data->backgroundMode; +} + +/*! + \brief Set the pen for drawing text labels + + \param pen Text pen + \sa textPen(), setFont() + */ +void QwtPlotLegendItem::setTextPen( const QPen& pen ) +{ + if ( m_data->textPen != pen ) + { + m_data->textPen = pen; + itemChanged(); + } +} + +/*! + \return Pen for drawing text labels + \sa setTextPen(), font() + */ +QPen QwtPlotLegendItem::textPen() const +{ + return m_data->textPen; +} + +/*! + Draw the legend + + \param painter Painter + \param xMap x Scale Map + \param yMap y Scale Map + \param canvasRect Contents rectangle of the canvas in painter coordinates + */ +void QwtPlotLegendItem::draw( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + Q_UNUSED( xMap ); + Q_UNUSED( yMap ); + + m_data->layout->setGeometry( geometry( canvasRect ) ); + if ( m_data->layout->geometry().isEmpty() ) + { + // don't draw a legend when having no content + return; + } + + if ( m_data->backgroundMode == QwtPlotLegendItem::LegendBackground ) + drawBackground( painter, m_data->layout->geometry() ); + + for ( int i = 0; i < m_data->layout->count(); i++ ) + { + const LayoutItem* layoutItem = + static_cast< LayoutItem* >( m_data->layout->itemAt( i ) ); + + if ( m_data->backgroundMode == QwtPlotLegendItem::ItemBackground ) + drawBackground( painter, layoutItem->geometry() ); + + painter->save(); + + drawLegendData( painter, layoutItem->plotItem(), + layoutItem->data(), layoutItem->geometry() ); + + painter->restore(); + } +} + +/*! + Draw a rounded rect + + \param painter Painter + \param rect Bounding rectangle + + \sa setBorderRadius(), setBorderPen(), + setBackgroundBrush(), setBackgroundMode() + */ +void QwtPlotLegendItem::drawBackground( + QPainter* painter, const QRectF& rect ) const +{ + painter->save(); + + painter->setPen( m_data->borderPen ); + painter->setBrush( m_data->backgroundBrush ); + + const double radius = m_data->borderRadius; + painter->drawRoundedRect( rect, radius, radius ); + + painter->restore(); +} + +/*! + Calculate the geometry of the legend on the canvas + + \param canvasRect Geometry of the canvas + \return Geometry of the legend + */ +QRect QwtPlotLegendItem::geometry( const QRectF& canvasRect ) const +{ + QRect rect; + rect.setSize( m_data->layout->sizeHint() ); + + if ( m_data->canvasAlignment & Qt::AlignHCenter ) + { + int x = qRound( canvasRect.center().x() ); + rect.moveCenter( QPoint( x, rect.center().y() ) ); + } + else if ( m_data->canvasAlignment & Qt::AlignRight ) + { + const int offset = offsetInCanvas( Qt::Horizontal ); + rect.moveRight( qwtFloor( canvasRect.right() - offset ) ); + } + else + { + const int offset = offsetInCanvas( Qt::Horizontal ); + rect.moveLeft( qwtCeil( canvasRect.left() + offset ) ); + } + + if ( m_data->canvasAlignment & Qt::AlignVCenter ) + { + int y = qRound( canvasRect.center().y() ); + rect.moveCenter( QPoint( rect.center().x(), y ) ); + } + else if ( m_data->canvasAlignment & Qt::AlignBottom ) + { + const int offset = offsetInCanvas( Qt::Vertical ); + rect.moveBottom( qwtFloor( canvasRect.bottom() - offset ) ); + } + else + { + const int offset = offsetInCanvas( Qt::Vertical ); + rect.moveTop( qwtCeil( canvasRect.top() + offset ) ); + } + + return rect; +} + +/*! + Update the legend items according to modifications of a + plot item + + \param plotItem Plot item + \param data Attributes of the legend entries + */ +void QwtPlotLegendItem::updateLegend( const QwtPlotItem* plotItem, + const QList< QwtLegendData >& data ) +{ + if ( plotItem == NULL ) + return; + + QList< LayoutItem* > layoutItems; + + QMap< const QwtPlotItem*, QList< LayoutItem* > >::const_iterator it = + m_data->map.constFind( plotItem ); + if ( it != m_data->map.constEnd() ) + layoutItems = it.value(); + + bool changed = false; + + if ( data.size() != layoutItems.size() ) + { + changed = true; + + for ( int i = 0; i < layoutItems.size(); i++ ) + { + m_data->layout->removeItem( layoutItems[i] ); + delete layoutItems[i]; + } + layoutItems.clear(); + + if ( it != m_data->map.constEnd() ) + m_data->map.remove( plotItem ); + + if ( !data.isEmpty() ) + { + layoutItems.reserve( data.size() ); + + for ( int i = 0; i < data.size(); i++ ) + { + LayoutItem* layoutItem = + new LayoutItem( this, plotItem ); + m_data->layout->addItem( layoutItem ); + layoutItems += layoutItem; + } + + m_data->map.insert( plotItem, layoutItems ); + } + } + + for ( int i = 0; i < data.size(); i++ ) + { + if ( layoutItems[i]->data().values() != data[i].values() ) + { + layoutItems[i]->setData( data[i] ); + changed = true; + } + } + + if ( changed ) + { + m_data->layout->invalidate(); + itemChanged(); + } +} + +//! Remove all items from the legend +void QwtPlotLegendItem::clearLegend() +{ + if ( !m_data->map.isEmpty() ) + { + m_data->map.clear(); + + for ( int i = m_data->layout->count() - 1; i >= 0; i-- ) + delete m_data->layout->takeAt( i ); + + itemChanged(); + } +} + +/*! + Draw an entry on the legend + + \param painter Qt Painter + \param plotItem Plot item, represented by the entry + \param data Attributes of the legend entry + \param rect Bounding rectangle for the entry + */ +void QwtPlotLegendItem::drawLegendData( QPainter* painter, + const QwtPlotItem* plotItem, const QwtLegendData& data, + const QRectF& rect ) const +{ + Q_UNUSED( plotItem ); + + const int m = m_data->itemMargin; + const QRectF r = rect.toRect().adjusted( m, m, -m, -m ); + + painter->setClipRect( r, Qt::IntersectClip ); + + int titleOff = 0; + + const QwtGraphic graphic = data.icon(); + if ( !graphic.isEmpty() ) + { + QRectF iconRect( r.topLeft(), graphic.defaultSize() ); + + iconRect.moveCenter( + QPoint( iconRect.center().x(), rect.center().y() ) ); + + graphic.render( painter, iconRect, Qt::KeepAspectRatio ); + + titleOff += iconRect.width() + m_data->itemSpacing; + } + + const QwtText text = data.title(); + if ( !text.isEmpty() ) + { + painter->setPen( textPen() ); + painter->setFont( font() ); + + const QRectF textRect = r.adjusted( titleOff, 0, 0, 0 ); + text.draw( painter, textRect ); + } +} + +/*! + Minimum size hint needed to display an entry + + \param data Attributes of the legend entry + \return Minimum size + */ +QSize QwtPlotLegendItem::minimumSize( const QwtLegendData& data ) const +{ + QSize size( 2 * m_data->itemMargin, 2 * m_data->itemMargin ); + + if ( !data.isValid() ) + return size; + + const QwtGraphic graphic = data.icon(); + const QwtText text = data.title(); + + int w = 0; + int h = 0; + + if ( !graphic.isNull() ) + { + w = graphic.width(); + h = graphic.height(); + } + + if ( !text.isEmpty() ) + { + const QSizeF sz = text.textSize( font() ); + + w += qwtCeil( sz.width() ); + h = qMax( h, qwtCeil( sz.height() ) ); + } + + if ( graphic.width() > 0 && !text.isEmpty() ) + w += m_data->itemSpacing; + + size += QSize( w, h ); + return size; +} + +/*! + \return The preferred height, for a width. + \param data Attributes of the legend entry + \param width Width + */ +int QwtPlotLegendItem::heightForWidth( + const QwtLegendData& data, int width ) const +{ + width -= 2 * m_data->itemMargin; + + const QwtGraphic graphic = data.icon(); + const QwtText text = data.title(); + + if ( text.isEmpty() ) + return graphic.height(); + + if ( graphic.width() > 0 ) + width -= graphic.width() + m_data->itemSpacing; + + int h = text.heightForWidth( width, font() ); + h += 2 * m_data->itemMargin; + + return qMax( graphic.height(), h ); +} + +/*! + \return All plot items with an entry on the legend + \note A plot item might have more than one entry on the legend + */ +QList< const QwtPlotItem* > QwtPlotLegendItem::plotItems() const +{ + return m_data->map.keys(); +} + +/*! + \return Geometries of the items of a plot item + \note Usually a plot item has only one entry on the legend + */ +QList< QRect > QwtPlotLegendItem::legendGeometries( + const QwtPlotItem* plotItem ) const +{ + QList< LayoutItem* > layoutItems; + + QMap< const QwtPlotItem*, QList< LayoutItem* > >::const_iterator it = + m_data->map.constFind( plotItem ); + if ( it != m_data->map.constEnd() ) + layoutItems = it.value(); + + QList< QRect > geometries; + geometries.reserve(layoutItems.size() ); + + for ( int i = 0; i < layoutItems.size(); i++ ) + geometries += layoutItems[i]->geometry(); + + return geometries; +} diff --git a/libs/qwt/src/qwt_plot_legenditem.h b/libs/qwt/src/qwt_plot_legenditem.h new file mode 100644 index 00000000..1be4286e --- /dev/null +++ b/libs/qwt/src/qwt_plot_legenditem.h @@ -0,0 +1,135 @@ +/****************************************************************************** + * 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_LEGEND_ITEM_H +#define QWT_PLOT_LEGEND_ITEM_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" + +class QFont; + +/*! + \brief A class which draws a legend inside the plot canvas + + QwtPlotLegendItem can be used to draw a inside the plot canvas. + It can be used together with a QwtLegend or instead of it + to have more space for the plot canvas. + + In opposite to QwtLegend the legend item is not interactive. + To identify mouse clicks on a legend item an event filter + needs to be installed catching mouse events ob the plot canvas. + The geometries of the legend items are available using + legendGeometries(). + + The legend item is aligned to plot canvas according to + its alignment() flags. It might have a background for the + complete legend ( usually semi transparent ) or for + each legend item. + + \note An external QwtLegend with a transparent background + on top the plot canvas might be another option + with a similar effect. + */ + +class QWT_EXPORT QwtPlotLegendItem : public QwtPlotItem +{ + public: + /*! + \brief Background mode + + Depending on the mode the complete legend or each item + might have an background. + + The default setting is LegendBackground. + + \sa setBackgroundMode(), setBackgroundBrush(), drawBackground() + */ + enum BackgroundMode + { + //! The legend has a background + LegendBackground, + + //! Each item has a background + ItemBackground + }; + + explicit QwtPlotLegendItem(); + virtual ~QwtPlotLegendItem(); + + virtual int rtti() const QWT_OVERRIDE; + + void setAlignmentInCanvas( Qt::Alignment ); + Qt::Alignment alignmentInCanvas() const; + + void setOffsetInCanvas( Qt::Orientations, int numPixels ); + int offsetInCanvas( Qt::Orientation ) const; + + void setMaxColumns( uint ); + uint maxColumns() const; + + void setMargin( int ); + int margin() const; + + void setSpacing( int ); + int spacing() const; + + void setItemMargin( int ); + int itemMargin() const; + + void setItemSpacing( int ); + int itemSpacing() const; + + void setFont( const QFont& ); + QFont font() const; + + void setBorderRadius( double ); + double borderRadius() const; + + void setBorderPen( const QPen& ); + QPen borderPen() const; + + void setBackgroundBrush( const QBrush& ); + QBrush backgroundBrush() const; + + void setBackgroundMode( BackgroundMode ); + BackgroundMode backgroundMode() const; + + void setTextPen( const QPen& ); + QPen textPen() const; + + virtual void draw( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + void clearLegend(); + + virtual void updateLegend( const QwtPlotItem*, + const QList< QwtLegendData >& ) QWT_OVERRIDE; + + virtual QRect geometry( const QRectF& canvasRect ) const; + + virtual QSize minimumSize( const QwtLegendData& ) const; + virtual int heightForWidth( const QwtLegendData&, int width ) const; + + QList< const QwtPlotItem* > plotItems() const; + QList< QRect > legendGeometries( const QwtPlotItem* ) const; + + protected: + virtual void drawLegendData( QPainter*, + const QwtPlotItem*, const QwtLegendData&, const QRectF& ) const; + + virtual void drawBackground( QPainter*, const QRectF& rect ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_magnifier.cpp b/libs/qwt/src/qwt_plot_magnifier.cpp new file mode 100644 index 00000000..a1f0d278 --- /dev/null +++ b/libs/qwt/src/qwt_plot_magnifier.cpp @@ -0,0 +1,172 @@ +/****************************************************************************** + * 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_scale_map.h" +#include "qwt_plot_magnifier.h" + +class QwtPlotMagnifier::PrivateData +{ + public: + PrivateData() + { + for ( int axis = 0; axis < QwtAxis::AxisPositions; axis++ ) + isAxisEnabled[axis] = true; + } + + bool isAxisEnabled[QwtAxis::AxisPositions]; +}; + +/*! + Constructor + \param canvas Plot canvas to be magnified + */ +QwtPlotMagnifier::QwtPlotMagnifier( QWidget* canvas ) + : QwtMagnifier( canvas ) +{ + m_data = new PrivateData(); +} + +//! Destructor +QwtPlotMagnifier::~QwtPlotMagnifier() +{ + delete m_data; +} + +/*! + \brief En/Disable an axis + + Only Axes that are enabled will be zoomed. + All other axes will remain unchanged. + + \param axisId Axis + \param on On/Off + + \sa isAxisEnabled() + */ +void QwtPlotMagnifier::setAxisEnabled( QwtAxisId axisId, bool on ) +{ + if ( QwtAxis::isValid( axisId ) ) + m_data->isAxisEnabled[axisId] = on; +} + +/*! + Test if an axis is enabled + + \param axisId Axis + \return True, if the axis is enabled + + \sa setAxisEnabled() + */ +bool QwtPlotMagnifier::isAxisEnabled( QwtAxisId axisId ) const +{ + if ( QwtAxis::isValid( axisId ) ) + return m_data->isAxisEnabled[axisId]; + + return true; +} + +//! Return observed plot canvas +QWidget* QwtPlotMagnifier::canvas() +{ + return parentWidget(); +} + +//! Return Observed plot canvas +const QWidget* QwtPlotMagnifier::canvas() const +{ + return parentWidget(); +} + +//! Return plot widget, containing the observed plot canvas +QwtPlot* QwtPlotMagnifier::plot() +{ + QWidget* w = canvas(); + if ( w ) + w = w->parentWidget(); + + return qobject_cast< QwtPlot* >( w ); +} + +//! Return plot widget, containing the observed plot canvas +const QwtPlot* QwtPlotMagnifier::plot() const +{ + const QWidget* w = canvas(); + if ( w ) + w = w->parentWidget(); + + return qobject_cast< const QwtPlot* >( w ); +} + +/*! + Zoom in/out the axes scales + \param factor A value < 1.0 zooms in, a value > 1.0 zooms out. + */ +void QwtPlotMagnifier::rescale( double factor ) +{ + QwtPlot* plt = plot(); + if ( plt == NULL ) + return; + + factor = qAbs( factor ); + if ( factor == 1.0 || factor == 0.0 ) + return; + + bool doReplot = false; + + const bool autoReplot = plt->autoReplot(); + plt->setAutoReplot( false ); + + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + if ( isAxisEnabled( axisId ) ) + { + const QwtScaleMap scaleMap = plt->canvasMap( axisId ); + + double v1 = scaleMap.s1(); + double v2 = scaleMap.s2(); + + if ( scaleMap.transformation() ) + { + // the coordinate system of the paint device is always linear + + v1 = scaleMap.transform( v1 ); // scaleMap.p1() + v2 = scaleMap.transform( v2 ); // scaleMap.p2() + } + + const double center = 0.5 * ( v1 + v2 ); + const double width_2 = 0.5 * ( v2 - v1 ) * factor; + + v1 = center - width_2; + v2 = center + width_2; + + if ( scaleMap.transformation() ) + { + v1 = scaleMap.invTransform( v1 ); + v2 = scaleMap.invTransform( v2 ); + } + + plt->setAxisScale( axisId, v1, v2 ); + doReplot = true; + } + } + } + + plt->setAutoReplot( autoReplot ); + + if ( doReplot ) + plt->replot(); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_plot_magnifier.cpp" +#endif diff --git a/libs/qwt/src/qwt_plot_magnifier.h b/libs/qwt/src/qwt_plot_magnifier.h new file mode 100644 index 00000000..cd2c1cb1 --- /dev/null +++ b/libs/qwt/src/qwt_plot_magnifier.h @@ -0,0 +1,55 @@ +/****************************************************************************** + * 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_MAGNIFIER_H +#define QWT_PLOT_MAGNIFIER_H + +#include "qwt_global.h" +#include "qwt_axis_id.h" +#include "qwt_magnifier.h" + +class QwtPlot; + +/*! + \brief QwtPlotMagnifier provides zooming, by magnifying in steps. + + Using QwtPlotMagnifier a plot can be zoomed in/out in steps using + keys, the mouse wheel or moving a mouse button in vertical direction. + + Together with QwtPlotZoomer and QwtPlotPanner it is possible to implement + individual and powerful navigation of the plot canvas. + + \sa QwtPlotZoomer, QwtPlotPanner, QwtPlot + */ +class QWT_EXPORT QwtPlotMagnifier : public QwtMagnifier +{ + Q_OBJECT + + public: + explicit QwtPlotMagnifier( QWidget* ); + virtual ~QwtPlotMagnifier(); + + void setAxisEnabled( QwtAxisId, bool on ); + bool isAxisEnabled( QwtAxisId ) const; + + QWidget* canvas(); + const QWidget* canvas() const; + + QwtPlot* plot(); + const QwtPlot* plot() const; + + public Q_SLOTS: + virtual void rescale( double factor ) QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_marker.cpp b/libs/qwt/src/qwt_plot_marker.cpp new file mode 100644 index 00000000..0f910f0e --- /dev/null +++ b/libs/qwt/src/qwt_plot_marker.cpp @@ -0,0 +1,644 @@ +/****************************************************************************** + * 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_marker.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include "qwt_symbol.h" +#include "qwt_text.h" +#include "qwt_graphic.h" +#include "qwt_math.h" + +#include + +class QwtPlotMarker::PrivateData +{ + public: + PrivateData() + : labelAlignment( Qt::AlignCenter ) + , labelOrientation( Qt::Horizontal ) + , spacing( 2 ) + , symbol( NULL ) + , style( QwtPlotMarker::NoLine ) + , xValue( 0.0 ) + , yValue( 0.0 ) + { + } + + ~PrivateData() + { + delete symbol; + } + + QwtText label; + Qt::Alignment labelAlignment; + Qt::Orientation labelOrientation; + int spacing; + + QPen pen; + const QwtSymbol* symbol; + LineStyle style; + + double xValue; + double yValue; +}; + +//! Sets alignment to Qt::AlignCenter, and style to QwtPlotMarker::NoLine +QwtPlotMarker::QwtPlotMarker() +{ + m_data = new PrivateData; + setZ( 30.0 ); +} + +//! Sets alignment to Qt::AlignCenter, and style to QwtPlotMarker::NoLine +QwtPlotMarker::QwtPlotMarker( const QString& title ) + : QwtPlotItem( QwtText( title ) ) +{ + m_data = new PrivateData; + setZ( 30.0 ); +} + +//! Sets alignment to Qt::AlignCenter, and style to QwtPlotMarker::NoLine +QwtPlotMarker::QwtPlotMarker( const QwtText& title ) + : QwtPlotItem( title ) +{ + m_data = new PrivateData; + setZ( 30.0 ); +} + +//! Destructor +QwtPlotMarker::~QwtPlotMarker() +{ + delete m_data; +} + +//! \return QwtPlotItem::Rtti_PlotMarker +int QwtPlotMarker::rtti() const +{ + return QwtPlotItem::Rtti_PlotMarker; +} + +//! Return Value +QPointF QwtPlotMarker::value() const +{ + return QPointF( m_data->xValue, m_data->yValue ); +} + +//! Return x Value +double QwtPlotMarker::xValue() const +{ + return m_data->xValue; +} + +//! Return y Value +double QwtPlotMarker::yValue() const +{ + return m_data->yValue; +} + +//! Set Value +void QwtPlotMarker::setValue( const QPointF& pos ) +{ + setValue( pos.x(), pos.y() ); +} + +//! Set Value +void QwtPlotMarker::setValue( double x, double y ) +{ + if ( x != m_data->xValue || y != m_data->yValue ) + { + m_data->xValue = x; + m_data->yValue = y; + itemChanged(); + } +} + +//! Set X Value +void QwtPlotMarker::setXValue( double x ) +{ + setValue( x, m_data->yValue ); +} + +//! Set Y Value +void QwtPlotMarker::setYValue( double y ) +{ + setValue( m_data->xValue, y ); +} + +/*! + Draw the marker + + \param painter Painter + \param xMap x Scale Map + \param yMap y Scale Map + \param canvasRect Contents rectangle of the canvas in painter coordinates + */ +void QwtPlotMarker::draw( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + const QPointF pos( xMap.transform( m_data->xValue ), + yMap.transform( m_data->yValue ) ); + + drawLines( painter, canvasRect, pos ); + drawSymbol( painter, canvasRect, pos ); + drawLabel( painter, canvasRect, pos ); +} + +/*! + Draw the lines marker + + \param painter Painter + \param canvasRect Contents rectangle of the canvas in painter coordinates + \param pos Position of the marker, translated into widget coordinates + + \sa drawLabel(), drawSymbol() + */ +void QwtPlotMarker::drawLines( QPainter* painter, + const QRectF& canvasRect, const QPointF& pos ) const +{ + if ( m_data->style == NoLine ) + return; + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + painter->setPen( m_data->pen ); + if ( m_data->style == QwtPlotMarker::HLine || + m_data->style == QwtPlotMarker::Cross ) + { + double y = pos.y(); + if ( doAlign ) + y = qRound( y ); + + QwtPainter::drawLine( painter, canvasRect.left(), + y, canvasRect.right() - 1.0, y ); + } + if ( m_data->style == QwtPlotMarker::VLine || + m_data->style == QwtPlotMarker::Cross ) + { + double x = pos.x(); + if ( doAlign ) + x = qRound( x ); + + QwtPainter::drawLine( painter, x, + canvasRect.top(), x, canvasRect.bottom() - 1.0 ); + } +} + +/*! + Draw the symbol of the marker + + \param painter Painter + \param canvasRect Contents rectangle of the canvas in painter coordinates + \param pos Position of the marker, translated into widget coordinates + + \sa drawLabel(), QwtSymbol::drawSymbol() + */ +void QwtPlotMarker::drawSymbol( QPainter* painter, + const QRectF& canvasRect, const QPointF& pos ) const +{ + if ( m_data->symbol == NULL ) + return; + + const QwtSymbol& symbol = *m_data->symbol; + + if ( symbol.style() != QwtSymbol::NoSymbol ) + { + const QSizeF sz = symbol.size(); + + const QRectF clipRect = canvasRect.adjusted( + -sz.width(), -sz.height(), sz.width(), sz.height() ); + + if ( clipRect.contains( pos ) ) + symbol.drawSymbol( painter, pos ); + } +} + +/*! + Align and draw the text label of the marker + + \param painter Painter + \param canvasRect Contents rectangle of the canvas in painter coordinates + \param pos Position of the marker, translated into widget coordinates + + \sa drawLabel(), drawSymbol() + */ +void QwtPlotMarker::drawLabel( QPainter* painter, + const QRectF& canvasRect, const QPointF& pos ) const +{ + if ( m_data->label.isEmpty() ) + return; + + Qt::Alignment align = m_data->labelAlignment; + QPointF alignPos = pos; + + QSizeF symbolOff( 0, 0 ); + + switch ( m_data->style ) + { + case QwtPlotMarker::VLine: + { + // In VLine-style the y-position is pointless and + // the alignment flags are relative to the canvas + + if ( m_data->labelAlignment & Qt::AlignTop ) + { + alignPos.setY( canvasRect.top() ); + align &= ~Qt::AlignTop; + align |= Qt::AlignBottom; + } + else if ( m_data->labelAlignment & Qt::AlignBottom ) + { + // In HLine-style the x-position is pointless and + // the alignment flags are relative to the canvas + + alignPos.setY( canvasRect.bottom() - 1 ); + align &= ~Qt::AlignBottom; + align |= Qt::AlignTop; + } + else + { + alignPos.setY( canvasRect.center().y() ); + } + break; + } + case QwtPlotMarker::HLine: + { + if ( m_data->labelAlignment & Qt::AlignLeft ) + { + alignPos.setX( canvasRect.left() ); + align &= ~Qt::AlignLeft; + align |= Qt::AlignRight; + } + else if ( m_data->labelAlignment & Qt::AlignRight ) + { + alignPos.setX( canvasRect.right() - 1 ); + align &= ~Qt::AlignRight; + align |= Qt::AlignLeft; + } + else + { + alignPos.setX( canvasRect.center().x() ); + } + break; + } + default: + { + if ( m_data->symbol && + ( m_data->symbol->style() != QwtSymbol::NoSymbol ) ) + { + symbolOff = m_data->symbol->size() + QSizeF( 1, 1 ); + symbolOff /= 2; + } + } + } + + qreal pw2 = m_data->pen.widthF() / 2.0; + if ( pw2 == 0.0 ) + pw2 = 0.5; + + const int spacing = m_data->spacing; + + const qreal xOff = qwtMaxF( pw2, symbolOff.width() ); + const qreal yOff = qwtMaxF( pw2, symbolOff.height() ); + + const QSizeF textSize = m_data->label.textSize( painter->font() ); + + if ( align & Qt::AlignLeft ) + { + alignPos.rx() -= xOff + spacing; + if ( m_data->labelOrientation == Qt::Vertical ) + alignPos.rx() -= textSize.height(); + else + alignPos.rx() -= textSize.width(); + } + else if ( align & Qt::AlignRight ) + { + alignPos.rx() += xOff + spacing; + } + else + { + if ( m_data->labelOrientation == Qt::Vertical ) + alignPos.rx() -= textSize.height() / 2; + else + alignPos.rx() -= textSize.width() / 2; + } + + if ( align & Qt::AlignTop ) + { + alignPos.ry() -= yOff + spacing; + if ( m_data->labelOrientation != Qt::Vertical ) + alignPos.ry() -= textSize.height(); + } + else if ( align & Qt::AlignBottom ) + { + alignPos.ry() += yOff + spacing; + if ( m_data->labelOrientation == Qt::Vertical ) + alignPos.ry() += textSize.width(); + } + else + { + if ( m_data->labelOrientation == Qt::Vertical ) + alignPos.ry() += textSize.width() / 2; + else + alignPos.ry() -= textSize.height() / 2; + } + + painter->translate( alignPos.x(), alignPos.y() ); + if ( m_data->labelOrientation == Qt::Vertical ) + painter->rotate( -90.0 ); + + const QRectF textRect( 0, 0, textSize.width(), textSize.height() ); + m_data->label.draw( painter, textRect ); +} + +/*! + \brief Set the line style + \param style Line style. + \sa lineStyle() + */ +void QwtPlotMarker::setLineStyle( LineStyle style ) +{ + if ( style != m_data->style ) + { + m_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return the line style + \sa setLineStyle() + */ +QwtPlotMarker::LineStyle QwtPlotMarker::lineStyle() const +{ + return m_data->style; +} + +/*! + \brief Assign a symbol + \param symbol New symbol + \sa symbol() + */ +void QwtPlotMarker::setSymbol( const QwtSymbol* symbol ) +{ + if ( symbol != m_data->symbol ) + { + delete m_data->symbol; + m_data->symbol = symbol; + + if ( symbol ) + setLegendIconSize( symbol->boundingRect().size() ); + + legendChanged(); + itemChanged(); + } +} + +/*! + \return the symbol + \sa setSymbol(), QwtSymbol + */ +const QwtSymbol* QwtPlotMarker::symbol() const +{ + return m_data->symbol; +} + +/*! + \brief Set the label + \param label Label text + \sa label() + */ +void QwtPlotMarker::setLabel( const QwtText& label ) +{ + if ( label != m_data->label ) + { + m_data->label = label; + itemChanged(); + } +} + +/*! + \return the label + \sa setLabel() + */ +QwtText QwtPlotMarker::label() const +{ + return m_data->label; +} + +/*! + \brief Set the alignment of the label + + In case of QwtPlotMarker::HLine the alignment is relative to the + y position of the marker, but the horizontal flags correspond to the + canvas rectangle. In case of QwtPlotMarker::VLine the alignment is + relative to the x position of the marker, but the vertical flags + correspond to the canvas rectangle. + + In all other styles the alignment is relative to the marker's position. + + \param align Alignment. + \sa labelAlignment(), labelOrientation() + */ +void QwtPlotMarker::setLabelAlignment( Qt::Alignment align ) +{ + if ( align != m_data->labelAlignment ) + { + m_data->labelAlignment = align; + itemChanged(); + } +} + +/*! + \return the label alignment + \sa setLabelAlignment(), setLabelOrientation() + */ +Qt::Alignment QwtPlotMarker::labelAlignment() const +{ + return m_data->labelAlignment; +} + +/*! + \brief Set the orientation of the label + + When orientation is Qt::Vertical the label is rotated by 90.0 degrees + ( from bottom to top ). + + \param orientation Orientation of the label + + \sa labelOrientation(), setLabelAlignment() + */ +void QwtPlotMarker::setLabelOrientation( Qt::Orientation orientation ) +{ + if ( orientation != m_data->labelOrientation ) + { + m_data->labelOrientation = orientation; + itemChanged(); + } +} + +/*! + \return the label orientation + \sa setLabelOrientation(), labelAlignment() + */ +Qt::Orientation QwtPlotMarker::labelOrientation() const +{ + return m_data->labelOrientation; +} + +/*! + \brief Set the spacing + + When the label is not centered on the marker position, the spacing + is the distance between the position and the label. + + \param spacing Spacing + \sa spacing(), setLabelAlignment() + */ +void QwtPlotMarker::setSpacing( int spacing ) +{ + if ( spacing < 0 ) + spacing = 0; + + if ( spacing == m_data->spacing ) + return; + + m_data->spacing = spacing; + itemChanged(); +} + +/*! + \return the spacing + \sa setSpacing() + */ +int QwtPlotMarker::spacing() const +{ + return m_data->spacing; +} + +/*! + Build and assign a line 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 QwtPlotMarker::setLinePen( const QColor& color, qreal width, Qt::PenStyle style ) +{ + setLinePen( QPen( color, width, style ) ); +} + +/*! + Specify a pen for the line. + + \param pen New pen + \sa linePen() + */ +void QwtPlotMarker::setLinePen( const QPen& pen ) +{ + if ( pen != m_data->pen ) + { + m_data->pen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return the line pen + \sa setLinePen() + */ +const QPen& QwtPlotMarker::linePen() const +{ + return m_data->pen; +} + +QRectF QwtPlotMarker::boundingRect() const +{ + // width/height of -1 does not affect the autoscale calculation + + switch (m_data->style) + { + case QwtPlotMarker::HLine: + return QRectF( m_data->xValue, m_data->yValue, -1.0, 0.0 ); + + case QwtPlotMarker::VLine: + return QRectF( m_data->xValue, m_data->yValue, 0.0, -1.0 ); + + default: + return QRectF( m_data->xValue, m_data->yValue, 0.0, 0.0 ); + } +} + +/*! + \return Icon representing the marker on the legend + + \param index Index of the legend entry + ( usually there is only one ) + \param size Icon size + + \sa setLegendIconSize(), legendData() + */ +QwtGraphic QwtPlotMarker::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 ( m_data->style != QwtPlotMarker::NoLine ) + { + painter.setPen( m_data->pen ); + + if ( m_data->style == QwtPlotMarker::HLine || + m_data->style == QwtPlotMarker::Cross ) + { + const double y = 0.5 * size.height(); + + QwtPainter::drawLine( &painter, + 0.0, y, size.width(), y ); + } + + if ( m_data->style == QwtPlotMarker::VLine || + m_data->style == QwtPlotMarker::Cross ) + { + const double x = 0.5 * size.width(); + + QwtPainter::drawLine( &painter, + x, 0.0, x, size.height() ); + } + } + + if ( m_data->symbol ) + { + const QRect r( 0.0, 0.0, size.width(), size.height() ); + m_data->symbol->drawSymbol( &painter, r ); + } + + return icon; +} + diff --git a/libs/qwt/src/qwt_plot_marker.h b/libs/qwt/src/qwt_plot_marker.h new file mode 100644 index 00000000..809466b3 --- /dev/null +++ b/libs/qwt/src/qwt_plot_marker.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_PLOT_MARKER_H +#define QWT_PLOT_MARKER_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" + +class QString; +class QRectF; +class QwtText; +class QwtSymbol; + +/*! + \brief A class for drawing markers + + A marker can be a horizontal line, a vertical line, + a symbol, a label or any combination of them, which can + be drawn around a center point inside a bounding rectangle. + + The setSymbol() member assigns a symbol to the marker. + The symbol is drawn at the specified point. + + With setLabel(), a label can be assigned to the marker. + The setLabelAlignment() member specifies where the label is + drawn. All the Align*-constants in Qt::AlignmentFlags (see Qt documentation) + are valid. The interpretation of the alignment depends on the marker's + line style. The alignment refers to the center point of + the marker, which means, for example, that the label would be printed + left above the center point if the alignment was set to + Qt::AlignLeft | Qt::AlignTop. + + \note QwtPlotTextLabel is intended to align a text label + according to the geometry of canvas + ( unrelated to plot coordinates ) + */ + +class QWT_EXPORT QwtPlotMarker : public QwtPlotItem +{ + public: + + /*! + Line styles. + \sa setLineStyle(), lineStyle() + */ + enum LineStyle + { + //! No line + NoLine, + + //! A horizontal line + HLine, + + //! A vertical line + VLine, + + //! A crosshair + Cross + }; + + explicit QwtPlotMarker(); + explicit QwtPlotMarker( const QString& title ); + explicit QwtPlotMarker( const QwtText& title ); + + virtual ~QwtPlotMarker(); + + virtual int rtti() const QWT_OVERRIDE; + + double xValue() const; + double yValue() const; + QPointF value() const; + + void setXValue( double ); + void setYValue( double ); + void setValue( double, double ); + void setValue( const QPointF& ); + + void setLineStyle( LineStyle ); + LineStyle lineStyle() const; + + void setLinePen( const QColor&, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setLinePen( const QPen& ); + const QPen& linePen() const; + + void setSymbol( const QwtSymbol* ); + const QwtSymbol* symbol() const; + + void setLabel( const QwtText& ); + QwtText label() const; + + void setLabelAlignment( Qt::Alignment ); + Qt::Alignment labelAlignment() const; + + void setLabelOrientation( Qt::Orientation ); + Qt::Orientation labelOrientation() const; + + void setSpacing( int ); + int spacing() const; + + virtual void draw( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& ) const QWT_OVERRIDE; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + virtual QwtGraphic legendIcon( + int index, const QSizeF& ) const QWT_OVERRIDE; + + protected: + virtual void drawLines( QPainter*, + const QRectF&, const QPointF& ) const; + + virtual void drawSymbol( QPainter*, + const QRectF&, const QPointF& ) const; + + virtual void drawLabel( QPainter*, + const QRectF&, const QPointF& ) const; + + private: + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_multi_barchart.cpp b/libs/qwt/src/qwt_plot_multi_barchart.cpp new file mode 100644 index 00000000..1de1a329 --- /dev/null +++ b/libs/qwt/src/qwt_plot_multi_barchart.cpp @@ -0,0 +1,739 @@ +/****************************************************************************** + * 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_multi_barchart.h" +#include "qwt_scale_map.h" +#include "qwt_column_symbol.h" +#include "qwt_text.h" +#include "qwt_graphic.h" +#include "qwt_legend_data.h" +#include "qwt_math.h" + +#include + +inline static bool qwtIsIncreasing( + const QwtScaleMap& map, const QVector< double >& values ) +{ + bool isInverting = map.isInverting(); + + for ( int i = 0; i < values.size(); i++ ) + { + const double y = values[ i ]; + if ( y != 0.0 ) + return ( map.isInverting() != ( y > 0.0 ) ); + } + + return !isInverting; +} + +class QwtPlotMultiBarChart::PrivateData +{ + public: + PrivateData() + : style( QwtPlotMultiBarChart::Grouped ) + { + } + + QwtPlotMultiBarChart::ChartStyle style; + QList< QwtText > barTitles; + QMap< int, QwtColumnSymbol* > symbolMap; +}; + +/*! + Constructor + \param title Title of the chart + */ +QwtPlotMultiBarChart::QwtPlotMultiBarChart( const QwtText& title ) + : QwtPlotAbstractBarChart( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the chart + */ +QwtPlotMultiBarChart::QwtPlotMultiBarChart( const QString& title ) + : QwtPlotAbstractBarChart( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPlotMultiBarChart::~QwtPlotMultiBarChart() +{ + resetSymbolMap(); + delete m_data; +} + +void QwtPlotMultiBarChart::init() +{ + m_data = new PrivateData; + setData( new QwtSetSeriesData() ); +} + +//! \return QwtPlotItem::Rtti_PlotBarChart +int QwtPlotMultiBarChart::rtti() const +{ + return QwtPlotItem::Rtti_PlotMultiBarChart; +} + +/*! + Initialize data with an array of samples. + \param samples Vector of points + */ +void QwtPlotMultiBarChart::setSamples( + const QVector< QwtSetSample >& samples ) +{ + setData( new QwtSetSeriesData( samples ) ); +} + +/*! + Initialize data with an array of samples. + \param samples Vector of points + */ +void QwtPlotMultiBarChart::setSamples( + const QVector< QVector< double > >& samples ) +{ + QVector< QwtSetSample > s; + s.reserve( samples.size() ); + + for ( int i = 0; i < samples.size(); i++ ) + s += QwtSetSample( i, samples[ i ] ); + + setData( new QwtSetSeriesData( s ) ); +} + +/*! + 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 QwtPlotMultiBarChart::setSamples( + QwtSeriesData< QwtSetSample >* data ) +{ + setData( data ); +} + +/*! + \brief Set the titles for the bars + + The titles are used for the legend. + + \param titles Bar titles + + \sa barTitles(), legendData() + */ +void QwtPlotMultiBarChart::setBarTitles( const QList< QwtText >& titles ) +{ + m_data->barTitles = titles; + itemChanged(); +} + +/*! + \return Bar titles + \sa setBarTitles(), legendData() + */ +QList< QwtText > QwtPlotMultiBarChart::barTitles() const +{ + return m_data->barTitles; +} + +/*! + \brief Add a symbol to the symbol map + + Assign a default symbol for drawing the bar representing all values + with the same index in a set. + + \param valueIndex Index of a value in a set + \param symbol Symbol used for drawing a bar + + \sa symbol(), resetSymbolMap(), specialSymbol() + */ +void QwtPlotMultiBarChart::setSymbol( int valueIndex, QwtColumnSymbol* symbol ) +{ + if ( valueIndex < 0 ) + return; + + QMap< int, QwtColumnSymbol* >::iterator it = + m_data->symbolMap.find(valueIndex); + if ( it == m_data->symbolMap.end() ) + { + if ( symbol != NULL ) + { + m_data->symbolMap.insert( valueIndex, symbol ); + + legendChanged(); + itemChanged(); + } + } + else + { + if ( symbol != it.value() ) + { + delete it.value(); + + if ( symbol == NULL ) + { + m_data->symbolMap.remove( valueIndex ); + } + else + { + it.value() = symbol; + } + + legendChanged(); + itemChanged(); + } + } +} + +/*! + Find a symbol in the symbol map + + \param valueIndex Index of a value in a set + \return The symbol, that had been set by setSymbol() or NULL. + + \sa setSymbol(), specialSymbol(), drawBar() + */ +const QwtColumnSymbol* QwtPlotMultiBarChart::symbol( int valueIndex ) const +{ + QMap< int, QwtColumnSymbol* >::const_iterator it = + m_data->symbolMap.constFind( valueIndex ); + + return ( it == m_data->symbolMap.constEnd() ) ? NULL : it.value(); +} + +/*! + Find a symbol in the symbol map + + \param valueIndex Index of a value in a set + \return The symbol, that had been set by setSymbol() or NULL. + + \sa setSymbol(), specialSymbol(), drawBar() + */ +QwtColumnSymbol* QwtPlotMultiBarChart::symbol( int valueIndex ) +{ + QMap< int, QwtColumnSymbol* >::const_iterator it = + m_data->symbolMap.constFind( valueIndex ); + + return ( it == m_data->symbolMap.constEnd() ) ? NULL : it.value(); +} + +/*! + Remove all symbols from the symbol map + */ +void QwtPlotMultiBarChart::resetSymbolMap() +{ + qDeleteAll( m_data->symbolMap ); + m_data->symbolMap.clear(); +} + +/*! + \brief Create a symbol for special values + + Usually the symbols for displaying a bar are set by setSymbols() and + common for all sets. By overloading specialSymbol() it is possible to + create a temporary symbol() for displaying a special value. + + The symbol has to be created by new each time specialSymbol() is + called. As soon as the symbol is painted this symbol gets deleted. + + When no symbol ( NULL ) is returned, the value will be displayed + with the standard symbol that is used for all symbols with the same + valueIndex. + + \param sampleIndex Index of the sample + \param valueIndex Index of the value in the set + + \return NULL, meaning that the value is not special + + */ +QwtColumnSymbol* QwtPlotMultiBarChart::specialSymbol( + int sampleIndex, int valueIndex ) const +{ + Q_UNUSED( sampleIndex ); + Q_UNUSED( valueIndex ); + + return NULL; +} + +/*! + Set the style of the chart + + \param style Chart style + \sa style() + */ +void QwtPlotMultiBarChart::setStyle( ChartStyle style ) +{ + if ( style != m_data->style ) + { + m_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Style of the chart + \sa setStyle() + */ +QwtPlotMultiBarChart::ChartStyle QwtPlotMultiBarChart::style() const +{ + return m_data->style; +} + +/*! + \return Bounding rectangle of all samples. + For an empty series the rectangle is invalid. + */ +QRectF QwtPlotMultiBarChart::boundingRect() const +{ + const size_t numSamples = dataSize(); + + if ( numSamples == 0 ) + return QwtPlotSeriesItem::boundingRect(); + + const double baseLine = baseline(); + + QRectF rect; + + if ( m_data->style != QwtPlotMultiBarChart::Stacked ) + { + rect = QwtPlotSeriesItem::boundingRect(); + + if ( rect.height() >= 0 ) + { + if ( rect.bottom() < baseLine ) + rect.setBottom( baseLine ); + if ( rect.top() > baseLine ) + rect.setTop( baseLine ); + } + } + else + { + double xMin, xMax, yMin, yMax; + + xMin = xMax = 0.0; + yMin = yMax = baseLine; + + const QwtSeriesData< QwtSetSample >* series = data(); + + for ( size_t i = 0; i < numSamples; i++ ) + { + const QwtSetSample sample = series->sample( i ); + if ( i == 0 ) + { + xMin = xMax = sample.value; + } + else + { + xMin = qwtMinF( xMin, sample.value ); + xMax = qwtMaxF( xMax, sample.value ); + } + + const double y = baseLine + sample.added(); + + yMin = qwtMinF( yMin, y ); + yMax = qwtMaxF( yMax, y ); + } + rect.setRect( xMin, yMin, xMax - xMin, yMax - yMin ); + } + + if ( 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 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 drawSymbols() + */ +void QwtPlotMultiBarChart::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 rectangle of the canvas + \param boundingInterval Bounding interval of sample values + \param index Index of the sample to be painted + \param sample Sample value + + \sa drawSeries() + */ +void QwtPlotMultiBarChart::drawSample( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, const QwtInterval& boundingInterval, + int index, const QwtSetSample& sample ) const +{ + if ( sample.set.size() <= 0 ) + return; + + double sampleW; + + if ( orientation() == Qt::Horizontal ) + { + sampleW = sampleWidth( yMap, canvasRect.height(), + boundingInterval.width(), sample.value ); + } + else + { + sampleW = sampleWidth( xMap, canvasRect.width(), + boundingInterval.width(), sample.value ); + } + + if ( m_data->style == Stacked ) + { + drawStackedBars( painter, xMap, yMap, + canvasRect, index, sampleW, sample ); + } + else + { + drawGroupedBars( painter, xMap, yMap, + canvasRect, index, sampleW, sample ); + } +} + +/*! + Draw a grouped sample + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param index Index of the sample to be painted + \param sampleWidth Bounding width for all bars of the sample + \param sample Sample + + \sa drawSeries(), sampleWidth() + */ +void QwtPlotMultiBarChart::drawGroupedBars( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int index, double sampleWidth, + const QwtSetSample& sample ) const +{ + Q_UNUSED( canvasRect ); + + const int numBars = sample.set.size(); + if ( numBars == 0 ) + return; + + if ( orientation() == Qt::Vertical ) + { + const double barWidth = sampleWidth / numBars; + + const double y1 = yMap.transform( baseline() ); + const double x0 = xMap.transform( sample.value ) - 0.5 * sampleWidth; + + for ( int i = 0; i < numBars; i++ ) + { + const double x1 = x0 + i * barWidth; + const double x2 = x1 + barWidth; + + const double y2 = yMap.transform( sample.set[i] ); + + QwtColumnRect barRect; + barRect.direction = ( y1 < y2 ) ? + QwtColumnRect::TopToBottom : QwtColumnRect::BottomToTop; + + barRect.hInterval = QwtInterval( x1, x2 ).normalized(); + if ( i != 0 ) + barRect.hInterval.setBorderFlags( QwtInterval::ExcludeMinimum ); + + barRect.vInterval = QwtInterval( y1, y2 ).normalized(); + + drawBar( painter, index, i, barRect ); + } + } + else + { + const double barHeight = sampleWidth / numBars; + + const double x1 = xMap.transform( baseline() ); + const double y0 = yMap.transform( sample.value ) - 0.5 * sampleWidth; + + for ( int i = 0; i < numBars; i++ ) + { + double y1 = y0 + i * barHeight; + double y2 = y1 + barHeight; + + double x2 = xMap.transform( sample.set[i] ); + + QwtColumnRect barRect; + barRect.direction = x1 < x2 ? + QwtColumnRect::LeftToRight : QwtColumnRect::RightToLeft; + + barRect.hInterval = QwtInterval( x1, x2 ).normalized(); + + barRect.vInterval = QwtInterval( y1, y2 ); + if ( i != 0 ) + barRect.vInterval.setBorderFlags( QwtInterval::ExcludeMinimum ); + + drawBar( painter, index, i, barRect ); + } + } +} + +/*! + Draw a stacked sample + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param index Index of the sample to be painted + \param sampleWidth Width of the bars + \param sample Sample + + \sa drawSeries(), sampleWidth() + */ +void QwtPlotMultiBarChart::drawStackedBars( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int index, + double sampleWidth, const QwtSetSample& sample ) const +{ + Q_UNUSED( canvasRect ); // clipping the bars ? + + const int numBars = sample.set.size(); + if ( numBars == 0 ) + return; + + QwtInterval::BorderFlag borderFlags = QwtInterval::IncludeBorders; + + if ( orientation() == Qt::Vertical ) + { + const double x1 = xMap.transform( sample.value ) - 0.5 * sampleWidth; + const double x2 = x1 + sampleWidth; + + const bool increasing = qwtIsIncreasing( yMap, sample.set ); + + QwtColumnRect bar; + bar.direction = increasing ? + QwtColumnRect::TopToBottom : QwtColumnRect::BottomToTop; + + bar.hInterval = QwtInterval( x1, x2 ).normalized(); + + double sum = baseline(); + + for ( int i = 0; i < numBars; i++ ) + { + const double si = sample.set[ i ]; + if ( si == 0.0 ) + continue; + + const double y1 = yMap.transform( sum ); + const double y2 = yMap.transform( sum + si ); + + if ( ( y2 > y1 ) != increasing ) + { + // stacked bars need to be in the same direction + continue; + } + + bar.vInterval = QwtInterval( y1, y2 ).normalized(); + bar.vInterval.setBorderFlags( borderFlags ); + + drawBar( painter, index, i, bar ); + + sum += si; + + if ( increasing ) + borderFlags = QwtInterval::ExcludeMinimum; + else + borderFlags = QwtInterval::ExcludeMaximum; + } + } + else + { + const double y1 = yMap.transform( sample.value ) - 0.5 * sampleWidth; + const double y2 = y1 + sampleWidth; + + const bool increasing = qwtIsIncreasing( xMap, sample.set ); + + QwtColumnRect bar; + bar.direction = increasing ? + QwtColumnRect::LeftToRight : QwtColumnRect::RightToLeft; + bar.vInterval = QwtInterval( y1, y2 ).normalized(); + + double sum = baseline(); + + for ( int i = 0; i < sample.set.size(); i++ ) + { + const double si = sample.set[ i ]; + if ( si == 0.0 ) + continue; + + const double x1 = xMap.transform( sum ); + const double x2 = xMap.transform( sum + si ); + + if ( ( x2 > x1 ) != increasing ) + { + // stacked bars need to be in the same direction + continue; + } + + bar.hInterval = QwtInterval( x1, x2 ).normalized(); + bar.hInterval.setBorderFlags( borderFlags ); + + drawBar( painter, index, i, bar ); + + sum += si; + + if ( increasing ) + borderFlags = QwtInterval::ExcludeMinimum; + else + borderFlags = QwtInterval::ExcludeMaximum; + } + } +} + +/*! + Draw a bar + + \param painter Painter + \param sampleIndex Index of the sample - might be -1 when the + bar is painted for the legend + \param valueIndex Index of a value in a set + \param rect Directed target rectangle for the bar + + \sa drawSeries() + */ +void QwtPlotMultiBarChart::drawBar( QPainter* painter, + int sampleIndex, int valueIndex, const QwtColumnRect& rect ) const +{ + const QwtColumnSymbol* specialSym = NULL; + if ( sampleIndex >= 0 ) + specialSym = specialSymbol( sampleIndex, valueIndex ); + + const QwtColumnSymbol* sym = specialSym; + if ( sym == NULL ) + sym = symbol( valueIndex ); + + if ( sym ) + { + sym->draw( painter, rect ); + } + else + { + // we build a temporary default symbol + QwtColumnSymbol columnSymbol( QwtColumnSymbol::Box ); + columnSymbol.setLineWidth( 1 ); + columnSymbol.setFrameStyle( QwtColumnSymbol::Plain ); + columnSymbol.draw( painter, rect ); + } + + delete specialSym; +} + +/*! + \return Information to be displayed on the legend + + The chart is represented by a list of entries - one for each bar title. + Each element contains a bar title and an icon showing its corresponding bar. + + \sa barTitles(), legendIcon(), legendIconSize() + */ +QList< QwtLegendData > QwtPlotMultiBarChart::legendData() const +{ + QList< QwtLegendData > list; + list.reserve( m_data->barTitles.size() ); + + for ( int i = 0; i < m_data->barTitles.size(); i++ ) + { + QwtLegendData data; + + data.setValue( QwtLegendData::TitleRole, + QVariant::fromValue( m_data->barTitles[i] ) ); + + if ( !legendIconSize().isEmpty() ) + { + data.setValue( QwtLegendData::IconRole, + QVariant::fromValue( legendIcon( i, legendIconSize() ) ) ); + } + + list += data; + } + + return list; +} + +/*! + \return Icon for representing a bar on the legend + + \param index Index of the bar + \param size Icon size + + \return An icon showing a bar + \sa drawBar(), legendData() + */ +QwtGraphic QwtPlotMultiBarChart::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 ) ); + + drawBar( &painter, -1, index, column ); + + return icon; +} + diff --git a/libs/qwt/src/qwt_plot_multi_barchart.h b/libs/qwt/src/qwt_plot_multi_barchart.h new file mode 100644 index 00000000..2b382e81 --- /dev/null +++ b/libs/qwt/src/qwt_plot_multi_barchart.h @@ -0,0 +1,129 @@ +/****************************************************************************** + * 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_MULTI_BAR_CHART_H +#define QWT_PLOT_MULTI_BAR_CHART_H + +#include "qwt_global.h" +#include "qwt_plot_abstract_barchart.h" + +class QwtColumnRect; +class QwtColumnSymbol; +template< typename T > class QwtSeriesData; + +/*! + \brief QwtPlotMultiBarChart displays a series of a samples that consist + each of a set of values. + + Each value is displayed as a bar, the bars of each set can be organized + side by side or accumulated. + + Each bar of a set is rendered by a QwtColumnSymbol, that is set by setSymbol(). + The bars of different sets use the same symbols. Exceptions are possible + by overloading specialSymbol() or overloading drawBar(). + + Depending on its orientation() the bars are displayed horizontally + or vertically. The bars cover the interval between the baseline() + and the value. + + In opposite to most other plot items, QwtPlotMultiBarChart returns more + than one entry for the legend - one for each symbol. + + \sa QwtPlotBarChart, QwtPlotHistogram + QwtPlotSeriesItem::orientation(), QwtPlotAbstractBarChart::baseline() + */ +class QWT_EXPORT QwtPlotMultiBarChart + : public QwtPlotAbstractBarChart + , public QwtSeriesStore< QwtSetSample > +{ + public: + /*! + \brief Chart styles. + + The default setting is QwtPlotMultiBarChart::Grouped. + \sa setStyle(), style() + */ + enum ChartStyle + { + //! The bars of a set are displayed side by side + Grouped, + + /*! + The bars are displayed on top of each other accumulating + to a single bar. All values of a set need to have the same + sign. + */ + Stacked + }; + + explicit QwtPlotMultiBarChart( const QString& title = QString() ); + explicit QwtPlotMultiBarChart( const QwtText& title ); + + virtual ~QwtPlotMultiBarChart(); + + virtual int rtti() const QWT_OVERRIDE; + + void setBarTitles( const QList< QwtText >& ); + QList< QwtText > barTitles() const; + + void setSamples( const QVector< QwtSetSample >& ); + void setSamples( const QVector< QVector< double > >& ); + void setSamples( QwtSeriesData< QwtSetSample >* ); + + void setStyle( ChartStyle style ); + ChartStyle style() const; + + void setSymbol( int valueIndex, QwtColumnSymbol* ); + const QwtColumnSymbol* symbol( int valueIndex ) const; + + void resetSymbolMap(); + + virtual void drawSeries( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const QWT_OVERRIDE; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + virtual QList< QwtLegendData > legendData() const QWT_OVERRIDE; + + virtual QwtGraphic legendIcon( + int index, const QSizeF& ) const QWT_OVERRIDE; + + protected: + QwtColumnSymbol* symbol( int valueIndex ); + + virtual QwtColumnSymbol* specialSymbol( + int sampleIndex, int valueIndex ) const; + + virtual void drawSample( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, const QwtInterval& boundingInterval, + int index, const QwtSetSample& ) const; + + virtual void drawBar( QPainter*, int sampleIndex, + int valueIndex, const QwtColumnRect& ) const; + + void drawStackedBars( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int index, + double sampleWidth, const QwtSetSample& ) const; + + void drawGroupedBars( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int index, + double sampleWidth, const QwtSetSample& ) const; + + private: + void init(); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_opengl_canvas.cpp b/libs/qwt/src/qwt_plot_opengl_canvas.cpp new file mode 100644 index 00000000..c61c18b6 --- /dev/null +++ b/libs/qwt/src/qwt_plot_opengl_canvas.cpp @@ -0,0 +1,258 @@ +/****************************************************************************** + * 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_opengl_canvas.h" +#include "qwt_plot.h" +#include "qwt_painter.h" + +#include +#include +#include +#include +#include + +class QwtPlotOpenGLCanvas::PrivateData +{ + public: + PrivateData() + : isPolished( false ) + , fboDirty( true ) + , fbo( NULL ) + { + } + + ~PrivateData() + { + delete fbo; + } + + int numSamples; + + bool isPolished; + bool fboDirty; + QOpenGLFramebufferObject* fbo; +}; + +/*! + \brief Constructor + + \param plot Parent plot widget + \sa QwtPlot::setCanvas() + */ +QwtPlotOpenGLCanvas::QwtPlotOpenGLCanvas( QwtPlot* plot ) + : QOpenGLWidget( plot ) + , QwtPlotAbstractGLCanvas( this ) +{ + QSurfaceFormat fmt = format(); + fmt.setSamples( 4 ); + + init( fmt ); +} + +/*! + \brief Constructor + + \param format OpenGL surface format + \param plot Parent plot widget + \sa QwtPlot::setCanvas() + */ +QwtPlotOpenGLCanvas::QwtPlotOpenGLCanvas( + const QSurfaceFormat& format, QwtPlot* plot ) + : QOpenGLWidget( plot ) + , QwtPlotAbstractGLCanvas( this ) +{ + init( format ); +} + +void QwtPlotOpenGLCanvas::init( const QSurfaceFormat& format ) +{ + m_data = new PrivateData; + m_data->numSamples = format.samples(); + + setFormat( format ); + +#if 1 + setAttribute( Qt::WA_OpaquePaintEvent, true ); +#endif + + setLineWidth( 2 ); + setFrameShadow( QFrame::Sunken ); + setFrameShape( QFrame::Panel ); +} + +//! Destructor +QwtPlotOpenGLCanvas::~QwtPlotOpenGLCanvas() +{ + delete m_data; +} + +/*! + Paint event + + \param event Paint event + \sa QwtPlot::drawCanvas() + */ +void QwtPlotOpenGLCanvas::paintEvent( QPaintEvent* event ) +{ + if ( m_data->isPolished ) + QOpenGLWidget::paintEvent( event ); +} + +/*! + Qt event handler for QEvent::PolishRequest and QEvent::StyleChange + \param event Qt Event + \return See QGLWidget::event() + */ +bool QwtPlotOpenGLCanvas::event( QEvent* event ) +{ + const bool ok = QOpenGLWidget::event( event ); + + if ( event->type() == QEvent::PolishRequest ) + { + // In opposite to non OpenGL widgets receive pointless + // early repaints. As we always have a QEvent::PolishRequest + // followed by QEvent::Paint, we can ignore all these repaints. + + m_data->isPolished = true; + } + + 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; +} + +/*! + Invalidate the paint cache and repaint the canvas + \sa invalidatePaintCache() + */ +void QwtPlotOpenGLCanvas::replot() +{ + QwtPlotAbstractGLCanvas::replot(); +} + +//! Invalidate the internal backing store +void QwtPlotOpenGLCanvas::invalidateBackingStore() +{ + m_data->fboDirty = true; +} + +void QwtPlotOpenGLCanvas::clearBackingStore() +{ + delete m_data->fbo; + m_data->fbo = NULL; +} + +/*! + 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 QwtPlotOpenGLCanvas::borderPath( const QRect& rect ) const +{ + return canvasBorderPath( rect ); +} + +//! No operation - reserved for some potential use in the future +void QwtPlotOpenGLCanvas::initializeGL() +{ +} + +//! Paint the plot +void QwtPlotOpenGLCanvas::paintGL() +{ + const bool hasFocusIndicator = + hasFocus() && focusIndicator() == CanvasFocusIndicator; + + QPainter painter; + + if ( testPaintAttribute( QwtPlotOpenGLCanvas::BackingStore ) && + QOpenGLFramebufferObject::hasOpenGLFramebufferBlit() ) + { + const qreal pixelRatio = QwtPainter::devicePixelRatio( NULL ); + const QSize fboSize = size() * pixelRatio; + + if ( hasFocusIndicator ) + painter.begin( this ); + + /* + QOpenGLWidget has its own internal FBO, that is used to restore + its content without having to repaint. This works fine when f.e + a rubberband is moving on top, but there are still situations, + where we can repaint without an potentially expensive replot: + + - when having the focus the top level window gets activated/deactivated + - ??? + */ + + if ( m_data->fbo ) + { + if ( m_data->fbo->size() != fboSize ) + { + delete m_data->fbo; + m_data->fbo = NULL; + } + } + + if ( m_data->fbo == NULL ) + { + QOpenGLFramebufferObjectFormat fboFormat; + fboFormat.setSamples( m_data->numSamples ); + fboFormat.setAttachment( QOpenGLFramebufferObject::CombinedDepthStencil ); + + m_data->fbo = new QOpenGLFramebufferObject( fboSize, fboFormat ); + m_data->fboDirty = true; + } + + if ( m_data->fboDirty ) + { + m_data->fbo->bind(); + + QOpenGLPaintDevice pd( fboSize ); + + QPainter fboPainter( &pd ); + fboPainter.scale( pixelRatio, pixelRatio ); + draw( &fboPainter); + fboPainter.end(); + + m_data->fboDirty = false; + } + + QOpenGLFramebufferObject::blitFramebuffer( NULL, m_data->fbo ); + } + else + { + painter.begin( this ); + draw( &painter ); + } + + if ( hasFocusIndicator ) + drawFocusIndicator( &painter ); +} + +//! No operation - reserved for some potential use in the future +void QwtPlotOpenGLCanvas::resizeGL( int, int ) +{ + // nothing to do +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_plot_opengl_canvas.cpp" +#endif diff --git a/libs/qwt/src/qwt_plot_opengl_canvas.h b/libs/qwt/src/qwt_plot_opengl_canvas.h new file mode 100644 index 00000000..a89bbb0d --- /dev/null +++ b/libs/qwt/src/qwt_plot_opengl_canvas.h @@ -0,0 +1,75 @@ +/****************************************************************************** + * 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_OPENGL_CANVAS_H +#define QWT_PLOT_OPENGL_CANVAS_H + +#include "qwt_global.h" +#include "qwt_plot_abstract_canvas.h" + +#include + +class QwtPlot; + +/*! + \brief An alternative canvas for a QwtPlot derived from QOpenGLWidget + + Even if QwtPlotOpenGLCanvas 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, QwtPlotCanvas::OpenGLBuffer + + \note Another way for getting hardware accelerated graphics is using + an OpenGL offscreen buffer ( QwtPlotCanvas::OpenGLBuffer ) with QwtPlotCanvas. + Performance is worse, than rendering straight to a QOpenGLWidget, but is usually + better integrated into a desktop application. + */ +class QWT_EXPORT QwtPlotOpenGLCanvas : public QOpenGLWidget, public QwtPlotAbstractGLCanvas +{ + Q_OBJECT + + Q_PROPERTY( QFrame::Shadow frameShadow READ frameShadow WRITE setFrameShadow ) + Q_PROPERTY( QFrame::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 ) + + Q_PROPERTY( double borderRadius READ borderRadius WRITE setBorderRadius ) + + public: + explicit QwtPlotOpenGLCanvas( QwtPlot* = NULL ); + explicit QwtPlotOpenGLCanvas( const QSurfaceFormat&, QwtPlot* = NULL); + virtual ~QwtPlotOpenGLCanvas(); + + Q_INVOKABLE virtual void invalidateBackingStore() QWT_OVERRIDE; + Q_INVOKABLE QPainterPath borderPath( const QRect& ) const; + + virtual bool event( QEvent* ) QWT_OVERRIDE; + + public Q_SLOTS: + void replot(); + + protected: + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + + virtual void initializeGL() QWT_OVERRIDE; + virtual void paintGL() QWT_OVERRIDE; + virtual void resizeGL( int width, int height ) QWT_OVERRIDE; + + private: + void init( const QSurfaceFormat& ); + virtual void clearBackingStore() QWT_OVERRIDE; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_panner.cpp b/libs/qwt/src/qwt_plot_panner.cpp new file mode 100644 index 00000000..0aa38c85 --- /dev/null +++ b/libs/qwt/src/qwt_plot_panner.cpp @@ -0,0 +1,299 @@ +/****************************************************************************** + * 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_panner.h" +#include "qwt_scale_div.h" +#include "qwt_plot.h" +#include "qwt_scale_map.h" +#include "qwt_painter.h" + +#include +#include +#include +#include +#include + +static QBitmap qwtBorderMask( const QWidget* canvas, const QSize& size ) +{ +#if QT_VERSION >= 0x050000 + const qreal pixelRatio = QwtPainter::devicePixelRatio( canvas ); +#endif + + const QRect r( 0, 0, size.width(), size.height() ); + + QPainterPath borderPath; + + ( void )QMetaObject::invokeMethod( + const_cast< QWidget* >( canvas ), "borderPath", Qt::DirectConnection, + Q_RETURN_ARG( QPainterPath, borderPath ), Q_ARG( QRect, r ) ); + + if ( borderPath.isEmpty() ) + { + if ( canvas->contentsRect() == canvas->rect() ) + return QBitmap(); + +#if QT_VERSION >= 0x050000 + QBitmap mask( size* pixelRatio ); + mask.setDevicePixelRatio( pixelRatio ); +#else + QBitmap mask( size ); +#endif + mask.fill( Qt::color0 ); + + QPainter painter( &mask ); + painter.fillRect( canvas->contentsRect(), Qt::color1 ); + + return mask; + } + +#if QT_VERSION >= 0x050000 + QImage image( size* pixelRatio, QImage::Format_ARGB32_Premultiplied ); + image.setDevicePixelRatio( pixelRatio ); +#else + QImage image( size, QImage::Format_ARGB32_Premultiplied ); +#endif + image.fill( Qt::color0 ); + + QPainter painter( &image ); + painter.setClipPath( borderPath ); + painter.fillRect( r, Qt::color1 ); + + // now erase the frame + + painter.setCompositionMode( QPainter::CompositionMode_DestinationOut ); + + if ( canvas->testAttribute(Qt::WA_StyledBackground ) ) + { + QStyleOptionFrame opt; + opt.initFrom(canvas); + opt.rect = r; + canvas->style()->drawPrimitive( QStyle::PE_Frame, &opt, &painter, canvas ); + } + else + { + const QVariant borderRadius = canvas->property( "borderRadius" ); + const QVariant frameWidth = canvas->property( "frameWidth" ); + + if ( borderRadius.canConvert< double >() && frameWidth.canConvert< int >() ) + { + const double br = borderRadius.value< double >(); + const int fw = frameWidth.value< int >(); + + if ( br > 0.0 && fw > 0 ) + { + painter.setPen( QPen( Qt::color1, fw ) ); + painter.setBrush( Qt::NoBrush ); + painter.setRenderHint( QPainter::Antialiasing, true ); + + painter.drawPath( borderPath ); + } + } + } + + painter.end(); + + const QImage mask = image.createMaskFromColor( + QColor( Qt::color1 ).rgb(), Qt::MaskOutColor ); + + return QBitmap::fromImage( mask ); +} + +class QwtPlotPanner::PrivateData +{ + public: + PrivateData() + { + for ( int axis = 0; axis < QwtAxis::AxisPositions; axis++ ) + isAxisEnabled[axis] = true; + } + + bool isAxisEnabled[QwtAxis::AxisPositions]; +}; + +/*! + \brief A panner for the canvas of a QwtPlot + + The panner is enabled for all axes + + \param canvas Plot canvas to pan, also the parent object + + \sa setAxisEnabled() + */ +QwtPlotPanner::QwtPlotPanner( QWidget* canvas ) + : QwtPanner( canvas ) +{ + m_data = new PrivateData(); + + connect( this, SIGNAL(panned(int,int)), + SLOT(moveCanvas(int,int)) ); +} + +//! Destructor +QwtPlotPanner::~QwtPlotPanner() +{ + delete m_data; +} + +/*! + \brief En/Disable an axis + + Axes that are enabled will be synchronized to the + result of panning. All other axes will remain unchanged. + + \param axisId Axis id + \param on On/Off + + \sa isAxisEnabled(), moveCanvas() + */ +void QwtPlotPanner::setAxisEnabled( QwtAxisId axisId, bool on ) +{ + if ( QwtAxis::isValid( axisId ) ) + m_data->isAxisEnabled[axisId] = on; +} + +/*! + Test if an axis is enabled + + \param axisId Axis + \return True, if the axis is enabled + + \sa setAxisEnabled(), moveCanvas() + */ +bool QwtPlotPanner::isAxisEnabled( QwtAxisId axisId ) const +{ + if ( QwtAxis::isValid( axisId ) ) + return m_data->isAxisEnabled[axisId]; + + return true; +} + +//! Return observed plot canvas +QWidget* QwtPlotPanner::canvas() +{ + return parentWidget(); +} + +//! Return Observed plot canvas +const QWidget* QwtPlotPanner::canvas() const +{ + return parentWidget(); +} + +//! Return plot widget, containing the observed plot canvas +QwtPlot* QwtPlotPanner::plot() +{ + QWidget* w = canvas(); + if ( w ) + w = w->parentWidget(); + + return qobject_cast< QwtPlot* >( w ); +} + +//! Return plot widget, containing the observed plot canvas +const QwtPlot* QwtPlotPanner::plot() const +{ + const QWidget* w = canvas(); + if ( w ) + w = w->parentWidget(); + + return qobject_cast< const QwtPlot* >( w ); +} + +/*! + Adjust the enabled axes according to dx/dy + + \param dx Pixel offset in x direction + \param dy Pixel offset in y direction + + \sa QwtPanner::panned() + */ +void QwtPlotPanner::moveCanvas( int dx, int dy ) +{ + if ( dx == 0 && dy == 0 ) + return; + + QwtPlot* plot = this->plot(); + if ( plot == NULL ) + return; + + const bool doAutoReplot = plot->autoReplot(); + plot->setAutoReplot( false ); + + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + if ( !m_data->isAxisEnabled[axisId] ) + continue; + + const QwtScaleMap map = plot->canvasMap( axisId ); + + const double p1 = map.transform( plot->axisScaleDiv( axisId ).lowerBound() ); + const double p2 = map.transform( plot->axisScaleDiv( axisId ).upperBound() ); + + double d1, d2; + if ( QwtAxis::isXAxis( axisPos ) ) + { + d1 = map.invTransform( p1 - dx ); + d2 = map.invTransform( p2 - dx ); + } + else + { + d1 = map.invTransform( p1 - dy ); + d2 = map.invTransform( p2 - dy ); + } + + plot->setAxisScale( axisId, d1, d2 ); + } + } + + plot->setAutoReplot( doAutoReplot ); + plot->replot(); +} + +/*! + Calculate a mask from the border path of the canvas + + \return Mask as bitmap + \sa QwtPlotCanvas::borderPath() + */ +QBitmap QwtPlotPanner::contentsMask() const +{ + if ( canvas() ) + return qwtBorderMask( canvas(), size() ); + + return QwtPanner::contentsMask(); +} + +/*! + \return Pixmap with the content of the canvas + */ +QPixmap QwtPlotPanner::grab() const +{ + const QWidget* cv = canvas(); + if ( cv && cv->inherits( "QGLWidget" ) ) + { + // we can't grab from a QGLWidget + + QPixmap pm( cv->size() ); + QwtPainter::fillPixmap( cv, pm ); + + QPainter painter( &pm ); + const_cast< QwtPlot* >( plot() )->drawCanvas( &painter ); + + return pm; + } + + return QwtPanner::grab(); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_plot_panner.cpp" +#endif diff --git a/libs/qwt/src/qwt_plot_panner.h b/libs/qwt/src/qwt_plot_panner.h new file mode 100644 index 00000000..a13b8ab0 --- /dev/null +++ b/libs/qwt/src/qwt_plot_panner.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * 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_PANNER_H +#define QWT_PLOT_PANNER_H + +#include "qwt_global.h" +#include "qwt_panner.h" +#include "qwt_axis_id.h" + +class QwtPlot; + +/*! + \brief QwtPlotPanner provides panning of a plot canvas + + QwtPlotPanner is a panner for a plot canvas, that + adjusts the scales of the axes after dropping + the canvas on its new position. + + Together with QwtPlotZoomer and QwtPlotMagnifier powerful ways + of navigating on a QwtPlot widget can be implemented easily. + + \note The axes are not updated, while dragging the canvas + \sa QwtPlotZoomer, QwtPlotMagnifier + */ +class QWT_EXPORT QwtPlotPanner : public QwtPanner +{ + Q_OBJECT + + public: + explicit QwtPlotPanner( QWidget* ); + virtual ~QwtPlotPanner(); + + QWidget* canvas(); + const QWidget* canvas() const; + + QwtPlot* plot(); + const QwtPlot* plot() const; + + void setAxisEnabled( QwtAxisId axisId, bool on ); + bool isAxisEnabled( QwtAxisId ) const; + + public Q_SLOTS: + virtual void moveCanvas( int dx, int dy ); + + protected: + virtual QBitmap contentsMask() const QWT_OVERRIDE; + virtual QPixmap grab() const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_picker.cpp b/libs/qwt/src/qwt_plot_picker.cpp new file mode 100644 index 00000000..b1c93032 --- /dev/null +++ b/libs/qwt/src/qwt_plot_picker.cpp @@ -0,0 +1,395 @@ +/****************************************************************************** + * 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_picker.h" +#include "qwt_plot.h" +#include "qwt_text.h" +#include "qwt_scale_div.h" +#include "qwt_scale_map.h" +#include "qwt_picker_machine.h" + +class QwtPlotPicker::PrivateData +{ + public: + PrivateData(): + xAxisId( -1 ), + yAxisId( -1 ) + { + } + + QwtAxisId xAxisId; + QwtAxisId yAxisId; +}; + +/*! + \brief Create a plot picker + + The picker is set to those x- and y-axis of the plot + that are enabled. If both or no x-axis are enabled, the picker + is set to QwtAxis::XBottom. If both or no y-axis are + enabled, it is set to QwtAxis::YLeft. + + \param canvas Plot canvas to observe, also the parent object + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), scaleRect() + */ + +QwtPlotPicker::QwtPlotPicker( QWidget* canvas ) + : QwtPicker( canvas ) +{ + m_data = new PrivateData; + + if ( !canvas ) + return; + + const QwtPlot* plot = QwtPlotPicker::plot(); + // attach axes + + using namespace QwtAxis; + + int xAxis = XBottom; + if ( !plot->isAxisVisible( XBottom ) && plot->isAxisVisible( XTop ) ) + xAxis = XTop; + + int yAxis = YLeft; + if ( !plot->isAxisVisible( YLeft ) && plot->isAxisVisible( YRight ) ) + yAxis = YRight; + + setAxes( xAxis, yAxis ); +} + +/*! + Create a plot picker + + \param xAxisId X axis of the picker + \param yAxisId Y axis of the picker + \param canvas Plot canvas to observe, also the parent object + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), scaleRect() + */ +QwtPlotPicker::QwtPlotPicker( QwtAxisId xAxisId, QwtAxisId yAxisId, QWidget* canvas ) + : QwtPicker( canvas ) +{ + m_data = new PrivateData; + m_data->xAxisId = xAxisId; + m_data->yAxisId = yAxisId; +} + +/*! + Create a plot picker + + \param xAxis X axis of the picker + \param yAxis Y axis of the picker + \param rubberBand Rubber band style + \param trackerMode Tracker mode + \param canvas Plot canvas to observe, also the parent object + + \sa QwtPicker, QwtPicker::setSelectionFlags(), QwtPicker::setRubberBand(), + QwtPicker::setTrackerMode + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), scaleRect() + */ +QwtPlotPicker::QwtPlotPicker( QwtAxisId xAxisId, QwtAxisId yAxisId, + RubberBand rubberBand, DisplayMode trackerMode, QWidget* canvas ) + : QwtPicker( rubberBand, trackerMode, canvas ) +{ + m_data = new PrivateData; + m_data->xAxisId = xAxisId; + m_data->yAxisId = yAxisId; +} + +//! Destructor +QwtPlotPicker::~QwtPlotPicker() +{ + delete m_data; +} + +//! \return Observed plot canvas +QWidget* QwtPlotPicker::canvas() +{ + return parentWidget(); +} + +//! \return Observed plot canvas +const QWidget* QwtPlotPicker::canvas() const +{ + return parentWidget(); +} + +//! \return Plot widget, containing the observed plot canvas +QwtPlot* QwtPlotPicker::plot() +{ + QWidget* w = canvas(); + if ( w ) + w = w->parentWidget(); + + return qobject_cast< QwtPlot* >( w ); +} + +//! \return Plot widget, containing the observed plot canvas +const QwtPlot* QwtPlotPicker::plot() const +{ + const QWidget* w = canvas(); + if ( w ) + w = w->parentWidget(); + + return qobject_cast< const QwtPlot* >( w ); +} + +/*! + \return Normalized bounding rectangle of the axes + \sa QwtPlot::autoReplot(), QwtPlot::replot(). + */ +QRectF QwtPlotPicker::scaleRect() const +{ + QRectF rect; + + if ( plot() ) + { + const QwtScaleDiv& xs = plot()->axisScaleDiv( xAxis() ); + const QwtScaleDiv& ys = plot()->axisScaleDiv( yAxis() ); + + rect = QRectF( xs.lowerBound(), ys.lowerBound(), + xs.range(), ys.range() ); + rect = rect.normalized(); + } + + return rect; +} + +/*! + Set the x and y axes of the picker + + \param xAxisId X axis + \param yAxisId Y axis + */ +void QwtPlotPicker::setAxes( QwtAxisId xAxisId, QwtAxisId yAxisId ) +{ + const QwtPlot* plt = plot(); + if ( !plt ) + return; + + if ( xAxisId != m_data->xAxisId || yAxisId != m_data->yAxisId ) + { + m_data->xAxisId = xAxisId; + m_data->yAxisId = yAxisId; + } +} + +//! Return x axis +QwtAxisId QwtPlotPicker::xAxis() const +{ + return m_data->xAxisId; +} + +//! Return y axis +QwtAxisId QwtPlotPicker::yAxis() const +{ + return m_data->yAxisId; +} + +/*! + Translate a pixel position into a position string + + \param pos Position in pixel coordinates + \return Position string + */ +QwtText QwtPlotPicker::trackerText( const QPoint& pos ) const +{ + if ( plot() == NULL ) + return QwtText(); + + return trackerTextF( invTransform( pos ) ); +} + +/*! + \brief Translate a position into a position string + + 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 double to string conversion is "%.4f". + + \param pos Position + \return Position string + */ +QwtText QwtPlotPicker::trackerTextF( const QPointF& pos ) const +{ + QString text; + + switch ( rubberBand() ) + { + case HLineRubberBand: + text = QString::number( pos.y(), 'f', 4 ); + break; + case VLineRubberBand: + text = QString::number( pos.x(), 'f', 4 ); + break; + default: + text = QString::number( pos.x(), 'f', 4 ) + + ", " + QString::number( pos.y(), 'f', 4 ); + } + return QwtText( text ); +} + +/*! + Append a point to the selection and update rubber band and tracker. + + \param pos Additional point + \sa isActive, begin(), end(), move(), appended() + + \note The appended(const QPoint &), appended(const QDoublePoint &) + signals are emitted. + */ +void QwtPlotPicker::append( const QPoint& pos ) +{ + QwtPicker::append( pos ); + Q_EMIT appended( invTransform( pos ) ); +} + +/*! + Move the last point of the selection + + \param pos New position + \sa isActive, begin(), end(), append() + + \note The moved(const QPoint &), moved(const QDoublePoint &) + signals are emitted. + */ +void QwtPlotPicker::move( const QPoint& pos ) +{ + QwtPicker::move( pos ); + Q_EMIT moved( invTransform( pos ) ); +} + +/*! + Close a selection setting the state to inactive. + + \param ok If true, complete the selection and emit selected signals + otherwise discard the selection. + \return True if the selection has been accepted, false otherwise + */ + +bool QwtPlotPicker::end( bool ok ) +{ + ok = QwtPicker::end( ok ); + if ( !ok ) + return false; + + QwtPlot* plot = QwtPlotPicker::plot(); + if ( !plot ) + return false; + + const QPolygon points = selection(); + if ( points.count() == 0 ) + return false; + + QwtPickerMachine::SelectionType selectionType = + QwtPickerMachine::NoSelection; + + if ( stateMachine() ) + selectionType = stateMachine()->selectionType(); + + switch ( selectionType ) + { + case QwtPickerMachine::PointSelection: + { + const QPointF pos = invTransform( points.first() ); + Q_EMIT selected( pos ); + break; + } + case QwtPickerMachine::RectSelection: + { + if ( points.count() >= 2 ) + { + const QPoint p1 = points.first(); + const QPoint p2 = points.last(); + + const QRect rect = QRect( p1, p2 ).normalized(); + Q_EMIT selected( invTransform( rect ) ); + } + break; + } + case QwtPickerMachine::PolygonSelection: + { + QVector< QPointF > dpa( points.count() ); + for ( int i = 0; i < points.count(); i++ ) + dpa[i] = invTransform( points[i] ); + + Q_EMIT selected( dpa ); + } + default: + break; + } + + return true; +} + +/*! + Translate a rectangle from pixel into plot coordinates + + \return Rectangle in plot coordinates + \sa transform() + */ +QRectF QwtPlotPicker::invTransform( const QRect& rect ) const +{ + const QwtScaleMap xMap = plot()->canvasMap( xAxis() ); + const QwtScaleMap yMap = plot()->canvasMap( yAxis() ); + + return QwtScaleMap::invTransform( xMap, yMap, rect ); +} + +/*! + Translate a rectangle from plot into pixel coordinates + \return Rectangle in pixel coordinates + \sa invTransform() + */ +QRect QwtPlotPicker::transform( const QRectF& rect ) const +{ + const QwtScaleMap xMap = plot()->canvasMap( xAxis() ); + const QwtScaleMap yMap = plot()->canvasMap( yAxis() ); + + return QwtScaleMap::transform( xMap, yMap, rect ).toRect(); +} + +/*! + Translate a point from pixel into plot coordinates + \return Point in plot coordinates + \sa transform() + */ +QPointF QwtPlotPicker::invTransform( const QPoint& pos ) const +{ + const QwtScaleMap xMap = plot()->canvasMap( xAxis() ); + const QwtScaleMap yMap = plot()->canvasMap( yAxis() ); + + return QPointF( + xMap.invTransform( pos.x() ), + yMap.invTransform( pos.y() ) + ); +} + +/*! + Translate a point from plot into pixel coordinates + \return Point in pixel coordinates + \sa invTransform() + */ +QPoint QwtPlotPicker::transform( const QPointF& pos ) const +{ + const QwtScaleMap xMap = plot()->canvasMap( xAxis() ); + const QwtScaleMap yMap = plot()->canvasMap( yAxis() ); + + const QPointF p( xMap.transform( pos.x() ), yMap.transform( pos.y() ) ); + + return p.toPoint(); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_plot_picker.cpp" +#endif diff --git a/libs/qwt/src/qwt_plot_picker.h b/libs/qwt/src/qwt_plot_picker.h new file mode 100644 index 00000000..f1508b3d --- /dev/null +++ b/libs/qwt/src/qwt_plot_picker.h @@ -0,0 +1,117 @@ +/****************************************************************************** + * 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_PICKER_H +#define QWT_PLOT_PICKER_H + +#include "qwt_global.h" +#include "qwt_picker.h" +#include "qwt_axis_id.h" + +class QwtPlot; +class QPointF; +class QRectF; + +#if QT_VERSION < 0x060000 +template< typename T > class QVector; +#endif + +/*! + \brief QwtPlotPicker provides selections on a plot canvas + + QwtPlotPicker is a QwtPicker tailored for selections on + a plot canvas. It is set to a x-Axis and y-Axis and + translates all pixel coordinates into this coordinate system. + */ + +class QWT_EXPORT QwtPlotPicker : public QwtPicker +{ + Q_OBJECT + + public: + explicit QwtPlotPicker( QWidget* canvas ); + virtual ~QwtPlotPicker(); + + explicit QwtPlotPicker( QwtAxisId xAxisId, QwtAxisId yAxisId, QWidget* ); + + explicit QwtPlotPicker( QwtAxisId xAxisId, QwtAxisId yAxisId, + RubberBand rubberBand, DisplayMode trackerMode, QWidget* ); + + virtual void setAxes( QwtAxisId xAxisId, QwtAxisId yAxisId ); + + QwtAxisId xAxis() const; + QwtAxisId yAxis() const; + + QwtPlot* plot(); + const QwtPlot* plot() const; + + QWidget* canvas(); + const QWidget* canvas() const; + + Q_SIGNALS: + + /*! + A signal emitted in case of QwtPickerMachine::PointSelection. + \param pos Selected point + */ + void selected( const QPointF& pos ); + + /*! + A signal emitted in case of QwtPickerMachine::RectSelection. + \param rect Selected rectangle + */ + void selected( const QRectF& rect ); + + /*! + A signal emitting the selected points, + at the end of a selection. + + \param pa Selected points + */ + void selected( const QVector< QPointF >& pa ); + + /*! + 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 QPointF& 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 QPointF& pos ); + + protected: + QRectF scaleRect() const; + + QRectF invTransform( const QRect& ) const; + QRect transform( const QRectF& ) const; + + QPointF invTransform( const QPoint& ) const; + QPoint transform( const QPointF& ) const; + + virtual QwtText trackerText( const QPoint& ) const QWT_OVERRIDE; + virtual QwtText trackerTextF( const QPointF& ) const; + + virtual void move( const QPoint& ) QWT_OVERRIDE; + virtual void append( const QPoint& ) QWT_OVERRIDE; + virtual bool end( bool ok = true ) QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_rasteritem.cpp b/libs/qwt/src/qwt_plot_rasteritem.cpp new file mode 100644 index 00000000..6f48bf14 --- /dev/null +++ b/libs/qwt/src/qwt_plot_rasteritem.cpp @@ -0,0 +1,971 @@ +/****************************************************************************** + * 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_rasteritem.h" +#include "qwt_scale_map.h" +#include "qwt_painter.h" +#include "qwt_text.h" +#include "qwt_interval.h" +#include "qwt_math.h" + +#include +#include +#include +#include +#include + +#include + +class QwtPlotRasterItem::PrivateData +{ + public: + PrivateData() + : alpha( -1 ) + , paintAttributes( QwtPlotRasterItem::PaintInDeviceResolution ) + { + cache.policy = QwtPlotRasterItem::NoCache; + } + + int alpha; + + QwtPlotRasterItem::PaintAttributes paintAttributes; + + struct ImageCache + { + QwtPlotRasterItem::CachePolicy policy; + QRectF area; + QSizeF size; + QImage image; + } cache; +}; + + +static QRectF qwtAlignRect(const QRectF& rect) +{ + QRectF r; + r.setLeft( qRound( rect.left() ) ); + r.setRight( qRound( rect.right() ) ); + r.setTop( qRound( rect.top() ) ); + r.setBottom( qRound( rect.bottom() ) ); + + return r; +} + +static QRectF qwtStripRect(const QRectF& rect, const QRectF& area, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtInterval& xInterval, const QwtInterval& yInterval) +{ + QRectF r = rect; + if ( xInterval.borderFlags() & QwtInterval::ExcludeMinimum ) + { + if ( area.left() <= xInterval.minValue() ) + { + if ( xMap.isInverting() ) + r.adjust(0, 0, -1, 0); + else + r.adjust(1, 0, 0, 0); + } + } + + if ( xInterval.borderFlags() & QwtInterval::ExcludeMaximum ) + { + if ( area.right() >= xInterval.maxValue() ) + { + if ( xMap.isInverting() ) + r.adjust(1, 0, 0, 0); + else + r.adjust(0, 0, -1, 0); + } + } + + if ( yInterval.borderFlags() & QwtInterval::ExcludeMinimum ) + { + if ( area.top() <= yInterval.minValue() ) + { + if ( yMap.isInverting() ) + r.adjust(0, 0, 0, -1); + else + r.adjust(0, 1, 0, 0); + } + } + + if ( yInterval.borderFlags() & QwtInterval::ExcludeMaximum ) + { + if ( area.bottom() >= yInterval.maxValue() ) + { + if ( yMap.isInverting() ) + r.adjust(0, 1, 0, 0); + else + r.adjust(0, 0, 0, -1); + } + } + + return r; +} + +static QImage qwtExpandImage(const QImage& image, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& area, const QRectF& area2, const QRectF& paintRect, + const QwtInterval& xInterval, const QwtInterval& yInterval ) +{ + const QRectF strippedRect = qwtStripRect(paintRect, area2, + xMap, yMap, xInterval, yInterval); + const QSize sz = strippedRect.toRect().size(); + + const int w = image.width(); + const int h = image.height(); + + const QRectF r = QwtScaleMap::transform(xMap, yMap, area).normalized(); + const double pw = ( r.width() - 1 ) / w; + const double ph = ( r.height() - 1 ) / h; + + double px0, py0; + if ( !xMap.isInverting() ) + { + px0 = xMap.transform( area2.left() ); + px0 = qRound( px0 ); + px0 = px0 - xMap.transform( area.left() ); + } + else + { + px0 = xMap.transform( area2.right() ); + px0 = qRound( px0 ); + px0 -= xMap.transform( area.right() ); + + px0 -= 1.0; + } + px0 += strippedRect.left() - paintRect.left(); + + if ( !yMap.isInverting() ) + { + py0 = yMap.transform( area2.top() ); + py0 = qRound( py0 ); + py0 -= yMap.transform( area.top() ); + } + else + { + py0 = yMap.transform( area2.bottom() ); + py0 = qRound( py0 ); + py0 -= yMap.transform( area.bottom() ); + + py0 -= 1.0; + } + py0 += strippedRect.top() - paintRect.top(); + + QImage expanded( sz, image.format() ); + if ( image.format() == QImage::Format_Indexed8 ) + expanded.setColorTable( image.colorTable() ); + + switch( image.depth() ) + { + case 32: + { + for ( int y1 = 0; y1 < h; y1++ ) + { + int yy1; + if ( y1 == 0 ) + { + yy1 = 0; + } + else + { + yy1 = qRound( y1 * ph - py0 ); + if ( yy1 < 0 ) + yy1 = 0; + } + + int yy2; + if ( y1 == h - 1 ) + { + yy2 = sz.height(); + } + else + { + yy2 = qRound( ( y1 + 1 ) * ph - py0 ); + if ( yy2 > sz.height() ) + yy2 = sz.height(); + } + + const quint32* line1 = + reinterpret_cast< const quint32* >( image.scanLine( y1 ) ); + + for ( int x1 = 0; x1 < w; x1++ ) + { + int xx1; + if ( x1 == 0 ) + { + xx1 = 0; + } + else + { + xx1 = qRound( x1 * pw - px0 ); + if ( xx1 < 0 ) + xx1 = 0; + } + + int xx2; + if ( x1 == w - 1 ) + { + xx2 = sz.width(); + } + else + { + xx2 = qRound( ( x1 + 1 ) * pw - px0 ); + if ( xx2 > sz.width() ) + xx2 = sz.width(); + } + + const quint32 rgb( line1[x1] ); + for ( int y2 = yy1; y2 < yy2; y2++ ) + { + quint32* line2 = reinterpret_cast< quint32* >( + expanded.scanLine( y2 ) ); + + for ( int x2 = xx1; x2 < xx2; x2++ ) + line2[x2] = rgb; + } + } + } + break; + } + case 8: + { + for ( int y1 = 0; y1 < h; y1++ ) + { + int yy1; + if ( y1 == 0 ) + { + yy1 = 0; + } + else + { + yy1 = qRound( y1 * ph - py0 ); + if ( yy1 < 0 ) + yy1 = 0; + } + + int yy2; + if ( y1 == h - 1 ) + { + yy2 = sz.height(); + } + else + { + yy2 = qRound( ( y1 + 1 ) * ph - py0 ); + if ( yy2 > sz.height() ) + yy2 = sz.height(); + } + + const uchar* line1 = image.scanLine( y1 ); + + for ( int x1 = 0; x1 < w; x1++ ) + { + int xx1; + if ( x1 == 0 ) + { + xx1 = 0; + } + else + { + xx1 = qRound( x1 * pw - px0 ); + if ( xx1 < 0 ) + xx1 = 0; + } + + int xx2; + if ( x1 == w - 1 ) + { + xx2 = sz.width(); + } + else + { + xx2 = qRound( ( x1 + 1 ) * pw - px0 ); + if ( xx2 > sz.width() ) + xx2 = sz.width(); + } + + for ( int y2 = yy1; y2 < yy2; y2++ ) + { + uchar* line2 = expanded.scanLine( y2 ); + memset( line2 + xx1, line1[x1], xx2 - xx1 ); + } + } + } + break; + } + default: + expanded = image; + } + + return expanded; +} + +static QRectF qwtExpandToPixels(const QRectF& rect, const QRectF& pixelRect) +{ + const double pw = pixelRect.width(); + const double ph = pixelRect.height(); + + const double dx1 = pixelRect.left() - rect.left(); + const double dx2 = pixelRect.right() - rect.right(); + const double dy1 = pixelRect.top() - rect.top(); + const double dy2 = pixelRect.bottom() - rect.bottom(); + + QRectF r; + r.setLeft( pixelRect.left() - qwtCeil( dx1 / pw ) * pw ); + r.setTop( pixelRect.top() - qwtCeil( dy1 / ph ) * ph ); + r.setRight( pixelRect.right() - qwtFloor( dx2 / pw ) * pw ); + r.setBottom( pixelRect.bottom() - qwtFloor( dy2 / ph ) * ph ); + + return r; +} + +static void qwtTransformMaps( const QTransform& tr, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + QwtScaleMap& xxMap, QwtScaleMap& yyMap ) +{ + const QPointF p1 = tr.map( QPointF( xMap.p1(), yMap.p1() ) ); + const QPointF p2 = tr.map( QPointF( xMap.p2(), yMap.p2() ) ); + + xxMap = xMap; + xxMap.setPaintInterval( p1.x(), p2.x() ); + + yyMap = yMap; + yyMap.setPaintInterval( p1.y(), p2.y() ); +} + +static void qwtAdjustMaps( QwtScaleMap& xMap, QwtScaleMap& yMap, + const QRectF& area, const QRectF& paintRect) +{ + double sx1 = area.left(); + double sx2 = area.right(); + if ( xMap.isInverting() ) + qSwap(sx1, sx2); + + double sy1 = area.top(); + double sy2 = area.bottom(); + + if ( yMap.isInverting() ) + qSwap(sy1, sy2); + + xMap.setPaintInterval(paintRect.left(), paintRect.right() ); + xMap.setScaleInterval(sx1, sx2); + + yMap.setPaintInterval(paintRect.top(), paintRect.bottom() ); + yMap.setScaleInterval(sy1, sy2); +} + +static bool qwtUseCache( QwtPlotRasterItem::CachePolicy policy, + const QPainter* painter ) +{ + bool doCache = false; + + if ( policy == QwtPlotRasterItem::PaintCache ) + { + // Caching doesn't make sense, when the item is + // not painted to screen + + switch ( painter->paintEngine()->type() ) + { + case QPaintEngine::SVG: + case QPaintEngine::Pdf: +#if QT_VERSION < 0x060000 + case QPaintEngine::PostScript: +#endif + case QPaintEngine::MacPrinter: + case QPaintEngine::Picture: + break; + default:; + doCache = true; + } + } + + return doCache; +} + +static void qwtToRgba( const QImage* from, QImage* to, + const QRect& tile, int alpha ) +{ + const QRgb mask1 = qRgba( 0, 0, 0, alpha ); + const QRgb mask2 = qRgba( 255, 255, 255, 0 ); + const QRgb mask3 = qRgba( 0, 0, 0, 255 ); + + const int y0 = tile.top(); + const int y1 = tile.bottom(); + const int x0 = tile.left(); + const int x1 = tile.right(); + + if ( from->depth() == 8 ) + { + for ( int y = y0; y <= y1; y++ ) + { + QRgb* alphaLine = reinterpret_cast< QRgb* >( to->scanLine( y ) ); + const unsigned char* line = from->scanLine( y ); + + for ( int x = x0; x <= x1; x++ ) + *alphaLine++ = ( from->color( *line++ ) & mask2 ) | mask1; + } + } + else if ( from->depth() == 32 ) + { + for ( int y = y0; y <= y1; y++ ) + { + QRgb* alphaLine = reinterpret_cast< QRgb* >( to->scanLine( y ) ); + const QRgb* line = reinterpret_cast< const QRgb* >( from->scanLine( y ) ); + + for ( int x = x0; x <= x1; x++ ) + { + const QRgb rgb = *line++; + if ( rgb & mask3 ) // alpha != 0 + *alphaLine++ = ( rgb & mask2 ) | mask1; + else + *alphaLine++ = rgb; + } + } + } +} + +//! Constructor +QwtPlotRasterItem::QwtPlotRasterItem( const QString& title ) + : QwtPlotItem( QwtText( title ) ) +{ + init(); +} + +//! Constructor +QwtPlotRasterItem::QwtPlotRasterItem( const QwtText& title ) + : QwtPlotItem( title ) +{ + init(); +} + +//! Destructor +QwtPlotRasterItem::~QwtPlotRasterItem() +{ + delete m_data; +} + +void QwtPlotRasterItem::init() +{ + m_data = new PrivateData(); + + setItemAttribute( QwtPlotItem::AutoScale, true ); + setItemAttribute( QwtPlotItem::Legend, false ); + + setZ( 8.0 ); +} + +/*! + Specify an attribute how to draw the raster item + + \param attribute Paint attribute + \param on On/Off + /sa PaintAttribute, testPaintAttribute() + */ +void QwtPlotRasterItem::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( on ) + m_data->paintAttributes |= attribute; + else + m_data->paintAttributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa PaintAttribute, setPaintAttribute() + */ +bool QwtPlotRasterItem::testPaintAttribute( PaintAttribute attribute ) const +{ + return ( m_data->paintAttributes & attribute ); +} + +/*! + \brief Set an alpha value for the raster data + + Often a plot has several types of raster data organized in layers. + ( f.e a geographical map, with weather statistics ). + Using setAlpha() raster items can be stacked easily. + + The alpha value is a value [0, 255] to + control the transparency of the image. 0 represents a fully + transparent color, while 255 represents a fully opaque color. + + \param alpha Alpha value + + - alpha >= 0\n + All alpha values of the pixels returned by renderImage() will be set to + alpha, beside those with an alpha value of 0 (invalid pixels). + - alpha < 0 + The alpha values returned by renderImage() are not changed. + + The default alpha value is -1. + + \sa alpha() + */ +void QwtPlotRasterItem::setAlpha( int alpha ) +{ + if ( alpha < 0 ) + alpha = -1; + + if ( alpha > 255 ) + alpha = 255; + + if ( alpha != m_data->alpha ) + { + m_data->alpha = alpha; + + itemChanged(); + } +} + +/*! + \return Alpha value of the raster item + \sa setAlpha() + */ +int QwtPlotRasterItem::alpha() const +{ + return m_data->alpha; +} + +/*! + Change the cache policy + + The default policy is NoCache + + \param policy Cache policy + \sa CachePolicy, cachePolicy() + */ +void QwtPlotRasterItem::setCachePolicy( + QwtPlotRasterItem::CachePolicy policy ) +{ + if ( m_data->cache.policy != policy ) + { + m_data->cache.policy = policy; + + invalidateCache(); + itemChanged(); + } +} + +/*! + \return Cache policy + \sa CachePolicy, setCachePolicy() + */ +QwtPlotRasterItem::CachePolicy QwtPlotRasterItem::cachePolicy() const +{ + return m_data->cache.policy; +} + +/*! + Invalidate the paint cache + \sa setCachePolicy() + */ +void QwtPlotRasterItem::invalidateCache() +{ + m_data->cache.image = QImage(); + m_data->cache.area = QRect(); + m_data->cache.size = QSize(); +} + +/*! + \brief Pixel hint + + The geometry of a pixel is used to calculated the resolution and + alignment of the rendered image. + + Width and height of the hint need to be the horizontal + and vertical distances between 2 neighbored points. + The center of the hint has to be the position of any point + ( it doesn't matter which one ). + + Limiting the resolution of the image might significantly improve + the performance and heavily reduce the amount of memory when rendering + a QImage from the raster data. + + The default implementation returns an empty rectangle (QRectF()), + meaning, that the image will be rendered in target device ( f.e screen ) + resolution. + + \param area In most implementations the resolution of the data doesn't + depend on the requested area. + + \return Bounding rectangle of a pixel + + \sa render(), renderImage() + */ +QRectF QwtPlotRasterItem::pixelHint( const QRectF& area ) const +{ + Q_UNUSED( area ); + return QRectF(); +} + +/*! + \brief Draw the raster data + \param painter Painter + \param xMap X-Scale Map + \param yMap Y-Scale Map + \param canvasRect Contents rectangle of the plot canvas + */ +void QwtPlotRasterItem::draw( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + if ( canvasRect.isEmpty() || m_data->alpha == 0 ) + return; + + const bool doCache = qwtUseCache( m_data->cache.policy, painter ); + + const QwtInterval xInterval = interval( Qt::XAxis ); + const QwtInterval yInterval = interval( Qt::YAxis ); + + /* + Scaling an image always results in a loss of + precision/quality. So we always render the image in + paint device resolution. + */ + + QwtScaleMap xxMap, yyMap; + qwtTransformMaps( painter->transform(), xMap, yMap, xxMap, yyMap ); + + QRectF paintRect = painter->transform().mapRect( canvasRect ); + QRectF area = QwtScaleMap::invTransform( xxMap, yyMap, paintRect ); + + const QRectF br = boundingRect(); + if ( br.isValid() && !br.contains( area ) ) + { + area &= br; + if ( !area.isValid() ) + return; + + paintRect = QwtScaleMap::transform( xxMap, yyMap, area ); + } + + QRectF imageRect; + QImage image; + + QRectF pixelRect = pixelHint(area); + if ( !pixelRect.isEmpty() ) + { + // one pixel of the target device in plot coordinates + const double dx = qAbs( xxMap.invTransform( 1 ) - xxMap.invTransform( 0 ) ); + const double dy = qAbs( yyMap.invTransform( 1 ) - yyMap.invTransform( 0 ) ); + + if ( dx > pixelRect.width() && dy > pixelRect.height() ) + { + /* + When the resolution of the data pixels is higher than + the resolution of the target device we render in + target device resolution. + */ + pixelRect = QRectF(); + } + else + { + /* + If only one dimension is of the data pixel is higher + we expand the pixel rect to the resolution of the target device. + */ + + if ( dx > pixelRect.width() ) + pixelRect.setWidth( dx ); + + if ( dy > pixelRect.height() ) + pixelRect.setHeight( dy ); + } + } + + if ( pixelRect.isEmpty() ) + { + if ( QwtPainter::roundingAlignment( painter ) ) + { + // we want to have maps, where the boundaries of + // the aligned paint rectangle exactly match the area + + paintRect = qwtAlignRect(paintRect); + qwtAdjustMaps(xxMap, yyMap, area, paintRect); + } + + // When we have no information about position and size of + // data pixels we render in resolution of the paint device. + + image = compose(xxMap, yyMap, + area, paintRect, paintRect.size().toSize(), doCache); + if ( image.isNull() ) + return; + + // Remove pixels at the boundaries, when explicitly + // excluded in the intervals + + imageRect = qwtStripRect(paintRect, area, + xxMap, yyMap, xInterval, yInterval); + + if ( imageRect != paintRect ) + { + const QRect r( + qRound( imageRect.x() - paintRect.x() ), + qRound( imageRect.y() - paintRect.y() ), + qRound( imageRect.width() ), + qRound( imageRect.height() ) ); + + image = image.copy(r); + } + } + else + { + if ( QwtPainter::roundingAlignment( painter ) ) + paintRect = qwtAlignRect(paintRect); + + // align the area to the data pixels + QRectF imageArea = qwtExpandToPixels(area, pixelRect); + + if ( imageArea.right() == xInterval.maxValue() && + !( xInterval.borderFlags() & QwtInterval::ExcludeMaximum ) ) + { + imageArea.adjust(0, 0, pixelRect.width(), 0); + } + if ( imageArea.bottom() == yInterval.maxValue() && + !( yInterval.borderFlags() & QwtInterval::ExcludeMaximum ) ) + { + imageArea.adjust(0, 0, 0, pixelRect.height() ); + } + + QSize imageSize; + imageSize.setWidth( qRound( imageArea.width() / pixelRect.width() ) ); + imageSize.setHeight( qRound( imageArea.height() / pixelRect.height() ) ); + + image = compose(xxMap, yyMap, + imageArea, paintRect, imageSize, doCache ); + + if ( image.isNull() ) + return; + + imageRect = qwtStripRect(paintRect, area, + xxMap, yyMap, xInterval, yInterval); + + if ( ( image.width() > 1 || image.height() > 1 ) && + testPaintAttribute( PaintInDeviceResolution ) ) + { + // Because of rounding errors the pixels + // need to be expanded manually to rectangles of + // different sizes + + image = qwtExpandImage(image, xxMap, yyMap, + imageArea, area, paintRect, xInterval, yInterval ); + } + } + + painter->save(); + painter->setWorldTransform( QTransform() ); + + QwtPainter::drawImage( painter, imageRect, image ); + + painter->restore(); +} + +/*! + \return Bounding interval for an axis + + This method is intended to be reimplemented by derived classes. + The default implementation returns an invalid interval. + + \param axis X, Y, or Z axis + */ +QwtInterval QwtPlotRasterItem::interval(Qt::Axis axis) const +{ + Q_UNUSED( axis ); + return QwtInterval(); +} + +/*! + \return Bounding rectangle of the data + \sa QwtPlotRasterItem::interval() + */ +QRectF QwtPlotRasterItem::boundingRect() const +{ + const QwtInterval intervalX = interval( Qt::XAxis ); + const QwtInterval intervalY = interval( Qt::YAxis ); + + if ( !intervalX.isValid() && !intervalY.isValid() ) + return QRectF(); // no bounding rect + + QRectF r; + + if ( intervalX.isValid() ) + { + r.setLeft( intervalX.minValue() ); + r.setRight( intervalX.maxValue() ); + } + else + { + const qreal max = std::numeric_limits< float >::max(); + + r.setLeft( -0.5 * max ); + r.setWidth( max ); + } + + if ( intervalY.isValid() ) + { + r.setTop( intervalY.minValue() ); + r.setBottom( intervalY.maxValue() ); + } + else + { + const qreal max = std::numeric_limits< float >::max(); + + r.setTop( -0.5 * max ); + r.setHeight( max ); + } + + return r.normalized(); +} + +QImage QwtPlotRasterItem::compose( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& imageArea, const QRectF& paintRect, + const QSize& imageSize, bool doCache) const +{ + QImage image; + if ( imageArea.isEmpty() || paintRect.isEmpty() || imageSize.isEmpty() ) + return image; + + if ( doCache ) + { + if ( !m_data->cache.image.isNull() + && m_data->cache.area == imageArea + && m_data->cache.size == paintRect.size() ) + { + image = m_data->cache.image; + } + } + + if ( image.isNull() ) + { + double dx = 0.0; + if ( paintRect.toRect().width() > imageSize.width() ) + dx = imageArea.width() / imageSize.width(); + + const QwtScaleMap xxMap = + imageMap(Qt::Horizontal, xMap, imageArea, imageSize, dx); + + double dy = 0.0; + if ( paintRect.toRect().height() > imageSize.height() ) + dy = imageArea.height() / imageSize.height(); + + const QwtScaleMap yyMap = + imageMap(Qt::Vertical, yMap, imageArea, imageSize, dy); + + image = renderImage( xxMap, yyMap, imageArea, imageSize ); + + if ( doCache ) + { + m_data->cache.area = imageArea; + m_data->cache.size = paintRect.size(); + m_data->cache.image = image; + } + } + + if ( m_data->alpha >= 0 && m_data->alpha < 255 ) + { + QImage alphaImage( image.size(), QImage::Format_ARGB32 ); + +#if !defined( QT_NO_QFUTURE ) + uint numThreads = renderThreadCount(); + + if ( numThreads <= 0 ) + numThreads = QThread::idealThreadCount(); + + if ( numThreads <= 0 ) + numThreads = 1; + + const int numRows = image.height() / numThreads; + + QVector< QFuture< void > > futures; + futures.reserve( numThreads - 1 ); + + for ( uint i = 0; i < numThreads; i++ ) + { + QRect tile( 0, i * numRows, image.width(), numRows ); + if ( i == numThreads - 1 ) + { + tile.setHeight( image.height() - i * numRows ); + qwtToRgba( &image, &alphaImage, tile, m_data->alpha ); + } + else + { + futures += QtConcurrent::run( + &qwtToRgba, &image, &alphaImage, tile, m_data->alpha ); + } + } + for ( int i = 0; i < futures.size(); i++ ) + futures[i].waitForFinished(); +#else + const QRect tile( 0, 0, image.width(), image.height() ); + qwtToRgba( &image, &alphaImage, tile, m_data->alpha ); +#endif + image = alphaImage; + } + + return image; +} + +/*! + \brief Calculate a scale map for painting to an image + + \param orientation Orientation, Qt::Horizontal means a X axis + \param map Scale map for rendering the plot item + \param area Area to be painted on the image + \param imageSize Image size + \param pixelSize Width/Height of a data pixel + + \return Calculated scale map + */ +QwtScaleMap QwtPlotRasterItem::imageMap( + Qt::Orientation orientation, + const QwtScaleMap& map, const QRectF& area, + const QSize& imageSize, double pixelSize) const +{ + double p1, p2, s1, s2; + + if ( orientation == Qt::Horizontal ) + { + p1 = 0.0; + p2 = imageSize.width(); + s1 = area.left(); + s2 = area.right(); + } + else + { + p1 = 0.0; + p2 = imageSize.height(); + s1 = area.top(); + s2 = area.bottom(); + } + + if ( pixelSize > 0.0 || p2 == 1.0 ) + { + double off = 0.5 * pixelSize; + if ( map.isInverting() ) + off = -off; + + s1 += off; + s2 += off; + } + else + { + p2--; + } + + if ( map.isInverting() && ( s1 < s2 ) ) + qSwap( s1, s2 ); + + QwtScaleMap newMap = map; + newMap.setPaintInterval( p1, p2 ); + newMap.setScaleInterval( s1, s2 ); + + return newMap; +} diff --git a/libs/qwt/src/qwt_plot_rasteritem.h b/libs/qwt/src/qwt_plot_rasteritem.h new file mode 100644 index 00000000..4b66f6e3 --- /dev/null +++ b/libs/qwt/src/qwt_plot_rasteritem.h @@ -0,0 +1,151 @@ +/****************************************************************************** + * 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_RASTERITEM_H +#define QWT_PLOT_RASTERITEM_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" + +#include + +class QwtInterval; + +/*! + \brief A class, which displays raster data + + Raster data is a grid of pixel values, that can be represented + as a QImage. It is used for many types of information like + spectrograms, cartograms, geographical maps ... + + Often a plot has several types of raster data organized in layers. + ( f.e a geographical map, with weather statistics ). + Using setAlpha() raster items can be stacked easily. + + QwtPlotRasterItem is only implemented for images of the following formats: + QImage::Format_Indexed8, QImage::Format_ARGB32. + + \sa QwtPlotSpectrogram + */ + +class QWT_EXPORT QwtPlotRasterItem : public QwtPlotItem +{ + public: + /*! + \brief Cache policy + The default policy is NoCache + */ + enum CachePolicy + { + /*! + renderImage() is called each time the item has to be repainted + */ + NoCache, + + /*! + renderImage() is called, whenever the image cache is not valid, + or the scales, or the size of the canvas has changed. + + This type of cache is useful for improving the performance + of hide/show operations or manipulations of the alpha value. + All other situations are handled by the canvas backing store. + */ + PaintCache + }; + + /*! + Attributes to modify the drawing algorithm. + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + /*! + When the image is rendered according to the data pixels + ( QwtRasterData::pixelHint() ) it can be expanded to paint + device resolution before it is passed to QPainter. + The expansion algorithm rounds the pixel borders in the same + way as the axis ticks, what is usually better than the + scaling algorithm implemented in Qt. + Disabling this flag might make sense, to reduce the size of a + document/file. If this is possible for a document format + depends on the implementation of the specific QPaintEngine. + */ + + PaintInDeviceResolution = 1 + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + explicit QwtPlotRasterItem( const QString& title = QString() ); + explicit QwtPlotRasterItem( const QwtText& title ); + virtual ~QwtPlotRasterItem(); + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setAlpha( int alpha ); + int alpha() const; + + void setCachePolicy( CachePolicy ); + CachePolicy cachePolicy() const; + + void invalidateCache(); + + virtual void draw( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + virtual QRectF pixelHint( const QRectF& ) const; + + virtual QwtInterval interval(Qt::Axis) const; + virtual QRectF boundingRect() const QWT_OVERRIDE; + + protected: + /*! + \brief Render an image + + An implementation of render() might iterate over all + pixels of imageRect. Each pixel has to be translated into + the corresponding position in scale coordinates using the maps. + This position can be used to look up a value in a implementation + specific way and to map it into a color. + + \param xMap X-Scale Map + \param yMap Y-Scale Map + \param area Requested area for the image in scale coordinates + \param imageSize Requested size of the image + + \return Rendered image + */ + virtual QImage renderImage( const QwtScaleMap& xMap, + const QwtScaleMap& yMap, const QRectF& area, + const QSize& imageSize ) const = 0; + + virtual QwtScaleMap imageMap( Qt::Orientation, + const QwtScaleMap& map, const QRectF& area, + const QSize& imageSize, double pixelSize) const; + + private: + explicit QwtPlotRasterItem( const QwtPlotRasterItem& ); + QwtPlotRasterItem& operator=( const QwtPlotRasterItem& ); + + void init(); + + QImage compose( const QwtScaleMap&, const QwtScaleMap&, + const QRectF& imageArea, const QRectF& paintRect, + const QSize& imageSize, bool doCache) const; + + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotRasterItem::PaintAttributes ) + +#endif diff --git a/libs/qwt/src/qwt_plot_renderer.cpp b/libs/qwt/src/qwt_plot_renderer.cpp new file mode 100644 index 00000000..058c6d0c --- /dev/null +++ b/libs/qwt/src/qwt_plot_renderer.cpp @@ -0,0 +1,1115 @@ +/****************************************************************************** + * 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_renderer.h" +#include "qwt_plot.h" +#include "qwt_painter.h" +#include "qwt_plot_layout.h" +#include "qwt_abstract_legend.h" +#include "qwt_scale_widget.h" +#include "qwt_scale_engine.h" +#include "qwt_scale_map.h" +#include "qwt_text.h" +#include "qwt_text_label.h" +#include "qwt_math.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef QWT_NO_SVG +#ifdef QT_SVG_LIB +#define QWT_FORMAT_SVG 1 +#endif +#endif + +#ifndef QT_NO_PRINTER +#define QWT_FORMAT_PDF 1 +#endif + +#ifndef QT_NO_PDF + +// QPdfWriter::setResolution() has been introduced with +// Qt 5.3. Guess it is o.k. to stay with QPrinter for older +// versions. + +#if QT_VERSION >= 0x050300 + +#ifndef QWT_FORMAT_PDF +#define QWT_FORMAT_PDF 1 +#endif + +#define QWT_PDF_WRITER 1 + +#endif +#endif + +#ifndef QT_NO_PRINTER +// postscript support has been dropped in Qt5 +#if QT_VERSION < 0x050000 +#define QWT_FORMAT_POSTSCRIPT 1 +#endif +#endif + +#if QWT_FORMAT_SVG +#include +#endif + +#if QWT_PDF_WRITER +#include +#endif + +static qreal qwtScalePenWidth( const QwtPlot* plot ) +{ + qreal pw = 0.0; + + for ( int axisId = 0; axisId < QwtAxis::AxisPositions; axisId++ ) + { + if ( plot->isAxisVisible( axisId ) ) + pw = qMax( pw, plot->axisScaleDraw( axisId )->penWidthF() ); + } + + return pw; +} + +static QColor qwtScalePenColor( const QwtPlot* plot ) +{ + const QPalette pal = plot->axisWidget( QwtAxis::YLeft )->palette(); + return pal.color( QPalette::WindowText ); +} + +static QPainterPath qwtCanvasClip( + const QWidget* canvas, const QRectF& canvasRect ) +{ + // The clip region is calculated in integers + // To avoid too much rounding errors better + // calculate it in target device resolution + + int x1 = qwtCeil( canvasRect.left() ); + int x2 = qwtFloor( canvasRect.right() ); + int y1 = qwtCeil( canvasRect.top() ); + int y2 = qwtFloor( canvasRect.bottom() ); + + const QRect r( x1, y1, x2 - x1 - 1, y2 - y1 - 1 ); + + QPainterPath clipPath; + + ( void ) QMetaObject::invokeMethod( + const_cast< QWidget* >( canvas ), "borderPath", + Qt::DirectConnection, + Q_RETURN_ARG( QPainterPath, clipPath ), Q_ARG( QRect, r ) ); + + return clipPath; +} + +static inline QFont qwtResolvedFont( const QWidget* widget ) +{ + QFont font = widget->font(); +#if QT_VERSION >= 0x060000 + font.setResolveMask( QFont::AllPropertiesResolved ); +#else + font.resolve( QFont::AllPropertiesResolved ); +#endif + + return font; +} + +class QwtPlotRenderer::PrivateData +{ + public: + PrivateData() + : discardFlags( QwtPlotRenderer::DiscardNone ) + , layoutFlags( QwtPlotRenderer::DefaultLayout ) + { + } + + QwtPlotRenderer::DiscardFlags discardFlags; + QwtPlotRenderer::LayoutFlags layoutFlags; +}; + +/*! + Constructor + \param parent Parent object + */ +QwtPlotRenderer::QwtPlotRenderer( QObject* parent ) + : QObject( parent ) +{ + m_data = new PrivateData; +} + +//! Destructor +QwtPlotRenderer::~QwtPlotRenderer() +{ + delete m_data; +} + +/*! + Change a flag, indicating what to discard from rendering + + \param flag Flag to change + \param on On/Off + + \sa DiscardFlag, testDiscardFlag(), setDiscardFlags(), discardFlags() + */ +void QwtPlotRenderer::setDiscardFlag( DiscardFlag flag, bool on ) +{ + if ( on ) + m_data->discardFlags |= flag; + else + m_data->discardFlags &= ~flag; +} + +/*! + \return True, if flag is enabled. + \param flag Flag to be tested + \sa DiscardFlag, setDiscardFlag(), setDiscardFlags(), discardFlags() + */ +bool QwtPlotRenderer::testDiscardFlag( DiscardFlag flag ) const +{ + return m_data->discardFlags & flag; +} + +/*! + Set the flags, indicating what to discard from rendering + + \param flags Flags + \sa DiscardFlag, setDiscardFlag(), testDiscardFlag(), discardFlags() + */ +void QwtPlotRenderer::setDiscardFlags( DiscardFlags flags ) +{ + m_data->discardFlags = flags; +} + +/*! + \return Flags, indicating what to discard from rendering + \sa DiscardFlag, setDiscardFlags(), setDiscardFlag(), testDiscardFlag() + */ +QwtPlotRenderer::DiscardFlags QwtPlotRenderer::discardFlags() const +{ + return m_data->discardFlags; +} + +/*! + Change a layout flag + + \param flag Flag to change + \param on On/Off + + \sa LayoutFlag, testLayoutFlag(), setLayoutFlags(), layoutFlags() + */ +void QwtPlotRenderer::setLayoutFlag( LayoutFlag flag, bool on ) +{ + if ( on ) + m_data->layoutFlags |= flag; + else + m_data->layoutFlags &= ~flag; +} + +/*! + \return True, if flag is enabled. + \param flag Flag to be tested + \sa LayoutFlag, setLayoutFlag(), setLayoutFlags(), layoutFlags() + */ +bool QwtPlotRenderer::testLayoutFlag( LayoutFlag flag ) const +{ + return m_data->layoutFlags & flag; +} + +/*! + Set the layout flags + + \param flags Flags + \sa LayoutFlag, setLayoutFlag(), testLayoutFlag(), layoutFlags() + */ +void QwtPlotRenderer::setLayoutFlags( LayoutFlags flags ) +{ + m_data->layoutFlags = flags; +} + +/*! + \return Layout flags + \sa LayoutFlag, setLayoutFlags(), setLayoutFlag(), testLayoutFlag() + */ +QwtPlotRenderer::LayoutFlags QwtPlotRenderer::layoutFlags() const +{ + return m_data->layoutFlags; +} + +/*! + Render a plot to a file + + The format of the document will be auto-detected from the + suffix of the file name. + + \param plot Plot widget + \param fileName Path of the file, where the document will be stored + \param sizeMM Size for the document in millimeters. + \param resolution Resolution in dots per Inch (dpi) + */ +void QwtPlotRenderer::renderDocument( QwtPlot* plot, + const QString& fileName, const QSizeF& sizeMM, int resolution ) +{ + renderDocument( plot, fileName, + QFileInfo( fileName ).suffix(), sizeMM, resolution ); +} + +/*! + Render a plot to a file + + Supported formats are: + + - pdf\n + Portable Document Format PDF + - ps\n + Postcript + - svg\n + Scalable Vector Graphics SVG + - all image formats supported by Qt\n + see QImageWriter::supportedImageFormats() + + Scalable vector graphic formats like PDF or SVG are superior to + raster graphics formats. + + \param plot Plot widget + \param fileName Path of the file, where the document will be stored + \param format Format for the document + \param sizeMM Size for the document in millimeters. + \param resolution Resolution in dots per Inch (dpi) + + \sa renderTo(), render(), QwtPainter::setRoundingAlignment() + */ +void QwtPlotRenderer::renderDocument( QwtPlot* plot, + const QString& fileName, const QString& format, + const QSizeF& sizeMM, int resolution ) +{ + if ( plot == NULL || sizeMM.isEmpty() || resolution <= 0 ) + return; + + QString title = plot->title().text(); + if ( title.isEmpty() ) + title = "Plot Document"; + + const double mmToInch = 1.0 / 25.4; + const QSizeF size = sizeMM * mmToInch * resolution; + + const QRectF documentRect( 0.0, 0.0, size.width(), size.height() ); + + const QString fmt = format.toLower(); + if ( fmt == QLatin1String( "pdf" ) ) + { +#if QWT_FORMAT_PDF + +#if QWT_PDF_WRITER + QPdfWriter pdfWriter( fileName ); + pdfWriter.setPageSize( QPageSize( sizeMM, QPageSize::Millimeter ) ); + pdfWriter.setTitle( title ); + pdfWriter.setPageMargins( QMarginsF() ); + pdfWriter.setResolution( resolution ); + + QPainter painter( &pdfWriter ); + render( plot, &painter, documentRect ); +#else + QPrinter printer; + printer.setOutputFormat( QPrinter::PdfFormat ); + printer.setColorMode( QPrinter::Color ); + printer.setFullPage( true ); + printer.setPaperSize( sizeMM, QPrinter::Millimeter ); + printer.setDocName( title ); + printer.setOutputFileName( fileName ); + printer.setResolution( resolution ); + + QPainter painter( &printer ); + render( plot, &painter, documentRect ); +#endif +#endif + } + else if ( fmt == QLatin1String( "ps" ) ) + { +#if QWT_FORMAT_POSTSCRIPT + QPrinter printer; + printer.setOutputFormat( QPrinter::PostScriptFormat ); + printer.setColorMode( QPrinter::Color ); + printer.setFullPage( true ); + printer.setPaperSize( sizeMM, QPrinter::Millimeter ); + printer.setDocName( title ); + printer.setOutputFileName( fileName ); + printer.setResolution( resolution ); + + QPainter painter( &printer ); + render( plot, &painter, documentRect ); +#endif + } + else if ( fmt == QLatin1String( "svg" ) ) + { +#if QWT_FORMAT_SVG + QSvgGenerator generator; + generator.setTitle( title ); + generator.setFileName( fileName ); + generator.setResolution( resolution ); + generator.setViewBox( documentRect ); + + QPainter painter( &generator ); + render( plot, &painter, documentRect ); +#endif + } + else + { + if ( QImageWriter::supportedImageFormats().indexOf( + format.toLatin1() ) >= 0 ) + { + const QRect imageRect = documentRect.toRect(); + const int dotsPerMeter = qRound( resolution * mmToInch * 1000.0 ); + + QImage image( imageRect.size(), QImage::Format_ARGB32 ); + image.setDotsPerMeterX( dotsPerMeter ); + image.setDotsPerMeterY( dotsPerMeter ); + image.fill( QColor( Qt::white ).rgb() ); + + QPainter painter( &image ); + render( plot, &painter, imageRect ); + painter.end(); + + image.save( fileName, format.toLatin1() ); + } + } +} + +/*! + \brief Render the plot to a \c QPaintDevice + + This function renders the contents of a QwtPlot instance to + \c QPaintDevice object. The target rectangle is derived from + its device metrics. + + \param plot Plot to be rendered + \param paintDevice device to paint on, f.e a QImage + + \sa renderDocument(), render(), QwtPainter::setRoundingAlignment() + */ + +void QwtPlotRenderer::renderTo( + QwtPlot* plot, QPaintDevice& paintDevice ) const +{ + int w = paintDevice.width(); + int h = paintDevice.height(); + + QPainter p( &paintDevice ); + render( plot, &p, QRectF( 0, 0, w, h ) ); +} + +/*! + \brief Render the plot to a QPrinter + + This function renders the contents of a QwtPlot instance to + \c QPaintDevice object. The size is derived from the printer + metrics. + + \param plot Plot to be rendered + \param printer Printer to paint on + + \sa renderDocument(), render(), QwtPainter::setRoundingAlignment() + */ + +#ifndef QT_NO_PRINTER + +void QwtPlotRenderer::renderTo( + QwtPlot* plot, QPrinter& printer ) const +{ + int w = printer.width(); + int h = printer.height(); + + QRectF rect( 0, 0, w, h ); + double aspect = rect.width() / rect.height(); + if ( ( aspect < 1.0 ) ) + rect.setHeight( aspect * rect.width() ); + + QPainter p( &printer ); + render( plot, &p, rect ); +} + +#endif + +#if QWT_FORMAT_SVG + +/*! + \brief Render the plot to a QSvgGenerator + + If the generator has a view box, the plot will be rendered into it. + If it has no viewBox but a valid size the target coordinates + will be (0, 0, generator.width(), generator.height()). Otherwise + the target rectangle will be QRectF(0, 0, 800, 600); + + \param plot Plot to be rendered + \param generator SVG generator + */ +void QwtPlotRenderer::renderTo( + QwtPlot* plot, QSvgGenerator& generator ) const +{ + QRectF rect = generator.viewBoxF(); + if ( rect.isEmpty() ) + rect.setRect( 0, 0, generator.width(), generator.height() ); + + if ( rect.isEmpty() ) + rect.setRect( 0, 0, 800, 600 ); // something + + QPainter p( &generator ); + render( plot, &p, rect ); +} + +#endif + +/*! + Paint the contents of a QwtPlot instance into a given rectangle. + + \param plot Plot to be rendered + \param painter Painter + \param plotRect Bounding rectangle + + \sa renderDocument(), renderTo(), QwtPainter::setRoundingAlignment() + */ +void QwtPlotRenderer::render( QwtPlot* plot, + QPainter* painter, const QRectF& plotRect ) const +{ + if ( painter == 0 || !painter->isActive() || + !plotRect.isValid() || plot->size().isNull() ) + { + return; + } + + if ( !( m_data->discardFlags & DiscardBackground ) ) + QwtPainter::drawBackgound( painter, plotRect, plot ); + + /* + The layout engine uses the same methods as they are used + by the Qt layout system. Therefore we need to calculate the + layout in screen coordinates and paint with a scaled painter. + */ + QTransform transform; + transform.scale( + double( painter->device()->logicalDpiX() ) / plot->logicalDpiX(), + double( painter->device()->logicalDpiY() ) / plot->logicalDpiY() ); + + QRectF layoutRect = transform.inverted().mapRect( plotRect ); + + if ( !( m_data->discardFlags & DiscardBackground ) ) + { + // subtract the contents margins + + const QMargins m = plot->contentsMargins(); + layoutRect.adjust( m.left(), m.top(), -m.right(), -m.bottom() ); + } + + QwtPlotLayout* layout = plot->plotLayout(); + + int baseLineDists[QwtAxis::AxisPositions]; + int canvasMargins[QwtAxis::AxisPositions]; + + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + canvasMargins[axisPos] = layout->canvasMargin( axisPos ); + + if ( m_data->layoutFlags & FrameWithScales ) + { + const QwtAxisId axisId( axisPos ); + + QwtScaleWidget* scaleWidget = plot->axisWidget( axisId ); + if ( scaleWidget ) + { + baseLineDists[axisPos] = scaleWidget->margin(); + scaleWidget->setMargin( 0 ); + } + + if ( !plot->isAxisVisible( axisId ) ) + { + // When we have a scale the frame is painted on + // the position of the backbone - otherwise we + // need to introduce a margin around the canvas + + const qreal fw = qwtScalePenWidth( plot ); + + switch( axisPos ) + { + case QwtAxis::YLeft: + layoutRect.adjust( fw, 0, 0, 0 ); + break; + + case QwtAxis::YRight: + layoutRect.adjust( 0, 0, -fw, 0 ); + break; + + case QwtAxis::XTop: + layoutRect.adjust( 0, fw, 0, 0 ); + break; + + case QwtAxis::XBottom: + layoutRect.adjust( 0, 0, 0, -fw ); + break; + + default: + ; + } + } + } + } + + // Calculate the layout for the document. + + QwtPlotLayout::Options layoutOptions = QwtPlotLayout::IgnoreScrollbars; + + if ( ( m_data->layoutFlags & FrameWithScales ) || + ( m_data->discardFlags & DiscardCanvasFrame ) ) + { + layoutOptions |= QwtPlotLayout::IgnoreFrames; + } + + if ( m_data->discardFlags & DiscardLegend ) + layoutOptions |= QwtPlotLayout::IgnoreLegend; + + if ( m_data->discardFlags & DiscardTitle ) + layoutOptions |= QwtPlotLayout::IgnoreTitle; + + if ( m_data->discardFlags & DiscardFooter ) + layoutOptions |= QwtPlotLayout::IgnoreFooter; + + layout->activate( plot, layoutRect, layoutOptions ); + + // canvas + + QwtScaleMap maps[QwtAxis::AxisPositions]; + buildCanvasMaps( plot, layout->canvasRect(), maps ); + if ( updateCanvasMargins( plot, layout->canvasRect(), maps ) ) + { + // recalculate maps and layout, when the margins + // have been changed + + layout->activate( plot, layoutRect, layoutOptions ); + buildCanvasMaps( plot, layout->canvasRect(), maps ); + } + + // now start painting + + painter->save(); + painter->setWorldTransform( transform, true ); + + renderCanvas( plot, painter, layout->canvasRect(), maps ); + + if ( !( m_data->discardFlags & DiscardTitle ) + && ( !plot->titleLabel()->text().isEmpty() ) ) + { + renderTitle( plot, painter, layout->titleRect() ); + } + + if ( !( m_data->discardFlags & DiscardFooter ) + && ( !plot->footerLabel()->text().isEmpty() ) ) + { + renderFooter( plot, painter, layout->footerRect() ); + } + + if ( !( m_data->discardFlags & DiscardLegend ) + && plot->legend() && !plot->legend()->isEmpty() ) + { + renderLegend( plot, painter, layout->legendRect() ); + } + + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + QwtScaleWidget* scaleWidget = plot->axisWidget( axisId ); + if ( scaleWidget ) + { + int baseDist = scaleWidget->margin(); + + int startDist, endDist; + scaleWidget->getBorderDistHint( startDist, endDist ); + + renderScale( plot, painter, axisId, startDist, endDist, + baseDist, layout->scaleRect( axisId ) ); + } + } + } + + painter->restore(); + + // restore all setting to their original attributes. + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + if ( m_data->layoutFlags & FrameWithScales ) + { + const QwtAxisId axisId( axisPos ); + + QwtScaleWidget* scaleWidget = plot->axisWidget( axisId ); + if ( scaleWidget ) + scaleWidget->setMargin( baseLineDists[axisPos] ); + } + + layout->setCanvasMargin( canvasMargins[axisPos] ); + } + + layout->invalidate(); + +} + +/*! + Render the title into a given rectangle. + + \param plot Plot widget + \param painter Painter + \param titleRect Bounding rectangle for the title + */ +void QwtPlotRenderer::renderTitle( const QwtPlot* plot, + QPainter* painter, const QRectF& titleRect ) const +{ + painter->setFont( qwtResolvedFont( plot->titleLabel() ) ); + + const QColor color = plot->titleLabel()->palette().color( + QPalette::Active, QPalette::Text ); + + painter->setPen( color ); + plot->titleLabel()->text().draw( painter, titleRect ); +} + +/*! + Render the footer into a given rectangle. + + \param plot Plot widget + \param painter Painter + \param footerRect Bounding rectangle for the footer + */ +void QwtPlotRenderer::renderFooter( const QwtPlot* plot, + QPainter* painter, const QRectF& footerRect ) const +{ + painter->setFont( qwtResolvedFont( plot->footerLabel() ) ); + + const QColor color = plot->footerLabel()->palette().color( + QPalette::Active, QPalette::Text ); + + painter->setPen( color ); + plot->footerLabel()->text().draw( painter, footerRect ); +} + +/*! + Render the legend into a given rectangle. + + \param plot Plot widget + \param painter Painter + \param legendRect Bounding rectangle for the legend + */ +void QwtPlotRenderer::renderLegend( const QwtPlot* plot, + QPainter* painter, const QRectF& legendRect ) const +{ + if ( plot->legend() ) + { + bool fillBackground = !( m_data->discardFlags & DiscardBackground ); + plot->legend()->renderLegend( painter, legendRect, fillBackground ); + } +} + +/*! + \brief Paint a scale into a given rectangle. + Paint the scale into a given rectangle. + + \param plot Plot widget + \param painter Painter + \param axisId Axis + \param startDist Start border distance + \param endDist End border distance + \param baseDist Base distance + \param scaleRect Bounding rectangle for the scale + */ +void QwtPlotRenderer::renderScale( const QwtPlot* plot, QPainter* painter, + QwtAxisId axisId, int startDist, int endDist, int baseDist, + const QRectF& scaleRect ) const +{ + if ( !plot->isAxisVisible( axisId ) ) + return; + + const QwtScaleWidget* scaleWidget = plot->axisWidget( axisId ); + if ( scaleWidget->isColorBarEnabled() + && scaleWidget->colorBarWidth() > 0 ) + { + scaleWidget->drawColorBar( painter, scaleWidget->colorBarRect( scaleRect ) ); + baseDist += scaleWidget->colorBarWidth() + scaleWidget->spacing(); + } + + painter->save(); + + QwtScaleDraw::Alignment align; + double x, y, w; + + qreal off = 0.0; + if ( m_data->layoutFlags & FrameWithScales ) + off = qwtScalePenWidth( plot ); + + switch ( axisId ) + { + case QwtAxis::YLeft: + { + x = scaleRect.right() - 1.0 - baseDist - off; + y = scaleRect.y() + startDist; + w = scaleRect.height() - startDist - endDist; + align = QwtScaleDraw::LeftScale; + break; + } + case QwtAxis::YRight: + { + x = scaleRect.left() + baseDist + off; + y = scaleRect.y() + startDist; + w = scaleRect.height() - startDist - endDist; + align = QwtScaleDraw::RightScale; + break; + } + case QwtAxis::XTop: + { + x = scaleRect.left() + startDist; + y = scaleRect.bottom() - 1.0 - baseDist - off; + w = scaleRect.width() - startDist - endDist; + align = QwtScaleDraw::TopScale; + break; + } + case QwtAxis::XBottom: + { + x = scaleRect.left() + startDist; + y = scaleRect.top() + baseDist + off; + w = scaleRect.width() - startDist - endDist; + align = QwtScaleDraw::BottomScale; + break; + } + default: + return; + } + + scaleWidget->drawTitle( painter, align, scaleRect ); + + painter->setFont( qwtResolvedFont( scaleWidget ) ); + + QwtScaleDraw* sd = const_cast< QwtScaleDraw* >( scaleWidget->scaleDraw() ); + const QPointF sdPos = sd->pos(); + const double sdLength = sd->length(); + + const bool hasBackbone = sd->hasComponent( QwtAbstractScaleDraw::Backbone ); + + if ( m_data->layoutFlags & FrameWithScales ) + sd->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + + sd->move( x, y ); + sd->setLength( w ); + + QPalette palette = scaleWidget->palette(); + palette.setCurrentColorGroup( QPalette::Active ); + sd->draw( painter, palette ); + + // reset previous values + sd->move( sdPos ); + sd->setLength( sdLength ); + sd->enableComponent( QwtAbstractScaleDraw::Backbone, hasBackbone ); + + painter->restore(); +} + +/*! + Render the canvas into a given rectangle. + + \param plot Plot widget + \param painter Painter + \param maps Maps mapping between plot and paint device coordinates + \param canvasRect Canvas rectangle + */ +void QwtPlotRenderer::renderCanvas( const QwtPlot* plot, + QPainter* painter, const QRectF& canvasRect, + const QwtScaleMap* maps ) const +{ + const QWidget* canvas = plot->canvas(); + + QRectF r = canvasRect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + + if ( m_data->layoutFlags & FrameWithScales ) + { + painter->save(); + + QPen pen; + pen.setColor( qwtScalePenColor( plot ) ); + pen.setWidth( qwtScalePenWidth( plot ) ); + pen.setJoinStyle( Qt::MiterJoin ); + + painter->setPen( pen ); + + const qreal pw2 = 0.5 * pen.widthF(); + r.adjust( -pw2, -pw2, pw2, pw2 ); + + if ( !( m_data->discardFlags & DiscardCanvasBackground ) ) + { + const QBrush bgBrush = + canvas->palette().brush( plot->backgroundRole() ); + painter->setBrush( bgBrush ); + } + + QwtPainter::drawRect( painter, r ); + + painter->restore(); + painter->save(); + + painter->setClipRect( canvasRect ); + plot->drawItems( painter, canvasRect, maps ); + + painter->restore(); + } + else if ( canvas->testAttribute( Qt::WA_StyledBackground ) ) + { + QPainterPath clipPath; + + painter->save(); + + if ( !( m_data->discardFlags & DiscardCanvasBackground ) ) + { + QwtPainter::drawBackgound( painter, r, canvas ); + clipPath = qwtCanvasClip( canvas, canvasRect ); + } + + painter->restore(); + painter->save(); + + if ( clipPath.isEmpty() ) + painter->setClipRect( canvasRect ); + else + painter->setClipPath( clipPath ); + + plot->drawItems( painter, canvasRect, maps ); + + painter->restore(); + } + else + { + QPainterPath clipPath; + + double frameWidth = 0.0; + + if ( !( m_data->discardFlags & DiscardCanvasFrame ) ) + { + const QVariant fw = canvas->property( "frameWidth" ); + if ( fw.canConvert< double >() ) + frameWidth = fw.value< double >(); + + clipPath = qwtCanvasClip( canvas, canvasRect ); + } + + QRectF innerRect = canvasRect.adjusted( + frameWidth, frameWidth, -frameWidth, -frameWidth ); + + painter->save(); + + if ( clipPath.isEmpty() ) + { + painter->setClipRect( innerRect ); + } + else + { + painter->setClipPath( clipPath ); + } + + if ( !( m_data->discardFlags & DiscardCanvasBackground ) ) + { + QwtPainter::drawBackgound( painter, innerRect, canvas ); + } + + plot->drawItems( painter, innerRect, maps ); + + painter->restore(); + + if ( frameWidth > 0 ) + { + painter->save(); + + const int frameStyle = + canvas->property( "frameShadow" ).toInt() | + canvas->property( "frameShape" ).toInt(); + + const QVariant borderRadius = canvas->property( "borderRadius" ); + if ( borderRadius.canConvert< double >() + && borderRadius.value< double >() > 0.0 ) + { + const double radius = borderRadius.value< double >(); + + QwtPainter::drawRoundedFrame( painter, canvasRect, + radius, radius, canvas->palette(), frameWidth, frameStyle ); + } + else + { + const int midLineWidth = canvas->property( "midLineWidth" ).toInt(); + + QwtPainter::drawFrame( painter, canvasRect, + canvas->palette(), canvas->foregroundRole(), + frameWidth, midLineWidth, frameStyle ); + } + painter->restore(); + } + } +} + +/*! + Calculated the scale maps for rendering the canvas + + \param plot Plot widget + \param canvasRect Target rectangle + \param maps Scale maps to be calculated + */ +void QwtPlotRenderer::buildCanvasMaps( const QwtPlot* plot, + const QRectF& canvasRect, QwtScaleMap maps[] ) const +{ + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + QwtScaleMap& scaleMap = maps[axisId]; + + scaleMap.setTransformation( + plot->axisScaleEngine( axisId )->transformation() ); + + const QwtScaleDiv& scaleDiv = plot->axisScaleDiv( axisId ); + scaleMap.setScaleInterval( + scaleDiv.lowerBound(), scaleDiv.upperBound() ); + + double from, to; + if ( plot->isAxisVisible( axisId ) ) + { + const int sDist = plot->axisWidget( axisId )->startBorderDist(); + const int eDist = plot->axisWidget( axisId )->endBorderDist(); + const QRectF scaleRect = plot->plotLayout()->scaleRect( axisId ); + + if ( QwtAxis::isXAxis( axisPos ) ) + { + from = scaleRect.left() + sDist; + to = scaleRect.right() - eDist; + } + else + { + from = scaleRect.bottom() - eDist; + to = scaleRect.top() + sDist; + } + } + else + { + int margin = 0; + if ( !plot->plotLayout()->alignCanvasToScale( axisPos ) ) + margin = plot->plotLayout()->canvasMargin( axisPos ); + + if ( QwtAxis::isYAxis( axisPos ) ) + { + from = canvasRect.bottom() - margin; + to = canvasRect.top() + margin; + } + else + { + from = canvasRect.left() + margin; + to = canvasRect.right() - margin; + } + } + scaleMap.setPaintInterval( from, to ); + } + } +} + +bool QwtPlotRenderer::updateCanvasMargins( QwtPlot* plot, + const QRectF& canvasRect, const QwtScaleMap maps[] ) const +{ + using namespace QwtAxis; + + double margins[AxisPositions]; + plot->getCanvasMarginsHint( maps, canvasRect, + margins[YLeft], margins[XTop], margins[YRight], margins[XBottom] ); + + bool marginsChanged = false; + for ( int axisId = 0; axisId < AxisPositions; axisId++ ) + { + if ( margins[axisId] >= 0.0 ) + { + const int m = qwtCeil( margins[axisId] ); + plot->plotLayout()->setCanvasMargin( m, axisId); + marginsChanged = true; + } + } + + return marginsChanged; +} + +/*! + \brief Execute a file dialog and render the plot to the selected file + + \param plot Plot widget + \param documentName Default document name + \param sizeMM Size for the document in millimeters. + \param resolution Resolution in dots per Inch (dpi) + + \return True, when exporting was successful + \sa renderDocument() + */ +bool QwtPlotRenderer::exportTo( QwtPlot* plot, const QString& documentName, + const QSizeF& sizeMM, int resolution ) +{ + if ( plot == NULL ) + return false; + + QString fileName = documentName; + + // What about translation + +#ifndef QT_NO_FILEDIALOG + const QList< QByteArray > imageFormats = + QImageWriter::supportedImageFormats(); + + QStringList filter; +#if QWT_FORMAT_PDF + filter += QString( "PDF " ) + tr( "Documents" ) + " (*.pdf)"; +#endif +#if QWT_FORMAT_SVG + filter += QString( "SVG " ) + tr( "Documents" ) + " (*.svg)"; +#endif +#if QWT_FORMAT_POSTSCRIPT + filter += QString( "Postscript " ) + tr( "Documents" ) + " (*.ps)"; +#endif + + if ( imageFormats.size() > 0 ) + { + QString imageFilter( tr( "Images" ) ); + imageFilter += " ("; + for ( int i = 0; i < imageFormats.size(); i++ ) + { + if ( i > 0 ) + imageFilter += " "; + imageFilter += "*."; + imageFilter += imageFormats[i]; + } + imageFilter += ")"; + + filter += imageFilter; + } + + fileName = QFileDialog::getSaveFileName( + NULL, tr( "Export File Name" ), fileName, + filter.join( ";;" ), NULL, QFileDialog::DontConfirmOverwrite ); +#endif + if ( fileName.isEmpty() ) + return false; + + renderDocument( plot, fileName, sizeMM, resolution ); + + return true; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_plot_renderer.cpp" +#endif diff --git a/libs/qwt/src/qwt_plot_renderer.h b/libs/qwt/src/qwt_plot_renderer.h new file mode 100644 index 00000000..e009b4db --- /dev/null +++ b/libs/qwt/src/qwt_plot_renderer.h @@ -0,0 +1,168 @@ +/****************************************************************************** + * 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_RENDERER_H +#define QWT_PLOT_RENDERER_H + +#include "qwt_global.h" +#include "qwt_axis_id.h" + +#include +#include + +class QwtPlot; +class QwtScaleMap; +class QRectF; +class QPainter; +class QPaintDevice; + +#ifndef QT_NO_PRINTER +class QPrinter; +#endif + +#ifndef QWT_NO_SVG +#ifdef QT_SVG_LIB +class QSvgGenerator; +#endif +#endif + +/*! + \brief Renderer for exporting a plot to a document, a printer + or anything else, that is supported by QPainter/QPaintDevice + */ +class QWT_EXPORT QwtPlotRenderer : public QObject +{ + Q_OBJECT + + public: + //! Discard flags + enum DiscardFlag + { + //! Render all components of the plot + DiscardNone = 0x00, + + //! Don't render the background of the plot + DiscardBackground = 0x01, + + //! Don't render the title of the plot + DiscardTitle = 0x02, + + //! Don't render the legend of the plot + DiscardLegend = 0x04, + + //! Don't render the background of the canvas + DiscardCanvasBackground = 0x08, + + //! Don't render the footer of the plot + DiscardFooter = 0x10, + + /*! + Don't render the frame of the canvas + + \note This flag has no effect when using + style sheets, where the frame is part + of the background + */ + DiscardCanvasFrame = 0x20 + + }; + + Q_DECLARE_FLAGS( DiscardFlags, DiscardFlag ) + + /*! + \brief Layout flags + \sa setLayoutFlag(), testLayoutFlag() + */ + enum LayoutFlag + { + //! Use the default layout as on screen + DefaultLayout = 0x00, + + /*! + Instead of the scales a box is painted around the plot canvas, + where the scale ticks are aligned to. + */ + FrameWithScales = 0x01 + }; + + Q_DECLARE_FLAGS( LayoutFlags, LayoutFlag ) + + explicit QwtPlotRenderer( QObject* = NULL ); + virtual ~QwtPlotRenderer(); + + void setDiscardFlag( DiscardFlag flag, bool on = true ); + bool testDiscardFlag( DiscardFlag flag ) const; + + void setDiscardFlags( DiscardFlags flags ); + DiscardFlags discardFlags() const; + + void setLayoutFlag( LayoutFlag flag, bool on = true ); + bool testLayoutFlag( LayoutFlag flag ) const; + + void setLayoutFlags( LayoutFlags flags ); + LayoutFlags layoutFlags() const; + + void renderDocument( QwtPlot*, const QString& fileName, + const QSizeF& sizeMM, int resolution = 85 ); + + void renderDocument( QwtPlot*, + const QString& fileName, const QString& format, + const QSizeF& sizeMM, int resolution = 85 ); + +#ifndef QWT_NO_SVG +#ifdef QT_SVG_LIB + void renderTo( QwtPlot*, QSvgGenerator& ) const; +#endif +#endif + +#ifndef QT_NO_PRINTER + void renderTo( QwtPlot*, QPrinter& ) const; +#endif + + void renderTo( QwtPlot*, QPaintDevice& ) const; + + virtual void render( QwtPlot*, + QPainter*, const QRectF& plotRect ) const; + + virtual void renderTitle( const QwtPlot*, + QPainter*, const QRectF& titleRect ) const; + + virtual void renderFooter( const QwtPlot*, + QPainter*, const QRectF& footerRect ) const; + + virtual void renderScale( const QwtPlot*, QPainter*, + QwtAxisId, int startDist, int endDist, + int baseDist, const QRectF& scaleRect ) const; + + virtual void renderCanvas( const QwtPlot*, + QPainter*, const QRectF& canvasRect, + const QwtScaleMap* maps ) const; + + virtual void renderLegend( + const QwtPlot*, QPainter*, const QRectF& legendRect ) const; + + bool exportTo( QwtPlot*, const QString& documentName, + const QSizeF& sizeMM = QSizeF( 300, 200 ), int resolution = 85 ); + + private: + void buildCanvasMaps( const QwtPlot*, + const QRectF&, QwtScaleMap maps[] ) const; + + bool updateCanvasMargins( QwtPlot*, + const QRectF&, const QwtScaleMap maps[] ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotRenderer::DiscardFlags ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotRenderer::LayoutFlags ) + +#endif diff --git a/libs/qwt/src/qwt_plot_rescaler.cpp b/libs/qwt/src/qwt_plot_rescaler.cpp new file mode 100644 index 00000000..34e7971a --- /dev/null +++ b/libs/qwt/src/qwt_plot_rescaler.cpp @@ -0,0 +1,656 @@ +/****************************************************************************** + * 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_rescaler.h" +#include "qwt_plot.h" +#include "qwt_scale_div.h" +#include "qwt_interval.h" +#include "qwt_plot_canvas.h" + +#include + +class QwtPlotRescaler::AxisData +{ + public: + AxisData() + : aspectRatio( 1.0 ) + , expandingDirection( QwtPlotRescaler::ExpandUp ) + { + } + + double aspectRatio; + QwtInterval intervalHint; + QwtPlotRescaler::ExpandingDirection expandingDirection; + mutable QwtScaleDiv scaleDiv; +}; + +class QwtPlotRescaler::PrivateData +{ + public: + PrivateData() + : referenceAxis( QwtAxis::XBottom ) + , rescalePolicy( QwtPlotRescaler::Expanding ) + , isEnabled( false ) + , inReplot( 0 ) + { + } + + QwtPlotRescaler::AxisData* axisData( QwtAxisId axisId ) + { + if ( !QwtAxis::isValid( axisId ) ) + return NULL; + + return &m_axisData[ axisId]; + } + + QwtAxisId referenceAxis; + RescalePolicy rescalePolicy; + bool isEnabled; + + mutable int inReplot; + + private: + QwtPlotRescaler::AxisData m_axisData[QwtAxis::AxisPositions]; +}; + +/*! + Constructor + + \param canvas Canvas + \param referenceAxis Reference axis, see RescalePolicy + \param policy Rescale policy + + \sa setRescalePolicy(), setReferenceAxis() + */ +QwtPlotRescaler::QwtPlotRescaler( QWidget* canvas, + QwtAxisId referenceAxis, RescalePolicy policy ) + : QObject( canvas ) +{ + m_data = new PrivateData; + m_data->referenceAxis = referenceAxis; + m_data->rescalePolicy = policy; + + setEnabled( true ); +} + +//! Destructor +QwtPlotRescaler::~QwtPlotRescaler() +{ + delete m_data; +} + +/*! + \brief En/disable the rescaler + + When enabled is true an event filter is installed for + the canvas, otherwise the event filter is removed. + + \param on true or false + \sa isEnabled(), eventFilter() + */ +void QwtPlotRescaler::setEnabled( bool on ) +{ + if ( m_data->isEnabled != on ) + { + m_data->isEnabled = on; + + QWidget* w = canvas(); + if ( w ) + { + if ( m_data->isEnabled ) + w->installEventFilter( this ); + else + w->removeEventFilter( this ); + } + } +} + +/*! + \return true when enabled, false otherwise + \sa setEnabled, eventFilter() + */ +bool QwtPlotRescaler::isEnabled() const +{ + return m_data->isEnabled; +} + +/*! + Change the rescale policy + + \param policy Rescale policy + \sa rescalePolicy() + */ +void QwtPlotRescaler::setRescalePolicy( RescalePolicy policy ) +{ + m_data->rescalePolicy = policy; +} + +/*! + \return Rescale policy + \sa setRescalePolicy() + */ +QwtPlotRescaler::RescalePolicy QwtPlotRescaler::rescalePolicy() const +{ + return m_data->rescalePolicy; +} + +/*! + Set the reference axis ( see RescalePolicy ) + + \param axisId Axis + \sa referenceAxis() + */ +void QwtPlotRescaler::setReferenceAxis( QwtAxisId axisId ) +{ + m_data->referenceAxis = axisId; +} + +/*! + \return Reference axis ( see RescalePolicy ) + \sa setReferenceAxis() + */ +QwtAxisId QwtPlotRescaler::referenceAxis() const +{ + return m_data->referenceAxis; +} + +/*! + Set the direction in which all axis should be expanded + + \param direction Direction + \sa expandingDirection() + */ +void QwtPlotRescaler::setExpandingDirection( + ExpandingDirection direction ) +{ + for ( int axis = 0; axis < QwtAxis::AxisPositions; axis++ ) + setExpandingDirection( axis, direction ); +} + +/*! + Set the direction in which an axis should be expanded + + \param axisId Axis + \param direction Direction + \sa expandingDirection() + */ +void QwtPlotRescaler::setExpandingDirection( + QwtAxisId axisId, ExpandingDirection direction ) +{ + if ( AxisData* axisData = m_data->axisData( axisId ) ) + axisData->expandingDirection = direction; +} + +/*! + \return Direction in which an axis should be expanded + + \param axisId Axis + \sa setExpandingDirection() + */ +QwtPlotRescaler::ExpandingDirection +QwtPlotRescaler::expandingDirection( QwtAxisId axisId ) const +{ + if ( const AxisData* axisData = m_data->axisData( axisId ) ) + return axisData->expandingDirection; + + return ExpandBoth; +} + +/*! + Set the aspect ratio between the scale of the reference axis + and the other scales. The default ratio is 1.0 + + \param ratio Aspect ratio + \sa aspectRatio() + */ +void QwtPlotRescaler::setAspectRatio( double ratio ) +{ + for ( int axis = 0; axis < QwtAxis::AxisPositions; axis++ ) + setAspectRatio( axis, ratio ); +} + +/*! + Set the aspect ratio between the scale of the reference axis + and another scale. The default ratio is 1.0 + + \param axisId Axis + \param ratio Aspect ratio + \sa aspectRatio() + */ +void QwtPlotRescaler::setAspectRatio( QwtAxisId axisId, double ratio ) +{ + if ( AxisData* axisData = m_data->axisData( axisId ) ) + { + if ( ratio < 0.0 ) + ratio = 0.0; + + axisData->aspectRatio = ratio; + } +} + +/*! + \return Aspect ratio between an axis and the reference axis. + + \param axisId Axis + \sa setAspectRatio() + */ +double QwtPlotRescaler::aspectRatio( QwtAxisId axisId ) const +{ + if ( AxisData* axisData = m_data->axisData( axisId ) ) + return axisData->aspectRatio; + + return 0.0; +} + +/*! + Set an interval hint for an axis + + In Fitting mode, the hint is used as minimal interval + that always needs to be displayed. + + \param axisId Axis + \param interval Axis + \sa intervalHint(), RescalePolicy + */ +void QwtPlotRescaler::setIntervalHint( QwtAxisId axisId, + const QwtInterval& interval ) +{ + if ( AxisData* axisData = m_data->axisData( axisId ) ) + axisData->intervalHint = interval; +} + +/*! + \param axisId Axis + \return Interval hint + \sa setIntervalHint(), RescalePolicy + */ +QwtInterval QwtPlotRescaler::intervalHint( QwtAxisId axisId ) const +{ + if ( AxisData* axisData = m_data->axisData( axisId ) ) + return axisData->intervalHint; + + return QwtInterval(); +} + +//! \return plot canvas +QWidget* QwtPlotRescaler::canvas() +{ + return qobject_cast< QWidget* >( parent() ); +} + +//! \return plot canvas +const QWidget* QwtPlotRescaler::canvas() const +{ + return qobject_cast< const QWidget* >( parent() ); +} + +//! \return plot widget +QwtPlot* QwtPlotRescaler::plot() +{ + QWidget* w = canvas(); + if ( w ) + w = w->parentWidget(); + + return qobject_cast< QwtPlot* >( w ); +} + +//! \return plot widget +const QwtPlot* QwtPlotRescaler::plot() const +{ + const QWidget* w = canvas(); + if ( w ) + w = w->parentWidget(); + + return qobject_cast< const QwtPlot* >( w ); +} + +//! Event filter for the plot canvas +bool QwtPlotRescaler::eventFilter( QObject* object, QEvent* event ) +{ + if ( object && object == canvas() ) + { + switch ( event->type() ) + { + case QEvent::Resize: + { + canvasResizeEvent( static_cast< QResizeEvent* >( event ) ); + break; + } + case QEvent::PolishRequest: + { + rescale(); + break; + } + default:; + } + } + + return false; +} + +/*! + Event handler for resize events of the plot canvas + + \param event Resize event + \sa rescale() + */ +void QwtPlotRescaler::canvasResizeEvent( QResizeEvent* event ) +{ + const QMargins m = canvas()->contentsMargins(); + const QSize marginSize( m.left() + m.right(), m.top() + m.bottom() ); + + const QSize newSize = event->size() - marginSize; + const QSize oldSize = event->oldSize() - marginSize; + + rescale( oldSize, newSize ); +} + +//! Adjust the plot axes scales +void QwtPlotRescaler::rescale() const +{ + const QSize size = canvas()->contentsRect().size(); + rescale( size, size ); +} + +/*! + Adjust the plot axes scales + + \param oldSize Previous size of the canvas + \param newSize New size of the canvas + */ +void QwtPlotRescaler::rescale( + const QSize& oldSize, const QSize& newSize ) const +{ + if ( newSize.isEmpty() ) + return; + + QwtInterval intervals[QwtAxis::AxisPositions]; + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + const QwtAxisId axisId( axisPos ); + intervals[axisPos] = interval( axisId ); + } + + const QwtAxisId refAxis = referenceAxis(); + intervals[refAxis] = expandScale( refAxis, oldSize, newSize ); + + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + const QwtAxisId axisId( axisPos ); + if ( aspectRatio( axisId ) > 0.0 && axisId != refAxis ) + { + intervals[axisPos] = syncScale( + axisId, intervals[refAxis], newSize ); + } + } + + updateScales( intervals ); +} + +/*! + Calculate the new scale interval of a plot axis + + \param axisId Axis + \param oldSize Previous size of the canvas + \param newSize New size of the canvas + + \return Calculated new interval for the axis + */ +QwtInterval QwtPlotRescaler::expandScale( QwtAxisId axisId, + const QSize& oldSize, const QSize& newSize ) const +{ + const QwtInterval oldInterval = interval( axisId ); + + QwtInterval expanded = oldInterval; + switch ( rescalePolicy() ) + { + case Fixed: + { + break; // do nothing + } + case Expanding: + { + if ( !oldSize.isEmpty() ) + { + double width = oldInterval.width(); + if ( orientation( axisId ) == Qt::Horizontal ) + width *= double( newSize.width() ) / oldSize.width(); + else + width *= double( newSize.height() ) / oldSize.height(); + + expanded = expandInterval( oldInterval, + width, expandingDirection( axisId ) ); + } + break; + } + case Fitting: + { + double dist = 0.0; + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + const QwtAxisId axisId( axisPos ); + const double d = pixelDist( axisId, newSize ); + if ( d > dist ) + dist = d; + } + if ( dist > 0.0 ) + { + double width; + if ( orientation( axisId ) == Qt::Horizontal ) + width = newSize.width() * dist; + else + width = newSize.height() * dist; + + expanded = expandInterval( intervalHint( axisId ), + width, expandingDirection( axisId ) ); + } + break; + } + } + + return expanded; +} + +/*! + Synchronize an axis scale according to the scale of the reference axis + + \param axisId Axis + \param reference Interval of the reference axis + \param size Size of the canvas + + \return New interval for axis + */ +QwtInterval QwtPlotRescaler::syncScale( QwtAxisId axisId, + const QwtInterval& reference, const QSize& size ) const +{ + double dist; + if ( orientation( referenceAxis() ) == Qt::Horizontal ) + dist = reference.width() / size.width(); + else + dist = reference.width() / size.height(); + + if ( orientation( axisId ) == Qt::Horizontal ) + dist *= size.width(); + else + dist *= size.height(); + + dist /= aspectRatio( axisId ); + + QwtInterval intv; + if ( rescalePolicy() == Fitting ) + intv = intervalHint( axisId ); + else + intv = interval( axisId ); + + intv = expandInterval( intv, dist, expandingDirection( axisId ) ); + + return intv; +} + +/*! + \return Orientation of an axis + \param axisId Axis + */ +Qt::Orientation QwtPlotRescaler::orientation( QwtAxisId axisId ) const +{ + return QwtAxis::isYAxis( axisId ) ? Qt::Vertical : Qt::Horizontal; +} + +/*! + \param axisId Axis + \return Normalized interval of an axis + */ +QwtInterval QwtPlotRescaler::interval( QwtAxisId axisId ) const +{ + if ( !plot()->isAxisValid( axisId ) ) + return QwtInterval(); + + return plot()->axisScaleDiv( axisId ).interval().normalized(); +} + +/*! + Expand the interval + + \param interval Interval to be expanded + \param width Distance to be added to the interval + \param direction Direction of the expand operation + + \return Expanded interval + */ +QwtInterval QwtPlotRescaler::expandInterval( + const QwtInterval& interval, double width, + ExpandingDirection direction ) const +{ + QwtInterval expanded = interval; + + switch ( direction ) + { + case ExpandUp: + expanded.setMinValue( interval.minValue() ); + expanded.setMaxValue( interval.minValue() + width ); + break; + + case ExpandDown: + expanded.setMaxValue( interval.maxValue() ); + expanded.setMinValue( interval.maxValue() - width ); + break; + + case ExpandBoth: + default: + expanded.setMinValue( interval.minValue() + + interval.width() / 2.0 - width / 2.0 ); + expanded.setMaxValue( expanded.minValue() + width ); + } + return expanded; +} + +double QwtPlotRescaler::pixelDist( QwtAxisId axisId, const QSize& size ) const +{ + const QwtInterval intv = intervalHint( axisId ); + + double dist = 0.0; + if ( !intv.isNull() ) + { + if ( axisId == referenceAxis() ) + { + dist = intv.width(); + } + else + { + const double r = aspectRatio( axisId ); + if ( r > 0.0 ) + dist = intv.width() * r; + } + } + + if ( dist > 0.0 ) + { + if ( orientation( axisId ) == Qt::Horizontal ) + dist /= size.width(); + else + dist /= size.height(); + } + + return dist; +} + +/*! + Update the axes scales + + \param intervals Scale intervals + */ +void QwtPlotRescaler::updateScales( + QwtInterval intervals[QwtAxis::AxisPositions] ) const +{ + if ( m_data->inReplot >= 5 ) + { + return; + } + + QwtPlot* plt = const_cast< QwtPlot* >( plot() ); + + const bool doReplot = plt->autoReplot(); + plt->setAutoReplot( false ); + + for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ ) + { + { + const QwtAxisId axisId( axisPos ); + + if ( axisId == referenceAxis() || aspectRatio( axisId ) > 0.0 ) + { + double v1 = intervals[axisPos].minValue(); + double v2 = intervals[axisPos].maxValue(); + + if ( !plt->axisScaleDiv( axisId ).isIncreasing() ) + qSwap( v1, v2 ); + + if ( m_data->inReplot >= 1 ) + m_data->axisData( axisId )->scaleDiv = plt->axisScaleDiv( axisId ); + + if ( m_data->inReplot >= 2 ) + { + QList< double > ticks[QwtScaleDiv::NTickTypes]; + for ( int t = 0; t < QwtScaleDiv::NTickTypes; t++ ) + ticks[t] = m_data->axisData( axisId )->scaleDiv.ticks( t ); + + plt->setAxisScaleDiv( axisId, QwtScaleDiv( v1, v2, ticks ) ); + } + else + { + plt->setAxisScale( axisId, v1, v2 ); + } + } + } + } + + QwtPlotCanvas* canvas = qobject_cast< QwtPlotCanvas* >( plt->canvas() ); + + bool immediatePaint = false; + if ( canvas ) + { + immediatePaint = canvas->testPaintAttribute( QwtPlotCanvas::ImmediatePaint ); + canvas->setPaintAttribute( QwtPlotCanvas::ImmediatePaint, false ); + } + + plt->setAutoReplot( doReplot ); + + m_data->inReplot++; + plt->replot(); + m_data->inReplot--; + + if ( canvas && immediatePaint ) + { + canvas->setPaintAttribute( QwtPlotCanvas::ImmediatePaint, true ); + } +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_plot_rescaler.cpp" +#endif diff --git a/libs/qwt/src/qwt_plot_rescaler.h b/libs/qwt/src/qwt_plot_rescaler.h new file mode 100644 index 00000000..5759e2bd --- /dev/null +++ b/libs/qwt/src/qwt_plot_rescaler.h @@ -0,0 +1,144 @@ +/****************************************************************************** + * 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_RESCALER_H +#define QWT_PLOT_RESCALER_H + +#include "qwt_global.h" +#include "qwt_plot.h" + +#include + +class QwtPlot; +class QwtInterval; +class QResizeEvent; + +/*! + \brief QwtPlotRescaler takes care of fixed aspect ratios for plot scales + + QwtPlotRescaler auto adjusts the axes of a QwtPlot according + to fixed aspect ratios. + */ + +class QWT_EXPORT QwtPlotRescaler : public QObject +{ + Q_OBJECT + + public: + /*! + The rescale policy defines how to rescale the reference axis and + their depending axes. + + \sa ExpandingDirection, setIntervalHint() + */ + enum RescalePolicy + { + /*! + The interval of the reference axis remains unchanged, when the + geometry of the canvas changes. All other axes + will be adjusted according to their aspect ratio. + */ + Fixed, + + /*! + The interval of the reference axis will be shrunk/expanded, + when the geometry of the canvas changes. All other axes + will be adjusted according to their aspect ratio. + + The interval, that is represented by one pixel is fixed. + + */ + Expanding, + + /*! + The intervals of the axes are calculated, so that all axes include + their interval hint. + */ + Fitting + }; + + /*! + When rescalePolicy() is set to Expanding its direction depends + on ExpandingDirection + */ + enum ExpandingDirection + { + //! The upper limit of the scale is adjusted + ExpandUp, + + //! The lower limit of the scale is adjusted + ExpandDown, + + //! Both limits of the scale are adjusted + ExpandBoth + }; + + explicit QwtPlotRescaler( QWidget* canvas, + QwtAxisId referenceAxis = QwtAxis::XBottom, + RescalePolicy = Expanding ); + + virtual ~QwtPlotRescaler(); + + void setEnabled( bool ); + bool isEnabled() const; + + void setRescalePolicy( RescalePolicy ); + RescalePolicy rescalePolicy() const; + + void setExpandingDirection( ExpandingDirection ); + void setExpandingDirection( QwtAxisId, ExpandingDirection ); + ExpandingDirection expandingDirection( QwtAxisId ) const; + + void setReferenceAxis( QwtAxisId ); + QwtAxisId referenceAxis() const; + + void setAspectRatio( double ratio ); + void setAspectRatio( QwtAxisId, double ratio ); + double aspectRatio( QwtAxisId ) const; + + void setIntervalHint( QwtAxisId, const QwtInterval& ); + QwtInterval intervalHint( QwtAxisId ) const; + + QWidget* canvas(); + const QWidget* canvas() const; + + QwtPlot* plot(); + const QwtPlot* plot() const; + + virtual bool eventFilter( QObject*, QEvent* ) QWT_OVERRIDE; + + void rescale() const; + + protected: + virtual void canvasResizeEvent( QResizeEvent* ); + + virtual void rescale( const QSize& oldSize, const QSize& newSize ) const; + virtual QwtInterval expandScale( + QwtAxisId, const QSize& oldSize, const QSize& newSize ) const; + + virtual QwtInterval syncScale( + QwtAxisId, const QwtInterval& reference, const QSize& size ) const; + + virtual void updateScales( + QwtInterval intervals[QwtAxis::AxisPositions] ) const; + + Qt::Orientation orientation( QwtAxisId ) const; + QwtInterval interval( QwtAxisId ) const; + QwtInterval expandInterval( const QwtInterval&, + double width, ExpandingDirection ) const; + + private: + double pixelDist( QwtAxisId, const QSize& ) const; + + class AxisData; + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_scaleitem.cpp b/libs/qwt/src/qwt_plot_scaleitem.cpp new file mode 100644 index 00000000..211298e2 --- /dev/null +++ b/libs/qwt/src/qwt_plot_scaleitem.cpp @@ -0,0 +1,480 @@ +/****************************************************************************** + * 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_scaleitem.h" +#include "qwt_plot.h" +#include "qwt_scale_map.h" +#include "qwt_interval.h" +#include "qwt_text.h" + +#include +#include + +class QwtPlotScaleItem::PrivateData +{ + public: + PrivateData() + : position( 0.0 ) + , borderDistance( -1 ) + , scaleDivFromAxis( true ) + , scaleDraw( new QwtScaleDraw() ) + { + } + + ~PrivateData() + { + delete scaleDraw; + } + + QwtInterval scaleInterval( const QRectF&, + const QwtScaleMap&, const QwtScaleMap& ) const; + + QPalette palette; + QFont font; + double position; + int borderDistance; + bool scaleDivFromAxis; + QwtScaleDraw* scaleDraw; +}; + +QwtInterval QwtPlotScaleItem::PrivateData::scaleInterval( const QRectF& canvasRect, + const QwtScaleMap& xMap, const QwtScaleMap& yMap ) const +{ + QwtInterval interval; + if ( scaleDraw->orientation() == Qt::Horizontal ) + { + interval.setMinValue( xMap.invTransform( canvasRect.left() ) ); + interval.setMaxValue( xMap.invTransform( canvasRect.right() - 1 ) ); + } + else + { + interval.setMinValue( yMap.invTransform( canvasRect.bottom() - 1 ) ); + interval.setMaxValue( yMap.invTransform( canvasRect.top() ) ); + } + + return interval; +} + +/*! + \brief Constructor for scale item at the position pos. + + \param alignment In case of QwtScaleDraw::BottomScale or QwtScaleDraw::TopScale + the scale item is corresponding to the xAxis(), + otherwise it corresponds to the yAxis(). + + \param pos x or y position, depending on the corresponding axis. + + \sa setPosition(), setAlignment() + */ +QwtPlotScaleItem::QwtPlotScaleItem( + QwtScaleDraw::Alignment alignment, const double pos ) + : QwtPlotItem( QwtText( "Scale" ) ) +{ + m_data = new PrivateData; + m_data->position = pos; + m_data->scaleDraw->setAlignment( alignment ); + + setItemInterest( QwtPlotItem::ScaleInterest, true ); + setZ( 11.0 ); +} + +//! Destructor +QwtPlotScaleItem::~QwtPlotScaleItem() +{ + delete m_data; +} + +//! \return QwtPlotItem::Rtti_PlotScale +int QwtPlotScaleItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotScale; +} + +/*! + \brief Assign a scale division + + When assigning a scaleDiv the scale division won't be synchronized + with the corresponding axis anymore. + + \param scaleDiv Scale division + \sa scaleDiv(), setScaleDivFromAxis(), isScaleDivFromAxis() + */ +void QwtPlotScaleItem::setScaleDiv( const QwtScaleDiv& scaleDiv ) +{ + m_data->scaleDivFromAxis = false; + m_data->scaleDraw->setScaleDiv( scaleDiv ); +} + +//! \return Scale division +const QwtScaleDiv& QwtPlotScaleItem::scaleDiv() const +{ + return m_data->scaleDraw->scaleDiv(); +} + +/*! + Enable/Disable the synchronization of the scale division with + the corresponding axis. + + \param on true/false + \sa isScaleDivFromAxis() + */ +void QwtPlotScaleItem::setScaleDivFromAxis( bool on ) +{ + if ( on != m_data->scaleDivFromAxis ) + { + m_data->scaleDivFromAxis = on; + if ( on ) + { + const QwtPlot* plt = plot(); + if ( plt ) + { + updateScaleDiv( plt->axisScaleDiv( xAxis() ), + plt->axisScaleDiv( yAxis() ) ); + itemChanged(); + } + } + } +} + +/*! + \return True, if the synchronization of the scale division with + the corresponding axis is enabled. + \sa setScaleDiv(), setScaleDivFromAxis() + */ +bool QwtPlotScaleItem::isScaleDivFromAxis() const +{ + return m_data->scaleDivFromAxis; +} + +/*! + Set the palette + \sa QwtAbstractScaleDraw::draw(), palette() + */ +void QwtPlotScaleItem::setPalette( const QPalette& palette ) +{ + if ( palette != m_data->palette ) + { + m_data->palette = palette; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return palette + \sa setPalette() + */ +QPalette QwtPlotScaleItem::palette() const +{ + return m_data->palette; +} + +/*! + Change the tick label font + \sa font() + */ +void QwtPlotScaleItem::setFont( const QFont& font ) +{ + if ( font != m_data->font ) + { + m_data->font = font; + itemChanged(); + } +} + +/*! + \return tick label font + \sa setFont() + */ +QFont QwtPlotScaleItem::font() const +{ + return m_data->font; +} + +/*! + \brief Set a scale draw + + \param scaleDraw object responsible for drawing scales. + + The main use case for replacing the default QwtScaleDraw is + to overload QwtAbstractScaleDraw::label, to replace or swallow + tick labels. + + \sa scaleDraw() + */ +void QwtPlotScaleItem::setScaleDraw( QwtScaleDraw* scaleDraw ) +{ + if ( scaleDraw == NULL ) + return; + + if ( scaleDraw != m_data->scaleDraw ) + delete m_data->scaleDraw; + + m_data->scaleDraw = scaleDraw; + + const QwtPlot* plt = plot(); + if ( plt ) + { + updateScaleDiv( plt->axisScaleDiv( xAxis() ), + plt->axisScaleDiv( yAxis() ) ); + } + + itemChanged(); +} + +/*! + \return Scale draw + \sa setScaleDraw() + */ +const QwtScaleDraw* QwtPlotScaleItem::scaleDraw() const +{ + return m_data->scaleDraw; +} + +/*! + \return Scale draw + \sa setScaleDraw() + */ +QwtScaleDraw* QwtPlotScaleItem::scaleDraw() +{ + return m_data->scaleDraw; +} + +/*! + Change the position of the scale + + The position is interpreted as y value for horizontal axes + and as x value for vertical axes. + + The border distance is set to -1. + + \param pos New position + \sa position(), setAlignment() + */ +void QwtPlotScaleItem::setPosition( double pos ) +{ + if ( m_data->position != pos ) + { + m_data->position = pos; + m_data->borderDistance = -1; + itemChanged(); + } +} + +/*! + \return Position of the scale + \sa setPosition(), setAlignment() + */ +double QwtPlotScaleItem::position() const +{ + return m_data->position; +} + +/*! + \brief Align the scale to the canvas + + If distance is >= 0 the scale will be aligned to a + border of the contents rectangle of the canvas. If + alignment() is QwtScaleDraw::LeftScale, the scale will + be aligned to the right border, if it is QwtScaleDraw::TopScale + it will be aligned to the bottom (and vice versa), + + If distance is < 0 the scale will be at the position(). + + \param distance Number of pixels between the canvas border and the + backbone of the scale. + + \sa setPosition(), borderDistance() + */ +void QwtPlotScaleItem::setBorderDistance( int distance ) +{ + if ( distance < 0 ) + distance = -1; + + if ( distance != m_data->borderDistance ) + { + m_data->borderDistance = distance; + itemChanged(); + } +} + +/*! + \return Distance from a canvas border + \sa setBorderDistance(), setPosition() + */ +int QwtPlotScaleItem::borderDistance() const +{ + return m_data->borderDistance; +} + +/*! + Change the alignment of the scale + + The alignment sets the orientation of the scale and the position of + the ticks: + + - QwtScaleDraw::BottomScale: horizontal, ticks below + - QwtScaleDraw::TopScale: horizontal, ticks above + - QwtScaleDraw::LeftScale: vertical, ticks left + - QwtScaleDraw::RightScale: vertical, ticks right + + For horizontal scales the position corresponds to QwtPlotItem::yAxis(), + otherwise to QwtPlotItem::xAxis(). + + \sa scaleDraw(), QwtScaleDraw::alignment(), setPosition() + */ +void QwtPlotScaleItem::setAlignment( QwtScaleDraw::Alignment alignment ) +{ + QwtScaleDraw* sd = m_data->scaleDraw; + if ( sd->alignment() != alignment ) + { + sd->setAlignment( alignment ); + itemChanged(); + } +} + +/*! + \brief Draw the scale + */ +void QwtPlotScaleItem::draw( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + QwtScaleDraw* sd = m_data->scaleDraw; + + if ( m_data->scaleDivFromAxis ) + { + const QwtInterval interval = + m_data->scaleInterval( canvasRect, xMap, yMap ); + + if ( interval != sd->scaleDiv().interval() ) + { + QwtScaleDiv scaleDiv = sd->scaleDiv(); + scaleDiv.setInterval( interval ); + sd->setScaleDiv( scaleDiv ); + } + } + + QPen pen = painter->pen(); + pen.setStyle( Qt::SolidLine ); + painter->setPen( pen ); + + if ( sd->orientation() == Qt::Horizontal ) + { + double y; + if ( m_data->borderDistance >= 0 ) + { + if ( sd->alignment() == QwtScaleDraw::BottomScale ) + y = canvasRect.top() + m_data->borderDistance; + else + { + y = canvasRect.bottom() - m_data->borderDistance; + } + + } + else + { + y = yMap.transform( m_data->position ); + } + + if ( y < canvasRect.top() || y > canvasRect.bottom() ) + return; + + sd->move( canvasRect.left(), y ); + sd->setLength( canvasRect.width() - 1 ); + + QwtTransform* transform = NULL; + if ( xMap.transformation() ) + transform = xMap.transformation()->copy(); + + sd->setTransformation( transform ); + } + else // == Qt::Vertical + { + double x; + if ( m_data->borderDistance >= 0 ) + { + if ( sd->alignment() == QwtScaleDraw::RightScale ) + x = canvasRect.left() + m_data->borderDistance; + else + { + x = canvasRect.right() - m_data->borderDistance; + } + } + else + { + x = xMap.transform( m_data->position ); + } + if ( x < canvasRect.left() || x > canvasRect.right() ) + return; + + sd->move( x, canvasRect.top() ); + sd->setLength( canvasRect.height() - 1 ); + + QwtTransform* transform = NULL; + if ( yMap.transformation() ) + transform = yMap.transformation()->copy(); + + sd->setTransformation( transform ); + } + + painter->setFont( m_data->font ); + + sd->draw( painter, m_data->palette ); +} + +/*! + \brief Update the item to changes of the axes scale division + + In case of isScaleDivFromAxis(), the scale draw is synchronized + to the correspond axis. + + \param xScaleDiv Scale division of the x-axis + \param yScaleDiv Scale division of the y-axis + + \sa QwtPlot::updateAxes() + */ + +void QwtPlotScaleItem::updateScaleDiv( const QwtScaleDiv& xScaleDiv, + const QwtScaleDiv& yScaleDiv ) +{ + QwtScaleDraw* scaleDraw = m_data->scaleDraw; + + if ( m_data->scaleDivFromAxis && scaleDraw ) + { + const QwtScaleDiv& scaleDiv = + scaleDraw->orientation() == Qt::Horizontal ? xScaleDiv : yScaleDiv; + + const QwtPlot* plt = plot(); + if ( plt != NULL ) + { + const QRectF canvasRect = plt->canvas()->contentsRect(); + + const QwtInterval interval = m_data->scaleInterval( + canvasRect, plt->canvasMap( xAxis() ), plt->canvasMap( yAxis() ) ); + + QwtScaleDiv sd = scaleDiv; + sd.setInterval( interval ); + + if ( sd != scaleDraw->scaleDiv() ) + { + // the internal label cache of QwtScaleDraw + // is cleared here, so better avoid pointless + // assignments. + + scaleDraw->setScaleDiv( sd ); + } + } + else + { + scaleDraw->setScaleDiv( scaleDiv ); + } + } +} diff --git a/libs/qwt/src/qwt_plot_scaleitem.h b/libs/qwt/src/qwt_plot_scaleitem.h new file mode 100644 index 00000000..536c6a64 --- /dev/null +++ b/libs/qwt/src/qwt_plot_scaleitem.h @@ -0,0 +1,94 @@ +/****************************************************************************** + * 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_SCALE_ITEM_H +#define QWT_PLOT_SCALE_ITEM_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" +#include "qwt_scale_draw.h" + +class QPalette; + +/*! + \brief A class which draws a scale inside the plot canvas + + QwtPlotScaleItem can be used to draw an axis inside the plot canvas. + It might by synchronized to one of the axis of the plot, but can + also display its own ticks and labels. + + It is allowed to synchronize the scale item with a disabled axis. + In plots with vertical and horizontal scale items, it might be + necessary to remove ticks at the intersections, by overloading + updateScaleDiv(). + + The scale might be at a specific position (f.e 0.0) or it might be + aligned to a canvas border. + + \par Example + The following example shows how to replace the left axis, by a scale item + at the x position 0.0. + \code + QwtPlotScaleItem *scaleItem = new QwtPlotScaleItem( QwtScaleDraw::RightScale, 0.0 ); + scaleItem->setFont( plot->axisWidget( QwtAxis::YLeft )->font() ); + scaleItem->attach(plot); + + plot->setAxisVisible( QwtAxis::YLeft, false ); + \endcode + */ + +class QWT_EXPORT QwtPlotScaleItem : public QwtPlotItem +{ + public: + explicit QwtPlotScaleItem( + QwtScaleDraw::Alignment = QwtScaleDraw::BottomScale, + const double pos = 0.0 ); + + virtual ~QwtPlotScaleItem(); + + virtual int rtti() const QWT_OVERRIDE; + + void setScaleDiv( const QwtScaleDiv& ); + const QwtScaleDiv& scaleDiv() const; + + void setScaleDivFromAxis( bool on ); + bool isScaleDivFromAxis() const; + + void setPalette( const QPalette& ); + QPalette palette() const; + + void setFont( const QFont& ); + QFont font() const; + + void setScaleDraw( QwtScaleDraw* ); + + const QwtScaleDraw* scaleDraw() const; + QwtScaleDraw* scaleDraw(); + + void setPosition( double pos ); + double position() const; + + void setBorderDistance( int ); + int borderDistance() const; + + void setAlignment( QwtScaleDraw::Alignment ); + + virtual void draw( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + virtual void updateScaleDiv( + const QwtScaleDiv&, const QwtScaleDiv& ) QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_seriesitem.cpp b/libs/qwt/src/qwt_plot_seriesitem.cpp new file mode 100644 index 00000000..d745e016 --- /dev/null +++ b/libs/qwt/src/qwt_plot_seriesitem.cpp @@ -0,0 +1,115 @@ +/****************************************************************************** + * 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_seriesitem.h" +#include "qwt_scale_div.h" +#include "qwt_text.h" + +class QwtPlotSeriesItem::PrivateData +{ + public: + PrivateData() + : orientation( Qt::Vertical ) + { + } + + Qt::Orientation orientation; +}; + +/*! + Constructor + \param title Title of the curve + */ +QwtPlotSeriesItem::QwtPlotSeriesItem( const QwtText& title ) + : QwtPlotItem( title ) +{ + m_data = new PrivateData(); + setItemInterest( QwtPlotItem::ScaleInterest, true ); +} + +/*! + Constructor + \param title Title of the curve + */ +QwtPlotSeriesItem::QwtPlotSeriesItem( const QString& title ) + : QwtPlotItem( QwtText( title ) ) +{ + m_data = new PrivateData(); + setItemInterest( QwtPlotItem::ScaleInterest, true ); +} + +//! Destructor +QwtPlotSeriesItem::~QwtPlotSeriesItem() +{ + delete m_data; +} + +/*! + Set the orientation of the item. + + The orientation() might be used in specific way by a plot item. + F.e. a QwtPlotCurve uses it to identify how to display the curve + int QwtPlotCurve::Steps or QwtPlotCurve::Sticks style. + + \sa orientation() + */ +void QwtPlotSeriesItem::setOrientation( Qt::Orientation orientation ) +{ + if ( m_data->orientation != orientation ) + { + m_data->orientation = orientation; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Orientation of the plot item + \sa setOrientation() + */ +Qt::Orientation QwtPlotSeriesItem::orientation() const +{ + return m_data->orientation; +} + +/*! + \brief Draw the complete series + + \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 + */ +void QwtPlotSeriesItem::draw( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + drawSeries( painter, xMap, yMap, canvasRect, 0, -1 ); +} + +QRectF QwtPlotSeriesItem::boundingRect() const +{ + return dataRect(); +} + +void QwtPlotSeriesItem::updateScaleDiv( + const QwtScaleDiv& xScaleDiv, const QwtScaleDiv& yScaleDiv ) +{ + const QRectF rect = QRectF( + xScaleDiv.lowerBound(), yScaleDiv.lowerBound(), + xScaleDiv.range(), yScaleDiv.range() ); + + setRectOfInterest( rect ); +} + +void QwtPlotSeriesItem::dataChanged() +{ + itemChanged(); +} diff --git a/libs/qwt/src/qwt_plot_seriesitem.h b/libs/qwt/src/qwt_plot_seriesitem.h new file mode 100644 index 00000000..b5c134a7 --- /dev/null +++ b/libs/qwt/src/qwt_plot_seriesitem.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * 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_SERIES_ITEM_H +#define QWT_PLOT_SERIES_ITEM_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" +#include "qwt_series_store.h" + +#include + +class QwtScaleDiv; + +/*! + \brief Base class for plot items representing a series of samples + */ +class QWT_EXPORT QwtPlotSeriesItem : public QwtPlotItem, + public virtual QwtAbstractSeriesStore +{ + public: + explicit QwtPlotSeriesItem( const QString& title = QString() ); + explicit QwtPlotSeriesItem( const QwtText& title ); + + virtual ~QwtPlotSeriesItem(); + + void setOrientation( Qt::Orientation ); + Qt::Orientation orientation() const; + + virtual void draw( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + /*! + 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 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. + */ + virtual void drawSeries( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const = 0; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + virtual void updateScaleDiv( + const QwtScaleDiv&, const QwtScaleDiv& ) QWT_OVERRIDE; + + protected: + virtual void dataChanged() QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_shapeitem.cpp b/libs/qwt/src/qwt_plot_shapeitem.cpp new file mode 100644 index 00000000..af7a607b --- /dev/null +++ b/libs/qwt/src/qwt_plot_shapeitem.cpp @@ -0,0 +1,501 @@ +/****************************************************************************** + * 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_shapeitem.h" +#include "qwt_scale_map.h" +#include "qwt_text.h" +#include "qwt_graphic.h" +#include "qwt_painter.h" +#include "qwt_weeding_curve_fitter.h" +#include "qwt_clipper.h" +#include "qwt_math.h" + +#include +#include + +static QPainterPath qwtTransformPath( const QwtScaleMap& xMap, + const QwtScaleMap& yMap, const QPainterPath& path, bool doAlign ) +{ + QPainterPath shape; + shape.setFillRule( path.fillRule() ); + + for ( int i = 0; i < path.elementCount(); i++ ) + { + const QPainterPath::Element element = path.elementAt( i ); + + double x = xMap.transform( element.x ); + double y = yMap.transform( element.y ); + + switch( element.type ) + { + case QPainterPath::MoveToElement: + { + if ( doAlign ) + { + x = qRound( x ); + y = qRound( y ); + } + + shape.moveTo( x, y ); + break; + } + case QPainterPath::LineToElement: + { + if ( doAlign ) + { + x = qRound( x ); + y = qRound( y ); + } + + shape.lineTo( x, y ); + break; + } + case QPainterPath::CurveToElement: + { + const QPainterPath::Element element1 = path.elementAt( ++i ); + const double x1 = xMap.transform( element1.x ); + const double y1 = yMap.transform( element1.y ); + + const QPainterPath::Element element2 = path.elementAt( ++i ); + const double x2 = xMap.transform( element2.x ); + const double y2 = yMap.transform( element2.y ); + + shape.cubicTo( x, y, x1, y1, x2, y2 ); + break; + } + case QPainterPath::CurveToDataElement: + { + break; + } + } + } + + return shape; +} + + +class QwtPlotShapeItem::PrivateData +{ + public: + PrivateData() + : legendMode( QwtPlotShapeItem::LegendColor ) + , renderTolerance( 0.0 ) + { + } + + QwtPlotShapeItem::PaintAttributes paintAttributes; + QwtPlotShapeItem::LegendMode legendMode; + + double renderTolerance; + QRectF boundingRect; + + QPen pen; + QBrush brush; + QPainterPath shape; +}; + +/*! + \brief Constructor + + Sets the following item attributes: + - QwtPlotItem::AutoScale: true + - QwtPlotItem::Legend: false + + \param title Title + */ +QwtPlotShapeItem::QwtPlotShapeItem( const QString& title ) + : QwtPlotItem( QwtText( title ) ) +{ + init(); +} + +/*! + \brief Constructor + + Sets the following item attributes: + - QwtPlotItem::AutoScale: true + - QwtPlotItem::Legend: false + + \param title Title + */ +QwtPlotShapeItem::QwtPlotShapeItem( const QwtText& title ) + : QwtPlotItem( title ) +{ + init(); +} + +//! Destructor +QwtPlotShapeItem::~QwtPlotShapeItem() +{ + delete m_data; +} + +void QwtPlotShapeItem::init() +{ + m_data = new PrivateData(); + m_data->boundingRect = QwtPlotItem::boundingRect(); + + setItemAttribute( QwtPlotItem::AutoScale, true ); + setItemAttribute( QwtPlotItem::Legend, false ); + + setZ( 8.0 ); +} + +//! \return QwtPlotItem::Rtti_PlotShape +int QwtPlotShapeItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotShape; +} + +/*! + Specify an attribute how to draw the shape + + \param attribute Paint attribute + \param on On/Off + \sa testPaintAttribute() + */ +void QwtPlotShapeItem::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( on ) + m_data->paintAttributes |= attribute; + else + m_data->paintAttributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa setPaintAttribute() + */ +bool QwtPlotShapeItem::testPaintAttribute( PaintAttribute attribute ) const +{ + return ( m_data->paintAttributes & attribute ); +} + +/*! + Set the mode how to represent the item on the legend + + \param mode Mode + \sa legendMode() + */ +void QwtPlotShapeItem::setLegendMode( LegendMode mode ) +{ + if ( mode != m_data->legendMode ) + { + m_data->legendMode = mode; + legendChanged(); + } +} + +/*! + \return Mode how to represent the item on the legend + \sa legendMode() + */ +QwtPlotShapeItem::LegendMode QwtPlotShapeItem::legendMode() const +{ + return m_data->legendMode; +} + +//! Bounding rectangle of the shape +QRectF QwtPlotShapeItem::boundingRect() const +{ + return m_data->boundingRect; +} + +/*! + \brief Set a path built from a rectangle + + \param rect Rectangle + \sa setShape(), setPolygon(), shape() + */ +void QwtPlotShapeItem::setRect( const QRectF& rect ) +{ + QPainterPath path; + path.addRect( rect ); + + setShape( path ); +} + +/*! + \brief Set a path built from a polygon + + \param polygon Polygon + \sa setShape(), setRect(), shape() + */ +void QwtPlotShapeItem::setPolygon( const QPolygonF& polygon ) +{ + QPainterPath shape; + shape.addPolygon( polygon ); + + setShape( shape ); +} + +/*! + \brief Set the shape to be displayed + + \param shape Shape + \sa setShape(), shape() + */ +void QwtPlotShapeItem::setShape( const QPainterPath& shape ) +{ + if ( shape != m_data->shape ) + { + m_data->shape = shape; + if ( shape.isEmpty() ) + { + m_data->boundingRect = QwtPlotItem::boundingRect(); + } + else + { + m_data->boundingRect = shape.boundingRect(); + } + + itemChanged(); + } +} + +/*! + \return Shape to be displayed + \sa setShape() + */ +QPainterPath QwtPlotShapeItem::shape() const +{ + return m_data->shape; +} + +/*! + 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 QwtPlotShapeItem::setPen( const QColor& color, qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + \brief Assign a pen + + The pen is used to draw the outline of the shape + + \param pen Pen + \sa pen(), brush() + */ +void QwtPlotShapeItem::setPen( const QPen& pen ) +{ + if ( pen != m_data->pen ) + { + m_data->pen = pen; + itemChanged(); + } +} + +/*! + \return Pen used to draw the outline of the shape + \sa setPen(), brush() + */ +QPen QwtPlotShapeItem::pen() const +{ + return m_data->pen; +} + +/*! + Assign a brush. + + The brush is used to fill the path + + \param brush Brush + \sa brush(), pen() + */ +void QwtPlotShapeItem::setBrush( const QBrush& brush ) +{ + if ( brush != m_data->brush ) + { + m_data->brush = brush; + itemChanged(); + } +} + +/*! + \return Brush used to fill the shape + \sa setBrush(), pen() + */ +QBrush QwtPlotShapeItem::brush() const +{ + return m_data->brush; +} + +/*! + \brief Set the tolerance for the weeding optimization + + After translating the shape into target device coordinate + ( usually widget geometries ) the painter path can be simplified + by a point weeding algorithm ( Douglas-Peucker ). + + For shapes built from curves and ellipses weeding might + have the opposite effect because they have to be expanded + to polygons. + + \param tolerance Accepted error when reducing the number of points + A value <= 0.0 disables weeding. + + \sa renderTolerance(), QwtWeedingCurveFitter + */ +void QwtPlotShapeItem::setRenderTolerance( double tolerance ) +{ + tolerance = qwtMaxF( tolerance, 0.0 ); + + if ( tolerance != m_data->renderTolerance ) + { + m_data->renderTolerance = tolerance; + itemChanged(); + } +} + +/*! + \return Tolerance for the weeding optimization + \sa setRenderTolerance() + */ +double QwtPlotShapeItem::renderTolerance() const +{ + return m_data->renderTolerance; +} + +/*! + Draw the shape item + + \param painter Painter + \param xMap X-Scale Map + \param yMap Y-Scale Map + \param canvasRect Contents rect of the plot canvas + */ +void QwtPlotShapeItem::draw( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + if ( m_data->shape.isEmpty() ) + return; + + if ( m_data->pen.style() == Qt::NoPen + && m_data->brush.style() == Qt::NoBrush ) + { + return; + } + + const QRectF cr = QwtScaleMap::invTransform( + xMap, yMap, canvasRect.toRect() ); + + const QRectF& br = m_data->boundingRect; + + if ( ( br.left() > cr.right() ) || ( br.right() < cr.left() ) + || ( br.top() > cr.bottom() ) || ( br.bottom() < cr.top() ) ) + { + // outside the visible area + return; + } + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + QPainterPath path = qwtTransformPath( xMap, yMap, + m_data->shape, doAlign ); + + if ( testPaintAttribute( QwtPlotShapeItem::ClipPolygons ) ) + { + const qreal pw = QwtPainter::effectivePenWidth( painter->pen() ); + const QRectF clipRect = canvasRect.adjusted( -pw, -pw, pw, pw ); + + QPainterPath clippedPath; + clippedPath.setFillRule( path.fillRule() ); + + QList< QPolygonF > polygons = path.toSubpathPolygons(); + for ( int i = 0; i < polygons.size(); i++ ) + { + QwtClipper::clipPolygonF( clipRect, polygons[i], true ); + clippedPath.addPolygon( polygons[i] ); + + } + + path = clippedPath; + } + + if ( m_data->renderTolerance > 0.0 ) + { + QwtWeedingCurveFitter fitter( m_data->renderTolerance ); + + QPainterPath fittedPath; + fittedPath.setFillRule( path.fillRule() ); + + const QList< QPolygonF > polygons = path.toSubpathPolygons(); + for ( int i = 0; i < polygons.size(); i++ ) + fittedPath.addPolygon( fitter.fitCurve( polygons[ i ] ) ); + + path = fittedPath; + } + + painter->setPen( m_data->pen ); + painter->setBrush( m_data->brush ); + + painter->drawPath( path ); +} + +/*! + \return A rectangle filled with the color of the brush ( or the pen ) + + \param index Index of the legend entry + ( usually there is only one ) + \param size Icon size + + \sa setLegendIconSize(), legendData() + */ +QwtGraphic QwtPlotShapeItem::legendIcon( int index, + const QSizeF& size ) const +{ + Q_UNUSED( index ); + + QwtGraphic icon; + icon.setDefaultSize( size ); + + if ( size.isEmpty() ) + return icon; + + if ( m_data->legendMode == QwtPlotShapeItem::LegendShape ) + { + const QRectF& br = m_data->boundingRect; + + QPainter painter( &icon ); + painter.setRenderHint( QPainter::Antialiasing, + testRenderHint( QwtPlotItem::RenderAntialiased ) ); + + painter.translate( -br.topLeft() ); + + painter.setPen( m_data->pen ); + painter.setBrush( m_data->brush ); + painter.drawPath( m_data->shape ); + } + else + { + QColor iconColor; + if ( m_data->brush.style() != Qt::NoBrush ) + iconColor = m_data->brush.color(); + else + iconColor = m_data->pen.color(); + + icon = defaultIcon( iconColor, size ); + } + + return icon; +} + diff --git a/libs/qwt/src/qwt_plot_shapeitem.h b/libs/qwt/src/qwt_plot_shapeitem.h new file mode 100644 index 00000000..e90e2a9a --- /dev/null +++ b/libs/qwt/src/qwt_plot_shapeitem.h @@ -0,0 +1,118 @@ +/****************************************************************************** + * 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_SHAPE_ITEM_H +#define QWT_PLOT_SHAPE_ITEM_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" + +#include + +class QPainterPath; +class QPolygonF; + +/*! + \brief A plot item, which displays any graphical shape, + that can be defined by a QPainterPath + + A QPainterPath is a shape composed from intersecting and uniting + regions, rectangles, ellipses or irregular areas defined by lines, and curves. + QwtPlotShapeItem displays a shape with a pen and brush. + + QwtPlotShapeItem offers a couple of optimizations like clipping or weeding. + These algorithms need to convert the painter path into polygons that might be + less performant for paths built from curves and ellipses. + + More complex shapes, that can't be expressed by a QPainterPath can be displayed + using QwtPlotGraphicItem. + + \sa QwtPlotZone, QwtPlotGraphicItem + */ +class QWT_EXPORT QwtPlotShapeItem : public QwtPlotItem +{ + public: + /*! + Attributes to modify the drawing algorithm. + The default disables all attributes + + \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 + + But polygon clipping will convert the painter path into + polygons what might introduce a negative impact on the + performance of paths composed from curves or ellipses. + */ + ClipPolygons = 0x01, + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + //! Mode how to display the item on the legend + enum LegendMode + { + //! Display a scaled down version of the shape + LegendShape, + + //! Display a filled rectangle + LegendColor + }; + + explicit QwtPlotShapeItem( const QString& title = QString() ); + explicit QwtPlotShapeItem( const QwtText& title ); + + virtual ~QwtPlotShapeItem(); + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setLegendMode( LegendMode ); + LegendMode legendMode() const; + + void setRect( const QRectF& ); + void setPolygon( const QPolygonF& ); + + void setShape( const QPainterPath& ); + QPainterPath shape() const; + + void setPen( const QColor&, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen& ); + QPen pen() const; + + void setBrush( const QBrush& ); + QBrush brush() const; + + void setRenderTolerance( double ); + double renderTolerance() const; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + virtual void draw( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + virtual QwtGraphic legendIcon( + int index, const QSizeF& ) const QWT_OVERRIDE; + + virtual int rtti() const QWT_OVERRIDE; + + private: + void init(); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_spectrocurve.cpp b/libs/qwt/src/qwt_plot_spectrocurve.cpp new file mode 100644 index 00000000..e52d303a --- /dev/null +++ b/libs/qwt/src/qwt_plot_spectrocurve.cpp @@ -0,0 +1,323 @@ +/****************************************************************************** + * 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_spectrocurve.h" +#include "qwt_color_map.h" +#include "qwt_scale_map.h" +#include "qwt_painter.h" +#include "qwt_text.h" + +#include + +class QwtPlotSpectroCurve::PrivateData +{ + public: + PrivateData() + : colorRange( 0.0, 1000.0 ) + , penWidth( 0.0 ) + , paintAttributes( QwtPlotSpectroCurve::ClipPoints ) + { + colorMap = new QwtLinearColorMap(); + } + + ~PrivateData() + { + delete colorMap; + } + + QwtColorMap* colorMap; + QwtInterval colorRange; + QVector< QRgb > colorTable; + double penWidth; + QwtPlotSpectroCurve::PaintAttributes paintAttributes; +}; + +/*! + Constructor + \param title Title of the curve + */ +QwtPlotSpectroCurve::QwtPlotSpectroCurve( const QwtText& title ) + : QwtPlotSeriesItem( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve + */ +QwtPlotSpectroCurve::QwtPlotSpectroCurve( const QString& title ) + : QwtPlotSeriesItem( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPlotSpectroCurve::~QwtPlotSpectroCurve() +{ + delete m_data; +} + +/*! + \brief Initialize data members + */ +void QwtPlotSpectroCurve::init() +{ + setItemAttribute( QwtPlotItem::Legend ); + setItemAttribute( QwtPlotItem::AutoScale ); + + m_data = new PrivateData; + setData( new QwtPoint3DSeriesData() ); + + setZ( 20.0 ); +} + +//! \return QwtPlotItem::Rtti_PlotSpectroCurve +int QwtPlotSpectroCurve::rtti() const +{ + return QwtPlotItem::Rtti_PlotSpectroCurve; +} + +/*! + Specify an attribute how to draw the curve + + \param attribute Paint attribute + \param on On/Off + /sa PaintAttribute, testPaintAttribute() + */ +void QwtPlotSpectroCurve::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( on ) + m_data->paintAttributes |= attribute; + else + m_data->paintAttributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa PaintAttribute, setPaintAttribute() + */ +bool QwtPlotSpectroCurve::testPaintAttribute( PaintAttribute attribute ) const +{ + return ( m_data->paintAttributes & attribute ); +} + +/*! + Initialize data with an array of samples. + \param samples Vector of points + */ +void QwtPlotSpectroCurve::setSamples( const QVector< QwtPoint3D >& samples ) +{ + setData( new QwtPoint3DSeriesData( 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 QwtPlotSpectroCurve::setSamples( + QwtSeriesData< QwtPoint3D >* data ) +{ + setData( data ); +} + +/*! + Change the color map + + Often it is useful to display the mapping between intensities and + colors as an additional plot axis, showing a color bar. + + \param colorMap Color Map + + \sa colorMap(), setColorRange(), QwtColorMap::color(), + QwtScaleWidget::setColorBarEnabled(), QwtScaleWidget::setColorMap() + */ +void QwtPlotSpectroCurve::setColorMap( QwtColorMap* colorMap ) +{ + if ( colorMap != m_data->colorMap ) + { + delete m_data->colorMap; + m_data->colorMap = colorMap; + } + + legendChanged(); + itemChanged(); +} + +/*! + \return Color Map used for mapping the intensity values to colors + \sa setColorMap(), setColorRange(), QwtColorMap::color() + */ +const QwtColorMap* QwtPlotSpectroCurve::colorMap() const +{ + return m_data->colorMap; +} + +/*! + Set the value interval, that corresponds to the color map + + \param interval interval.minValue() corresponds to 0.0, + interval.maxValue() to 1.0 on the color map. + + \sa colorRange(), setColorMap(), QwtColorMap::color() + */ +void QwtPlotSpectroCurve::setColorRange( const QwtInterval& interval ) +{ + if ( interval != m_data->colorRange ) + { + m_data->colorRange = interval; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Value interval, that corresponds to the color map + \sa setColorRange(), setColorMap(), QwtColorMap::color() + */ +QwtInterval& QwtPlotSpectroCurve::colorRange() const +{ + return m_data->colorRange; +} + +/*! + Assign a pen width + + \param penWidth New pen width + \sa penWidth() + */ +void QwtPlotSpectroCurve::setPenWidth(double penWidth) +{ + if ( penWidth < 0.0 ) + penWidth = 0.0; + + if ( m_data->penWidth != penWidth ) + { + m_data->penWidth = penWidth; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Pen width used to draw a dot + \sa setPenWidth() + */ +double QwtPlotSpectroCurve::penWidth() const +{ + return m_data->penWidth; +} + +/*! + Draw a subset of the points + + \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 drawDots() + */ +void QwtPlotSpectroCurve::drawSeries( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const +{ + if ( !painter || dataSize() <= 0 ) + return; + + if ( to < 0 ) + to = dataSize() - 1; + + if ( from < 0 ) + from = 0; + + if ( from > to ) + return; + + drawDots( painter, xMap, yMap, canvasRect, from, to ); +} + +/*! + Draw a subset of the points + + \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() + */ +void QwtPlotSpectroCurve::drawDots( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const +{ + if ( !m_data->colorRange.isValid() ) + return; + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + const QwtColorMap::Format format = m_data->colorMap->format(); + if ( format == QwtColorMap::Indexed ) + m_data->colorTable = m_data->colorMap->colorTable256(); + + const QwtSeriesData< QwtPoint3D >* series = data(); + + for ( int i = from; i <= to; i++ ) + { + const QwtPoint3D 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 ( m_data->paintAttributes & QwtPlotSpectroCurve::ClipPoints ) + { + if ( !canvasRect.contains( xi, yi ) ) + continue; + } + + if ( format == QwtColorMap::RGB ) + { + const QRgb rgb = m_data->colorMap->rgb( + m_data->colorRange, sample.z() ); + + painter->setPen( QPen( QColor::fromRgba( rgb ), m_data->penWidth ) ); + } + else + { + const unsigned char index = m_data->colorMap->colorIndex( + 256, m_data->colorRange, sample.z() ); + + painter->setPen( QPen( QColor::fromRgba( m_data->colorTable[index] ), + m_data->penWidth ) ); + } + + QwtPainter::drawPoint( painter, QPointF( xi, yi ) ); + } + + m_data->colorTable.clear(); +} diff --git a/libs/qwt/src/qwt_plot_spectrocurve.h b/libs/qwt/src/qwt_plot_spectrocurve.h new file mode 100644 index 00000000..6dc19dca --- /dev/null +++ b/libs/qwt/src/qwt_plot_spectrocurve.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_PLOT_CURVE_3D_H +#define QWT_PLOT_CURVE_3D_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" + +class QwtColorMap; + +/*! + \brief Curve that displays 3D points as dots, where the z coordinate is + mapped to a color. + */ +class QWT_EXPORT QwtPlotSpectroCurve + : public QwtPlotSeriesItem + , public QwtSeriesStore< QwtPoint3D > +{ + public: + //! Paint attributes + enum PaintAttribute + { + //! Clip points outside the canvas rectangle + ClipPoints = 1 + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + explicit QwtPlotSpectroCurve( const QString& title = QString() ); + explicit QwtPlotSpectroCurve( const QwtText& title ); + + virtual ~QwtPlotSpectroCurve(); + + virtual int rtti() const QWT_OVERRIDE; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setSamples( const QVector< QwtPoint3D >& ); + void setSamples( QwtSeriesData< QwtPoint3D >* ); + + + void setColorMap( QwtColorMap* ); + const QwtColorMap* colorMap() const; + + void setColorRange( const QwtInterval& ); + QwtInterval& colorRange() const; + + virtual void drawSeries( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const QWT_OVERRIDE; + + void setPenWidth( double ); + double penWidth() const; + + protected: + virtual void drawDots( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const; + + private: + void init(); + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotSpectroCurve::PaintAttributes ) + +#endif diff --git a/libs/qwt/src/qwt_plot_spectrogram.cpp b/libs/qwt/src/qwt_plot_spectrogram.cpp new file mode 100644 index 00000000..a677ad29 --- /dev/null +++ b/libs/qwt/src/qwt_plot_spectrogram.cpp @@ -0,0 +1,790 @@ +/****************************************************************************** + * 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_spectrogram.h" +#include "qwt_painter.h" +#include "qwt_interval.h" +#include "qwt_scale_map.h" +#include "qwt_color_map.h" +#include "qwt_math.h" + +#include +#include +#include +#include +#include +#include + +#define DEBUG_RENDER 0 + +#if DEBUG_RENDER +#include +#endif + +#include + +static inline bool qwtIsNaN( double d ) +{ + // qt_is_nan is private header and qIsNaN is not inlined + // so we need these code here too + + const uchar* ch = (const uchar*)&d; + if ( QSysInfo::ByteOrder == QSysInfo::BigEndian ) + { + return ( ch[0] & 0x7f ) == 0x7f && ch[1] > 0xf0; + } + else + { + return ( ch[7] & 0x7f ) == 0x7f && ch[6] > 0xf0; + } +} + +class QwtPlotSpectrogram::PrivateData +{ + public: + PrivateData() + : data( NULL ) + , colorTableSize( 0 ) + { + colorMap = new QwtLinearColorMap(); + displayMode = ImageMode; + + conrecFlags = QwtRasterData::IgnoreAllVerticesOnLevel; +#if 0 + conrecFlags |= QwtRasterData::IgnoreOutOfRange; +#endif + } + + ~PrivateData() + { + delete data; + delete colorMap; + } + + void updateColorTable() + { + if ( colorMap->format() == QwtColorMap::Indexed ) + { + colorTable = colorMap->colorTable256(); + } + else + { + if ( colorTableSize == 0 ) + colorTable.clear(); + else + colorTable = colorMap->colorTable( colorTableSize ); + } + } + + QwtRasterData* data; + QwtColorMap* colorMap; + DisplayModes displayMode; + + QList< double > contourLevels; + QPen defaultContourPen; + QwtRasterData::ConrecFlags conrecFlags; + + int colorTableSize; + QVector< QRgb > colorTable; +}; + +/*! + Sets the following item attributes: + - QwtPlotItem::AutoScale: true + - QwtPlotItem::Legend: false + + The z value is initialized by 8.0. + + \param title Title + + \sa QwtPlotItem::setItemAttribute(), QwtPlotItem::setZ() + */ +QwtPlotSpectrogram::QwtPlotSpectrogram( const QString& title ) + : QwtPlotRasterItem( title ) +{ + m_data = new PrivateData(); + + setItemAttribute( QwtPlotItem::AutoScale, true ); + setItemAttribute( QwtPlotItem::Legend, false ); + + setZ( 8.0 ); +} + +//! Destructor +QwtPlotSpectrogram::~QwtPlotSpectrogram() +{ + delete m_data; +} + +//! \return QwtPlotItem::Rtti_PlotSpectrogram +int QwtPlotSpectrogram::rtti() const +{ + return QwtPlotItem::Rtti_PlotSpectrogram; +} + +/*! + The display mode controls how the raster data will be represented. + + \param mode Display mode + \param on On/Off + + The default setting enables ImageMode. + + \sa DisplayMode, displayMode() + */ +void QwtPlotSpectrogram::setDisplayMode( DisplayMode mode, bool on ) +{ + if ( on != bool( mode & m_data->displayMode ) ) + { + if ( on ) + m_data->displayMode |= mode; + else + m_data->displayMode &= ~mode; + } + + legendChanged(); + itemChanged(); +} + +/*! + The display mode controls how the raster data will be represented. + + \param mode Display mode + \return true if mode is enabled + */ +bool QwtPlotSpectrogram::testDisplayMode( DisplayMode mode ) const +{ + return ( m_data->displayMode & mode ); +} + +/*! + Change the color map + + Often it is useful to display the mapping between intensities and + colors as an additional plot axis, showing a color bar. + + \param colorMap Color Map + + \sa colorMap(), QwtScaleWidget::setColorBarEnabled(), + QwtScaleWidget::setColorMap() + */ +void QwtPlotSpectrogram::setColorMap( QwtColorMap* colorMap ) +{ + if ( colorMap == NULL ) + return; + + if ( colorMap != m_data->colorMap ) + { + delete m_data->colorMap; + m_data->colorMap = colorMap; + } + + m_data->updateColorTable(); + + invalidateCache(); + + legendChanged(); + itemChanged(); +} + +/*! + \return Color Map used for mapping the intensity values to colors + \sa setColorMap() + */ +const QwtColorMap* QwtPlotSpectrogram::colorMap() const +{ + return m_data->colorMap; +} + +/*! + Limit the number of colors being used by the color map + + When using a color table the mapping from the value into a color + is usually faster as it can be done by simple lookups into a + precalculated color table. + + Setting a table size > 0 enables using a color table, while setting + the size to 0 disables it. + + The default size = 0, and no color table is used. + + \param numColors Number of colors. 0 means not using a color table + \note The colorTableSize has no effect when using a color table + of QwtColorMap::Indexed, where the size is always 256. + + + \sa QwtColorMap::colorTable(), colorTableSize() + */ +void QwtPlotSpectrogram::setColorTableSize( int numColors ) +{ + numColors = qMax( numColors, 0 ); + if ( numColors != m_data->colorTableSize ) + { + m_data->colorTableSize = numColors; + m_data->updateColorTable(); + invalidateCache(); + } +} +/*! + \return Size of the color table, 0 means not using a color table + \sa QwtColorMap::colorTable(), setColorTableSize() + */ +int QwtPlotSpectrogram::colorTableSize() const +{ + return m_data->colorTableSize; +} + +/*! + Build and assign the default pen for the contour 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 QwtPlotSpectrogram::setDefaultContourPen( + const QColor& color, qreal width, Qt::PenStyle style ) +{ + setDefaultContourPen( QPen( color, width, style ) ); +} + +/*! + \brief Set the default pen for the contour lines + + If the spectrogram has a valid default contour pen + a contour line is painted using the default contour pen. + Otherwise (pen.style() == Qt::NoPen) the pen is calculated + for each contour level using contourPen(). + + \sa defaultContourPen(), contourPen() + */ +void QwtPlotSpectrogram::setDefaultContourPen( const QPen& pen ) +{ + if ( pen != m_data->defaultContourPen ) + { + m_data->defaultContourPen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Default contour pen + \sa setDefaultContourPen() + */ +QPen QwtPlotSpectrogram::defaultContourPen() const +{ + return m_data->defaultContourPen; +} + +/*! + \brief Calculate the pen for a contour line + + The color of the pen is the color for level calculated by the color map + + \param level Contour level + \return Pen for the contour line + \note contourPen is only used if defaultContourPen().style() == Qt::NoPen + + \sa setDefaultContourPen(), setColorMap(), setContourLevels() + */ +QPen QwtPlotSpectrogram::contourPen( double level ) const +{ + if ( m_data->data == NULL || m_data->colorMap == NULL ) + return QPen(); + + const QwtInterval intensityRange = m_data->data->interval(Qt::ZAxis); + const QColor c( m_data->colorMap->rgb( intensityRange, level ) ); + + return QPen( c ); +} + +/*! + Modify an attribute of the CONREC algorithm, used to calculate + the contour lines. + + \param flag CONREC flag + \param on On/Off + + \sa testConrecFlag(), renderContourLines(), + QwtRasterData::contourLines() + */ +void QwtPlotSpectrogram::setConrecFlag( + QwtRasterData::ConrecFlag flag, bool on ) +{ + if ( bool( m_data->conrecFlags & flag ) == on ) + return; + + if ( on ) + m_data->conrecFlags |= flag; + else + m_data->conrecFlags &= ~flag; + + itemChanged(); +} + +/*! + Test an attribute of the CONREC algorithm, used to calculate + the contour lines. + + \param flag CONREC flag + \return true, is enabled + + The default setting enables QwtRasterData::IgnoreAllVerticesOnLevel + + \sa setConrecClag(), renderContourLines(), + QwtRasterData::contourLines() + */ +bool QwtPlotSpectrogram::testConrecFlag( + QwtRasterData::ConrecFlag flag ) const +{ + return m_data->conrecFlags & flag; +} + +/*! + Set the levels of the contour lines + + \param levels Values of the contour levels + \sa contourLevels(), renderContourLines(), + QwtRasterData::contourLines() + + \note contourLevels returns the same levels but sorted. + */ +void QwtPlotSpectrogram::setContourLevels( const QList< double >& levels ) +{ + m_data->contourLevels = levels; + std::sort( m_data->contourLevels.begin(), m_data->contourLevels.end() ); + + legendChanged(); + itemChanged(); +} + +/*! + \return Levels of the contour lines. + + The levels are sorted in increasing order. + + \sa contourLevels(), renderContourLines(), + QwtRasterData::contourLines() + */ +QList< double > QwtPlotSpectrogram::contourLevels() const +{ + return m_data->contourLevels; +} + +/*! + Set the data to be displayed + + \param data Spectrogram Data + \sa data() + */ +void QwtPlotSpectrogram::setData( QwtRasterData* data ) +{ + if ( data != m_data->data ) + { + delete m_data->data; + m_data->data = data; + + invalidateCache(); + itemChanged(); + } +} + +/*! + \return Spectrogram data + \sa setData() + */ +const QwtRasterData* QwtPlotSpectrogram::data() const +{ + return m_data->data; +} + +/*! + \return Spectrogram data + \sa setData() + */ +QwtRasterData* QwtPlotSpectrogram::data() +{ + return m_data->data; +} + +/*! + \return Bounding interval for an axis + + The default implementation returns the interval of the + associated raster data object. + + \param axis X, Y, or Z axis + \sa QwtRasterData::interval() + */ +QwtInterval QwtPlotSpectrogram::interval(Qt::Axis axis) const +{ + if ( m_data->data == NULL ) + return QwtInterval(); + + return m_data->data->interval( axis ); +} + +/*! + \brief Pixel hint + + The geometry of a pixel is used to calculated the resolution and + alignment of the rendered image. + + The default implementation returns data()->pixelHint( rect ); + + \param area In most implementations the resolution of the data doesn't + depend on the requested area. + + \return Bounding rectangle of a pixel + + \sa QwtPlotRasterItem::pixelHint(), QwtRasterData::pixelHint(), + render(), renderImage() + */ +QRectF QwtPlotSpectrogram::pixelHint( const QRectF& area ) const +{ + if ( m_data->data == NULL ) + return QRectF(); + + return m_data->data->pixelHint( area ); +} + +/*! + \brief Render an image from data and color map. + + For each pixel of area the value is mapped into a color. + + \param xMap X-Scale Map + \param yMap Y-Scale Map + \param area Requested area for the image in scale coordinates + \param imageSize Size of the requested image + + \return A QImage::Format_Indexed8 or QImage::Format_ARGB32 depending + on the color map. + + \sa QwtRasterData::value(), QwtColorMap::rgb(), + QwtColorMap::colorIndex() + */ +QImage QwtPlotSpectrogram::renderImage( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& area, const QSize& imageSize ) const +{ + if ( imageSize.isEmpty() || m_data->data == NULL + || m_data->colorMap == NULL ) + { + return QImage(); + } + + const QwtInterval intensityRange = m_data->data->interval( Qt::ZAxis ); + if ( !intensityRange.isValid() ) + return QImage(); + + const QImage::Format format = ( m_data->colorMap->format() == QwtColorMap::RGB ) + ? QImage::Format_ARGB32 : QImage::Format_Indexed8; + + QImage image( imageSize, format ); + + if ( m_data->colorMap->format() == QwtColorMap::Indexed ) + image.setColorTable( m_data->colorMap->colorTable256() ); + + m_data->data->initRaster( area, image.size() ); + +#if DEBUG_RENDER + QElapsedTimer time; + time.start(); +#endif + +#if !defined( QT_NO_QFUTURE ) + uint numThreads = renderThreadCount(); + + if ( numThreads <= 0 ) + numThreads = QThread::idealThreadCount(); + + if ( numThreads <= 0 ) + numThreads = 1; + + const int numRows = imageSize.height() / numThreads; + + QVector< QFuture< void > > futures; + futures.reserve( numThreads - 1 ); + + for ( uint i = 0; i < numThreads; i++ ) + { + QRect tile( 0, i * numRows, image.width(), numRows ); + if ( i == numThreads - 1 ) + { + tile.setHeight( image.height() - i * numRows ); + renderTile( xMap, yMap, tile, &image ); + } + else + { + futures += QtConcurrent::run( +#if QT_VERSION >= 0x060000 + &QwtPlotSpectrogram::renderTile, this, +#else + this, &QwtPlotSpectrogram::renderTile, +#endif + xMap, yMap, tile, &image ); + } + } + + for ( int i = 0; i < futures.size(); i++ ) + futures[i].waitForFinished(); + +#else + const QRect tile( 0, 0, image.width(), image.height() ); + renderTile( xMap, yMap, tile, &image ); +#endif + +#if DEBUG_RENDER + const qint64 elapsed = time.elapsed(); + qDebug() << "renderImage" << imageSize << elapsed; +#endif + + m_data->data->discardRaster(); + + return image; +} + +/*! + \brief Render a tile of an image. + + Rendering in tiles can be used to composite an image in parallel + threads. + + \param xMap X-Scale Map + \param yMap Y-Scale Map + \param tile Geometry of the tile in image coordinates + \param image Image to be rendered + */ +void QwtPlotSpectrogram::renderTile( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRect& tile, QImage* image ) const +{ + const QwtInterval range = m_data->data->interval( Qt::ZAxis ); + if ( range.width() <= 0.0 ) + return; + + const bool hasGaps = !m_data->data->testAttribute( QwtRasterData::WithoutGaps ); + + if ( m_data->colorMap->format() == QwtColorMap::RGB ) + { + const int numColors = m_data->colorTable.size(); + const QRgb* rgbTable = m_data->colorTable.constData(); + const QwtColorMap* colorMap = m_data->colorMap; + + for ( int y = tile.top(); y <= tile.bottom(); y++ ) + { + const double ty = yMap.invTransform( y ); + + QRgb* line = reinterpret_cast< QRgb* >( image->scanLine( y ) ); + line += tile.left(); + + for ( int x = tile.left(); x <= tile.right(); x++ ) + { + const double tx = xMap.invTransform( x ); + + const double value = m_data->data->value( tx, ty ); + + if ( hasGaps && qwtIsNaN( value ) ) + { + *line++ = 0u; + } + else if ( numColors == 0 ) + { + *line++ = colorMap->rgb( range, value ); + } + else + { + const uint index = colorMap->colorIndex( numColors, range, value ); + *line++ = rgbTable[index]; + } + } + } + } + else if ( m_data->colorMap->format() == QwtColorMap::Indexed ) + { + for ( int y = tile.top(); y <= tile.bottom(); y++ ) + { + const double ty = yMap.invTransform( y ); + + unsigned char* line = image->scanLine( y ); + line += tile.left(); + + for ( int x = tile.left(); x <= tile.right(); x++ ) + { + const double tx = xMap.invTransform( x ); + + const double value = m_data->data->value( tx, ty ); + + if ( hasGaps && qwtIsNaN( value ) ) + { + *line++ = 0; + } + else + { + const uint index = m_data->colorMap->colorIndex( 256, range, value ); + *line++ = static_cast< unsigned char >( index ); + } + } + } + } +} + +/*! + \brief Return the raster to be used by the CONREC contour algorithm. + + A larger size will improve the precision of the CONREC algorithm, + but will slow down the time that is needed to calculate the lines. + + The default implementation returns rect.size() / 2 bounded to + the resolution depending on pixelSize(). + + \param area Rectangle, where to calculate the contour lines + \param rect Rectangle in pixel coordinates, where to paint the contour lines + \return Raster to be used by the CONREC contour algorithm. + + \note The size will be bounded to rect.size(). + + \sa drawContourLines(), QwtRasterData::contourLines() + */ +QSize QwtPlotSpectrogram::contourRasterSize( + const QRectF& area, const QRect& rect ) const +{ + QSize raster = rect.size() / 2; + + const QRectF pixelRect = pixelHint( area ); + if ( !pixelRect.isEmpty() ) + { + const QSize res( qwtCeil( rect.width() / pixelRect.width() ), + qwtCeil( rect.height() / pixelRect.height() ) ); + raster = raster.boundedTo( res ); + } + + return raster; +} + +/*! + Calculate contour lines + + \param rect Rectangle, where to calculate the contour lines + \param raster Raster, used by the CONREC algorithm + \return Calculated contour lines + + \sa contourLevels(), setConrecFlag(), + QwtRasterData::contourLines() + */ +QwtRasterData::ContourLines QwtPlotSpectrogram::renderContourLines( + const QRectF& rect, const QSize& raster ) const +{ + if ( m_data->data == NULL ) + return QwtRasterData::ContourLines(); + + return m_data->data->contourLines( rect, raster, + m_data->contourLevels, m_data->conrecFlags ); +} + +/*! + Paint the contour lines + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param contourLines Contour lines + + \sa renderContourLines(), defaultContourPen(), contourPen() + */ +void QwtPlotSpectrogram::drawContourLines( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtRasterData::ContourLines& contourLines ) const +{ + if ( m_data->data == NULL ) + return; + + const int numLevels = m_data->contourLevels.size(); + for ( int l = 0; l < numLevels; l++ ) + { + const double level = m_data->contourLevels[l]; + + QPen pen = defaultContourPen(); + if ( pen.style() == Qt::NoPen ) + pen = contourPen( level ); + + if ( pen.style() == Qt::NoPen ) + continue; + + painter->setPen( pen ); + + const QPolygonF& lines = contourLines[level]; + for ( int i = 0; i < lines.size(); i += 2 ) + { + const QPointF p1( xMap.transform( lines[i].x() ), + yMap.transform( lines[i].y() ) ); + const QPointF p2( xMap.transform( lines[i + 1].x() ), + yMap.transform( lines[i + 1].y() ) ); + + QwtPainter::drawLine( painter, p1, p2 ); + } + } +} + +/*! + \brief Draw the spectrogram + + \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 in painter coordinates + + \sa setDisplayMode(), renderImage(), + QwtPlotRasterItem::draw(), drawContourLines() + */ +void QwtPlotSpectrogram::draw( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + if ( m_data->displayMode & ImageMode ) + QwtPlotRasterItem::draw( painter, xMap, yMap, canvasRect ); + + if ( m_data->displayMode & ContourMode ) + { + // Add some pixels at the borders + const int margin = 2; + QRectF rasterRect( canvasRect.x() - margin, canvasRect.y() - margin, + canvasRect.width() + 2 * margin, canvasRect.height() + 2 * margin ); + + QRectF area = QwtScaleMap::invTransform( xMap, yMap, rasterRect ); + + const QRectF br = boundingRect(); + if ( br.isValid() ) + { + area &= br; + if ( area.isEmpty() ) + return; + + rasterRect = QwtScaleMap::transform( xMap, yMap, area ); + } + + QSize raster = contourRasterSize( area, rasterRect.toRect() ); + raster = raster.boundedTo( rasterRect.toRect().size() ); + if ( raster.isValid() ) + { + const QwtRasterData::ContourLines lines = + renderContourLines( area, raster ); + + drawContourLines( painter, xMap, yMap, lines ); + } + } +} diff --git a/libs/qwt/src/qwt_plot_spectrogram.h b/libs/qwt/src/qwt_plot_spectrogram.h new file mode 100644 index 00000000..cf07de09 --- /dev/null +++ b/libs/qwt/src/qwt_plot_spectrogram.h @@ -0,0 +1,118 @@ +/****************************************************************************** + * 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_SPECTROGRAM_H +#define QWT_PLOT_SPECTROGRAM_H + +#include "qwt_global.h" +#include "qwt_raster_data.h" +#include "qwt_plot_rasteritem.h" + +class QwtColorMap; +template< typename T > class QList; + +/*! + \brief A plot item, which displays a spectrogram + + A spectrogram displays 3-dimensional data, where the 3rd dimension + ( the intensity ) is displayed using colors. The colors are calculated + from the values using a color map. + + On multi-core systems the performance of the image composition + can often be improved by dividing the area into tiles - each of them + rendered in a different thread ( see QwtPlotItem::setRenderThreadCount() ). + + In ContourMode contour lines are painted for the contour levels. + + \sa QwtRasterData, QwtColorMap, QwtPlotItem::setRenderThreadCount() + */ + +class QWT_EXPORT QwtPlotSpectrogram : public QwtPlotRasterItem +{ + public: + /*! + The display mode controls how the raster data will be represented. + \sa setDisplayMode(), testDisplayMode() + */ + + enum DisplayMode + { + //! The values are mapped to colors using a color map. + ImageMode = 0x01, + + //! The data is displayed using contour lines + ContourMode = 0x02 + }; + + Q_DECLARE_FLAGS( DisplayModes, DisplayMode ) + + explicit QwtPlotSpectrogram( const QString& title = QString() ); + virtual ~QwtPlotSpectrogram(); + + void setDisplayMode( DisplayMode, bool on = true ); + bool testDisplayMode( DisplayMode ) const; + + void setData( QwtRasterData* data ); + const QwtRasterData* data() const; + QwtRasterData* data(); + + void setColorMap( QwtColorMap* ); + const QwtColorMap* colorMap() const; + + void setColorTableSize( int numColors ); + int colorTableSize() const; + + virtual QwtInterval interval( Qt::Axis ) const QWT_OVERRIDE; + virtual QRectF pixelHint( const QRectF& ) const QWT_OVERRIDE; + + void setDefaultContourPen( const QColor&, + qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setDefaultContourPen( const QPen& ); + QPen defaultContourPen() const; + + virtual QPen contourPen( double level ) const; + + void setConrecFlag( QwtRasterData::ConrecFlag, bool on ); + bool testConrecFlag( QwtRasterData::ConrecFlag ) const; + + void setContourLevels( const QList< double >& ); + QList< double > contourLevels() const; + + virtual int rtti() const QWT_OVERRIDE; + + virtual void draw( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + protected: + virtual QImage renderImage( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& area, const QSize& imageSize ) const QWT_OVERRIDE; + + virtual QSize contourRasterSize( + const QRectF&, const QRect& ) const; + + virtual QwtRasterData::ContourLines renderContourLines( + const QRectF& rect, const QSize& raster ) const; + + virtual void drawContourLines( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtRasterData::ContourLines& ) const; + + void renderTile( const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRect& tile, QImage* ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotSpectrogram::DisplayModes ) + +#endif diff --git a/libs/qwt/src/qwt_plot_svgitem.cpp b/libs/qwt/src/qwt_plot_svgitem.cpp new file mode 100644 index 00000000..af72547f --- /dev/null +++ b/libs/qwt/src/qwt_plot_svgitem.cpp @@ -0,0 +1,91 @@ +/****************************************************************************** + * 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_svgitem.h" +#include "qwt_text.h" +#include "qwt_graphic.h" + +#include + +/*! + \brief Constructor + \param title Title + */ +QwtPlotSvgItem::QwtPlotSvgItem( const QString& title ) + : QwtPlotGraphicItem( QwtText( title ) ) +{ +} + +/*! + \brief Constructor + \param title Title + */ +QwtPlotSvgItem::QwtPlotSvgItem( const QwtText& title ) + : QwtPlotGraphicItem( title ) +{ +} + +//! Destructor +QwtPlotSvgItem::~QwtPlotSvgItem() +{ +} + +/*! + Load a SVG file + + \param rect Bounding rectangle + \param fileName SVG file name + + \return true, if the SVG file could be loaded + */ +bool QwtPlotSvgItem::loadFile( const QRectF& rect, + const QString& fileName ) +{ + QwtGraphic graphic; + + QSvgRenderer renderer; + + const bool ok = renderer.load( fileName ); + if ( ok ) + { + QPainter p( &graphic ); + renderer.render( &p ); + } + + setGraphic( rect, graphic ); + + return ok; +} + +/*! + Load SVG data + + \param rect Bounding rectangle + \param data in SVG format + + \return true, if the SVG data could be loaded + */ +bool QwtPlotSvgItem::loadData( const QRectF& rect, + const QByteArray& data ) +{ + QwtGraphic graphic; + + QSvgRenderer renderer; + + const bool ok = renderer.load( data ); + if ( ok ) + { + QPainter p( &graphic ); + renderer.render( &p ); + } + + setGraphic( rect, graphic ); + + return ok; +} diff --git a/libs/qwt/src/qwt_plot_svgitem.h b/libs/qwt/src/qwt_plot_svgitem.h new file mode 100644 index 00000000..398cc3e6 --- /dev/null +++ b/libs/qwt/src/qwt_plot_svgitem.h @@ -0,0 +1,39 @@ +/****************************************************************************** + * 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_SVG_ITEM_H +#define QWT_PLOT_SVG_ITEM_H + +#include "qwt_global.h" +#include "qwt_plot_graphicitem.h" + +class QByteArray; + +/*! + \brief A plot item, which displays + data in Scalable Vector Graphics (SVG) format. + + SVG images are often used to display maps + + QwtPlotSvgItem is only a small convenience wrapper class for + QwtPlotGraphicItem, that creates a QwtGraphic from SVG data. + */ + +class QWT_EXPORT QwtPlotSvgItem : public QwtPlotGraphicItem +{ + public: + explicit QwtPlotSvgItem( const QString& title = QString() ); + explicit QwtPlotSvgItem( const QwtText& title ); + virtual ~QwtPlotSvgItem(); + + bool loadFile( const QRectF&, const QString& fileName ); + bool loadData( const QRectF&, const QByteArray& ); +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_textlabel.cpp b/libs/qwt/src/qwt_plot_textlabel.cpp new file mode 100644 index 00000000..f2d93022 --- /dev/null +++ b/libs/qwt/src/qwt_plot_textlabel.cpp @@ -0,0 +1,273 @@ +/****************************************************************************** + * 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_textlabel.h" +#include "qwt_painter.h" +#include "qwt_text.h" +#include "qwt_math.h" + +#include +#include +#include + +static QRect qwtItemRect( int renderFlags, + const QRectF& rect, const QSizeF& itemSize ) +{ + int x; + if ( renderFlags & Qt::AlignLeft ) + { + x = rect.left(); + } + else if ( renderFlags & Qt::AlignRight ) + { + x = rect.right() - itemSize.width(); + } + else + { + x = rect.center().x() - 0.5 * itemSize.width(); + } + + int y; + if ( renderFlags & Qt::AlignTop ) + { + y = rect.top(); + } + else if ( renderFlags & Qt::AlignBottom ) + { + y = rect.bottom() - itemSize.height(); + } + else + { + y = rect.center().y() - 0.5 * itemSize.height(); + } + + return QRect( x, y, itemSize.width(), itemSize.height() ); +} + +class QwtPlotTextLabel::PrivateData +{ + public: + PrivateData() + : margin( 5 ) + { + } + + QwtText text; + int margin; + + QPixmap pixmap; +}; + +/*! + \brief Constructor + + Initializes an text label with an empty text + + Sets the following item attributes: + + - QwtPlotItem::AutoScale: true + - QwtPlotItem::Legend: false + + The z value is initialized by 150 + + \sa QwtPlotItem::setItemAttribute(), QwtPlotItem::setZ() + */ + +QwtPlotTextLabel::QwtPlotTextLabel() + : QwtPlotItem( QwtText( "Label" ) ) +{ + m_data = new PrivateData; + + setItemAttribute( QwtPlotItem::AutoScale, false ); + setItemAttribute( QwtPlotItem::Legend, false ); + + setZ( 150 ); +} + +//! Destructor +QwtPlotTextLabel::~QwtPlotTextLabel() +{ + delete m_data; +} + +//! \return QwtPlotItem::Rtti_PlotTextLabel +int QwtPlotTextLabel::rtti() const +{ + return QwtPlotItem::Rtti_PlotTextLabel; +} + +/*! + Set the text + + The label will be aligned to the plot canvas according to + the alignment flags of text. + + \param text Text to be displayed + + \sa text(), QwtText::renderFlags() + */ +void QwtPlotTextLabel::setText( const QwtText& text ) +{ + if ( m_data->text != text ) + { + m_data->text = text; + + invalidateCache(); + itemChanged(); + } +} + +/*! + \return Text to be displayed + \sa setText() + */ +QwtText QwtPlotTextLabel::text() const +{ + return m_data->text; +} + +/*! + Set the margin + + The margin is the distance between the contentsRect() + of the plot canvas and the rectangle where the label can + be displayed. + + \param margin Margin + + \sa margin(), textRect() + */ +void QwtPlotTextLabel::setMargin( int margin ) +{ + margin = qMax( margin, 0 ); + if ( m_data->margin != margin ) + { + m_data->margin = margin; + itemChanged(); + } +} + +/*! + \return Margin added to the contentsMargins() of the canvas + \sa setMargin() + */ +int QwtPlotTextLabel::margin() const +{ + return m_data->margin; +} + +/*! + Draw the text label + + \param painter Painter + \param xMap x Scale Map + \param yMap y Scale Map + \param canvasRect Contents rectangle of the canvas in painter coordinates + + \sa textRect() + */ + +void QwtPlotTextLabel::draw( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + Q_UNUSED( xMap ); + Q_UNUSED( yMap ); + + const int m = m_data->margin; + + const QRectF rect = textRect( canvasRect.adjusted( m, m, -m, -m ), + m_data->text.textSize( painter->font() ) ); + + bool doCache = QwtPainter::roundingAlignment( painter ); + if ( doCache ) + { + switch( painter->paintEngine()->type() ) + { + case QPaintEngine::Picture: + case QPaintEngine::User: // usually QwtGraphic + { + // don't use a cache for record/replay devices + doCache = false; + break; + } + default:; + } + } + + if ( doCache ) + { + // when the paint device is aligning it is not one + // where scalability matters ( PDF, SVG ). + // As rendering a text label is an expensive operation + // we use a cache. + + int pw = 0; + if ( m_data->text.borderPen().style() != Qt::NoPen ) + pw = qMax( m_data->text.borderPen().width(), 1 ); + + QRect pixmapRect; + pixmapRect.setLeft( qwtFloor( rect.left() ) - pw ); + pixmapRect.setTop( qwtFloor( rect.top() ) - pw ); + pixmapRect.setRight( qwtCeil( rect.right() ) + pw ); + pixmapRect.setBottom( qwtCeil( rect.bottom() ) + pw ); + +#if QT_VERSION >= 0x050000 + const qreal pixelRatio = QwtPainter::devicePixelRatio( painter->device() ); + const QSize scaledSize = pixmapRect.size() * pixelRatio; +#else + const QSize scaledSize = pixmapRect.size(); +#endif + + if ( m_data->pixmap.isNull() || + ( scaledSize != m_data->pixmap.size() ) ) + { + m_data->pixmap = QPixmap( scaledSize ); +#if QT_VERSION >= 0x050000 + m_data->pixmap.setDevicePixelRatio( pixelRatio ); +#endif + m_data->pixmap.fill( Qt::transparent ); + + const QRect r( pw, pw, + pixmapRect.width() - 2 * pw, pixmapRect.height() - 2 * pw ); + + QPainter pmPainter( &m_data->pixmap ); + m_data->text.draw( &pmPainter, r ); + } + + painter->drawPixmap( pixmapRect, m_data->pixmap ); + } + else + { + m_data->text.draw( painter, rect ); + } +} + +/*! + \brief Align the text label + + \param rect Canvas rectangle with margins subtracted + \param textSize Size required to draw the text + + \return A rectangle aligned according the the alignment flags of + the text. + + \sa setMargin(), QwtText::renderFlags(), QwtText::textSize() + */ +QRectF QwtPlotTextLabel::textRect( + const QRectF& rect, const QSizeF& textSize ) const +{ + return qwtItemRect( m_data->text.renderFlags(), rect, textSize ); +} + +//! Invalidate all internal cache +void QwtPlotTextLabel::invalidateCache() +{ + m_data->pixmap = QPixmap(); +} diff --git a/libs/qwt/src/qwt_plot_textlabel.h b/libs/qwt/src/qwt_plot_textlabel.h new file mode 100644 index 00000000..f5a6f834 --- /dev/null +++ b/libs/qwt/src/qwt_plot_textlabel.h @@ -0,0 +1,75 @@ +/****************************************************************************** + * 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_TEXT_LABEL_H +#define QWT_PLOT_TEXT_LABEL_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" + +class QwtText; + +/*! + \brief A plot item, which displays a text label + + QwtPlotTextLabel displays a text label aligned to the plot canvas. + + In opposite to QwtPlotMarker the position of the label is unrelated to + plot coordinates. + + As drawing a text is an expensive operation the label is cached + in a pixmap to speed up replots. + + \par Example + The following code shows how to add a title. + \code + QwtText title( "Plot Title" ); + title.setRenderFlags( Qt::AlignHCenter | Qt::AlignTop ); + + QFont font; + font.setBold( true ); + title.setFont( font ); + + QwtPlotTextLabel *titleItem = new QwtPlotTextLabel(); + titleItem->setText( title ); + titleItem->attach( plot ); + \endcode + + \sa QwtPlotMarker + */ + +class QWT_EXPORT QwtPlotTextLabel : public QwtPlotItem +{ + public: + QwtPlotTextLabel(); + virtual ~QwtPlotTextLabel(); + + virtual int rtti() const QWT_OVERRIDE; + + void setText( const QwtText& ); + QwtText text() const; + + void setMargin( int margin ); + int margin() const; + + virtual QRectF textRect( const QRectF&, const QSizeF& ) const; + + protected: + virtual void draw( QPainter*, + const QwtScaleMap&, const QwtScaleMap&, + const QRectF&) const QWT_OVERRIDE; + + void invalidateCache(); + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_tradingcurve.cpp b/libs/qwt/src/qwt_plot_tradingcurve.cpp new file mode 100644 index 00000000..ff815875 --- /dev/null +++ b/libs/qwt/src/qwt_plot_tradingcurve.cpp @@ -0,0 +1,688 @@ +/****************************************************************************** + * 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_tradingcurve.h" +#include "qwt_scale_map.h" +#include "qwt_painter.h" +#include "qwt_text.h" +#include "qwt_graphic.h" +#include "qwt_math.h" + +#include + +static inline bool qwtIsSampleInside( const QwtOHLCSample& sample, + double tMin, double tMax, double vMin, double vMax ) +{ + const double t = sample.time; + const QwtInterval interval = sample.boundingInterval(); + + const bool isOffScreen = ( t < tMin ) || ( t > tMax ) + || ( interval.maxValue() < vMin ) || ( interval.minValue() > vMax ); + + return !isOffScreen; +} + +class QwtPlotTradingCurve::PrivateData +{ + public: + PrivateData() + : symbolStyle( QwtPlotTradingCurve::CandleStick ) + , symbolExtent( 0.6 ) + , minSymbolWidth( 2.0 ) + , maxSymbolWidth( -1.0 ) + , paintAttributes( QwtPlotTradingCurve::ClipSymbols ) + { + symbolBrush[0] = QBrush( Qt::white ); + symbolBrush[1] = QBrush( Qt::black ); + } + + QwtPlotTradingCurve::SymbolStyle symbolStyle; + double symbolExtent; + double minSymbolWidth; + double maxSymbolWidth; + + QPen symbolPen; + QBrush symbolBrush[2]; // Increasing/Decreasing + + QwtPlotTradingCurve::PaintAttributes paintAttributes; +}; + +/*! + Constructor + \param title Title of the curve + */ +QwtPlotTradingCurve::QwtPlotTradingCurve( const QwtText& title ) + : QwtPlotSeriesItem( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve + */ +QwtPlotTradingCurve::QwtPlotTradingCurve( const QString& title ) + : QwtPlotSeriesItem( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPlotTradingCurve::~QwtPlotTradingCurve() +{ + delete m_data; +} + +//! Initialize internal members +void QwtPlotTradingCurve::init() +{ + setItemAttribute( QwtPlotItem::Legend, true ); + setItemAttribute( QwtPlotItem::AutoScale, true ); + + m_data = new PrivateData; + setData( new QwtTradingChartData() ); + + setZ( 19.0 ); +} + +//! \return QwtPlotItem::Rtti_PlotTradingCurve +int QwtPlotTradingCurve::rtti() const +{ + return QwtPlotTradingCurve::Rtti_PlotTradingCurve; +} + +/*! + Specify an attribute how to draw the curve + + \param attribute Paint attribute + \param on On/Off + \sa testPaintAttribute() + */ +void QwtPlotTradingCurve::setPaintAttribute( + PaintAttribute attribute, bool on ) +{ + if ( on ) + m_data->paintAttributes |= attribute; + else + m_data->paintAttributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa PaintAttribute, setPaintAttribute() + */ +bool QwtPlotTradingCurve::testPaintAttribute( + PaintAttribute attribute ) const +{ + return ( m_data->paintAttributes & attribute ); +} + +/*! + Initialize data with an array of samples. + \param samples Vector of samples + + \sa QwtPlotSeriesItem::setData() + */ +void QwtPlotTradingCurve::setSamples( + const QVector< QwtOHLCSample >& samples ) +{ + setData( new QwtTradingChartData( 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 QwtPlotTradingCurve::setSamples( + QwtSeriesData< QwtOHLCSample >* data ) +{ + setData( data ); +} + +/*! + Set the symbol style + + \param style Symbol style + + \sa symbolStyle(), setSymbolExtent(), + setSymbolPen(), setSymbolBrush() + */ +void QwtPlotTradingCurve::setSymbolStyle( SymbolStyle style ) +{ + if ( style != m_data->symbolStyle ) + { + m_data->symbolStyle = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Symbol style + \sa setSymbolStyle(), symbolExtent(), symbolPen(), symbolBrush() + */ +QwtPlotTradingCurve::SymbolStyle QwtPlotTradingCurve::symbolStyle() const +{ + return m_data->symbolStyle; +} + +/*! + Build and assign the symbol 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 QwtPlotTradingCurve::setSymbolPen( + const QColor& color, qreal width, Qt::PenStyle style ) +{ + setSymbolPen( QPen( color, width, style ) ); +} + +/*! + \brief Set the symbol pen + + The symbol pen is used for rendering the lines of the + bar or candlestick symbols + + \sa symbolPen(), setSymbolBrush() + */ +void QwtPlotTradingCurve::setSymbolPen( const QPen& pen ) +{ + if ( pen != m_data->symbolPen ) + { + m_data->symbolPen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Symbol pen + \sa setSymbolPen(), symbolBrush() + */ +QPen QwtPlotTradingCurve::symbolPen() const +{ + return m_data->symbolPen; +} + +/*! + Set the symbol brush + + \param direction Direction type + \param brush Brush used to fill the body of all candlestick + symbols with the direction + + \sa symbolBrush(), setSymbolPen() + */ +void QwtPlotTradingCurve::setSymbolBrush( + Direction direction, const QBrush& brush ) +{ + // silencing -Wtautological-constant-out-of-range-compare + const int index = static_cast< int >( direction ); + if ( index < 0 || index >= 2 ) + return; + + if ( brush != m_data->symbolBrush[ index ] ) + { + m_data->symbolBrush[ index ] = brush; + + legendChanged(); + itemChanged(); + } +} + +/*! + \param direction + \return Brush used to fill the body of all candlestick + symbols with the direction + + \sa setSymbolPen(), symbolBrush() + */ +QBrush QwtPlotTradingCurve::symbolBrush( Direction direction ) const +{ + const int index = static_cast< int >( direction ); + if ( index < 0 || index >= 2 ) + return QBrush(); + + return m_data->symbolBrush[ index ]; +} + +/*! + \brief Set the extent of the symbol + + The width of the symbol is given in scale coordinates. When painting + a symbol the width is scaled into paint device coordinates + by scaledSymbolWidth(). The scaled width is bounded by + minSymbolWidth(), maxSymbolWidth() + + \param extent Symbol width in scale coordinates + + \sa symbolExtent(), scaledSymbolWidth(), + setMinSymbolWidth(), setMaxSymbolWidth() + */ +void QwtPlotTradingCurve::setSymbolExtent( double extent ) +{ + extent = qwtMaxF( 0.0, extent ); + if ( extent != m_data->symbolExtent ) + { + m_data->symbolExtent = extent; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Extent of a symbol in scale coordinates + \sa setSymbolExtent(), scaledSymbolWidth(), + minSymbolWidth(), maxSymbolWidth() + */ +double QwtPlotTradingCurve::symbolExtent() const +{ + return m_data->symbolExtent; +} + +/*! + Set a minimum for the symbol width + + \param width Width in paint device coordinates + \sa minSymbolWidth(), setMaxSymbolWidth(), setSymbolExtent() + */ +void QwtPlotTradingCurve::setMinSymbolWidth( double width ) +{ + width = qwtMaxF( width, 0.0 ); + if ( width != m_data->minSymbolWidth ) + { + m_data->minSymbolWidth = width; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Minmum for the symbol width + \sa setMinSymbolWidth(), maxSymbolWidth(), symbolExtent() + */ +double QwtPlotTradingCurve::minSymbolWidth() const +{ + return m_data->minSymbolWidth; +} + +/*! + Set a maximum for the symbol width + + A value <= 0.0 means an unlimited width + + \param width Width in paint device coordinates + \sa maxSymbolWidth(), setMinSymbolWidth(), setSymbolExtent() + */ +void QwtPlotTradingCurve::setMaxSymbolWidth( double width ) +{ + if ( width != m_data->maxSymbolWidth ) + { + m_data->maxSymbolWidth = width; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Maximum for the symbol width + \sa setMaxSymbolWidth(), minSymbolWidth(), symbolExtent() + */ +double QwtPlotTradingCurve::maxSymbolWidth() const +{ + return m_data->maxSymbolWidth; +} + +/*! + \return Bounding rectangle of all samples. + For an empty series the rectangle is invalid. + */ +QRectF QwtPlotTradingCurve::boundingRect() const +{ + QRectF rect = QwtPlotSeriesItem::boundingRect(); + if ( orientation() == Qt::Vertical ) + rect.setRect( rect.y(), rect.x(), rect.height(), rect.width() ); + + return rect; +} + +/*! + 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 drawSymbols() + */ +void QwtPlotTradingCurve::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; + + painter->save(); + + if ( m_data->symbolStyle != QwtPlotTradingCurve::NoSymbol ) + drawSymbols( painter, xMap, yMap, canvasRect, from, to ); + + painter->restore(); +} + +/*! + Draw symbols + + \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 drawSeries() + */ +void QwtPlotTradingCurve::drawSymbols( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const +{ + const QRectF tr = QwtScaleMap::invTransform( xMap, yMap, canvasRect ); + + const QwtScaleMap* timeMap, * valueMap; + double tMin, tMax, vMin, vMax; + + const Qt::Orientation orient = orientation(); + if ( orient == Qt::Vertical ) + { + timeMap = &xMap; + valueMap = &yMap; + + tMin = tr.left(); + tMax = tr.right(); + vMin = tr.top(); + vMax = tr.bottom(); + } + else + { + timeMap = &yMap; + valueMap = &xMap; + + vMin = tr.left(); + vMax = tr.right(); + tMin = tr.top(); + tMax = tr.bottom(); + } + + const bool inverted = timeMap->isInverting(); + const bool doClip = m_data->paintAttributes & ClipSymbols; + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + double symbolWidth = scaledSymbolWidth( xMap, yMap, canvasRect ); + if ( doAlign ) + symbolWidth = std::floor( 0.5 * symbolWidth ) * 2.0; + + QPen pen = m_data->symbolPen; + pen.setCapStyle( Qt::FlatCap ); + + painter->setPen( pen ); + + for ( int i = from; i <= to; i++ ) + { + const QwtOHLCSample s = sample( i ); + + if ( !doClip || qwtIsSampleInside( s, tMin, tMax, vMin, vMax ) ) + { + QwtOHLCSample translatedSample; + + translatedSample.time = timeMap->transform( s.time ); + translatedSample.open = valueMap->transform( s.open ); + translatedSample.high = valueMap->transform( s.high ); + translatedSample.low = valueMap->transform( s.low ); + translatedSample.close = valueMap->transform( s.close ); + + const int brushIndex = ( s.open < s.close ) + ? QwtPlotTradingCurve::Increasing + : QwtPlotTradingCurve::Decreasing; + + if ( doAlign ) + { + translatedSample.time = qRound( translatedSample.time ); + translatedSample.open = qRound( translatedSample.open ); + translatedSample.high = qRound( translatedSample.high ); + translatedSample.low = qRound( translatedSample.low ); + translatedSample.close = qRound( translatedSample.close ); + } + + switch( m_data->symbolStyle ) + { + case Bar: + { + drawBar( painter, translatedSample, + orient, inverted, symbolWidth ); + break; + } + case CandleStick: + { + painter->setBrush( m_data->symbolBrush[ brushIndex ] ); + drawCandleStick( painter, translatedSample, + orient, symbolWidth ); + break; + } + default: + { + if ( m_data->symbolStyle >= UserSymbol ) + { + painter->setBrush( m_data->symbolBrush[ brushIndex ] ); + drawUserSymbol( painter, m_data->symbolStyle, + translatedSample, orient, inverted, symbolWidth ); + } + } + } + } + } +} + +/*! + \brief Draw a symbol for a symbol style >= UserSymbol + + The implementation does nothing and is intended to be overloaded + + \param painter Qt painter, initialized with pen/brush + \param symbolStyle Symbol style + \param sample Samples already translated into paint device coordinates + \param orientation Vertical or horizontal + \param inverted True, when the opposite scale + ( Qt::Vertical: x, Qt::Horizontal: y ) is increasing + in the opposite direction as QPainter coordinates. + \param symbolWidth Width of the symbol in paint device coordinates + */ +void QwtPlotTradingCurve::drawUserSymbol( QPainter* painter, + SymbolStyle symbolStyle, const QwtOHLCSample& sample, + Qt::Orientation orientation, bool inverted, double symbolWidth ) const +{ + Q_UNUSED( painter ) + Q_UNUSED( symbolStyle ) + Q_UNUSED( orientation ) + Q_UNUSED( inverted ) + Q_UNUSED( symbolWidth ) + Q_UNUSED( sample ) +} + +/*! + \brief Draw a bar + + \param painter Qt painter, initialized with pen/brush + \param sample Sample, already translated into paint device coordinates + \param orientation Vertical or horizontal + \param inverted When inverted is false the open tick is painted + to the left/top, otherwise it is painted right/bottom. + The close tick is painted in the opposite direction + of the open tick. + painted in the opposite d + opposite direction. + \param width Width or height of the candle, depending on the orientation + + \sa Bar + */ +void QwtPlotTradingCurve::drawBar( QPainter* painter, + const QwtOHLCSample& sample, Qt::Orientation orientation, + bool inverted, double width ) const +{ + double w2 = 0.5 * width; + if ( inverted ) + w2 *= -1; + + if ( orientation == Qt::Vertical ) + { + QwtPainter::drawLine( painter, + sample.time, sample.low, sample.time, sample.high ); + + QwtPainter::drawLine( painter, + sample.time - w2, sample.open, sample.time, sample.open ); + QwtPainter::drawLine( painter, + sample.time + w2, sample.close, sample.time, sample.close ); + } + else + { + QwtPainter::drawLine( painter, sample.low, sample.time, + sample.high, sample.time ); + QwtPainter::drawLine( painter, + sample.open, sample.time - w2, sample.open, sample.time ); + QwtPainter::drawLine( painter, + sample.close, sample.time + w2, sample.close, sample.time ); + } +} + +/*! + \brief Draw a candle stick + + \param painter Qt painter, initialized with pen/brush + \param sample Samples already translated into paint device coordinates + \param orientation Vertical or horizontal + \param width Width or height of the candle, depending on the orientation + + \sa CandleStick + */ +void QwtPlotTradingCurve::drawCandleStick( QPainter* painter, + const QwtOHLCSample& sample, Qt::Orientation orientation, + double width ) const +{ + const double t = sample.time; + const double v1 = qwtMinF( sample.low, sample.high ); + const double v2 = qwtMinF( sample.open, sample.close ); + const double v3 = qwtMaxF( sample.low, sample.high ); + const double v4 = qwtMaxF( sample.open, sample.close ); + + if ( orientation == Qt::Vertical ) + { + QwtPainter::drawLine( painter, t, v1, t, v2 ); + QwtPainter::drawLine( painter, t, v3, t, v4 ); + + QRectF rect( t - 0.5 * width, sample.open, + width, sample.close - sample.open ); + + QwtPainter::drawRect( painter, rect ); + } + else + { + QwtPainter::drawLine( painter, v1, t, v2, t ); + QwtPainter::drawLine( painter, v3, t, v4, t ); + + const QRectF rect( sample.open, t - 0.5 * width, + sample.close - sample.open, width ); + + QwtPainter::drawRect( painter, rect ); + } +} + +/*! + \return A rectangle filled with the color of the symbol pen + + \param index Index of the legend entry + ( usually there is only one ) + \param size Icon size + + \sa setLegendIconSize(), legendData() + */ +QwtGraphic QwtPlotTradingCurve::legendIcon( int index, + const QSizeF& size ) const +{ + Q_UNUSED( index ); + return defaultIcon( m_data->symbolPen.color(), size ); +} + +/*! + Calculate the symbol width in paint coordinates + + The width is calculated by scaling the symbol extent into + paint device coordinates bounded by the minimum/maximum + symbol width. + + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas + + \return Symbol width in paint coordinates + + \sa symbolExtent(), minSymbolWidth(), maxSymbolWidth() + */ +double QwtPlotTradingCurve::scaledSymbolWidth( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + Q_UNUSED( canvasRect ); + + if ( m_data->maxSymbolWidth > 0.0 && + m_data->minSymbolWidth >= m_data->maxSymbolWidth ) + { + return m_data->minSymbolWidth; + } + + const QwtScaleMap* map = + ( orientation() == Qt::Vertical ) ? &xMap : &yMap; + + const double pos = map->transform( map->s1() + m_data->symbolExtent ); + + double width = qAbs( pos - map->p1() ); + + width = qwtMaxF( width, m_data->minSymbolWidth ); + if ( m_data->maxSymbolWidth > 0.0 ) + width = qwtMinF( width, m_data->maxSymbolWidth ); + + return width; +} diff --git a/libs/qwt/src/qwt_plot_tradingcurve.h b/libs/qwt/src/qwt_plot_tradingcurve.h new file mode 100644 index 00000000..8da366a4 --- /dev/null +++ b/libs/qwt/src/qwt_plot_tradingcurve.h @@ -0,0 +1,173 @@ +/****************************************************************************** + * 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_TRADING_CURVE_H +#define QWT_PLOT_TRADING_CURVE_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" + +/*! + \brief QwtPlotTradingCurve illustrates movements in the price of a + financial instrument over time. + + QwtPlotTradingCurve supports candlestick or bar ( OHLC ) charts + that are used in the domain of technical analysis. + + While the length ( height or width depending on orientation() ) + of each symbol depends on the corresponding OHLC sample the size + of the other dimension can be controlled using: + + - setSymbolExtent() + - setSymbolMinWidth() + - setSymbolMaxWidth() + + The extent is a size in scale coordinates, so that the symbol width + is increasing when the plot is zoomed in. Minimum/Maximum width + is in widget coordinates independent from the zoom level. + When setting the minimum and maximum to the same value, the width of + the symbol is fixed. + */ +class QWT_EXPORT QwtPlotTradingCurve + : public QwtPlotSeriesItem + , public QwtSeriesStore< QwtOHLCSample > +{ + public: + /*! + \brief Symbol styles. + + The default setting is QwtPlotSeriesItem::CandleStick. + \sa setSymbolStyle(), symbolStyle() + */ + enum SymbolStyle + { + //! Nothing is displayed + NoSymbol = -1, + + /*! + A line on the chart shows the price range (the highest and lowest + prices) over one unit of time, e.g. one day or one hour. + Tick marks project from each side of the line indicating the + opening and closing price. + */ + Bar, + + /*! + The range between opening/closing price are displayed as + a filled box. The fill brush depends on the direction of the + price movement. The box is connected to the highest/lowest + values by lines. + */ + CandleStick, + + /*! + SymbolTypes >= UserSymbol are displayed by drawUserSymbol(), + that needs to be overloaded and implemented in derived + curve classes. + + \sa drawUserSymbol() + */ + UserSymbol = 100 + }; + + /*! + \brief Direction of a price movement + */ + enum Direction + { + //! The closing price is higher than the opening price + Increasing, + + //! The closing price is lower than the opening price + Decreasing + }; + + /*! + Attributes to modify the drawing algorithm. + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + //! Check if a symbol is on the plot canvas before painting it. + ClipSymbols = 0x01 + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + explicit QwtPlotTradingCurve( const QString& title = QString() ); + explicit QwtPlotTradingCurve( const QwtText& title ); + + virtual ~QwtPlotTradingCurve(); + + virtual int rtti() const QWT_OVERRIDE; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setSamples( const QVector< QwtOHLCSample >& ); + void setSamples( QwtSeriesData< QwtOHLCSample >* ); + + void setSymbolStyle( SymbolStyle style ); + SymbolStyle symbolStyle() const; + + void setSymbolPen( const QColor&, + qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setSymbolPen( const QPen& ); + QPen symbolPen() const; + + void setSymbolBrush( Direction, const QBrush& ); + QBrush symbolBrush( Direction ) const; + + void setSymbolExtent( double ); + double symbolExtent() const; + + void setMinSymbolWidth( double ); + double minSymbolWidth() const; + + void setMaxSymbolWidth( double ); + double maxSymbolWidth() const; + + virtual void drawSeries( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const QWT_OVERRIDE; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + virtual QwtGraphic legendIcon( int index, const QSizeF& ) const QWT_OVERRIDE; + + protected: + + void init(); + + virtual void drawSymbols( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const; + + virtual void drawUserSymbol( QPainter*, + SymbolStyle, const QwtOHLCSample&, + Qt::Orientation, bool inverted, double symbolWidth ) const; + + void drawBar( QPainter*, const QwtOHLCSample&, + Qt::Orientation, bool inverted, double width ) const; + + void drawCandleStick( QPainter*, const QwtOHLCSample&, + Qt::Orientation, double width ) const; + + virtual double scaledSymbolWidth( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotTradingCurve::PaintAttributes ) + +#endif diff --git a/libs/qwt/src/qwt_plot_vectorfield.cpp b/libs/qwt/src/qwt_plot_vectorfield.cpp new file mode 100644 index 00000000..0db1a047 --- /dev/null +++ b/libs/qwt/src/qwt_plot_vectorfield.cpp @@ -0,0 +1,1038 @@ +/****************************************************************************** + * 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_vectorfield.h" +#include "qwt_vectorfield_symbol.h" +#include "qwt_scale_map.h" +#include "qwt_color_map.h" +#include "qwt_painter.h" +#include "qwt_text.h" +#include "qwt_graphic.h" +#include "qwt_math.h" + +#include +#include +#include +#include +#include + +#define DEBUG_RENDER 0 + +#if DEBUG_RENDER +#include +#endif + + +static inline double qwtVector2Radians( double vx, double vy ) +{ + if ( vx == 0.0 ) + return ( vy >= 0 ) ? M_PI_2 : 3 * M_PI_2; + + return std::atan2( vy, vx ); +} + +static inline double qwtVector2Magnitude( double vx, double vy ) +{ + return sqrt( vx * vx + vy * vy ); +} + +static QwtInterval qwtMagnitudeRange( + const QwtSeriesData< QwtVectorFieldSample >* series ) +{ + if ( series->size() == 0 ) + return QwtInterval( 0, 1 ); + + const QwtVectorFieldSample s0 = series->sample( 0 ); + + double min = s0.vx * s0.vx + s0.vy * s0.vy; + double max = min; + + for ( uint i = 1; i < series->size(); i++ ) + { + const QwtVectorFieldSample s = series->sample( i ); + const double l = s.vx * s.vx + s.vy * s.vy; + + if ( l < min ) + min = l; + + if ( l > max ) + max = l; + } + + min = std::sqrt( min ); + max = std::sqrt( max ); + + if ( max == min ) + max += 1.0; + + return QwtInterval( min, max ); +} + +static inline QTransform qwtSymbolTransformation( + const QTransform& oldTransform, double x, double y, + double vx, double vy, double magnitude ) +{ + QTransform transform = oldTransform; + + if ( !transform.isIdentity() ) + { + transform.translate( x, y ); + + const double radians = qwtVector2Radians( vx, vy ); + transform.rotateRadians( radians ); + } + else + { + /* + When starting with no transformation ( f.e on screen ) + the matrix can be found without having to use + trigonometric functions + */ + + qreal sin, cos; + if ( magnitude == 0.0 ) + { + // something + sin = 1.0; + cos = 0.0; + } + else + { + sin = vy / magnitude; + cos = vx / magnitude; + } + + transform.setMatrix( cos, sin, 0.0, -sin, cos, 0.0, x, y, 1.0 ); + } + + return transform; +} + +namespace +{ + class FilterMatrix + { + public: + class Entry + { + public: + inline void addSample( double sx, double sy, + double svx, double svy ) + { + x += sx; + y += sy; + + vx += svx; + vy += svy; + + count++; + } + + quint32 count; + + // screen positions -> float is good enough + float x; + float y; + float vx; + float vy; + }; + + FilterMatrix( const QRectF& dataRect, + const QRectF& canvasRect, const QSizeF& cellSize ) + { + m_dx = cellSize.width(); + m_dy = cellSize.height(); + + m_x0 = dataRect.x(); + if ( m_x0 < canvasRect.x() ) + m_x0 += int( ( canvasRect.x() - m_x0 ) / m_dx ) * m_dx; + + m_y0 = dataRect.y(); + if ( m_y0 < canvasRect.y() ) + m_y0 += int( ( canvasRect.y() - m_y0 ) / m_dy ) * m_dy; + + m_numColumns = canvasRect.width() / m_dx + 1; + m_numRows = canvasRect.height() / m_dy + 1; + +#if 1 + /* + limit column and row count to a maximum of 1000000, + so that memory usage is not an issue + */ + if ( m_numColumns > 1000 ) + { + m_dx = canvasRect.width() / 1000; + m_numColumns = canvasRect.width() / m_dx + 1; + } + + if ( m_numRows > 1000 ) + { + m_dy = canvasRect.height() / 1000; + m_numRows = canvasRect.height() / m_dx + 1; + } +#endif + + m_x1 = m_x0 + m_numColumns * m_dx; + m_y1 = m_y0 + m_numRows * m_dy; + + m_entries = ( Entry* )::calloc( m_numRows * m_numColumns, sizeof( Entry ) ); + if ( m_entries == NULL ) + { + qWarning() << "QwtPlotVectorField: raster for filtering too fine - running out of memory"; + } + } + + ~FilterMatrix() + { + if ( m_entries ) + std::free( m_entries ); + } + + inline int numColumns() const + { + return m_numColumns; + } + + inline int numRows() const + { + return m_numRows; + } + + inline void addSample( double x, double y, + double u, double v ) + { + if ( x >= m_x0 && x < m_x1 + && y >= m_y0 && y < m_y1 ) + { + Entry& entry = m_entries[ indexOf( x, y ) ]; + entry.addSample( x, y, u, v ); + } + } + + const FilterMatrix::Entry* entries() const + { + return m_entries; + } + + private: + inline int indexOf( qreal x, qreal y ) const + { + const int col = ( x - m_x0 ) / m_dx; + const int row = ( y - m_y0 ) / m_dy; + + return row * m_numColumns + col; + } + + qreal m_x0, m_x1, m_y0, m_y1, m_dx, m_dy; + int m_numColumns; + int m_numRows; + + Entry* m_entries; + }; +} + +class QwtPlotVectorField::PrivateData +{ + public: + PrivateData() + : pen( Qt::black ) + , brush( Qt::black ) + , indicatorOrigin( QwtPlotVectorField::OriginHead ) + , magnitudeScaleFactor( 1.0 ) + , rasterSize( 20, 20 ) + , minArrowLength( 0.0 ) + , maxArrowLength( std::numeric_limits< short >::max() ) + , magnitudeModes( MagnitudeAsLength ) + { + colorMap = NULL; + symbol = new QwtVectorFieldThinArrow(); + } + + ~PrivateData() + { + delete colorMap; + delete symbol; + } + + QPen pen; + QBrush brush; + + IndicatorOrigin indicatorOrigin; + QwtVectorFieldSymbol* symbol; + QwtColorMap* colorMap; + + /* + Stores the range of magnitudes to be used for the color map. + If invalid (min=max or negative values), the range is determined + from the data samples themselves. + */ + QwtInterval magnitudeRange; + QwtInterval boundingMagnitudeRange; + + qreal magnitudeScaleFactor; + QSizeF rasterSize; + + double minArrowLength; + double maxArrowLength; + + PaintAttributes paintAttributes; + MagnitudeModes magnitudeModes; +}; + +/*! + Constructor + \param title Title of the curve + */ +QwtPlotVectorField::QwtPlotVectorField( const QwtText& title ) + : QwtPlotSeriesItem( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve + */ +QwtPlotVectorField::QwtPlotVectorField( const QString& title ) + : QwtPlotSeriesItem( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPlotVectorField::~QwtPlotVectorField() +{ + delete m_data; +} + +/*! + \brief Initialize data members + */ +void QwtPlotVectorField::init() +{ + setItemAttribute( QwtPlotItem::Legend ); + setItemAttribute( QwtPlotItem::AutoScale ); + + m_data = new PrivateData; + setData( new QwtVectorFieldData() ); + + setZ( 20.0 ); +} + +/*! + Assign a pen + + \param pen New pen + \sa pen(), brush() + + \note the pen is ignored in MagnitudeAsColor mode + */ +void QwtPlotVectorField::setPen( const QPen& pen ) +{ + if ( m_data->pen != pen ) + { + m_data->pen = pen; + + itemChanged(); + legendChanged(); + } +} + +/*! + \return Pen used to draw the lines + \sa setPen(), brush() + */ +QPen QwtPlotVectorField::pen() const +{ + return m_data->pen; +} + +/*! + \brief Assign a brush. + + \param brush New brush + \sa brush(), pen() + + \note the brush is ignored in MagnitudeAsColor mode + */ +void QwtPlotVectorField::setBrush( const QBrush& brush ) +{ + if ( m_data->brush != brush ) + { + m_data->brush = brush; + + itemChanged(); + legendChanged(); + } +} + +/*! + \return Brush used to fill the symbol + \sa setBrush(), pen() + */ +QBrush QwtPlotVectorField::brush() const +{ + return m_data->brush; +} + +/*! + Set the origin for the symbols/arrows + + \param origin Origin + \sa indicatorOrigin() + */ +void QwtPlotVectorField::setIndicatorOrigin( IndicatorOrigin origin ) +{ + m_data->indicatorOrigin = origin; + if ( m_data->indicatorOrigin != origin ) + { + m_data->indicatorOrigin = origin; + itemChanged(); + } +} + +//! \return origin for the symbols/arrows +QwtPlotVectorField::IndicatorOrigin QwtPlotVectorField::indicatorOrigin() const +{ + return m_data->indicatorOrigin; +} + +/*! + \brief Set the magnitudeScaleFactor + + The length of the arrow in screen coordinate units is calculated by + scaling the magnitude by the magnitudeScaleFactor. + + \param factor Scale factor + + \sa magnitudeScaleFactor(), arrowLength() + \note Has no effect when QwtPlotVectorField::MagnitudeAsLength is not enabled + */ +void QwtPlotVectorField::setMagnitudeScaleFactor( double factor ) +{ + if ( factor != m_data->magnitudeScaleFactor ) + { + m_data->magnitudeScaleFactor = factor; + itemChanged(); + } +} + +/*! + \return Scale factor used to calculate the arrow length from the magnitude + + The length of the arrow in screen coordinate units is calculated by + scaling the magnitude by the magnitudeScaleFactor. + + Default implementation simply scales the vector using the magnitudeScaleFactor + property. Re-implement this function to provide special handling for + zero/non-zero magnitude arrows, or impose minimum/maximum arrow length limits. + + \return Length of arrow to be drawn in dependence of vector magnitude. + \sa magnitudeScaleFactor + \note Has no effect when QwtPlotVectorField::MagnitudeAsLength is not enabled + */ +double QwtPlotVectorField::magnitudeScaleFactor() const +{ + return m_data->magnitudeScaleFactor; +} + +/*! + Set the raster size used for filtering samples + + \sa rasterSize(), QwtPlotVectorField::FilterVectors + */ +void QwtPlotVectorField::setRasterSize( const QSizeF& size ) +{ + if ( size != m_data->rasterSize ) + { + m_data->rasterSize = size; + itemChanged(); + } +} + +/*! + \return raster size used for filtering samples + \sa setRasterSize(), QwtPlotVectorField::FilterVectors + */ +QSizeF QwtPlotVectorField::rasterSize() const +{ + return m_data->rasterSize; +} + +/*! + Specify an attribute how to draw the curve + + \param attribute Paint attribute + \param on On/Off + \sa testPaintAttribute() + */ +void QwtPlotVectorField::setPaintAttribute( + PaintAttribute attribute, bool on ) +{ + PaintAttributes attributes = m_data->paintAttributes; + + if ( on ) + attributes |= attribute; + else + attributes &= ~attribute; + + if ( m_data->paintAttributes != attributes ) + { + m_data->paintAttributes = attributes; + itemChanged(); + } +} + +/*! + \return True, when attribute is enabled + \sa PaintAttribute, setPaintAttribute() + */ +bool QwtPlotVectorField::testPaintAttribute( + PaintAttribute attribute ) const +{ + return ( m_data->paintAttributes & attribute ); +} + +//! \return QwtPlotItem::Rtti_PlotField +int QwtPlotVectorField::rtti() const +{ + return QwtPlotItem::Rtti_PlotVectorField; +} + +/*! + Sets a new arrow symbol (implementation of arrow drawing code). + + \param symbol Arrow symbol + + \sa symbol(), drawSymbol() + \note Ownership is transferred to QwtPlotVectorField. + */ +void QwtPlotVectorField::setSymbol( QwtVectorFieldSymbol* symbol ) +{ + if ( m_data->symbol == symbol ) + return; + + delete m_data->symbol; + m_data->symbol = symbol; + + itemChanged(); + legendChanged(); +} + +/*! + \return arrow symbol + \sa setSymbol(), drawSymbol() + */ +const QwtVectorFieldSymbol* QwtPlotVectorField::symbol() const +{ + return m_data->symbol; +} + +/*! + Initialize data with an array of samples. + \param samples Vector of points + */ +void QwtPlotVectorField::setSamples( const QVector< QwtVectorFieldSample >& samples ) +{ + setData( new QwtVectorFieldData( 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 QwtPlotVectorField::setSamples( QwtVectorFieldData* data ) +{ + setData( data ); +} + +/*! + Change the color map + + The color map is used to map the magnitude of a sample into + a color using a known range for the magnitudes. + + \param colorMap Color Map + + \sa colorMap(), magnitudeRange() + */ +void QwtPlotVectorField::setColorMap( QwtColorMap* colorMap ) +{ + if ( colorMap == NULL ) + return; + + if ( colorMap != m_data->colorMap ) + { + delete m_data->colorMap; + m_data->colorMap = colorMap; + } + + legendChanged(); + itemChanged(); +} + +/*! + \return Color Map used for mapping the intensity values to colors + \sa setColorMap() + */ +const QwtColorMap* QwtPlotVectorField::colorMap() const +{ + return m_data->colorMap; +} + +/*! + Specify a mode how to represent the magnitude a n arrow/symbol + + \param mode Mode + \param on On/Off + \sa testMagnitudeMode() + */ +void QwtPlotVectorField::setMagnitudeMode( MagnitudeMode mode, bool on ) +{ + if ( on == testMagnitudeMode( mode ) ) + return; + + if ( on ) + m_data->magnitudeModes |= mode; + else + m_data->magnitudeModes &= ~mode; + + itemChanged(); +} + +/*! + \return True, when mode is enabled + \sa MagnitudeMode, setMagnitudeMode() + */ +bool QwtPlotVectorField::testMagnitudeMode( MagnitudeMode mode ) const +{ + return m_data->magnitudeModes & mode; +} + +/*! + Sets the min/max magnitudes to be used for color map lookups. + + If invalid (min=max=0 or negative values), the range is determined from + the current range of magnitudes in the vector samples. + + \sa magnitudeRange(), colorMap() + */ +void QwtPlotVectorField::setMagnitudeRange( const QwtInterval& magnitudeRange ) +{ + if ( m_data->magnitudeRange != magnitudeRange ) + { + m_data->magnitudeRange = magnitudeRange; + itemChanged(); + } +} + +/*! + \return min/max magnitudes to be used for color map lookups + \sa setMagnitudeRange(), colorMap() + */ +QwtInterval QwtPlotVectorField::magnitudeRange() const +{ + return m_data->magnitudeRange; +} + +/*! + Set a minimum for the arrow length of non zero vectors + + \param length Minimum for the arrow length in pixels + + \sa minArrowLength(), setMaxArrowLength(), arrowLength() + \note Has no effect when QwtPlotVectorField::MagnitudeAsLength is not enabled + */ +void QwtPlotVectorField::setMinArrowLength( double length ) +{ + length = qMax( length, 0.0 ); + + if ( m_data->minArrowLength != length ) + { + m_data->minArrowLength = length; + itemChanged(); + } +} + +/*! + \return minimum for the arrow length of non zero vectors + + \sa setMinArrowLength(), maxArrowLength(), arrowLength() + \note Has no effect when QwtPlotVectorField::MagnitudeAsLength is not enabled + */ +double QwtPlotVectorField::minArrowLength() const +{ + return m_data->minArrowLength; +} + +/*! + Set a maximum for the arrow length + + \param length Maximum for the arrow length in pixels + + \sa maxArrowLength(), setMinArrowLength(), arrowLength() + \note Has no effect when QwtPlotVectorField::MagnitudeAsLength is not enabled + */ +void QwtPlotVectorField::setMaxArrowLength( double length ) +{ + length = qMax( length, 0.0 ); + + if ( m_data->maxArrowLength != length ) + { + m_data->maxArrowLength = length; + itemChanged(); + } +} + +/*! + \return maximum for the arrow length + + \sa setMinArrowLength(), maxArrowLength(), arrowLength() + \note Has no effect when QwtPlotVectorField::MagnitudeAsLength is not enabled + */ +double QwtPlotVectorField::maxArrowLength() const +{ + return m_data->maxArrowLength; +} + +/*! + Computes length of the arrow in screen coordinate units based on its magnitude. + + Default implementation simply scales the vector using the magnitudeScaleFactor() + If the result is not null, the length is then bounded into the interval + [ minArrowLength(), maxArrowLength() ]. + + Re-implement this function to provide special handling for + zero/non-zero magnitude arrows, or impose minimum/maximum arrow length limits. + + \param magnitude Magnitude + \return Length of arrow to be drawn in dependence of vector magnitude. + + \sa magnitudeScaleFactor, minArrowLength(), maxArrowLength() + \note Has no effect when QwtPlotVectorField::MagnitudeAsLength is not enabled + */ +double QwtPlotVectorField::arrowLength( double magnitude ) const +{ +#if 0 + /* + Normalize magnitude with respect to value range. Then, magnitudeScaleFactor + is the number of pixels to draw for a vector of length equal to + magnitudeRange.maxValue(). The relative scaling ensures that change of data + samples of very different magnitudes will always lead to a reasonable + display on screen. + */ + const QwtVectorFieldData* vectorData = dynamic_cast< const QwtVectorFieldData* >( data() ); + if ( m_data->magnitudeRange.maxValue() > 0 ) + magnitude /= m_data->magnitudeRange.maxValue(); +#endif + + double length = magnitude * m_data->magnitudeScaleFactor; + + if ( length > 0.0 ) + length = qBound( m_data->minArrowLength, length, m_data->maxArrowLength ); + + return length; +} + +QRectF QwtPlotVectorField::boundingRect() const +{ +#if 0 + /* + The bounding rectangle of the samples comes from the origins + only, but as we know the scaling factor for the magnitude + ( qwtVector2Magnitude ) here, we could try to include it ? + */ +#endif + + return QwtPlotSeriesItem::boundingRect(); +} + +/*! + \return Icon representing the vector fields 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 QwtPlotVectorField::legendIcon( + int index, const QSizeF& size ) const +{ + Q_UNUSED( index ); + + QwtGraphic icon; + icon.setDefaultSize( size ); + + if ( size.isEmpty() ) + return icon; + + QPainter painter( &icon ); + painter.setRenderHint( QPainter::Antialiasing, + testRenderHint( QwtPlotItem::RenderAntialiased ) ); + + painter.translate( -size.width(), -0.5 * size.height() ); + + painter.setPen( m_data->pen ); + painter.setBrush( m_data->brush ); + + m_data->symbol->setLength( size.width() - 2 ); + m_data->symbol->paint( &painter ); + + return icon; +} + +/*! + Draw a subset of the points + + \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. + */ +void QwtPlotVectorField::drawSeries( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const +{ + if ( !painter || dataSize() <= 0 ) + return; + + if ( to < 0 ) + to = dataSize() - 1; + + if ( from < 0 ) + from = 0; + + if ( from > to ) + return; + +#if DEBUG_RENDER + QElapsedTimer timer; + timer.start(); +#endif + + drawSymbols( painter, xMap, yMap, canvasRect, from, to ); + +#if DEBUG_RENDER + qDebug() << timer.elapsed(); +#endif +} + +/*! + Draw symbols + + \param painter Painter + \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(), drawSymbol(), drawSeries() + */ +void QwtPlotVectorField::drawSymbols( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + const bool doClip = false; + + const bool isInvertingX = xMap.isInverting(); + const bool isInvertingY = yMap.isInverting(); + + const QwtSeriesData< QwtVectorFieldSample >* series = data(); + + if ( m_data->magnitudeModes & MagnitudeAsColor ) + { + // user input error, can't draw without color map + // TODO: Discuss! Without colormap, silently fall back to uniform colors? + if ( m_data->colorMap == NULL) + return; + } + else + { + painter->setPen( m_data->pen ); + painter->setBrush( m_data->brush ); + } + + if ( ( m_data->paintAttributes & FilterVectors ) && !m_data->rasterSize.isEmpty() ) + { + const QRectF dataRect = QwtScaleMap::transform( + xMap, yMap, boundingRect() ); + + // TODO: Discuss. How to handle raster size when switching from screen to print size! + // DPI-aware adjustment of rastersize? Or make "rastersize in screen coordinate" + // or "rastersize in plotcoordinetes" a user option? +#if 1 + // define filter matrix based on screen/print coordinates + FilterMatrix matrix( dataRect, canvasRect, m_data->rasterSize ); +#else + // define filter matrix based on real coordinates + + // get scale factor from real coordinates to screen coordinates + double xScale = 1; + if (xMap.sDist() != 0) + xScale = xMap.pDist() / xMap.sDist(); + + double yScale = 1; + if (yMap.sDist() != 0) + yScale = yMap.pDist() / yMap.sDist(); + + QSizeF canvasRasterSize(xScale * m_data->rasterSize.width(), yScale * m_data->rasterSize.height() ); + FilterMatrix matrix( dataRect, canvasRect, canvasRasterSize ); +#endif + + for ( int i = from; i <= to; i++ ) + { + const QwtVectorFieldSample sample = series->sample( i ); + if ( !sample.isNull() ) + { + matrix.addSample( xMap.transform( sample.x ), + yMap.transform( sample.y ), sample.vx, sample.vy ); + } + } + + const int numEntries = matrix.numRows() * matrix.numColumns(); + const FilterMatrix::Entry* entries = matrix.entries(); + + for ( int i = 0; i < numEntries; i++ ) + { + const FilterMatrix::Entry& entry = entries[i]; + + if ( entry.count == 0 ) + continue; + + double xi = entry.x / entry.count; + double yi = entry.y / entry.count; + + if ( doAlign ) + { + xi = qRound( xi ); + yi = qRound( yi ); + } + + const double vx = entry.vx / entry.count; + const double vy = entry.vy / entry.count; + + drawSymbol( painter, xi, yi, + isInvertingX ? -vx : vx, isInvertingY ? -vy : vy ); + } + } + else + { + for ( int i = from; i <= to; i++ ) + { + const QwtVectorFieldSample sample = series->sample( i ); + + // arrows with zero length are never drawn + if ( sample.isNull() ) + continue; + + double xi = xMap.transform( sample.x ); + double yi = yMap.transform( sample.y ); + + if ( doAlign ) + { + xi = qRound( xi ); + yi = qRound( yi ); + } + + if ( doClip ) + { + if ( !canvasRect.contains( xi, yi ) ) + continue; + } + + drawSymbol( painter, xi, yi, + isInvertingX ? -sample.vx : sample.vx, + isInvertingY ? -sample.vy : sample.vy ); + } + } +} + +/*! + Draw a arrow/symbols at a specific position + + x, y, are paint device coordinates, while vx, vy are from + the corresponding sample. + + \sa setSymbol(), drawSeries() + */ +void QwtPlotVectorField::drawSymbol( QPainter* painter, + double x, double y, double vx, double vy ) const +{ + const double magnitude = qwtVector2Magnitude( vx, vy ); + + const QTransform oldTransform = painter->transform(); + + QTransform transform = qwtSymbolTransformation( oldTransform, + x, y, vx, vy, magnitude ); + + QwtVectorFieldSymbol* symbol = m_data->symbol; + + double length = 0.0; + + if ( m_data->magnitudeModes & MagnitudeAsLength ) + { + length = arrowLength( magnitude ); + } + + symbol->setLength( length ); + + if( m_data->indicatorOrigin == OriginTail ) + { + const qreal dx = symbol->length(); + transform.translate( dx, 0.0 ); + } + else if ( m_data->indicatorOrigin == OriginCenter ) + { + const qreal dx = symbol->length(); + transform.translate( 0.5 * dx, 0.0 ); + } + + if ( m_data->magnitudeModes & MagnitudeAsColor ) + { + // Determine color for arrow if colored by magnitude. + + QwtInterval range = m_data->magnitudeRange; + + if ( !range.isValid() ) + { + if ( !m_data->boundingMagnitudeRange.isValid() ) + m_data->boundingMagnitudeRange = qwtMagnitudeRange( data() ); + + range = m_data->boundingMagnitudeRange; + } + + const QColor c = m_data->colorMap->rgb( range, magnitude ); + +#if 1 + painter->setBrush( c ); + painter->setPen( c ); +#endif + } + + painter->setWorldTransform( transform, false ); + symbol->paint( painter ); + painter->setWorldTransform( oldTransform, false ); +} + +void QwtPlotVectorField::dataChanged() +{ + m_data->boundingMagnitudeRange.invalidate(); + QwtPlotSeriesItem::dataChanged(); +} diff --git a/libs/qwt/src/qwt_plot_vectorfield.h b/libs/qwt/src/qwt_plot_vectorfield.h new file mode 100644 index 00000000..a80b63ba --- /dev/null +++ b/libs/qwt/src/qwt_plot_vectorfield.h @@ -0,0 +1,169 @@ +/****************************************************************************** + * 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_VECTOR_FIELD_H +#define QWT_PLOT_VECTOR_FIELD_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" + +class QwtVectorFieldSymbol; +class QwtColorMap; +class QPen; +class QBrush; + +/*! + \brief A plot item, that represents a vector field + + A vector field is a representation of a points with a given magnitude and direction + as arrows. While the direction affects the direction of the arrow, the magnitude + might be represented as a color or by the length of the arrow. + + \sa QwtVectorFieldSymbol, QwtVectorFieldSample + */ +class QWT_EXPORT QwtPlotVectorField + : public QwtPlotSeriesItem + , public QwtSeriesStore< QwtVectorFieldSample > +{ + public: + /*! + Depending on the origin the indicator symbol ( usually an arrow ) + will be to the position of the corresponding sample. + */ + enum IndicatorOrigin + { + //! symbol points to the sample position + OriginHead, + + //! The arrow starts at the sample position + OriginTail, + + //! The arrow is centered at the sample position + OriginCenter + }; + + /*! + Attributes to modify the rendering + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + /* + FilterVectors calculates an average sample from all samples + that lie in the same cell of a grid that is determined by + setting the rasterSize(). + + \sa setRasterSize() + */ + FilterVectors = 0x01 + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + /*! + Depending on the MagnitudeMode the magnitude component will have + an impact on the attributes of the symbol/arrow. + + \sa setMagnitudeMode() + */ + enum MagnitudeMode + { + /*! + The magnitude will be mapped to a color using a color map + \sa magnitudeRange(), colorMap() + */ + MagnitudeAsColor = 0x01, + + /*! + The magnitude will have an impact on the length of the arrow/symbol + \sa arrowLength(), magnitudeScaleFactor() + */ + MagnitudeAsLength = 0x02 + }; + + Q_DECLARE_FLAGS( MagnitudeModes, MagnitudeMode ) + + explicit QwtPlotVectorField( const QString& title = QString() ); + explicit QwtPlotVectorField( const QwtText& title ); + + virtual ~QwtPlotVectorField(); + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setMagnitudeMode( MagnitudeMode, bool on = true ); + bool testMagnitudeMode( MagnitudeMode ) const; + + void setSymbol( QwtVectorFieldSymbol* ); + const QwtVectorFieldSymbol* symbol() const; + + void setPen( const QPen& ); + QPen pen() const; + + void setBrush( const QBrush& ); + QBrush brush() const; + + void setRasterSize( const QSizeF& ); + QSizeF rasterSize() const; + + void setIndicatorOrigin( IndicatorOrigin ); + IndicatorOrigin indicatorOrigin() const; + + void setSamples( const QVector< QwtVectorFieldSample >& ); + void setSamples( QwtVectorFieldData* ); + + void setColorMap( QwtColorMap* ); + const QwtColorMap* colorMap() const; + + void setMagnitudeRange( const QwtInterval& ); + QwtInterval magnitudeRange() const; + + void setMinArrowLength( double ); + double minArrowLength() const; + + void setMaxArrowLength( double ); + double maxArrowLength() const; + + virtual double arrowLength( double magnitude ) const; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + virtual void drawSeries( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const QWT_OVERRIDE; + + virtual int rtti() const QWT_OVERRIDE; + + virtual QwtGraphic legendIcon( + int index, const QSizeF& ) const QWT_OVERRIDE; + + void setMagnitudeScaleFactor( double factor ); + double magnitudeScaleFactor() const; + + protected: + virtual void drawSymbols( QPainter*, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect, int from, int to ) const; + + virtual void drawSymbol( QPainter*, + double x, double y, double vx, double vy ) const; + + virtual void dataChanged() QWT_OVERRIDE; + + private: + void init(); + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotVectorField::PaintAttributes ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotVectorField::MagnitudeModes ) + +#endif diff --git a/libs/qwt/src/qwt_plot_zoneitem.cpp b/libs/qwt/src/qwt_plot_zoneitem.cpp new file mode 100644 index 00000000..d596adaa --- /dev/null +++ b/libs/qwt/src/qwt_plot_zoneitem.cpp @@ -0,0 +1,318 @@ +/****************************************************************************** + * 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_zoneitem.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include "qwt_text.h" +#include "qwt_interval.h" + +#include + +class QwtPlotZoneItem::PrivateData +{ + public: + PrivateData() + : orientation( Qt::Vertical ) + , pen( Qt::NoPen ) + { + QColor c( Qt::darkGray ); + c.setAlpha( 100 ); + brush = QBrush( c ); + } + + Qt::Orientation orientation; + QPen pen; + QBrush brush; + QwtInterval interval; +}; + +/*! + \brief Constructor + + Initializes the zone with no pen and a semi transparent gray brush + + Sets the following item attributes: + + - QwtPlotItem::AutoScale: false + - QwtPlotItem::Legend: false + + The z value is initialized by 5 + + \sa QwtPlotItem::setItemAttribute(), QwtPlotItem::setZ() + */ +QwtPlotZoneItem::QwtPlotZoneItem() + : QwtPlotItem( QwtText( "Zone" ) ) +{ + m_data = new PrivateData; + + setItemAttribute( QwtPlotItem::AutoScale, false ); + setItemAttribute( QwtPlotItem::Legend, false ); + + setZ( 5 ); +} + +//! Destructor +QwtPlotZoneItem::~QwtPlotZoneItem() +{ + delete m_data; +} + +//! \return QwtPlotItem::Rtti_PlotZone +int QwtPlotZoneItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotZone; +} + +/*! + 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 QwtPlotZoneItem::setPen( const QColor& color, qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + \brief Assign a pen + + The pen is used to draw the border lines of the zone + + \param pen Pen + \sa pen(), setBrush() + */ +void QwtPlotZoneItem::setPen( const QPen& pen ) +{ + if ( m_data->pen != pen ) + { + m_data->pen = pen; + itemChanged(); + } +} + +/*! + \return Pen used to draw the border lines + \sa setPen(), brush() + */ +const QPen& QwtPlotZoneItem::pen() const +{ + return m_data->pen; +} + +/*! + \brief Assign a brush + + The brush is used to fill the zone + + \param brush Brush + \sa pen(), setBrush() + */ +void QwtPlotZoneItem::setBrush( const QBrush& brush ) +{ + if ( m_data->brush != brush ) + { + m_data->brush = brush; + itemChanged(); + } +} + +/*! + \return Brush used to fill the zone + \sa setPen(), brush() + */ +const QBrush& QwtPlotZoneItem::brush() const +{ + return m_data->brush; +} + +/*! + \brief Set the orientation of the zone + + A horizontal zone highlights an interval of the y axis, + a vertical zone of the x axis. It is unbounded in the + opposite direction. + + \sa orientation(), QwtPlotItem::setAxes() + */ +void QwtPlotZoneItem::setOrientation( Qt::Orientation orientation ) +{ + if ( m_data->orientation != orientation ) + { + m_data->orientation = orientation; + itemChanged(); + } +} + +/*! + \return Orientation of the zone + \sa setOrientation() + */ +Qt::Orientation QwtPlotZoneItem::orientation() const +{ + return m_data->orientation; +} + +/*! + Set the interval of the zone + + For a horizontal zone the interval is related to the y axis, + for a vertical zone it is related to the x axis. + + \param min Minimum of the interval + \param max Maximum of the interval + + \sa interval(), setOrientation() + */ +void QwtPlotZoneItem::setInterval( double min, double max ) +{ + setInterval( QwtInterval( min, max ) ); +} + +/*! + Set the interval of the zone + + For a horizontal zone the interval is related to the y axis, + for a vertical zone it is related to the x axis. + + \param interval Zone interval + + \sa interval(), setOrientation() + */ +void QwtPlotZoneItem::setInterval( const QwtInterval& interval ) +{ + if ( m_data->interval != interval ) + { + m_data->interval = interval; + itemChanged(); + } +} + +/*! + \return Zone interval + \sa setInterval(), orientation() + */ +QwtInterval QwtPlotZoneItem::interval() const +{ + return m_data->interval; +} + +/*! + Draw the zone + + \param painter Painter + \param xMap x Scale Map + \param yMap y Scale Map + \param canvasRect Contents rectangle of the canvas in painter coordinates + */ + +void QwtPlotZoneItem::draw( QPainter* painter, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QRectF& canvasRect ) const +{ + if ( !m_data->interval.isValid() ) + return; + + QPen pen = m_data->pen; + pen.setCapStyle( Qt::FlatCap ); + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + if ( m_data->orientation == Qt::Horizontal ) + { + double y1 = yMap.transform( m_data->interval.minValue() ); + double y2 = yMap.transform( m_data->interval.maxValue() ); + + if ( doAlign ) + { + y1 = qRound( y1 ); + y2 = qRound( y2 ); + } + + QRectF r( canvasRect.left(), y1, canvasRect.width(), y2 - y1 ); + r = r.normalized(); + + if ( ( m_data->brush.style() != Qt::NoBrush ) && ( y1 != y2 ) ) + { + QwtPainter::fillRect( painter, r, m_data->brush ); + } + + if ( m_data->pen.style() != Qt::NoPen ) + { + painter->setPen( m_data->pen ); + + QwtPainter::drawLine( painter, r.left(), r.top(), r.right(), r.top() ); + QwtPainter::drawLine( painter, r.left(), r.bottom(), r.right(), r.bottom() ); + } + } + else + { + double x1 = xMap.transform( m_data->interval.minValue() ); + double x2 = xMap.transform( m_data->interval.maxValue() ); + + if ( doAlign ) + { + x1 = qRound( x1 ); + x2 = qRound( x2 ); + } + + QRectF r( x1, canvasRect.top(), x2 - x1, canvasRect.height() ); + r = r.normalized(); + + if ( ( m_data->brush.style() != Qt::NoBrush ) && ( x1 != x2 ) ) + { + QwtPainter::fillRect( painter, r, m_data->brush ); + } + + if ( m_data->pen.style() != Qt::NoPen ) + { + painter->setPen( m_data->pen ); + + QwtPainter::drawLine( painter, r.left(), r.top(), r.left(), r.bottom() ); + QwtPainter::drawLine( painter, r.right(), r.top(), r.right(), r.bottom() ); + } + } +} + +/*! + The bounding rectangle is build from the interval in one direction + and something invalid for the opposite direction. + + \return An invalid rectangle with valid boundaries in one direction + */ +QRectF QwtPlotZoneItem::boundingRect() const +{ + QRectF br = QwtPlotItem::boundingRect(); + + const QwtInterval& intv = m_data->interval; + + if ( intv.isValid() ) + { + if ( m_data->orientation == Qt::Horizontal ) + { + br.setTop( intv.minValue() ); + br.setBottom( intv.maxValue() ); + } + else + { + br.setLeft( intv.minValue() ); + br.setRight( intv.maxValue() ); + } + } + + return br; +} diff --git a/libs/qwt/src/qwt_plot_zoneitem.h b/libs/qwt/src/qwt_plot_zoneitem.h new file mode 100644 index 00000000..5292f3c1 --- /dev/null +++ b/libs/qwt/src/qwt_plot_zoneitem.h @@ -0,0 +1,67 @@ +/****************************************************************************** + * 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_ZONE_ITEM_H +#define QWT_PLOT_ZONE_ITEM_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" + +#include + +class QwtInterval; +class QPen; +class QBrush; + +/*! + \brief A plot item, which displays a zone + + A horizontal zone highlights an interval of the y axis - a vertical + zone an interval of the x axis - and is unbounded in the opposite direction. + It is filled with a brush and its border lines are optionally displayed with a pen. + + \note For displaying an area that is bounded for x and y coordinates + use QwtPlotShapeItem + */ + +class QWT_EXPORT QwtPlotZoneItem : + public QwtPlotItem +{ + public: + explicit QwtPlotZoneItem(); + virtual ~QwtPlotZoneItem(); + + virtual int rtti() const QWT_OVERRIDE; + + void setOrientation( Qt::Orientation ); + Qt::Orientation orientation() const; + + void setInterval( double min, double max ); + void setInterval( const QwtInterval& ); + QwtInterval interval() 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; + + virtual void draw( QPainter*, + const QwtScaleMap&, const QwtScaleMap&, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_plot_zoomer.cpp b/libs/qwt/src/qwt_plot_zoomer.cpp new file mode 100644 index 00000000..c0d2d4f5 --- /dev/null +++ b/libs/qwt/src/qwt_plot_zoomer.cpp @@ -0,0 +1,675 @@ +/****************************************************************************** + * 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_zoomer.h" +#include "qwt_plot.h" +#include "qwt_scale_div.h" +#include "qwt_scale_map.h" +#include "qwt_interval.h" +#include "qwt_picker_machine.h" + +#include + +static QwtInterval qwtExpandedZoomInterval( double v1, double v2, + double minRange, const QwtTransform* transform ) +{ + double min = v1; + double max = v2; + + if ( max - min < minRange ) + { + min = 0.5 * ( min + max - minRange ); + max = min + minRange; + + if ( transform ) + { + // f.e the logarithmic scale doesn't allow values + // outside [QwtLogTransform::LogMin/QwtLogTransform::LogMax] + + double minBounded = transform->bounded( min ); + double maxBounded = transform->bounded( max ); + + if ( minBounded != min ) + { + maxBounded = transform->bounded( minBounded + minRange ); + } + else if ( maxBounded != max ) + { + minBounded = transform->bounded( maxBounded - minRange ); + } + + min = minBounded; + max = maxBounded; + } + } + + return QwtInterval( min, max ); +} + +static QRectF qwtExpandedZoomRect( const QRectF& zoomRect, const QSizeF& minSize, + const QwtTransform* transformX, const QwtTransform* transformY ) +{ + QRectF r = zoomRect; + + if ( minSize.width() > r.width() ) + { + const QwtInterval intv = qwtExpandedZoomInterval( + r.left(), r.right(), minSize.width(), transformX ); + + r.setLeft( intv.minValue() ); + r.setRight( intv.maxValue() ); + } + + if ( minSize.height() > r.height() ) + { + const QwtInterval intv = qwtExpandedZoomInterval( + zoomRect.top(), zoomRect.bottom(), minSize.height(), transformY ); + + r.setTop( intv.minValue() ); + r.setBottom( intv.maxValue() ); + } + + return r; +} + +class QwtPlotZoomer::PrivateData +{ + public: + uint zoomRectIndex; + QStack< QRectF > zoomStack; + + int maxStackDepth; +}; + +/*! + \brief Create a zoomer for a plot canvas. + + The zoomer is set to those x- and y-axis of the parent plot of the + canvas that are enabled. If both or no x-axis are enabled, the picker + is set to QwtAxis::XBottom. If both or no y-axis are + enabled, it is set to QwtAxis::YLeft. + + The zoomer is initialized with a QwtPickerDragRectMachine, + the tracker mode is set to QwtPicker::ActiveOnly and the rubber band + is set to QwtPicker::RectRubberBand + + \param canvas Plot canvas to observe, also the parent object + \param doReplot Call QwtPlot::replot() for the attached plot before initializing + the zoomer with its scales. This might be necessary, + when the plot is in a state with pending scale changes. + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase() + */ +QwtPlotZoomer::QwtPlotZoomer( QWidget* canvas, bool doReplot ) + : QwtPlotPicker( canvas ) +{ + if ( canvas ) + init( doReplot ); +} + +/*! + \brief Create a zoomer for a plot canvas. + + The zoomer is initialized with a QwtPickerDragRectMachine, + the tracker mode is set to QwtPicker::ActiveOnly and the rubber band + is set to QwtPicker::RectRubberBand + + \param xAxisId X axis of the zoomer + \param yAxisId Y axis of the zoomer + \param canvas Plot canvas to observe, also the parent object + \param doReplot Call QwtPlot::replot() for the attached plot before initializing + the zoomer with its scales. This might be necessary, + when the plot is in a state with pending scale changes. + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase() + */ + +QwtPlotZoomer::QwtPlotZoomer( QwtAxisId xAxisId, QwtAxisId yAxisId, + QWidget* canvas, bool doReplot ) + : QwtPlotPicker( xAxisId, yAxisId, canvas ) +{ + if ( canvas ) + init( doReplot ); +} + +//! Init the zoomer, used by the constructors +void QwtPlotZoomer::init( bool doReplot ) +{ + m_data = new PrivateData; + + m_data->maxStackDepth = -1; + + setTrackerMode( ActiveOnly ); + setRubberBand( RectRubberBand ); + setStateMachine( new QwtPickerDragRectMachine() ); + + if ( doReplot && plot() ) + plot()->replot(); + + setZoomBase( scaleRect() ); +} + +QwtPlotZoomer::~QwtPlotZoomer() +{ + delete m_data; +} + +/*! + \brief Limit the number of recursive zoom operations to depth. + + A value of -1 set the depth to unlimited, 0 disables zooming. + If the current zoom rectangle is below depth, the plot is unzoomed. + + \param depth Maximum for the stack depth + \sa maxStackDepth() + \note depth doesn't include the zoom base, so zoomStack().count() might be + maxStackDepth() + 1. + */ +void QwtPlotZoomer::setMaxStackDepth( int depth ) +{ + m_data->maxStackDepth = depth; + + if ( depth >= 0 ) + { + // unzoom if the current depth is below m_data->maxStackDepth + + const int zoomOut = + m_data->zoomStack.count() - 1 - depth; // -1 for the zoom base + + if ( zoomOut > 0 ) + { + zoom( -zoomOut ); + for ( int i = m_data->zoomStack.count() - 1; + i > int( m_data->zoomRectIndex ); i-- ) + { + ( void )m_data->zoomStack.pop(); // remove trailing rects + } + } + } +} + +/*! + \return Maximal depth of the zoom stack. + \sa setMaxStackDepth() + */ +int QwtPlotZoomer::maxStackDepth() const +{ + return m_data->maxStackDepth; +} + +/*! + \return The zoom stack. zoomStack()[0] is the zoom base, + zoomStack()[1] the first zoomed rectangle. + + \sa setZoomStack(), zoomRectIndex() + */ +const QStack< QRectF >& QwtPlotZoomer::zoomStack() const +{ + return m_data->zoomStack; +} + +/*! + \return Initial rectangle of the zoomer + \sa setZoomBase(), zoomRect() + */ +QRectF QwtPlotZoomer::zoomBase() const +{ + return m_data->zoomStack[0]; +} + +/*! + Reinitialized the zoom stack with scaleRect() as base. + + \param doReplot Call QwtPlot::replot() for the attached plot before initializing + the zoomer with its scales. This might be necessary, + when the plot is in a state with pending scale changes. + + \sa zoomBase(), scaleRect() QwtPlot::autoReplot(), QwtPlot::replot(). + */ +void QwtPlotZoomer::setZoomBase( bool doReplot ) +{ + QwtPlot* plt = plot(); + if ( plt == NULL ) + return; + + if ( doReplot ) + plt->replot(); + + m_data->zoomStack.clear(); + m_data->zoomStack.push( scaleRect() ); + m_data->zoomRectIndex = 0; + + rescale(); +} + +/*! + \brief Set the initial size of the zoomer. + + base is united with the current scaleRect() and the zoom stack is + reinitialized with it as zoom base. plot is zoomed to scaleRect(). + + \param base Zoom base + + \sa zoomBase(), scaleRect() + */ +void QwtPlotZoomer::setZoomBase( const QRectF& base ) +{ + const QwtPlot* plt = plot(); + if ( !plt ) + return; + + const QRectF sRect = scaleRect(); + const QRectF bRect = base | sRect; + + m_data->zoomStack.clear(); + m_data->zoomStack.push( bRect ); + m_data->zoomRectIndex = 0; + + if ( base != sRect ) + { + m_data->zoomStack.push( sRect ); + m_data->zoomRectIndex++; + } + + rescale(); +} + +/*! + \return Rectangle at the current position on the zoom stack. + \sa zoomRectIndex(), scaleRect(). + */ +QRectF QwtPlotZoomer::zoomRect() const +{ + return m_data->zoomStack[m_data->zoomRectIndex]; +} + +/*! + \return Index of current position of zoom stack. + */ +uint QwtPlotZoomer::zoomRectIndex() const +{ + return m_data->zoomRectIndex; +} + +/*! + \brief Zoom in + + Clears all rectangles above the current position of the + zoom stack and pushes the normalized rectangle on it. + + \note If the maximal stack depth is reached, zoom is ignored. + \note The zoomed signal is emitted. + */ + +void QwtPlotZoomer::zoom( const QRectF& rect ) +{ + if ( m_data->maxStackDepth >= 0 && + int( m_data->zoomRectIndex ) >= m_data->maxStackDepth ) + { + return; + } + + const QRectF zoomRect = rect.normalized(); + if ( zoomRect != m_data->zoomStack[m_data->zoomRectIndex] ) + { + for ( uint i = m_data->zoomStack.count() - 1; + i > m_data->zoomRectIndex; i-- ) + { + ( void )m_data->zoomStack.pop(); + } + + m_data->zoomStack.push( zoomRect ); + m_data->zoomRectIndex++; + + rescale(); + + Q_EMIT zoomed( zoomRect ); + } +} + +/*! + \brief Zoom in or out + + Activate a rectangle on the zoom stack with an offset relative + to the current position. Negative values of offset will zoom out, + positive zoom in. A value of 0 zooms out to the zoom base. + + \param offset Offset relative to the current position of the zoom stack. + \note The zoomed signal is emitted. + \sa zoomRectIndex() + */ +void QwtPlotZoomer::zoom( int offset ) +{ + int newIndex; + + if ( offset == 0 ) + { + newIndex = 0; + } + else + { + newIndex = m_data->zoomRectIndex + offset; + newIndex = qBound( 0, newIndex, m_data->zoomStack.count() - 1 ); + } + + if ( newIndex != static_cast< int >( m_data->zoomRectIndex ) ) + { + m_data->zoomRectIndex = newIndex; + rescale(); + Q_EMIT zoomed( zoomRect() ); + } +} + +/*! + \brief Assign a zoom stack + + In combination with other types of navigation it might be useful to + modify to manipulate the complete zoom stack. + + \param zoomStack New zoom stack + \param zoomRectIndex Index of the current position of zoom stack. + In case of -1 the current position is at the top + of the stack. + + \note The zoomed signal might be emitted. + \sa zoomStack(), zoomRectIndex() + */ +void QwtPlotZoomer::setZoomStack( + const QStack< QRectF >& zoomStack, int zoomRectIndex ) +{ + if ( zoomStack.isEmpty() ) + return; + + if ( m_data->maxStackDepth >= 0 && + zoomStack.count() > m_data->maxStackDepth ) + { + return; + } + + if ( zoomRectIndex < 0 || zoomRectIndex > zoomStack.count() ) + zoomRectIndex = zoomStack.count() - 1; + + const bool doRescale = zoomStack[zoomRectIndex] != zoomRect(); + + m_data->zoomStack = zoomStack; + m_data->zoomRectIndex = uint( zoomRectIndex ); + + if ( doRescale ) + { + rescale(); + Q_EMIT zoomed( zoomRect() ); + } +} + +/*! + Adjust the observed plot to zoomRect() + + \note Initiates QwtPlot::replot() + */ + +void QwtPlotZoomer::rescale() +{ + QwtPlot* plt = plot(); + if ( !plt ) + return; + + const QRectF& rect = m_data->zoomStack[m_data->zoomRectIndex]; + if ( rect != scaleRect() ) + { + const bool doReplot = plt->autoReplot(); + plt->setAutoReplot( false ); + + double x1 = rect.left(); + double x2 = rect.right(); + if ( !plt->axisScaleDiv( xAxis() ).isIncreasing() ) + qSwap( x1, x2 ); + + plt->setAxisScale( xAxis(), x1, x2 ); + + double y1 = rect.top(); + double y2 = rect.bottom(); + if ( !plt->axisScaleDiv( yAxis() ).isIncreasing() ) + qSwap( y1, y2 ); + + plt->setAxisScale( yAxis(), y1, y2 ); + + plt->setAutoReplot( doReplot ); + + plt->replot(); + } +} + +/*! + Reinitialize the axes, and set the zoom base to their scales. + + \param xAxisId X axis + \param yAxisId Y axis + */ + +void QwtPlotZoomer::setAxes( QwtAxisId xAxisId, QwtAxisId yAxisId ) +{ + if ( xAxisId != QwtPlotPicker::xAxis() || yAxisId != QwtPlotPicker::yAxis() ) + { + QwtPlotPicker::setAxes( xAxisId, yAxisId ); + setZoomBase( scaleRect() ); + } +} + +/*! + Qt::MidButton zooms out one position on the zoom stack, + Qt::RightButton to the zoom base. + + Changes the current position on the stack, but doesn't pop + any rectangle. + + \note The mouse events can be changed, using + QwtEventPattern::setMousePattern: 2, 1 + */ +void QwtPlotZoomer::widgetMouseReleaseEvent( QMouseEvent* me ) +{ + if ( mouseMatch( MouseSelect2, me ) ) + zoom( 0 ); + else if ( mouseMatch( MouseSelect3, me ) ) + zoom( -1 ); + else if ( mouseMatch( MouseSelect6, me ) ) + zoom( +1 ); + else + QwtPlotPicker::widgetMouseReleaseEvent( me ); +} + +/*! + Qt::Key_Plus zooms in, Qt::Key_Minus zooms out one position on the + zoom stack, Qt::Key_Escape zooms out to the zoom base. + + Changes the current position on the stack, but doesn't pop + any rectangle. + + \note The keys codes can be changed, using + QwtEventPattern::setKeyPattern: 3, 4, 5 + */ + +void QwtPlotZoomer::widgetKeyPressEvent( QKeyEvent* ke ) +{ + if ( !isActive() ) + { + if ( keyMatch( KeyUndo, ke ) ) + zoom( -1 ); + else if ( keyMatch( KeyRedo, ke ) ) + zoom( +1 ); + else if ( keyMatch( KeyHome, ke ) ) + zoom( 0 ); + } + + QwtPlotPicker::widgetKeyPressEvent( ke ); +} + +/*! + Move the current zoom rectangle. + + \param dx X offset + \param dy Y offset + + \note The changed rectangle is limited by the zoom base + */ +void QwtPlotZoomer::moveBy( double dx, double dy ) +{ + const QRectF& rect = m_data->zoomStack[m_data->zoomRectIndex]; + moveTo( QPointF( rect.left() + dx, rect.top() + dy ) ); +} + +/*! + Move the the current zoom rectangle. + + \param pos New position + + \sa QRectF::moveTo() + \note The changed rectangle is limited by the zoom base + */ +void QwtPlotZoomer::moveTo( const QPointF& pos ) +{ + double x = pos.x(); + double y = pos.y(); + + if ( x < zoomBase().left() ) + x = zoomBase().left(); + if ( x > zoomBase().right() - zoomRect().width() ) + x = zoomBase().right() - zoomRect().width(); + + if ( y < zoomBase().top() ) + y = zoomBase().top(); + if ( y > zoomBase().bottom() - zoomRect().height() ) + y = zoomBase().bottom() - zoomRect().height(); + + if ( x != zoomRect().left() || y != zoomRect().top() ) + { + m_data->zoomStack[m_data->zoomRectIndex].moveTo( x, y ); + rescale(); + } +} + +/*! + \brief Check and correct a selected rectangle + + Reject rectangles with a height or width < 2, otherwise + expand the selected rectangle to a minimum size of 11x11 + and accept it. + + \return true If the rectangle is accepted, or has been changed + to an accepted one. + */ + +bool QwtPlotZoomer::accept( QPolygon& pa ) const +{ + if ( pa.count() < 2 ) + return false; + + QRect rect = QRect( pa.first(), pa.last() ); + rect = rect.normalized(); + + const int minSize = 2; + if ( rect.width() < minSize && rect.height() < minSize ) + return false; + + const int minZoomSize = 11; + + const QPoint center = rect.center(); + rect.setSize( rect.size().expandedTo( QSize( minZoomSize, minZoomSize ) ) ); + rect.moveCenter( center ); + + pa.resize( 2 ); + pa[0] = rect.topLeft(); + pa[1] = rect.bottomRight(); + + return true; +} + +/*! + \brief Limit zooming by a minimum rectangle + + \return zoomBase().width() / 10e4, zoomBase().height() / 10e4 + */ +QSizeF QwtPlotZoomer::minZoomSize() const +{ + return QSizeF( m_data->zoomStack[0].width() / 10e4, + m_data->zoomStack[0].height() / 10e4 ); +} + +/*! + Rejects selections, when the stack depth is too deep, or + the zoomed rectangle is minZoomSize(). + + \sa minZoomSize(), maxStackDepth() + */ +void QwtPlotZoomer::begin() +{ + if ( m_data->maxStackDepth >= 0 ) + { + if ( m_data->zoomRectIndex >= uint( m_data->maxStackDepth ) ) + return; + } + + const QSizeF minSize = minZoomSize(); + if ( minSize.isValid() ) + { + const QSizeF sz = + m_data->zoomStack[m_data->zoomRectIndex].size() * 0.9999; + + if ( minSize.width() >= sz.width() && + minSize.height() >= sz.height() ) + { + return; + } + } + + QwtPlotPicker::begin(); +} + +/*! + Expand the selected rectangle to minZoomSize() and zoom in + if accepted. + + \param ok If true, complete the selection and emit selected signals + otherwise discard the selection. + + \sa accept(), minZoomSize() + \return True if the selection has been accepted, false otherwise + */ +bool QwtPlotZoomer::end( bool ok ) +{ + ok = QwtPlotPicker::end( ok ); + if ( !ok ) + return false; + + QwtPlot* plot = QwtPlotZoomer::plot(); + if ( !plot ) + return false; + + const QPolygon& pa = selection(); + if ( pa.count() < 2 ) + return false; + + QRect rect = QRect( pa.first(), pa.last() ); + rect = rect.normalized(); + + const QwtScaleMap xMap = plot->canvasMap( xAxis() ); + const QwtScaleMap yMap = plot->canvasMap( yAxis() ); + + QRectF zoomRect = QwtScaleMap::invTransform( xMap, yMap, rect ).normalized(); + + zoomRect = qwtExpandedZoomRect( zoomRect, minZoomSize(), + xMap.transformation(), yMap.transformation() ); + + zoom( zoomRect ); + + return true; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_plot_zoomer.cpp" +#endif diff --git a/libs/qwt/src/qwt_plot_zoomer.h b/libs/qwt/src/qwt_plot_zoomer.h new file mode 100644 index 00000000..eac7ce58 --- /dev/null +++ b/libs/qwt/src/qwt_plot_zoomer.h @@ -0,0 +1,142 @@ +/****************************************************************************** + * 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_ZOOMER_H +#define QWT_PLOT_ZOOMER_H + +#include "qwt_global.h" +#include "qwt_plot_picker.h" + +class QSizeF; +template< typename T > class QStack; + +/*! + \brief QwtPlotZoomer provides stacked zooming for a plot widget + + QwtPlotZoomer selects rectangles from user inputs ( mouse or keyboard ) + translates them into plot coordinates and adjusts the axes to them. + The selection is supported by a rubber band and optionally by displaying + the coordinates of the current mouse position. + + Zooming can be repeated as often as possible, limited only by + maxStackDepth() or minZoomSize(). Each rectangle is pushed on a stack. + + The default setting how to select rectangles is + a QwtPickerDragRectMachine with the following bindings: + + - QwtEventPattern::MouseSelect1\n + The first point of the zoom rectangle is selected by a mouse press, + the second point from the position, where the mouse is released. + + - QwtEventPattern::KeySelect1\n + The first key press selects the first, the second key press + selects the second point. + + - QwtEventPattern::KeyAbort\n + Discard the selection in the state, where the first point + is selected. + + To traverse the zoom stack the following bindings are used: + + - QwtEventPattern::MouseSelect3, QwtEventPattern::KeyUndo\n + Zoom out one position on the zoom stack + + - QwtEventPattern::MouseSelect6, QwtEventPattern::KeyRedo\n + Zoom in one position on the zoom stack + + - QwtEventPattern::MouseSelect2, QwtEventPattern::KeyHome\n + Zoom to the zoom base + + The setKeyPattern() and setMousePattern() functions can be used + to configure the zoomer actions. The following example + shows, how to configure the 'I' and 'O' keys for zooming in and out + one position on the zoom stack. The "Home" key is used to + "unzoom" the plot. + + \code + zoomer = new QwtPlotZoomer( plot ); + zoomer->setKeyPattern( QwtEventPattern::KeyRedo, Qt::Key_I, Qt::ShiftModifier ); + zoomer->setKeyPattern( QwtEventPattern::KeyUndo, Qt::Key_O, Qt::ShiftModifier ); + zoomer->setKeyPattern( QwtEventPattern::KeyHome, Qt::Key_Home ); + \endcode + + QwtPlotZoomer is tailored for plots with one x and y axis, but it is + allowed to attach a second QwtPlotZoomer ( without rubber band and tracker ) + for the other axes. + + \note The realtime example includes an derived zoomer class that adds + scrollbars to the plot canvas. + + \sa QwtPlotPanner, QwtPlotMagnifier + */ + +class QWT_EXPORT QwtPlotZoomer : public QwtPlotPicker +{ + Q_OBJECT + public: + explicit QwtPlotZoomer( QWidget*, bool doReplot = true ); + explicit QwtPlotZoomer( QwtAxisId xAxis, QwtAxisId yAxis, + QWidget*, bool doReplot = true ); + + virtual ~QwtPlotZoomer(); + + virtual void setZoomBase( bool doReplot = true ); + virtual void setZoomBase( const QRectF& ); + + QRectF zoomBase() const; + QRectF zoomRect() const; + + virtual void setAxes( QwtAxisId xAxis, QwtAxisId yAxis ) QWT_OVERRIDE; + + void setMaxStackDepth( int ); + int maxStackDepth() const; + + const QStack< QRectF >& zoomStack() const; + void setZoomStack( const QStack< QRectF >&, + int zoomRectIndex = -1 ); + + uint zoomRectIndex() const; + + public Q_SLOTS: + void moveBy( double dx, double dy ); + virtual void moveTo( const QPointF& ); + + virtual void zoom( const QRectF& ); + virtual void zoom( int offset ); + + Q_SIGNALS: + /*! + A signal emitting the zoomRect(), when the plot has been + zoomed in or out. + + \param rect Current zoom rectangle. + */ + + void zoomed( const QRectF& rect ); + + protected: + virtual void rescale(); + + virtual QSizeF minZoomSize() const; + + virtual void widgetMouseReleaseEvent( QMouseEvent* ) QWT_OVERRIDE; + virtual void widgetKeyPressEvent( QKeyEvent* ) QWT_OVERRIDE; + + virtual void begin() QWT_OVERRIDE; + virtual bool end( bool ok = true ) QWT_OVERRIDE; + virtual bool accept( QPolygon& ) const QWT_OVERRIDE; + + private: + void init( bool doReplot ); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_point_3d.cpp b/libs/qwt/src/qwt_point_3d.cpp new file mode 100644 index 00000000..b722d5af --- /dev/null +++ b/libs/qwt/src/qwt_point_3d.cpp @@ -0,0 +1,48 @@ +/****************************************************************************** + * 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_point_3d.h" + +#if QT_VERSION >= 0x050200 + +static QwtPoint3D qwtPointToPoint3D( const QPointF& point ) +{ + return QwtPoint3D( point ); +} + +#endif + +namespace +{ + static const struct RegisterQwtPoint3D + { + inline RegisterQwtPoint3D() + { + qRegisterMetaType< QwtPoint3D >(); + +#if QT_VERSION >= 0x050200 + QMetaType::registerConverter< QPointF, QwtPoint3D >( qwtPointToPoint3D ); +#endif + } + } qwtRegisterQwtPoint3D; +} + +#ifndef QT_NO_DEBUG_STREAM + +#include + +QDebug operator<<( QDebug debug, const QwtPoint3D& point ) +{ + debug.nospace() << "QwtPoint3D(" << point.x() + << "," << point.y() << "," << point.z() << ")"; + return debug.space(); +} + +#endif + diff --git a/libs/qwt/src/qwt_point_3d.h b/libs/qwt/src/qwt_point_3d.h new file mode 100644 index 00000000..4ce57445 --- /dev/null +++ b/libs/qwt/src/qwt_point_3d.h @@ -0,0 +1,176 @@ +/****************************************************************************** + * 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_POINT_3D_H +#define QWT_POINT_3D_H + +#include "qwt_global.h" +#include +#include + +/*! + \brief QwtPoint3D class defines a 3D point in double coordinates + */ + +class QWT_EXPORT QwtPoint3D +{ + public: + QwtPoint3D(); + QwtPoint3D( double x, double y, double z ); + QwtPoint3D( const QPointF& ); + + bool isNull() const; + + double x() const; + double y() const; + double z() const; + + double& rx(); + double& ry(); + double& rz(); + + void setX( double x ); + void setY( double y ); + void setZ( double y ); + + QPointF toPoint() const; + + bool operator==( const QwtPoint3D& ) const; + bool operator!=( const QwtPoint3D& ) const; + + private: + double m_x; + double m_y; + double m_z; +}; + +Q_DECLARE_TYPEINFO( QwtPoint3D, Q_MOVABLE_TYPE ); +Q_DECLARE_METATYPE( QwtPoint3D ); + +#ifndef QT_NO_DEBUG_STREAM +QWT_EXPORT QDebug operator<<( QDebug, const QwtPoint3D& ); +#endif + +/*! + Constructs a null point. + \sa isNull() + */ +inline QwtPoint3D::QwtPoint3D() + : m_x( 0.0 ) + , m_y( 0.0 ) + , m_z( 0.0 ) +{ +} + +//! Constructs a point with coordinates specified by x, y and z. +inline QwtPoint3D::QwtPoint3D( double x, double y, double z = 0.0 ) + : m_x( x ) + , m_y( y ) + , m_z( z ) +{ +} + +/*! + Constructs a point with x and y coordinates from a 2D point, + and a z coordinate of 0. + */ +inline QwtPoint3D::QwtPoint3D( const QPointF& other ) + : m_x( other.x() ) + , m_y( other.y() ) + , m_z( 0.0 ) +{ +} + +/*! + \return True if the point is null; otherwise returns false. + + A point is considered to be null if x, y and z-coordinates + are equal to zero. + */ +inline bool QwtPoint3D::isNull() const +{ + return m_x == 0.0 && m_y == 0.0 && m_z == 0.0; +} + +//! \return The x-coordinate of the point. +inline double QwtPoint3D::x() const +{ + return m_x; +} + +//! \return The y-coordinate of the point. +inline double QwtPoint3D::y() const +{ + return m_y; +} + +//! \return The z-coordinate of the point. +inline double QwtPoint3D::z() const +{ + return m_z; +} + +//! \return A reference to the x-coordinate of the point. +inline double& QwtPoint3D::rx() +{ + return m_x; +} + +//! \return A reference to the y-coordinate of the point. +inline double& QwtPoint3D::ry() +{ + return m_y; +} + +//! \return A reference to the z-coordinate of the point. +inline double& QwtPoint3D::rz() +{ + return m_z; +} + +//! Sets the x-coordinate of the point to the value specified by x. +inline void QwtPoint3D::setX( double x ) +{ + m_x = x; +} + +//! Sets the y-coordinate of the point to the value specified by y. +inline void QwtPoint3D::setY( double y ) +{ + m_y = y; +} + +//! Sets the z-coordinate of the point to the value specified by z. +inline void QwtPoint3D::setZ( double z ) +{ + m_z = z; +} + +/*! + \return 2D point, where the z coordinate is dropped. + */ +inline QPointF QwtPoint3D::toPoint() const +{ + return QPointF( m_x, m_y ); +} + +//! \return True, if this point and other are equal; otherwise returns false. +inline bool QwtPoint3D::operator==( const QwtPoint3D& other ) const +{ + return ( m_x == other.m_x ) && ( m_y == other.m_y ) && ( m_z == other.m_z ); +} + +//! \return True if this rect and other are different; otherwise returns false. +inline bool QwtPoint3D::operator!=( const QwtPoint3D& other ) const +{ + return !operator==( other ); +} + +#endif diff --git a/libs/qwt/src/qwt_point_data.cpp b/libs/qwt/src/qwt_point_data.cpp new file mode 100644 index 00000000..f8925a09 --- /dev/null +++ b/libs/qwt/src/qwt_point_data.cpp @@ -0,0 +1,161 @@ +/****************************************************************************** + * 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_point_data.h" + +/*! + Constructor + + \param size Number of points + \param interval Bounding interval for the points + + \sa setInterval(), setSize() + */ +QwtSyntheticPointData::QwtSyntheticPointData( + size_t size, const QwtInterval& interval ) + : m_size( size ) + , m_interval( interval ) +{ +} + +/*! + Change the number of points + + \param size Number of points + \sa size(), setInterval() + */ +void QwtSyntheticPointData::setSize( size_t size ) +{ + m_size = size; +} + +/*! + \return Number of points + \sa setSize(), interval() + */ +size_t QwtSyntheticPointData::size() const +{ + return m_size; +} + +/*! + Set the bounding interval + + \param interval Interval + \sa interval(), setSize() + */ +void QwtSyntheticPointData::setInterval( const QwtInterval& interval ) +{ + m_interval = interval.normalized(); +} + +/*! + \return Bounding interval + \sa setInterval(), size() + */ +QwtInterval QwtSyntheticPointData::interval() const +{ + return m_interval; +} + +/*! + Set a the "rectangle of interest" + + QwtPlotSeriesItem defines the current area of the plot canvas + as "rect of interest" ( QwtPlotSeriesItem::updateScaleDiv() ). + + If interval().isValid() == false the x values are calculated + in the interval rect.left() -> rect.right(). + + \sa rectOfInterest() + */ +void QwtSyntheticPointData::setRectOfInterest( const QRectF& rect ) +{ + m_rectOfInterest = rect; + m_intervalOfInterest = QwtInterval( + rect.left(), rect.right() ).normalized(); +} + +/*! + \return "rectangle of interest" + \sa setRectOfInterest() + */ +QRectF QwtSyntheticPointData::rectOfInterest() const +{ + return m_rectOfInterest; +} + +/*! + \brief Calculate the bounding rectangle + + This implementation iterates over all points, what could often + be implemented much faster using the characteristics of the series. + When there are many points it is recommended to overload and + reimplement this method using the characteristics of the series + ( if possible ). + + \return Bounding rectangle + */ +QRectF QwtSyntheticPointData::boundingRect() const +{ + if ( m_size == 0 || + !( m_interval.isValid() || m_intervalOfInterest.isValid() ) ) + { + return QRectF( 1.0, 1.0, -2.0, -2.0 ); // something invalid + } + + return qwtBoundingRect( *this ); +} + +/*! + Calculate the point from an index + + \param index Index + \return QPointF(x(index), y(x(index))); + + \warning For invalid indices ( index < 0 || index >= size() ) + (0, 0) is returned. + */ +QPointF QwtSyntheticPointData::sample( size_t index ) const +{ + if ( index >= m_size ) + return QPointF( 0, 0 ); + + const double xValue = x( index ); + const double yValue = y( xValue ); + + return QPointF( xValue, yValue ); +} + +/*! + Calculate a x-value from an index + + x values are calculated by dividing an interval into + equidistant steps. If !interval().isValid() the + interval is calculated from the "rectangle of interest". + + \param index Index of the requested point + \return Calculated x coordinate + + \sa interval(), rectOfInterest(), y() + */ +double QwtSyntheticPointData::x( uint index ) const +{ + const QwtInterval& interval = m_interval.isValid() ? + m_interval : m_intervalOfInterest; + + if ( !interval.isValid() ) + return 0.0; + + if ( m_size <= 1 ) + return interval.minValue(); + + const double dx = interval.width() / ( m_size - 1 ); + return interval.minValue() + index * dx; +} diff --git a/libs/qwt/src/qwt_point_data.h b/libs/qwt/src/qwt_point_data.h new file mode 100644 index 00000000..cfda18db --- /dev/null +++ b/libs/qwt/src/qwt_point_data.h @@ -0,0 +1,412 @@ +/****************************************************************************** + * 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_POINT_DATA_H +#define QWT_POINT_DATA_H + +#include "qwt_global.h" +#include "qwt_series_data.h" + +#include + +/*! + \brief Interface for iterating over two QVector objects. + */ +template< typename T > +class QwtPointArrayData : public QwtPointSeriesData +{ + public: + QwtPointArrayData( const QVector< T >& x, const QVector< T >& y ); + QwtPointArrayData( const T* x, const T* y, size_t size ); + + virtual size_t size() const QWT_OVERRIDE; + virtual QPointF sample( size_t index ) const QWT_OVERRIDE; + + const QVector< T >& xData() const; + const QVector< T >& yData() const; + + private: + QVector< T > m_x; + QVector< T > m_y; +}; + +/*! + \brief Data class containing two pointers to memory blocks of T. + */ +template< typename T > +class QwtCPointerData : public QwtPointSeriesData +{ + public: + QwtCPointerData( const T* x, const T* y, size_t size ); + + virtual size_t size() const QWT_OVERRIDE; + virtual QPointF sample( size_t index ) const QWT_OVERRIDE; + + const T* xData() const; + const T* yData() const; + + private: + const T* m_x; + const T* m_y; + size_t m_size; +}; + +/*! + \brief Interface for iterating over a QVector. + + The memory contains the y coordinates, while the index is + interpreted as x coordinate. + */ +template< typename T > +class QwtValuePointData : public QwtPointSeriesData +{ + public: + QwtValuePointData( const QVector< T >& y ); + QwtValuePointData( const T* y, size_t size ); + + virtual size_t size() const QWT_OVERRIDE; + virtual QPointF sample( size_t index ) const QWT_OVERRIDE; + + const QVector< T >& yData() const; + + private: + QVector< T > m_y; +}; + +/*! + \brief Data class containing a pointer to memory of y coordinates + + The memory contains the y coordinates, while the index is + interpreted as x coordinate. + */ +template< typename T > +class QwtCPointerValueData : public QwtPointSeriesData +{ + public: + QwtCPointerValueData( const T* y, size_t size ); + + virtual size_t size() const QWT_OVERRIDE; + virtual QPointF sample( size_t index ) const QWT_OVERRIDE; + + const T* yData() const; + + private: + const T* m_y; + size_t m_size; +}; + +/*! + \brief Synthetic point data + + QwtSyntheticPointData provides a fixed number of points for an interval. + The points are calculated in equidistant steps in x-direction. + + If the interval is invalid, the points are calculated for + the "rectangle of interest", what normally is the displayed area on the + plot canvas. In this mode you get different levels of detail, when + zooming in/out. + + \par Example + + The following example shows how to implement a sinus curve. + + \code + #include + #include + #include + #include + #include + + class SinusData: public QwtSyntheticPointData + { + public: + SinusData(): + QwtSyntheticPointData( 100 ) + { + } + + virtual double y( double x ) const + { + return qSin( x ); + } + }; + + int main(int argc, char **argv) + { + QApplication a( argc, argv ); + + QwtPlot plot; + plot.setAxisScale( QwtAxis::XBottom, 0.0, 10.0 ); + plot.setAxisScale( QwtAxis::YLeft, -1.0, 1.0 ); + + QwtPlotCurve *curve = new QwtPlotCurve( "y = sin(x)" ); + curve->setData( new SinusData() ); + curve->attach( &plot ); + + plot.show(); + return a.exec(); + } + \endcode + */ +class QWT_EXPORT QwtSyntheticPointData : public QwtPointSeriesData +{ + public: + QwtSyntheticPointData( size_t size, + const QwtInterval& = QwtInterval() ); + + void setSize( size_t size ); + virtual size_t size() const QWT_OVERRIDE; + + void setInterval( const QwtInterval& ); + QwtInterval interval() const; + + virtual QRectF boundingRect() const QWT_OVERRIDE; + virtual QPointF sample( size_t index ) const QWT_OVERRIDE; + + /*! + Calculate a y value for a x value + + \param x x value + \return Corresponding y value + */ + virtual double y( double x ) const = 0; + virtual double x( uint index ) const; + + virtual void setRectOfInterest( const QRectF& ) QWT_OVERRIDE; + QRectF rectOfInterest() const; + + private: + size_t m_size; + QwtInterval m_interval; + QRectF m_rectOfInterest; + QwtInterval m_intervalOfInterest; +}; + +/*! + Constructor + + \param x Array of x values + \param y Array of y values + + \sa QwtPlotCurve::setData(), QwtPlotCurve::setSamples() + */ +template< typename T > +QwtPointArrayData< T >::QwtPointArrayData( + const QVector< T >& x, const QVector< T >& y ) + : m_x( x ) + , m_y( y ) +{ +} + +/*! + Constructor + + \param x Array of x values + \param y Array of y values + \param size Size of the x and y arrays + \sa QwtPlotCurve::setData(), QwtPlotCurve::setSamples() + */ +template< typename T > +QwtPointArrayData< T >::QwtPointArrayData( const T* x, const T* y, size_t size ) +{ + m_x.resize( size ); + std::memcpy( m_x.data(), x, size * sizeof( T ) ); + + m_y.resize( size ); + std::memcpy( m_y.data(), y, size * sizeof( T ) ); +} + +//! \return Size of the data set +template< typename T > +size_t QwtPointArrayData< T >::size() const +{ + return qMin( m_x.size(), m_y.size() ); +} + +/*! + Return the sample at position i + + \param index Index + \return Sample at position i + */ +template< typename T > +QPointF QwtPointArrayData< T >::sample( size_t index ) const +{ + return QPointF( m_x[int( index )], m_y[int( index )] ); +} + +//! \return Array of the x-values +template< typename T > +const QVector< T >& QwtPointArrayData< T >::xData() const +{ + return m_x; +} + +//! \return Array of the y-values +template< typename T > +const QVector< T >& QwtPointArrayData< T >::yData() const +{ + return m_y; +} + +/*! + Constructor + + \param y Array of y values + + \sa QwtPlotCurve::setData(), QwtPlotCurve::setSamples() + */ +template< typename T > +QwtValuePointData< T >::QwtValuePointData( const QVector< T >& y ) + : m_y( y ) +{ +} + +/*! + Constructor + + \param y Array of y values + \param size Size of the x and y arrays + \sa QwtPlotCurve::setData(), QwtPlotCurve::setSamples() + */ +template< typename T > +QwtValuePointData< T >::QwtValuePointData( const T* y, size_t size ) +{ + m_y.resize( size ); + std::memcpy( m_y.data(), y, size * sizeof( T ) ); +} + +//! \return Size of the data set +template< typename T > +size_t QwtValuePointData< T >::size() const +{ + return m_y.size(); +} + +/*! + Return the sample at position i + + \param index Index + \return Sample at position i + */ +template< typename T > +QPointF QwtValuePointData< T >::sample( size_t index ) const +{ + return QPointF( index, m_y[int( index )] ); +} + +//! \return Array of the y-values +template< typename T > +const QVector< T >& QwtValuePointData< T >::yData() const +{ + return m_y; +} + +/*! + Constructor + + \param x Array of x values + \param y Array of y values + \param size Size of the x and y arrays + + \warning The programmer must assure that the memory blocks referenced + by the pointers remain valid during the lifetime of the + QwtPlotCPointer object. + + \sa QwtPlotCurve::setData(), QwtPlotCurve::setRawSamples() + */ + +template< typename T > +QwtCPointerData< T >::QwtCPointerData( const T* x, const T* y, size_t size ) + : m_x( x ) + , m_y( y ) + , m_size( size ) +{ +} + +//! \return Size of the data set +template< typename T > +size_t QwtCPointerData< T >::size() const +{ + return m_size; +} + +/*! + Return the sample at position i + + \param index Index + \return Sample at position i + */ +template< typename T > +QPointF QwtCPointerData< T >::sample( size_t index ) const +{ + return QPointF( m_x[int( index )], m_y[int( index )] ); +} + +//! \return Array of the x-values +template< typename T > +const T* QwtCPointerData< T >::xData() const +{ + return m_x; +} + +//! \return Array of the y-values +template< typename T > +const T* QwtCPointerData< T >::yData() const +{ + return m_y; +} + +/*! + Constructor + + \param y Array of y values + \param size Size of the x and y arrays + + \warning The programmer must assure that the memory blocks referenced + by the pointers remain valid during the lifetime of the + QwtCPointerValueData object. + + \sa QwtPlotCurve::setData(), QwtPlotCurve::setRawSamples() + */ + +template< typename T > +QwtCPointerValueData< T >::QwtCPointerValueData( const T* y, size_t size ) + : m_y( y ) + , m_size( size ) +{ +} + +//! \return Size of the data set +template< typename T > +size_t QwtCPointerValueData< T >::size() const +{ + return m_size; +} + +/*! + Return the sample at position i + + \param index Index + \return Sample at position i + */ +template< typename T > +QPointF QwtCPointerValueData< T >::sample( size_t index ) const +{ + return QPointF( index, m_y[ int( index ) ] ); +} + +//! \return Array of the y-values +template< typename T > +const T* QwtCPointerValueData< T >::yData() const +{ + return m_y; +} + +#endif diff --git a/libs/qwt/src/qwt_point_mapper.cpp b/libs/qwt/src/qwt_point_mapper.cpp new file mode 100644 index 00000000..f714f449 --- /dev/null +++ b/libs/qwt/src/qwt_point_mapper.cpp @@ -0,0 +1,969 @@ +/****************************************************************************** + * 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_point_mapper.h" +#include "qwt_scale_map.h" +#include "qwt_pixel_matrix.h" +#include "qwt_series_data.h" +#include "qwt_math.h" + +#include +#include +#include +#include + +#include +#include +#include + +#if !defined( QT_NO_QFUTURE ) +#define QWT_USE_THREADS 1 +#endif + +static QRectF qwtInvalidRect( 0.0, 0.0, -1.0, -1.0 ); + +static inline int qwtRoundValue( double value ) +{ + return qRound( value ); +} + +static inline double qwtRoundValueF( double value ) +{ +#if 1 + // MS Windows and at least IRIX does not have C99's nearbyint() function + return ( value >= 0.0 ) ? std::floor( value + 0.5 ) : std::ceil( value - 0.5 ); +#else + return nearbyint( value ); +#endif +} + +static Qt::Orientation qwtProbeOrientation( + const QwtSeriesData< QPointF >* series, int from, int to ) +{ + if ( to - from < 20 ) + { + // not enough points to "have an orientation" + return Qt::Horizontal; + } + + const double x0 = series->sample( from ).x(); + const double xn = series->sample( to ).x(); + + if ( x0 == xn ) + return Qt::Vertical; + + const int step = ( to - from ) / 10; + const bool isIncreasing = xn > x0; + + double x1 = x0; + for ( int i = from + step; i < to; i += step ) + { + const double x2 = series->sample( i ).x(); + if ( x2 != x1 ) + { + if ( ( x2 > x1 ) != isIncreasing ) + return Qt::Vertical; + } + + x1 = x2; + } + + return Qt::Horizontal; +} + +namespace +{ + template< class Polygon, class Point > + class QwtPolygonQuadrupelX + { + public: + inline void start( int x, int y ) + { + x0 = x; + y1 = yMin = yMax = y2 = y; + } + + inline bool append( int x, int y ) + { + if ( x0 != x ) + return false; + + if ( y < yMin ) + yMin = y; + else if ( y > yMax ) + yMax = y; + + y2 = y; + + return true; + } + + inline void flush( Polygon& polyline ) + { + appendTo( y1, polyline ); + + if ( y2 > y1 ) + qSwap( yMin, yMax ); + + if ( yMax != y1 ) + appendTo( yMax, polyline ); + + if ( yMin != yMax ) + appendTo( yMin, polyline ); + + if ( y2 != yMin ) + appendTo( y2, polyline ); + } + + private: + inline void appendTo( int y, Polygon& polyline ) + { + polyline += Point( x0, y ); + } + + private: + int x0, y1, yMin, yMax, y2; + }; + + template< class Polygon, class Point > + class QwtPolygonQuadrupelY + { + public: + inline void start( int x, int y ) + { + y0 = y; + x1 = xMin = xMax = x2 = x; + } + + inline bool append( int x, int y ) + { + if ( y0 != y ) + return false; + + if ( x < xMin ) + xMin = x; + else if ( x > xMax ) + xMax = x; + + x2 = x; + + return true; + } + + inline void flush( Polygon& polyline ) + { + appendTo( x1, polyline ); + + if ( x2 > x1 ) + qSwap( xMin, xMax ); + + if ( xMax != x1 ) + appendTo( xMax, polyline ); + + if ( xMin != xMax ) + appendTo( xMin, polyline ); + + if ( x2 != xMin ) + appendTo( x2, polyline ); + } + + private: + inline void appendTo( int x, Polygon& polyline ) + { + polyline += Point( x, y0 ); + } + + int y0, x1, xMin, xMax, x2; + }; +} + +template< class Polygon, class Point, class PolygonQuadrupel > +static Polygon qwtMapPointsQuad( const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) +{ + const QPointF sample0 = series->sample( from ); + + PolygonQuadrupel q; + q.start( qwtRoundValue( xMap.transform( sample0.x() ) ), + qwtRoundValue( yMap.transform( sample0.y() ) ) ); + + Polygon polyline; + for ( int i = from; i <= to; i++ ) + { + const QPointF sample = series->sample( i ); + + const int x = qwtRoundValue( xMap.transform( sample.x() ) ); + const int y = qwtRoundValue( yMap.transform( sample.y() ) ); + + if ( !q.append( x, y ) ) + { + q.flush( polyline ); + q.start( x, y ); + } + } + q.flush( polyline ); + + return polyline; +} + +template< class Polygon, class Point, class PolygonQuadrupel > +static Polygon qwtMapPointsQuad( const Polygon& polyline ) +{ + const int numPoints = polyline.size(); + + if ( numPoints < 3 ) + return polyline; + + const Point* points = polyline.constData(); + + Polygon polylineXY; + + PolygonQuadrupel q; + q.start( points[0].x(), points[0].y() ); + + for ( int i = 0; i < numPoints; i++ ) + { + const int x = points[i].x(); + const int y = points[i].y(); + + if ( !q.append( x, y ) ) + { + q.flush( polylineXY ); + q.start( x, y ); + } + } + q.flush( polylineXY ); + + return polylineXY; +} + + +template< class Polygon, class Point > +static Polygon qwtMapPointsQuad( const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) +{ + Polygon polyline; + if ( from > to ) + return polyline; + + /* + probing some values, to decide if it is better + to start with x or y coordinates + */ + const Qt::Orientation orientation = qwtProbeOrientation( series, from, to ); + + if ( orientation == Qt::Horizontal ) + { + polyline = qwtMapPointsQuad< Polygon, Point, + QwtPolygonQuadrupelY< Polygon, Point > >( xMap, yMap, series, from, to ); + + polyline = qwtMapPointsQuad< Polygon, Point, + QwtPolygonQuadrupelX< Polygon, Point > >( polyline ); + } + else + { + polyline = qwtMapPointsQuad< Polygon, Point, + QwtPolygonQuadrupelX< Polygon, Point > >( xMap, yMap, series, from, to ); + + polyline = qwtMapPointsQuad< Polygon, Point, + QwtPolygonQuadrupelY< Polygon, Point > >( polyline ); + } + + return polyline; +} + +// Helper class to work around the 5 parameters +// limitation of QtConcurrent::run() +class QwtDotsCommand +{ + public: + const QwtSeriesData< QPointF >* series; + int from; + int to; + QRgb rgb; +}; + +static void qwtRenderDots( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtDotsCommand& command, const QPoint& pos, QImage* image ) +{ + const QRgb rgb = command.rgb; + QRgb* bits = reinterpret_cast< QRgb* >( image->bits() ); + + const int w = image->width(); + const int h = image->height(); + + const int x0 = pos.x(); + const int y0 = pos.y(); + + for ( int i = command.from; i <= command.to; i++ ) + { + const QPointF sample = command.series->sample( i ); + + const int x = static_cast< int >( xMap.transform( sample.x() ) + 0.5 ) - x0; + const int y = static_cast< int >( yMap.transform( sample.y() ) + 0.5 ) - y0; + + if ( x >= 0 && x < w && y >= 0 && y < h ) + bits[ y * w + x ] = rgb; + } +} + +// some functors, so that the compile can inline +struct QwtRoundI +{ + inline int operator()( double value ) const + { + return qwtRoundValue( value ); + } +}; + +struct QwtRoundF +{ + inline double operator()( double value ) const + { + return qwtRoundValueF( value ); + } +}; + +struct QwtNoRoundF +{ + inline double operator()( double value ) const + { + return value; + } +}; + +// mapping points without any filtering - beside checking +// the bounding rectangle + +template< class Polygon, class Point, class Round > +static inline Polygon qwtToPoints( + const QRectF& boundingRect, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, + int from, int to, Round round ) +{ + Polygon polyline( to - from + 1 ); + Point* points = polyline.data(); + + int numPoints = 0; + + if ( boundingRect.isValid() ) + { + // iterating over all values + // filtering out all points outside of + // the bounding rectangle + + for ( int i = from; i <= to; i++ ) + { + const QPointF sample = series->sample( i ); + + const double x = xMap.transform( sample.x() ); + const double y = yMap.transform( sample.y() ); + + if ( boundingRect.contains( x, y ) ) + { + points[ numPoints ].rx() = round( x ); + points[ numPoints ].ry() = round( y ); + + numPoints++; + } + } + + polyline.resize( numPoints ); + } + else + { + // simply iterating over all values + // without any filtering + + for ( int i = from; i <= to; i++ ) + { + const QPointF sample = series->sample( i ); + + const double x = xMap.transform( sample.x() ); + const double y = yMap.transform( sample.y() ); + + points[ numPoints ].rx() = round( x ); + points[ numPoints ].ry() = round( y ); + + numPoints++; + } + } + + return polyline; +} + +static inline QPolygon qwtToPointsI( + const QRectF& boundingRect, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, + int from, int to ) +{ + return qwtToPoints< QPolygon, QPoint >( + boundingRect, xMap, yMap, series, from, to, QwtRoundI() ); +} + +template< class Round > +static inline QPolygonF qwtToPointsF( + const QRectF& boundingRect, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, + int from, int to, Round round ) +{ + return qwtToPoints< QPolygonF, QPointF >( + boundingRect, xMap, yMap, series, from, to, round ); +} + +// Mapping points with filtering out consecutive +// points mapped to the same position + +template< class Polygon, class Point, class Round > +static inline Polygon qwtToPolylineFiltered( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, + int from, int to, Round round ) +{ + // in curves with many points consecutive points + // are often mapped to the same position. As this might + // result in empty lines ( or symbols hidden by others ) + // we try to filter them out + + Polygon polyline( to - from + 1 ); + Point* points = polyline.data(); + + const QPointF sample0 = series->sample( from ); + + points[0].rx() = round( xMap.transform( sample0.x() ) ); + points[0].ry() = round( yMap.transform( sample0.y() ) ); + + int pos = 0; + for ( int i = from + 1; i <= to; i++ ) + { + const QPointF sample = series->sample( i ); + + const Point p( round( xMap.transform( sample.x() ) ), + round( yMap.transform( sample.y() ) ) ); + + if ( points[pos] != p ) + points[++pos] = p; + } + + polyline.resize( pos + 1 ); + return polyline; +} + +static inline QPolygon qwtToPolylineFilteredI( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, + int from, int to ) +{ + return qwtToPolylineFiltered< QPolygon, QPoint >( + xMap, yMap, series, from, to, QwtRoundI() ); +} + +template< class Round > +static inline QPolygonF qwtToPolylineFilteredF( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, + int from, int to, Round round ) +{ + return qwtToPolylineFiltered< QPolygonF, QPointF >( + xMap, yMap, series, from, to, round ); +} + +template< class Polygon, class Point > +static inline Polygon qwtToPointsFiltered( + const QRectF& boundingRect, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) +{ + // F.e. in scatter plots ( no connecting lines ) we + // can sort out all duplicates ( not only consecutive points ) + + Polygon polygon( to - from + 1 ); + Point* points = polygon.data(); + + QwtPixelMatrix pixelMatrix( boundingRect.toAlignedRect() ); + + int numPoints = 0; + for ( int i = from; i <= to; i++ ) + { + const QPointF sample = series->sample( i ); + + const int x = qwtRoundValue( xMap.transform( sample.x() ) ); + const int y = qwtRoundValue( yMap.transform( sample.y() ) ); + + if ( pixelMatrix.testAndSetPixel( x, y, true ) == false ) + { + points[ numPoints ].rx() = x; + points[ numPoints ].ry() = y; + + numPoints++; + } + } + + polygon.resize( numPoints ); + return polygon; +} + +static inline QPolygon qwtToPointsFilteredI( + const QRectF& boundingRect, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) +{ + return qwtToPointsFiltered< QPolygon, QPoint >( + boundingRect, xMap, yMap, series, from, to ); +} + +static inline QPolygonF qwtToPointsFilteredF( + const QRectF& boundingRect, + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) +{ + return qwtToPointsFiltered< QPolygonF, QPointF >( + boundingRect, xMap, yMap, series, from, to ); +} + +class QwtPointMapper::PrivateData +{ + public: + PrivateData() + : boundingRect( qwtInvalidRect ) + { + } + + QRectF boundingRect; + QwtPointMapper::TransformationFlags flags; +}; + +//! Constructor +QwtPointMapper::QwtPointMapper() +{ + m_data = new PrivateData(); +} + +//! Destructor +QwtPointMapper::~QwtPointMapper() +{ + delete m_data; +} + +/*! + Set the flags affecting the transformation process + + \param flags Flags + \sa flags(), setFlag() + */ +void QwtPointMapper::setFlags( TransformationFlags flags ) +{ + m_data->flags = flags; +} + +/*! + \return Flags affecting the transformation process + \sa setFlags(), setFlag() + */ +QwtPointMapper::TransformationFlags QwtPointMapper::flags() const +{ + return m_data->flags; +} + +/*! + Modify a flag affecting the transformation process + + \param flag Flag type + \param on Value + + \sa flag(), setFlags() + */ +void QwtPointMapper::setFlag( TransformationFlag flag, bool on ) +{ + if ( on ) + m_data->flags |= flag; + else + m_data->flags &= ~flag; +} + +/*! + \return True, when the flag is set + \param flag Flag type + \sa setFlag(), setFlags() + */ +bool QwtPointMapper::testFlag( TransformationFlag flag ) const +{ + return m_data->flags & flag; +} + +/*! + Set a bounding rectangle for the point mapping algorithm + + A valid bounding rectangle can be used for optimizations + + \param rect Bounding rectangle + \sa boundingRect() + */ +void QwtPointMapper::setBoundingRect( const QRectF& rect ) +{ + m_data->boundingRect = rect; +} + +/*! + \return Bounding rectangle + \sa setBoundingRect() + */ +QRectF QwtPointMapper::boundingRect() const +{ + return m_data->boundingRect; +} + +/*! + \brief Translate a series of points into a QPolygonF + + When the WeedOutPoints flag is enabled consecutive points, + that are mapped to the same position will be one point. + + When RoundPoints is set all points are rounded to integers + but returned as PolygonF - what only makes sense + when the further processing of the values need a QPolygonF. + + When RoundPoints & WeedOutIntermediatePoints is enabled an even more + aggressive weeding algorithm is enabled. + + \param xMap x map + \param yMap y map + \param series Series of points to be mapped + \param from Index of the first point to be painted + \param to Index of the last point to be painted + + \return Translated polygon + */ +QPolygonF QwtPointMapper::toPolygonF( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) const +{ + QPolygonF polyline; + + if ( m_data->flags & RoundPoints ) + { + if ( m_data->flags & WeedOutIntermediatePoints ) + { + polyline = qwtMapPointsQuad< QPolygonF, QPointF >( + xMap, yMap, series, from, to ); + } + else if ( m_data->flags & WeedOutPoints ) + { + polyline = qwtToPolylineFilteredF( + xMap, yMap, series, from, to, QwtRoundF() ); + } + else + { + polyline = qwtToPointsF( qwtInvalidRect, + xMap, yMap, series, from, to, QwtRoundF() ); + } + } + else + { + if ( m_data->flags & WeedOutPoints ) + { + polyline = qwtToPolylineFilteredF( + xMap, yMap, series, from, to, QwtNoRoundF() ); + } + else + { + polyline = qwtToPointsF( qwtInvalidRect, + xMap, yMap, series, from, to, QwtNoRoundF() ); + } + } + + return polyline; +} + +/*! + \brief Translate a series of points into a QPolygon + + When the WeedOutPoints flag is enabled consecutive points, + that are mapped to the same position will be one point. + + \param xMap x map + \param yMap y map + \param series Series of points to be mapped + \param from Index of the first point to be painted + \param to Index of the last point to be painted + + \return Translated polygon + */ +QPolygon QwtPointMapper::toPolygon( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) const +{ + QPolygon polyline; + + if ( m_data->flags & WeedOutIntermediatePoints ) + { + // TODO WeedOutIntermediatePointsY ... + polyline = qwtMapPointsQuad< QPolygon, QPoint >( + xMap, yMap, series, from, to ); + } + else if ( m_data->flags & WeedOutPoints ) + { + polyline = qwtToPolylineFilteredI( + xMap, yMap, series, from, to ); + } + else + { + polyline = qwtToPointsI( + qwtInvalidRect, xMap, yMap, series, from, to ); + } + + return polyline; +} + +/*! + \brief Translate a series into a QPolygonF + + - WeedOutPoints & RoundPoints & boundingRect().isValid() + All points that are mapped to the same position + will be one point. Points outside of the bounding + rectangle are ignored. + + - WeedOutPoints & RoundPoints & !boundingRect().isValid() + All consecutive points that are mapped to the same position + will one point + + - WeedOutPoints & !RoundPoints + All consecutive points that are mapped to the same position + will one point + + - !WeedOutPoints & boundingRect().isValid() + Points outside of the bounding rectangle are ignored. + + When RoundPoints is set all points are rounded to integers + but returned as PolygonF - what only makes sense + when the further processing of the values need a QPolygonF. + + \param xMap x map + \param yMap y map + \param series Series of points to be mapped + \param from Index of the first point to be painted + \param to Index of the last point to be painted + + \return Translated polygon + */ +QPolygonF QwtPointMapper::toPointsF( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) const +{ + QPolygonF points; + + if ( m_data->flags & WeedOutPoints ) + { + if ( m_data->flags & RoundPoints ) + { + if ( m_data->boundingRect.isValid() ) + { + points = qwtToPointsFilteredF( m_data->boundingRect, + xMap, yMap, series, from, to ); + } + else + { + // without a bounding rectangle all we can + // do is to filter out duplicates of + // consecutive points + + points = qwtToPolylineFilteredF( + xMap, yMap, series, from, to, QwtRoundF() ); + } + } + else + { + // when rounding is not allowed we can't use + // qwtToPointsFilteredF + + points = qwtToPolylineFilteredF( + xMap, yMap, series, from, to, QwtNoRoundF() ); + } + } + else + { + if ( m_data->flags & RoundPoints ) + { + points = qwtToPointsF( m_data->boundingRect, + xMap, yMap, series, from, to, QwtRoundF() ); + } + else + { + points = qwtToPointsF( m_data->boundingRect, + xMap, yMap, series, from, to, QwtNoRoundF() ); + } + } + + return points; +} + +/*! + \brief Translate a series of points into a QPolygon + + - WeedOutPoints & boundingRect().isValid() + All points that are mapped to the same position + will be one point. Points outside of the bounding + rectangle are ignored. + + - WeedOutPoints & !boundingRect().isValid() + All consecutive points that are mapped to the same position + will one point + + - !WeedOutPoints & boundingRect().isValid() + Points outside of the bounding rectangle are ignored. + + \param xMap x map + \param yMap y map + \param series Series of points to be mapped + \param from Index of the first point to be painted + \param to Index of the last point to be painted + + \return Translated polygon + */ +QPolygon QwtPointMapper::toPoints( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) const +{ + QPolygon points; + + if ( m_data->flags & WeedOutPoints ) + { + if ( m_data->boundingRect.isValid() ) + { + points = qwtToPointsFilteredI( m_data->boundingRect, + xMap, yMap, series, from, to ); + } + else + { + // when we don't have the bounding rectangle all + // we can do is to filter out consecutive duplicates + + points = qwtToPolylineFilteredI( + xMap, yMap, series, from, to ); + } + } + else + { + points = qwtToPointsI( + m_data->boundingRect, xMap, yMap, series, from, to ); + } + + return points; +} + + +/*! + \brief Translate a series into a QImage + + \param xMap x map + \param yMap y map + \param series Series of points to be mapped + \param from Index of the first point to be painted + \param to Index of the last point to be painted + \param pen Pen used for drawing a point + of the image, where a point is mapped to + \param antialiased True, when the dots should be displayed + antialiased + \param numThreads Number of threads to be used for rendering. + If numThreads is set to 0, the system specific + ideal thread count is used. + + \return Image displaying the series + */ +QImage QwtPointMapper::toImage( + const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to, + const QPen& pen, bool antialiased, uint numThreads ) const +{ + Q_UNUSED( antialiased ) + +#if QWT_USE_THREADS + if ( numThreads == 0 ) + numThreads = QThread::idealThreadCount(); + + if ( numThreads <= 0 ) + numThreads = 1; +#else + Q_UNUSED( numThreads ) +#endif + + // a very special optimization for scatter plots + // where every sample is mapped to one pixel only. + + const QRect rect = m_data->boundingRect.toAlignedRect(); + + QImage image( rect.size(), QImage::Format_ARGB32 ); + image.fill( Qt::transparent ); + + if ( pen.width() <= 1 && pen.color().alpha() == 255 ) + { + QwtDotsCommand command; + command.series = series; + command.rgb = pen.color().rgba(); + +#if QWT_USE_THREADS + const int numPoints = ( to - from + 1 ) / numThreads; + + QList< QFuture< void > > futures; + for ( uint i = 0; i < numThreads; i++ ) + { + const QPoint pos = rect.topLeft(); + + const int index0 = from + i * numPoints; + if ( i == numThreads - 1 ) + { + command.from = index0; + command.to = to; + + qwtRenderDots( xMap, yMap, command, pos, &image ); + } + else + { + command.from = index0; + command.to = index0 + numPoints - 1; + + futures += QtConcurrent::run( &qwtRenderDots, + xMap, yMap, command, pos, &image ); + } + } + for ( int i = 0; i < futures.size(); i++ ) + futures[i].waitForFinished(); +#else + command.from = from; + command.to = to; + + qwtRenderDots( xMap, yMap, command, rect.topLeft(), &image ); +#endif + } + else + { + // fallback implementation: to be replaced later by + // setting the pixels of the image like above, TODO ... + + QPainter painter( &image ); + painter.setPen( pen ); + painter.setRenderHint( QPainter::Antialiasing, antialiased ); + + const int chunkSize = 1000; + for ( int i = from; i <= to; i += chunkSize ) + { + const int indexTo = qMin( i + chunkSize - 1, to ); + const QPolygon points = toPoints( + xMap, yMap, series, i, indexTo ); + + painter.drawPoints( points ); + } + } + + return image; +} diff --git a/libs/qwt/src/qwt_point_mapper.h b/libs/qwt/src/qwt_point_mapper.h new file mode 100644 index 00000000..8ce4be7b --- /dev/null +++ b/libs/qwt/src/qwt_point_mapper.h @@ -0,0 +1,110 @@ +/****************************************************************************** + * 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_POINT_MAPPER_H +#define QWT_POINT_MAPPER_H + +#include "qwt_global.h" + +class QwtScaleMap; +template< typename T > class QwtSeriesData; +class QPolygonF; +class QPointF; +class QRectF; +class QPolygon; +class QPen; +class QImage; + +/*! + \brief A helper class for translating a series of points + + QwtPointMapper is a collection of methods and optimizations + for translating a series of points into paint device coordinates. + It is used by QwtPlotCurve but might also be useful for + similar plot items displaying a QwtSeriesData. + */ +class QWT_EXPORT QwtPointMapper +{ + public: + /*! + \brief Flags affecting the transformation process + \sa setFlag(), setFlags() + */ + enum TransformationFlag + { + //! Round points to integer values + RoundPoints = 0x01, + + /*! + Try to remove points, that are translated to the + same position. + */ + WeedOutPoints = 0x02, + + /*! + An even more aggressive weeding algorithm, that + can be used in toPolygon(). + + A consecutive chunk of points being mapped to the + same x coordinate is reduced to 4 points: + + - first point + - point with the minimum y coordinate + - point with the maximum y coordinate + - last point + + In the worst case ( first and last points are never one of the extremes ) + the number of points will be 4 times the width. + + As the algorithm is fast it can be used inside of + a polyline render cycle. + */ + WeedOutIntermediatePoints = 0x04 + }; + + Q_DECLARE_FLAGS( TransformationFlags, TransformationFlag ) + + QwtPointMapper(); + ~QwtPointMapper(); + + void setFlags( TransformationFlags ); + TransformationFlags flags() const; + + void setFlag( TransformationFlag, bool on = true ); + bool testFlag( TransformationFlag ) const; + + void setBoundingRect( const QRectF& ); + QRectF boundingRect() const; + + QPolygonF toPolygonF( const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) const; + + QPolygon toPolygon( const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) const; + + QPolygon toPoints( const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) const; + + QPolygonF toPointsF( const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to ) const; + + QImage toImage( const QwtScaleMap& xMap, const QwtScaleMap& yMap, + const QwtSeriesData< QPointF >* series, int from, int to, + const QPen&, bool antialiased, uint numThreads ) const; + + private: + Q_DISABLE_COPY(QwtPointMapper) + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPointMapper::TransformationFlags ) + +#endif diff --git a/libs/qwt/src/qwt_point_polar.cpp b/libs/qwt/src/qwt_point_polar.cpp new file mode 100644 index 00000000..fdfd7123 --- /dev/null +++ b/libs/qwt/src/qwt_point_polar.cpp @@ -0,0 +1,145 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_point_polar.h" +#include "qwt_math.h" + +#if QT_VERSION >= 0x050200 + +static QwtPointPolar qwtPointToPolar( const QPointF& point ) +{ + return QwtPointPolar( point ); +} + +#endif + +namespace +{ + static const struct RegisterQwtPointPolar + { + inline RegisterQwtPointPolar() + { + qRegisterMetaType< QwtPointPolar >(); + +#if QT_VERSION >= 0x050200 + QMetaType::registerConverter< QPointF, QwtPointPolar >( qwtPointToPolar ); + QMetaType::registerConverter< QwtPointPolar, QPointF >( &QwtPointPolar::toPoint ); +#endif + } + + } qwtRegisterQwtPointPolar; +} + +/*! + Convert and assign values from a point in Cartesian coordinates + + \param p Point in Cartesian coordinates + \sa setPoint(), toPoint() + */ +QwtPointPolar::QwtPointPolar( const QPointF& p ) +{ + m_radius = std::sqrt( qwtSqr( p.x() ) + qwtSqr( p.y() ) ); + m_azimuth = std::atan2( p.y(), p.x() ); +} + +/*! + Convert and assign values from a point in Cartesian coordinates + \param p Point in Cartesian coordinates + */ +void QwtPointPolar::setPoint( const QPointF& p ) +{ + m_radius = std::sqrt( qwtSqr( p.x() ) + qwtSqr( p.y() ) ); + m_azimuth = std::atan2( p.y(), p.x() ); +} + +/*! + Convert and return values in Cartesian coordinates + + \return Converted point in Cartesian coordinates + + \note Invalid or null points will be returned as QPointF(0.0, 0.0) + \sa isValid(), isNull() + */ +QPointF QwtPointPolar::toPoint() const +{ + if ( m_radius <= 0.0 ) + return QPointF( 0.0, 0.0 ); + + const double x = m_radius * std::cos( m_azimuth ); + const double y = m_radius * std::sin( m_azimuth ); + + return QPointF( x, y ); +} + +/*! + \brief Compare 2 points + + Two points are equal to each other if radius and + azimuth-coordinates are the same. Points are not equal, when + the azimuth differs, but other.azimuth() == azimuth() % (2 * PI). + + \return True if the point is equal to other; otherwise return false. + + \sa normalized() + */ +bool QwtPointPolar::operator==( const QwtPointPolar& other ) const +{ + return m_radius == other.m_radius && m_azimuth == other.m_azimuth; +} + +/*! + Compare 2 points + + Two points are equal to each other if radius and + azimuth-coordinates are the same. Points are not equal, when + the azimuth differs, but other.azimuth() == azimuth() % (2 * PI). + + \return True if the point is not equal to other; otherwise return false. + \sa normalized() + */ +bool QwtPointPolar::operator!=( const QwtPointPolar& other ) const +{ + return m_radius != other.m_radius || m_azimuth != other.m_azimuth; +} + +/*! + Normalize radius and azimuth + + When the radius is < 0.0 it is set to 0.0. The azimuth is + a value >= 0.0 and < 2 * M_PI. + + \return Normalized point + */ +QwtPointPolar QwtPointPolar::normalized() const +{ + const double radius = qwtMaxF( m_radius, 0.0 ); + + double azimuth = m_azimuth; + if ( azimuth < -2.0 * M_PI || azimuth >= 2 * M_PI ) + azimuth = std::fmod( m_azimuth, 2 * M_PI ); + + if ( azimuth < 0.0 ) + azimuth += 2 * M_PI; + + return QwtPointPolar( azimuth, radius ); +} + +#ifndef QT_NO_DEBUG_STREAM + +#include + +QDebug operator<<( QDebug debug, const QwtPointPolar& point ) +{ + debug.nospace() << "QwtPointPolar(" + << point.azimuth() << "," << point.radius() << ")"; + + return debug.space(); +} + +#endif + diff --git a/libs/qwt/src/qwt_point_polar.h b/libs/qwt/src/qwt_point_polar.h new file mode 100644 index 00000000..13897a44 --- /dev/null +++ b/libs/qwt/src/qwt_point_polar.h @@ -0,0 +1,188 @@ +/****************************************************************************** + * 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_POINT_POLAR_H +#define QWT_POINT_POLAR_H + +#include "qwt_global.h" +#include "qwt_math.h" + +#include +#include +#include + +/*! + \brief A point in polar coordinates + + In polar coordinates a point is determined by an angle and a distance. + See http://en.wikipedia.org/wiki/Polar_coordinate_system + */ + +class QWT_EXPORT QwtPointPolar +{ + public: + QwtPointPolar(); + QwtPointPolar( double azimuth, double radius ); + QwtPointPolar( const QPointF& ); + + void setPoint( const QPointF& ); + QPointF toPoint() const; + + bool isValid() const; + bool isNull() const; + + double radius() const; + double azimuth() const; + + double& rRadius(); + double& rAzimuth(); + + void setRadius( double ); + void setAzimuth( double ); + + bool operator==( const QwtPointPolar& ) const; + bool operator!=( const QwtPointPolar& ) const; + + QwtPointPolar normalized() const; + + private: + double m_azimuth; + double m_radius; +}; + +Q_DECLARE_TYPEINFO( QwtPointPolar, Q_MOVABLE_TYPE ); +Q_DECLARE_METATYPE( QwtPointPolar ); + +#ifndef QT_NO_DEBUG_STREAM +QWT_EXPORT QDebug operator<<( QDebug, const QwtPointPolar& ); +#endif + +/*! + Constructs a null point, with a radius and azimuth set to 0.0. + \sa QPointF::isNull() + */ +inline QwtPointPolar::QwtPointPolar() + : m_azimuth( 0.0 ) + , m_radius( 0.0 ) +{ +} + +/*! + Constructs a point with coordinates specified by radius and azimuth. + + \param azimuth Azimuth + \param radius Radius + */ +inline QwtPointPolar::QwtPointPolar( double azimuth, double radius ) + : m_azimuth( azimuth ) + , m_radius( radius ) +{ +} + +//! Returns true if radius() >= 0.0 +inline bool QwtPointPolar::isValid() const +{ + return m_radius >= 0.0; +} + +//! Returns true if radius() >= 0.0 +inline bool QwtPointPolar::isNull() const +{ + return m_radius == 0.0; +} + +//! Returns the radius. +inline double QwtPointPolar::radius() const +{ + return m_radius; +} + +//! Returns the azimuth. +inline double QwtPointPolar::azimuth() const +{ + return m_azimuth; +} + +//! Returns the radius. +inline double& QwtPointPolar::rRadius() +{ + return m_radius; +} + +//! Returns the azimuth. +inline double& QwtPointPolar::rAzimuth() +{ + return m_azimuth; +} + +//! Sets the radius to radius. +inline void QwtPointPolar::setRadius( double radius ) +{ + m_radius = radius; +} + +//! Sets the azimuth to azimuth. +inline void QwtPointPolar::setAzimuth( double azimuth ) +{ + m_azimuth = azimuth; +} + +inline QPoint qwtPolar2Pos( const QPoint& pole, + double radius, double angle ) +{ + const double x = pole.x() + radius * std::cos( angle ); + const double y = pole.y() - radius * std::sin( angle ); + + return QPoint( qRound( x ), qRound( y ) ); +} + +inline QPoint qwtDegree2Pos( const QPoint& pole, + double radius, double angle ) +{ + return qwtPolar2Pos( pole, radius, angle / 180.0 * M_PI ); +} + +inline QPointF qwtPolar2Pos( const QPointF& pole, + double radius, double angle ) +{ + const double x = pole.x() + radius * std::cos( angle ); + const double y = pole.y() - radius * std::sin( angle ); + + return QPointF( x, y); +} + +inline QPointF qwtDegree2Pos( const QPointF& pole, + double radius, double angle ) +{ + return qwtPolar2Pos( pole, radius, angle / 180.0 * M_PI ); +} + +inline QPointF qwtFastPolar2Pos( const QPointF& pole, + double radius, double angle ) +{ + const double x = pole.x() + radius * qFastCos( angle ); + const double y = pole.y() - radius * qFastSin( angle ); + + return QPointF( x, y); +} + +inline QPointF qwtFastDegree2Pos( const QPointF& pole, + double radius, double angle ) +{ + return qwtFastPolar2Pos( pole, radius, angle / 180.0 * M_PI ); +} + +inline QwtPointPolar qwtFastPos2Polar( const QPointF& pos ) +{ + return QwtPointPolar( qwtFastAtan2( pos.y(), pos.x() ), + qSqrt( qwtSqr( pos.x() ) + qwtSqr( pos.y() ) ) ); +} + +#endif diff --git a/libs/qwt/src/qwt_polar.h b/libs/qwt/src/qwt_polar.h new file mode 100644 index 00000000..94b41ca4 --- /dev/null +++ b/libs/qwt/src/qwt_polar.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_H +#define QWT_POLAR_H + +namespace QwtPolar +{ + //! Unit of an angle + enum AngleUnit + { + //! 0.0 -> 2_M_PI + Radians, + + //! 0.0 -> 360.0 + Degrees, + + //! 0.0 - 400.0 + Gradians, + + //! 0.0 - 1.0 + Turns + }; + + //! An enum, that identifies the type of a coordinate + enum Coordinate + { + //! Azimuth + Azimuth, + + //! Radius + Radius + }; + + /*! + Indices used to identify an axis. + \sa Scale + */ + enum Axis + { + //! Azimuth axis + AxisAzimuth, + + //! Left axis + AxisLeft, + + //! Right axis + AxisRight, + + //! Top axis + AxisTop, + + //! Bottom axis + AxisBottom, + + //! Number of available axis + AxesCount + }; + + /*! + Indices used to identify a scale. + \sa Axis + */ + enum Scale + { + //! Azimuth scale + ScaleAzimuth = Azimuth, + + //! Radial scale + ScaleRadius = Radius, + + //! Number of scales + ScaleCount + }; +} + +#endif diff --git a/libs/qwt/src/qwt_polar_canvas.cpp b/libs/qwt/src/qwt_polar_canvas.cpp new file mode 100644 index 00000000..f5924352 --- /dev/null +++ b/libs/qwt/src/qwt_polar_canvas.cpp @@ -0,0 +1,328 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_canvas.h" +#include "qwt_polar_plot.h" +#include "qwt_painter.h" + +#include +#include +#include +#include +#include +#ifdef Q_WS_X11 +#include +#endif + +static inline void qwtDrawStyledBackground( + QWidget* widget, QPainter* painter ) +{ + QStyleOption opt; + opt.initFrom( widget ); + widget->style()->drawPrimitive( QStyle::PE_Widget, &opt, painter, widget ); +} + +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() ); +} + +class QwtPolarCanvas::PrivateData +{ + public: + PrivateData() + : backingStore( NULL ) + { + } + + ~PrivateData() + { + delete backingStore; + } + + QwtPolarCanvas::PaintAttributes paintAttributes; + QPixmap* backingStore; +}; + +//! Constructor +QwtPolarCanvas::QwtPolarCanvas( QwtPolarPlot* plot ) + : QFrame( plot ) +{ + m_data = new PrivateData; + +#ifndef QT_NO_CURSOR + setCursor( Qt::CrossCursor ); +#endif + setFocusPolicy( Qt::WheelFocus ); + + setPaintAttribute( BackingStore, true ); +} + +//! Destructor +QwtPolarCanvas::~QwtPolarCanvas() +{ + delete m_data; +} + +//! \return Parent plot widget +QwtPolarPlot* QwtPolarCanvas::plot() +{ + return qobject_cast< QwtPolarPlot* >( parent() ); +} + +//! \return Parent plot widget +const QwtPolarPlot* QwtPolarCanvas::plot() const +{ + return qobject_cast< QwtPolarPlot* >( parent() ); +} + +/*! + \brief Changing the paint attributes + + \param attribute Paint attribute + \param on On/Off + + The default setting enables BackingStore + + \sa testPaintAttribute(), paintCache() + */ +void QwtPolarCanvas::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( bool( m_data->paintAttributes & attribute ) == on ) + return; + + if ( on ) + m_data->paintAttributes |= attribute; + else + m_data->paintAttributes &= ~attribute; + + switch( attribute ) + { + case BackingStore: + { + if ( on ) + { + if ( m_data->backingStore == NULL ) + m_data->backingStore = new QPixmap(); + + if ( isVisible() ) + { + const QRect cr = contentsRect(); +#if QT_VERSION >= 0x050000 + *m_data->backingStore = grab( cr ); +#else + *m_data->backingStore = QPixmap::grabWidget( this, cr ); +#endif + } + } + else + { + delete m_data->backingStore; + m_data->backingStore = NULL; + } + break; + } + } +} + +/*! + Test whether a paint attribute is enabled + + \param attribute Paint attribute + \return true if the attribute is enabled + \sa setPaintAttribute() + */ +bool QwtPolarCanvas::testPaintAttribute( PaintAttribute attribute ) const +{ + return ( m_data->paintAttributes & attribute ) != 0; +} + +//! \return Backing store, might be null +const QPixmap* QwtPolarCanvas::backingStore() const +{ + return m_data->backingStore; +} + +//! Invalidate the internal backing store +void QwtPolarCanvas::invalidateBackingStore() +{ + if ( m_data->backingStore ) + *m_data->backingStore = QPixmap(); +} + +/*! + Paint event + \param event Paint event + */ +void QwtPolarCanvas::paintEvent( QPaintEvent* event ) +{ + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + if ( ( m_data->paintAttributes & BackingStore ) + && m_data->backingStore != NULL ) + { + QPixmap& bs = *m_data->backingStore; + if ( bs.size() != size() ) + { + bs = QPixmap( size() ); +#ifdef Q_WS_X11 + if ( bs.x11Info().screen() != x11Info().screen() ) + bs.x11SetScreen( x11Info().screen() ); +#endif + + QPainter p; + + if ( testAttribute( Qt::WA_StyledBackground ) ) + { + p.begin( &bs ); + qwtDrawStyledBackground( this, &p ); + } + else + { + if ( autoFillBackground() ) + { + p.begin( &bs ); + p.fillRect( rect(), palette().brush( backgroundRole() ) ); + } + else + { + QWidget* bgWidget = qwtBackgroundWidget( plot() ); + + QwtPainter::fillPixmap( bgWidget, bs, + mapTo( bgWidget, rect().topLeft() ) ); + + p.begin( &bs ); + } + } + + plot()->drawCanvas( &p, contentsRect() ); + + if ( frameWidth() > 0 ) + drawFrame( &p ); + } + + painter.drawPixmap( 0, 0, *m_data->backingStore ); + } + else + { + qwtDrawStyledBackground( this, &painter ); + + plot()->drawCanvas( &painter, contentsRect() ); + + if ( frameWidth() > 0 ) + drawFrame( &painter ); + } +} + +/*! + Resize event + \param event Resize event + */ +void QwtPolarCanvas::resizeEvent( QResizeEvent* event ) +{ + QFrame::resizeEvent( event ); + + for ( int scaleId = 0; scaleId < QwtPolar::ScaleCount; scaleId++ ) + plot()->updateScale( scaleId ); +} + +/*! + Translate a point from widget into plot coordinates + + \param pos Point in widget coordinates of the plot canvas + \return Point in plot coordinates + + \sa transform() + */ +QwtPointPolar QwtPolarCanvas::invTransform( const QPoint& pos ) const +{ + const QwtPolarPlot* pl = plot(); + + const QwtScaleMap azimuthMap = pl->scaleMap( QwtPolar::Azimuth ); + const QwtScaleMap radialMap = pl->scaleMap( QwtPolar::Radius ); + + const QPointF center = pl->plotRect().center(); + + double dx = pos.x() - center.x(); + double dy = -( pos.y() - center.y() ); + + const QwtPointPolar polarPos = QwtPointPolar( QPoint( dx, dy ) ).normalized(); + + double azimuth = azimuthMap.invTransform( polarPos.azimuth() ); + + // normalize the azimuth + double min = azimuthMap.s1(); + double max = azimuthMap.s2(); + if ( max < min ) + qSwap( min, max ); + + if ( azimuth < min ) + { + azimuth += max - min; + } + else if ( azimuth > max ) + { + azimuth -= max - min; + } + + const double radius = radialMap.invTransform( polarPos.radius() ); + + return QwtPointPolar( azimuth, radius ); +} + +/*! + Translate a point from plot into widget coordinates + + \param polarPos Point in plot coordinates + \return Point in widget coordinates + \sa transform() + */ +QPoint QwtPolarCanvas::transform( const QwtPointPolar& polarPos ) const +{ + const QwtPolarPlot* pl = plot(); + + const QwtScaleMap azimuthMap = pl->scaleMap( QwtPolar::Azimuth ); + const QwtScaleMap radialMap = pl->scaleMap( QwtPolar::Radius ); + + const double radius = radialMap.transform( polarPos.radius() ); + const double azimuth = azimuthMap.transform( polarPos.azimuth() ); + + const QPointF pos = qwtPolar2Pos( + pl->plotRect().center(), radius, azimuth ); + + return pos.toPoint(); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_polar_canvas.cpp" +#endif diff --git a/libs/qwt/src/qwt_polar_canvas.h b/libs/qwt/src/qwt_polar_canvas.h new file mode 100644 index 00000000..97dff309 --- /dev/null +++ b/libs/qwt/src/qwt_polar_canvas.h @@ -0,0 +1,78 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_CANVAS_H +#define QWT_POLAR_CANVAS_H + +#include "qwt_global.h" +#include "qwt_point_polar.h" +#include + +class QPainter; +class QwtPolarPlot; + +/*! + \brief Canvas of a QwtPolarPlot. + + The canvas is the widget, where all polar items are painted to. + + \note In opposite to QwtPlot all axes are painted on the canvas. + \sa QwtPolarPlot + */ +class QWT_EXPORT QwtPolarCanvas : public QFrame +{ + Q_OBJECT + + public: + /*! + \brief Paint attributes + + The default setting enables BackingStore + + \sa setPaintAttribute(), testPaintAttribute(), backingStore() + */ + + enum PaintAttribute + { + /*! + Paint double buffered and reuse the content of the pixmap buffer + for some spontaneous repaints that happen when a plot gets unhidden, + deiconified or changes the focus. + */ + BackingStore = 0x01 + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + explicit QwtPolarCanvas( QwtPolarPlot* ); + virtual ~QwtPolarCanvas(); + + QwtPolarPlot* plot(); + const QwtPolarPlot* plot() const; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + const QPixmap* backingStore() const; + void invalidateBackingStore(); + + QwtPointPolar invTransform( const QPoint& ) const; + QPoint transform( const QwtPointPolar& ) const; + + protected: + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + virtual void resizeEvent( QResizeEvent* ) QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPolarCanvas::PaintAttributes ) + +#endif diff --git a/libs/qwt/src/qwt_polar_curve.cpp b/libs/qwt/src/qwt_polar_curve.cpp new file mode 100644 index 00000000..6ceb24cc --- /dev/null +++ b/libs/qwt/src/qwt_polar_curve.cpp @@ -0,0 +1,596 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_curve.h" +#include "qwt_polar.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include "qwt_math.h" +#include "qwt_symbol.h" +#include "qwt_legend.h" +#include "qwt_curve_fitter.h" +#include "qwt_clipper.h" + +#include + +static inline bool qwtInsidePole( const QwtScaleMap& map, double radius ) +{ + return map.isInverting() ? ( radius > map.s1() ) : ( radius < map.s1() ); +} + +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 QwtPolarCurve::PrivateData +{ + public: + PrivateData() + : style( QwtPolarCurve::Lines ) + , curveFitter( NULL ) + { + symbol = new QwtSymbol(); + pen = QPen( Qt::black ); + } + + ~PrivateData() + { + delete symbol; + delete curveFitter; + } + + QwtPolarCurve::CurveStyle style; + const QwtSymbol* symbol; + QPen pen; + QwtCurveFitter* curveFitter; + + QwtPolarCurve::LegendAttributes legendAttributes; +}; + +//! Constructor +QwtPolarCurve::QwtPolarCurve() + : QwtPolarItem( QwtText() ) +{ + init(); +} + +/*! + Constructor + \param title title of the curve + */ +QwtPolarCurve::QwtPolarCurve( const QwtText& title ) + : QwtPolarItem( title ) +{ + init(); +} + +/*! + Constructor + \param title title of the curve + */ +QwtPolarCurve::QwtPolarCurve( const QString& title ) + : QwtPolarItem( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPolarCurve::~QwtPolarCurve() +{ + delete m_series; + delete m_data; +} + +//! Initialize data members +void QwtPolarCurve::init() +{ + m_data = new PrivateData; + m_series = NULL; + + setItemAttribute( QwtPolarItem::AutoScale ); + setItemAttribute( QwtPolarItem::Legend ); + setZ( 20.0 ); + + setRenderHint( RenderAntialiased, true ); +} + +//! \return QwtPolarCurve::Rtti_PolarCurve +int QwtPolarCurve::rtti() const +{ + return QwtPolarItem::Rtti_PolarCurve; +} + +/*! + Specify an attribute how to draw the legend identifier + + \param attribute Attribute + \param on On/Off + /sa LegendAttribute, testLegendAttribute() + */ +void QwtPolarCurve::setLegendAttribute( LegendAttribute attribute, bool on ) +{ + if ( on ) + m_data->legendAttributes |= attribute; + else + m_data->legendAttributes &= ~attribute; +} + +/*! + \brief Test if a legend attribute is enabled + + \param attribute Legend attribute + + \return True if attribute is enabled + \sa LegendAttribute, setLegendAttribute() + */ +bool QwtPolarCurve::testLegendAttribute( LegendAttribute attribute ) const +{ + return ( m_data->legendAttributes & attribute ); +} + +/*! + Set the curve's drawing style + + \param style Curve style + \sa CurveStyle, style() + */ +void QwtPolarCurve::setStyle( CurveStyle style ) +{ + if ( style != m_data->style ) + { + m_data->style = style; + itemChanged(); + } +} + +/*! + \return Current style + \sa CurveStyle, setStyle() + */ +QwtPolarCurve::CurveStyle QwtPolarCurve::style() const +{ + return m_data->style; +} + +/*! + \brief Assign a symbol + \param symbol Symbol + \sa symbol() + */ +void QwtPolarCurve::setSymbol( QwtSymbol* symbol ) +{ + if ( symbol != m_data->symbol ) + { + delete m_data->symbol; + m_data->symbol = symbol; + itemChanged(); + } +} + +/*! + \return The current symbol + \sa setSymbol() + */ +const QwtSymbol* QwtPolarCurve::symbol() const +{ + return m_data->symbol; +} + +/*! + \brief Assign a pen + \param pen New pen + \sa pen() + */ +void QwtPolarCurve::setPen( const QPen& pen ) +{ + if ( pen != m_data->pen ) + { + m_data->pen = pen; + itemChanged(); + } +} + +/*! + \return Pen used to draw the lines + \sa setPen() + */ +const QPen& QwtPolarCurve::pen() const +{ + return m_data->pen; +} + +/*! + Initialize data with a pointer to QwtSeriesData. + + The x-values of the data object represent the azimuth, + the y-value represents the radius. + + \param data Data + */ +void QwtPolarCurve::setData( QwtSeriesData< QwtPointPolar >* data ) +{ + if ( m_series != data ) + { + delete m_series; + m_series = data; + itemChanged(); + } +} + +/*! + \brief Insert a curve fitter + + \param curveFitter Curve fitter + + A curve fitter interpolates the curve points. F.e QwtPolarFitter + adds equidistant points so that the connection gets rounded instead + of having straight lines. If curveFitter is NULL fitting is disabled. + + \sa curveFitter() + */ +void QwtPolarCurve::setCurveFitter( QwtCurveFitter* curveFitter ) +{ + if ( curveFitter != m_data->curveFitter ) + { + delete m_data->curveFitter; + m_data->curveFitter = curveFitter; + + itemChanged(); + } +} + +/*! + \return The curve fitter + \sa setCurveFitter() + */ +QwtCurveFitter* QwtPolarCurve::curveFitter() const +{ + return m_data->curveFitter; +} + +/*! + Draw the curve + + \param painter Painter + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param radius Radius of the complete plot area in painter coordinates + \param canvasRect Contents rect of the canvas in painter coordinates + */ +void QwtPolarCurve::draw( QPainter* painter, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double radius, + const QRectF& canvasRect ) const +{ + Q_UNUSED( radius ); + Q_UNUSED( canvasRect ); + + draw( painter, azimuthMap, radialMap, pole, 0, -1 ); +} + +/*! + \brief Draw an interval of the curve + \param painter Painter + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \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 QwtPolarCurve::draw( QPainter* painter, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, int from, int to ) const +{ + if ( !painter || dataSize() <= 0 ) + return; + + if ( to < 0 ) + to = dataSize() - 1; + + if ( qwtVerifyRange( dataSize(), from, to ) > 0 ) + { + painter->save(); + painter->setPen( m_data->pen ); + + drawCurve( painter, m_data->style, + azimuthMap, radialMap, pole, from, to ); + + painter->restore(); + + if ( m_data->symbol->style() != QwtSymbol::NoSymbol ) + { + painter->save(); + drawSymbols( painter, *m_data->symbol, + azimuthMap, radialMap, pole, from, to ); + painter->restore(); + } + } +} + +/*! + Draw the line part (without symbols) of a curve interval. + + \param painter Painter + \param style Curve style, see QwtPolarCurve::CurveStyle + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param from index of the first point to be painted + \param to index of the last point to be painted. + \sa draw(), drawLines() + */ +void QwtPolarCurve::drawCurve( QPainter* painter, int style, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, int from, int to ) const +{ + switch ( style ) + { + case Lines: + drawLines( painter, azimuthMap, radialMap, pole, from, to ); + break; + case NoCurve: + default: + break; + } +} + +/*! + Draw lines + + \param painter Painter + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param from index of the first point to be painted + \param to index of the last point to be painted. + \sa draw(), drawLines(), setCurveFitter() + */ +void QwtPolarCurve::drawLines( QPainter* painter, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, int from, int to ) const +{ + int size = to - from + 1; + if ( size <= 0 ) + return; + + QPolygonF polyline; + + if ( m_data->curveFitter ) + { + QPolygonF points( size ); + for ( int j = from; j <= to; j++ ) + { + const QwtPointPolar point = sample( j ); + points[j - from] = QPointF( point.azimuth(), point.radius() ); + } + + points = m_data->curveFitter->fitCurve( points ); + + polyline.resize( points.size() ); + + QPointF* polylineData = polyline.data(); + QPointF* pointsData = points.data(); + + for ( int i = 0; i < points.size(); i++ ) + { + const QwtPointPolar point( pointsData[i].x(), pointsData[i].y() ); + + double r = radialMap.transform( point.radius() ); + const double a = azimuthMap.transform( point.azimuth() ); + + polylineData[i] = qwtPolar2Pos( pole, r, a ); + } + } + else + { + polyline.resize( size ); + QPointF* polylineData = polyline.data(); + + for ( int i = from; i <= to; i++ ) + { + QwtPointPolar point = sample( i ); + if ( !qwtInsidePole( radialMap, point.radius() ) ) + { + double r = radialMap.transform( point.radius() ); + const double a = azimuthMap.transform( point.azimuth() ); + polylineData[i - from] = qwtPolar2Pos( pole, r, a ); + } + else + { + polylineData[i - from] = pole; + } + } + } + + QRectF clipRect; + if ( painter->hasClipping() ) + { + clipRect = painter->clipRegion().boundingRect(); + } + else + { + clipRect = painter->window(); + if ( !clipRect.isEmpty() ) + clipRect = painter->transform().inverted().mapRect( clipRect ); + } + + if ( !clipRect.isEmpty() ) + { + double off = qCeil( qMax( qreal( 1.0 ), painter->pen().widthF() ) ); + clipRect = clipRect.toRect().adjusted( -off, -off, off, off ); + QwtClipper::clipPolygonF( clipRect, polyline ); + } + + QwtPainter::drawPolyline( painter, polyline ); +} + +/*! + Draw symbols + + \param painter Painter + \param symbol Curve symbol + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param from index of the first point to be painted + \param to index of the last point to be painted. + + \sa setSymbol(), draw(), drawCurve() + */ +void QwtPolarCurve::drawSymbols( QPainter* painter, const QwtSymbol& symbol, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, int from, int to ) const +{ + painter->setBrush( symbol.brush() ); + painter->setPen( symbol.pen() ); + + const int chunkSize = 500; + + for ( int i = from; i <= to; i += chunkSize ) + { + const int n = qMin( chunkSize, to - i + 1 ); + + QPolygonF points; + for ( int j = 0; j < n; j++ ) + { + const QwtPointPolar point = sample( i + j ); + + if ( !qwtInsidePole( radialMap, point.radius() ) ) + { + const double r = radialMap.transform( point.radius() ); + const double a = azimuthMap.transform( point.azimuth() ); + + points += qwtPolar2Pos( pole, r, a ); + } + else + { + points += pole; + } + } + + if ( points.size() > 0 ) + symbol.drawSymbols( painter, points ); + } +} + +/*! + \return Number of points + \sa setData() + */ +size_t QwtPolarCurve::dataSize() const +{ + return m_series->size(); +} + +/*! + \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 QwtPolarItem::setLegendIconSize(), QwtPolarItem::legendData() + */ +QwtGraphic QwtPolarCurve::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( QwtPolarItem::RenderAntialiased ) ); + + if ( m_data->legendAttributes == 0 ) + { + QBrush brush; + + if ( style() != QwtPolarCurve::NoCurve ) + { + brush = QBrush( pen().color() ); + } + else if ( m_data->symbol && + ( m_data->symbol->style() != QwtSymbol::NoSymbol ) ) + { + brush = QBrush( m_data->symbol->pen().color() ); + } + + if ( brush.style() != Qt::NoBrush ) + { + QRectF r( 0, 0, size.width(), size.height() ); + painter.fillRect( r, brush ); + } + } + + if ( m_data->legendAttributes & QwtPolarCurve::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 ( m_data->legendAttributes & QwtPolarCurve::LegendShowSymbol ) + { + if ( m_data->symbol ) + { + QRectF r( 0, 0, size.width(), size.height() ); + m_data->symbol->drawSymbol( &painter, r ); + } + } + + return graphic; +} + +/*! + Interval, that is necessary to display the item + This interval can be useful for operations like clipping or autoscaling + + \param scaleId Scale index + \return bounding interval + + \sa QwtData::boundingRect() + */ +QwtInterval QwtPolarCurve::boundingInterval( int scaleId ) const +{ + const QRectF boundingRect = m_series->boundingRect(); + + if ( scaleId == QwtPolar::ScaleAzimuth ) + return QwtInterval( boundingRect.left(), boundingRect.right() ); + + if ( scaleId == QwtPolar::ScaleRadius ) + return QwtInterval( boundingRect.top(), boundingRect.bottom() ); + + return QwtInterval(); +} diff --git a/libs/qwt/src/qwt_polar_curve.h b/libs/qwt/src/qwt_polar_curve.h new file mode 100644 index 00000000..768749e3 --- /dev/null +++ b/libs/qwt/src/qwt_polar_curve.h @@ -0,0 +1,160 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_CURVE_H +#define QWT_POLAR_CURVE_H + +#include "qwt_global.h" +#include "qwt_polar_item.h" +#include "qwt_point_polar.h" +#include "qwt_series_data.h" + +class QPainter; +class QwtSymbol; +class QwtCurveFitter; + +/*! + \brief An item, that represents a series of points + + A curve is the representation of a series of points in polar coordinates. + The points are connected to the curve using the abstract QwtData interface. + + \sa QwtPolarPlot, QwtSymbol, QwtScaleMap + */ + +class QWT_EXPORT QwtPolarCurve : public QwtPolarItem +{ + public: + /*! + Curve styles. + \sa setStyle(), style() + */ + enum CurveStyle + { + //! Don't draw a curve. Note: This doesn't affect the symbols. + NoCurve, + + /*! + Connect the points with straight lines. The lines might + be interpolated depending on the 'Fitted' attribute. Curve + fitting can be configured using setCurveFitter(). + */ + Lines, + + //! Values > 100 are reserved for user specific curve styles + UserCurve = 100 + }; + + /*! + \brief Attributes how to represent the curve on the legend + + If none of the flags is activated QwtPlotCurve tries to find + a color representing the curve and paints a rectangle with it. + In the default setting all attributes are off. + + \sa setLegendAttribute(), testLegendAttribute() + */ + + enum LegendAttribute + { + /*! + If the curveStyle() is not NoCurve a line is painted with the + curvePen(). + */ + LegendShowLine = 0x01, + + //! If the curve has a valid symbol it is painted. + LegendShowSymbol = 0x02 + }; + + Q_DECLARE_FLAGS( LegendAttributes, LegendAttribute ) + + + explicit QwtPolarCurve(); + explicit QwtPolarCurve( const QwtText& title ); + explicit QwtPolarCurve( const QString& title ); + + virtual ~QwtPolarCurve(); + + virtual int rtti() const QWT_OVERRIDE; + + void setLegendAttribute( LegendAttribute, bool on = true ); + bool testLegendAttribute( LegendAttribute ) const; + + void setData( QwtSeriesData< QwtPointPolar >* data ); + const QwtSeriesData< QwtPointPolar >* data() const; + + size_t dataSize() const; + QwtPointPolar sample( int i ) const; + + void setPen( const QPen& ); + const QPen& pen() const; + + void setStyle( CurveStyle style ); + CurveStyle style() const; + + void setSymbol( QwtSymbol* ); + const QwtSymbol* symbol() const; + + void setCurveFitter( QwtCurveFitter* ); + QwtCurveFitter* curveFitter() const; + + virtual void draw( QPainter* p, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double radius, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + virtual void draw( QPainter* p, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, int from, int to ) const; + + virtual QwtInterval boundingInterval( int scaleId ) const QWT_OVERRIDE; + + virtual QwtGraphic legendIcon( int index, const QSizeF& ) const QWT_OVERRIDE; + + protected: + + void init(); + + virtual void drawCurve( QPainter*, int style, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, int from, int to ) const; + + virtual void drawSymbols( QPainter*, const QwtSymbol&, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, int from, int to ) const; + + void drawLines( QPainter*, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, int from, int to ) const; + + private: + QwtSeriesData< QwtPointPolar >* m_series; + + class PrivateData; + PrivateData* m_data; +}; + +//! \return the the curve data +inline const QwtSeriesData< QwtPointPolar >* QwtPolarCurve::data() const +{ + return m_series; +} + +/*! + \param i index + \return point at position i + */ +inline QwtPointPolar QwtPolarCurve::sample( int i ) const +{ + return m_series->sample( i ); +} + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPolarCurve::LegendAttributes ) + +#endif diff --git a/libs/qwt/src/qwt_polar_fitter.cpp b/libs/qwt/src/qwt_polar_fitter.cpp new file mode 100644 index 00000000..ec1cfea0 --- /dev/null +++ b/libs/qwt/src/qwt_polar_fitter.cpp @@ -0,0 +1,115 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_fitter.h" +#include +#include + +class QwtPolarFitter::PrivateData +{ + public: + PrivateData() + : stepCount( 5 ) + { + } + + int stepCount; +}; + +/*! + Constructor + + \param stepCount Number of points, that will be inserted between 2 points + \sa setStepCount() + */ +QwtPolarFitter::QwtPolarFitter( int stepCount ) + : QwtCurveFitter( QwtPolarFitter::Polygon ) +{ + m_data = new PrivateData; + m_data->stepCount = stepCount; +} + +//! Destructor +QwtPolarFitter::~QwtPolarFitter() +{ + delete m_data; +} + +/*! + Assign the number of points, that will be inserted between 2 points + The default value is 5. + + \param stepCount Number of steps + + \sa stepCount() + */ +void QwtPolarFitter::setStepCount( int stepCount ) +{ + m_data->stepCount = qMax( stepCount, 0 ); +} + +/*! + \return Number of points, that will be inserted between 2 points + \sa setStepCount() + */ +int QwtPolarFitter::stepCount() const +{ + return m_data->stepCount; +} + +/*! + Insert stepCount() number of additional points between 2 elements + of points. + + \param points Array of points + \return Array of points including the additional points + */ +QPolygonF QwtPolarFitter::fitCurve( const QPolygonF& points ) const +{ + if ( m_data->stepCount <= 0 || points.size() <= 1 ) + return points; + + QPolygonF fittedPoints; + + int numPoints = points.size() + ( points.size() - 1 ) * m_data->stepCount; + + fittedPoints.resize( numPoints ); + + int index = 0; + fittedPoints[index++] = points[0]; + for ( int i = 1; i < points.size(); i++ ) + { + const QPointF& p1 = points[i - 1]; + const QPointF& p2 = points[i]; + + const double dx = ( p2.x() - p1.x() ) / m_data->stepCount; + const double dy = ( p2.y() - p1.y() ) / m_data->stepCount; + for ( int j = 1; j <= m_data->stepCount; j++ ) + { + const double x = p1.x() + j * dx; + const double y = p1.y() + j * dy; + + fittedPoints[index++] = QPointF( x, y ); + } + } + fittedPoints.resize( index ); + + return fittedPoints; +} + +/*! + \param points Series of data points + \return Curve path + \sa fitCurve() + */ +QPainterPath QwtPolarFitter::fitCurvePath( const QPolygonF& points ) const +{ + QPainterPath path; + path.addPolygon( fitCurve( points ) ); + return path; +} diff --git a/libs/qwt/src/qwt_polar_fitter.h b/libs/qwt/src/qwt_polar_fitter.h new file mode 100644 index 00000000..ad19b508 --- /dev/null +++ b/libs/qwt/src/qwt_polar_fitter.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_FITTER_H +#define QWT_POLAR_FITTER_H + +#include "qwt_global.h" +#include "qwt_curve_fitter.h" + +/*! + \brief A simple curve fitter for polar points + + QwtPolarFitter adds equidistant points between 2 curve points, + so that the connection gets rounded according to the nature of + a polar plot. + + \sa QwtPolarCurve::setCurveFitter() + */ +class QWT_EXPORT QwtPolarFitter : public QwtCurveFitter +{ + public: + QwtPolarFitter( int stepCount = 5 ); + virtual ~QwtPolarFitter(); + + void setStepCount( int size ); + int stepCount() const; + + virtual QPolygonF fitCurve( const QPolygonF& ) const QWT_OVERRIDE; + virtual QPainterPath fitCurvePath( const QPolygonF& ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_polar_grid.cpp b/libs/qwt/src/qwt_polar_grid.cpp new file mode 100644 index 00000000..2ce7992e --- /dev/null +++ b/libs/qwt/src/qwt_polar_grid.cpp @@ -0,0 +1,1148 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_grid.h" +#include "qwt_painter.h" +#include "qwt_text.h" +#include "qwt_clipper.h" +#include "qwt_scale_map.h" +#include "qwt_scale_engine.h" +#include "qwt_scale_div.h" +#include "qwt_scale_draw.h" +#include "qwt_round_scale_draw.h" + +#include +#include +#include + +static inline bool isClose( double value1, double value2 ) +{ + return qAbs( value1 - value2 ) < DBL_EPSILON; +} + +namespace +{ + class AxisData + { + public: + AxisData() + : isVisible( false ) + , scaleDraw( NULL ) + { + } + + ~AxisData() + { + delete scaleDraw; + } + + bool isVisible; + mutable QwtAbstractScaleDraw* scaleDraw; + QPen pen; + QFont font; + }; + + class GridData + { + public: + GridData() + : isVisible( true ) + , isMinorVisible( false ) + { + } + + bool isVisible; + bool isMinorVisible; + QwtScaleDiv scaleDiv; + + QPen majorPen; + QPen minorPen; + }; +} + +class QwtPolarGrid::PrivateData +{ + public: + GridData gridData[QwtPolar::ScaleCount]; + AxisData axisData[QwtPolar::AxesCount]; + QwtPolarGrid::DisplayFlags displayFlags; + QwtPolarGrid::GridAttributes attributes; +}; + +/*! + \brief Constructor + + Enables major and disables minor grid lines. + The azimuth and right radial axis are visible. all other axes + are hidden. Autoscaling is enabled. + */ +QwtPolarGrid::QwtPolarGrid() + : QwtPolarItem( QwtText( "Grid" ) ) +{ + m_data = new PrivateData; + + for ( int axisId = 0; axisId < QwtPolar::AxesCount; axisId++ ) + { + AxisData& axis = m_data->axisData[axisId]; + switch( axisId ) + { + case QwtPolar::AxisAzimuth: + { + axis.scaleDraw = new QwtRoundScaleDraw; + axis.scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 2 ); + axis.scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 2 ); + axis.scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 4 ); + axis.isVisible = true; + break; + } + case QwtPolar::AxisLeft: + { + QwtScaleDraw* scaleDraw = new QwtScaleDraw; + scaleDraw->setAlignment( QwtScaleDraw::BottomScale ); + + axis.scaleDraw = scaleDraw; + axis.isVisible = false; + break; + } + case QwtPolar::AxisRight: + { + QwtScaleDraw* scaleDraw = new QwtScaleDraw; + scaleDraw->setAlignment( QwtScaleDraw::BottomScale ); + + axis.scaleDraw = scaleDraw; + axis.isVisible = true; + break; + } + case QwtPolar::AxisTop: + { + QwtScaleDraw* scaleDraw = new QwtScaleDraw; + scaleDraw->setAlignment( QwtScaleDraw::LeftScale ); + + axis.scaleDraw = scaleDraw; + axis.isVisible = false; + break; + } + case QwtPolar::AxisBottom: + { + QwtScaleDraw* scaleDraw = new QwtScaleDraw; + scaleDraw->setAlignment( QwtScaleDraw::LeftScale ); + + axis.scaleDraw = scaleDraw; + axis.isVisible = true; + break; + } + default:; + } + } + + m_data->attributes = AutoScaling; + + m_data->displayFlags = DisplayFlags(); + m_data->displayFlags |= SmartOriginLabel; + m_data->displayFlags |= HideMaxRadiusLabel; + m_data->displayFlags |= ClipAxisBackground; + m_data->displayFlags |= SmartScaleDraw; + m_data->displayFlags |= ClipGridLines; + + setZ( 10.0 ); + setRenderHint( RenderAntialiased, true ); +} + +//! Destructor +QwtPolarGrid::~QwtPolarGrid() +{ + delete m_data; +} + +//! \return QwtPlotItem::Rtti_PolarGrid +int QwtPolarGrid::rtti() const +{ + return QwtPolarItem::Rtti_PolarGrid; +} + +/*! + Change the display flags + + \param flag See DisplayFlag + \param on true/false + */ +void QwtPolarGrid::setDisplayFlag( DisplayFlag flag, bool on ) +{ + if ( ( ( m_data->displayFlags & flag ) != 0 ) != on ) + { + if ( on ) + m_data->displayFlags |= flag; + else + m_data->displayFlags &= ~flag; + + itemChanged(); + } +} + +/*! + \return true, if flag is enabled + \param flag See DisplayFlag + */ +bool QwtPolarGrid::testDisplayFlag( DisplayFlag flag ) const +{ + return ( m_data->displayFlags & flag ); +} + +/*! + \brief Specify an attribute for the grid + + \param attribute Grid attribute + \param on On/Off + + /sa GridAttribute, testGridAttribute(), updateScaleDiv(), + QwtPolarPlot::zoom(), QwtPolarPlot::scaleDiv() + */ +void QwtPolarGrid::setGridAttribute( GridAttribute attribute, bool on ) +{ + if ( bool( m_data->attributes & attribute ) == on ) + return; + + if ( on ) + m_data->attributes |= attribute; + else + m_data->attributes &= ~attribute; + + itemChanged(); +} + +/*! + \return true, if attribute is enabled + \sa GridAttribute, setGridAttribute() + */ +bool QwtPolarGrid::testGridAttribute( GridAttribute attribute ) const +{ + return m_data->attributes & attribute; +} + +/*! + Assign a pen for painting an axis + + \param axisId Axis id (QwtPolar::Axis) + \param pen Pen + + \sa axisPen() + */ +void QwtPolarGrid::setAxisPen( int axisId, const QPen& pen ) +{ + if ( axisId < 0 || axisId >= QwtPolar::AxesCount ) + return; + + AxisData& axisData = m_data->axisData[axisId]; + if ( axisData.pen != pen ) + { + axisData.pen = pen; + itemChanged(); + } +} + +/*! + Show/Hide grid lines for a scale + + \param scaleId Scale id ( QwtPolar::Scale ) + \param show true/false + */ +void QwtPolarGrid::showGrid( int scaleId, bool show ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return; + + GridData& grid = m_data->gridData[scaleId]; + if ( grid.isVisible != show ) + { + grid.isVisible = show; + itemChanged(); + } +} + +/*! + \return true if grid lines are enabled + \param scaleId Scale id ( QwtPolar::Scale ) + \sa QwtPolar::Scale, showGrid() + */ +bool QwtPolarGrid::isGridVisible( int scaleId ) const +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return false; + + return m_data->gridData[scaleId].isVisible; +} + +/*! + Show/Hide minor grid lines for a scale + + To display minor grid lines. showGrid() needs to be enabled too. + + \param scaleId Scale id ( QwtPolar::Scale ) + \param show true/false + + \sa showGrid + */ +void QwtPolarGrid::showMinorGrid( int scaleId, bool show ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return; + + GridData& grid = m_data->gridData[scaleId]; + if ( grid.isMinorVisible != show ) + { + grid.isMinorVisible = show; + itemChanged(); + } +} + +/*! + \return true if minor grid lines are enabled + \param scaleId Scale id ( QwtPolar::Scale ) + \sa showMinorGrid() + */ +bool QwtPolarGrid::isMinorGridVisible( int scaleId ) const +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return false; + + return m_data->gridData[scaleId].isMinorVisible; +} + +/*! + Show/Hide an axis + + \param axisId Axis id (QwtPolar::Axis) + \param show true/false + + \sa isAxisVisible() + */ +void QwtPolarGrid::showAxis( int axisId, bool show ) +{ + if ( axisId < 0 || axisId >= QwtPolar::AxesCount ) + return; + + AxisData& axisData = m_data->axisData[axisId]; + if ( axisData.isVisible != show ) + { + axisData.isVisible = show; + itemChanged(); + } +} + +/*! + \return true if the axis is visible + \param axisId Axis id (QwtPolar::Axis) + + \sa showAxis() + */ +bool QwtPolarGrid::isAxisVisible( int axisId ) const +{ + if ( axisId < 0 || axisId >= QwtPolar::AxesCount ) + return false; + + return m_data->axisData[axisId].isVisible; +} + +/*! + Assign a pen for all axes and grid lines + + \param pen Pen + \sa setMajorGridPen(), setMinorGridPen(), setAxisPen() + */ +void QwtPolarGrid::setPen( const QPen& pen ) +{ + bool isChanged = false; + + for ( int scaleId = 0; scaleId < QwtPolar::ScaleCount; scaleId++ ) + { + GridData& grid = m_data->gridData[scaleId]; + if ( grid.majorPen != pen || grid.minorPen != pen ) + { + grid.majorPen = pen; + grid.minorPen = pen; + isChanged = true; + } + } + for ( int axisId = 0; axisId < QwtPolar::AxesCount; axisId++ ) + { + AxisData& axis = m_data->axisData[axisId]; + if ( axis.pen != pen ) + { + axis.pen = pen; + isChanged = true; + } + } + if ( isChanged ) + itemChanged(); +} + +/*! + Assign a font for all scale tick labels + + \param font Font + \sa setAxisFont() + */ +void QwtPolarGrid::setFont( const QFont& font ) +{ + bool isChanged = false; + for ( int axisId = 0; axisId < QwtPolar::AxesCount; axisId++ ) + { + AxisData& axis = m_data->axisData[axisId]; + if ( axis.font != font ) + { + axis.font = font; + isChanged = true; + } + } + if ( isChanged ) + itemChanged(); +} + +/*! + Assign a pen for the major grid lines + + \param pen Pen + \sa setPen(), setMinorGridPen(), majorGridPen + */ +void QwtPolarGrid::setMajorGridPen( const QPen& pen ) +{ + bool isChanged = false; + + for ( int scaleId = 0; scaleId < QwtPolar::ScaleCount; scaleId++ ) + { + GridData& grid = m_data->gridData[scaleId]; + if ( grid.majorPen != pen ) + { + grid.majorPen = pen; + isChanged = true; + } + } + if ( isChanged ) + itemChanged(); +} + +/*! + Assign a pen for the major grid lines of a specific scale + + \param scaleId Scale id ( QwtPolar::Scale ) + \param pen Pen + \sa setPen(), setMinorGridPen(), majorGridPen + */ +void QwtPolarGrid::setMajorGridPen( int scaleId, const QPen& pen ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return; + + GridData& grid = m_data->gridData[scaleId]; + if ( grid.majorPen != pen ) + { + grid.majorPen = pen; + itemChanged(); + } +} + +/*! + \return Pen for painting the major grid lines of a specific scale + \param scaleId Scale id ( QwtPolar::Scale ) + \sa setMajorGridPen(), minorGridPen() + */ +QPen QwtPolarGrid::majorGridPen( int scaleId ) const +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return QPen(); + + const GridData& grid = m_data->gridData[scaleId]; + return grid.majorPen; +} + +/*! + Assign a pen for the minor grid lines + + \param pen Pen + \sa setPen(), setMajorGridPen(), minorGridPen() + */ +void QwtPolarGrid::setMinorGridPen( const QPen& pen ) +{ + bool isChanged = false; + + for ( int scaleId = 0; scaleId < QwtPolar::ScaleCount; scaleId++ ) + { + GridData& grid = m_data->gridData[scaleId]; + if ( grid.minorPen != pen ) + { + grid.minorPen = pen; + isChanged = true; + } + } + if ( isChanged ) + itemChanged(); +} + +/*! + Assign a pen for the minor grid lines of a specific scale + + \param scaleId Scale id ( QwtPolar::Scale ) + \param pen Pen + \sa setPen(), setMajorGridPen(), minorGridPen + */ +void QwtPolarGrid::setMinorGridPen( int scaleId, const QPen& pen ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return; + + GridData& grid = m_data->gridData[scaleId]; + if ( grid.minorPen != pen ) + { + grid.minorPen = pen; + itemChanged(); + } +} + +/*! + \return Pen for painting the minor grid lines of a specific scale + \param scaleId Scale id ( QwtPolar::Scale ) + */ +QPen QwtPolarGrid::minorGridPen( int scaleId ) const +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return QPen(); + + const GridData& grid = m_data->gridData[scaleId]; + return grid.minorPen; +} + +/*! + \return Pen for painting a specific axis + + \param axisId Axis id (QwtPolar::Axis) + \sa setAxisPen() + */ +QPen QwtPolarGrid::axisPen( int axisId ) const +{ + if ( axisId < 0 || axisId >= QwtPolar::AxesCount ) + return QPen(); + + return m_data->axisData[axisId].pen; +} + +/*! + Assign a font for the tick labels of a specific axis + + \param axisId Axis id (QwtPolar::Axis) + \param font new Font + */ +void QwtPolarGrid::setAxisFont( int axisId, const QFont& font ) +{ + if ( axisId < 0 || axisId >= QwtPolar::AxesCount ) + return; + + AxisData& axisData = m_data->axisData[axisId]; + if ( axisData.font != font ) + { + axisData.font = font; + itemChanged(); + } +} + +/*! + \return Font for the tick labels of a specific axis + \param axisId Axis id (QwtPolar::Axis) + */ +QFont QwtPolarGrid::axisFont( int axisId ) const +{ + if ( axisId < 0 || axisId >= QwtPolar::AxesCount ) + return QFont(); + + return m_data->axisData[axisId].font; +} + +/*! + Draw the grid and axes + + \param painter Painter + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param radius Radius of the complete plot area in painter coordinates + \param canvasRect Contents rect of the canvas in painter coordinates + */ +void QwtPolarGrid::draw( QPainter* painter, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double radius, + const QRectF& canvasRect ) const +{ + updateScaleDraws( azimuthMap, radialMap, pole, radius ); + + painter->save(); + + if ( testDisplayFlag( ClipAxisBackground ) ) + { + QRegion clipRegion( canvasRect.toRect() ); + for ( int axisId = 0; axisId < QwtPolar::AxesCount; axisId++ ) + { + const AxisData& axis = m_data->axisData[axisId]; + if ( axisId != QwtPolar::AxisAzimuth && axis.isVisible ) + { + QwtScaleDraw* scaleDraw = static_cast< QwtScaleDraw* >( axis.scaleDraw ); + if ( scaleDraw->hasComponent( QwtScaleDraw::Labels ) ) + { + const QList< double >& ticks = + scaleDraw->scaleDiv().ticks( QwtScaleDiv::MajorTick ); + for ( int i = 0; i < int( ticks.size() ); i++ ) + { + if ( !scaleDraw->scaleDiv().contains( ticks[i] ) ) + continue; + + QRect labelRect = + scaleDraw->boundingLabelRect( axis.font, ticks[i] ); + + const int margin = 2; + labelRect.adjust( -margin, -margin, margin, margin ); + + if ( labelRect.isValid() ) + clipRegion -= QRegion( labelRect ); + } + } + } + } + painter->setClipRegion( clipRegion ); + } + + // draw radial grid + + const GridData& radialGrid = m_data->gridData[QwtPolar::Radius]; + if ( radialGrid.isVisible && radialGrid.isMinorVisible ) + { + painter->setPen( radialGrid.minorPen ); + + drawCircles( painter, canvasRect, pole, radialMap, + radialGrid.scaleDiv.ticks( QwtScaleDiv::MinorTick ) ); + drawCircles( painter, canvasRect, pole, radialMap, + radialGrid.scaleDiv.ticks( QwtScaleDiv::MediumTick ) ); + } + if ( radialGrid.isVisible ) + { + painter->setPen( radialGrid.majorPen ); + + drawCircles( painter, canvasRect, pole, radialMap, + radialGrid.scaleDiv.ticks( QwtScaleDiv::MajorTick ) ); + } + + // draw azimuth grid + + const GridData& azimuthGrid = + m_data->gridData[QwtPolar::Azimuth]; + + if ( azimuthGrid.isVisible && azimuthGrid.isMinorVisible ) + { + painter->setPen( azimuthGrid.minorPen ); + + drawRays( painter, canvasRect, pole, radius, azimuthMap, + azimuthGrid.scaleDiv.ticks( QwtScaleDiv::MinorTick ) ); + drawRays( painter, canvasRect, pole, radius, azimuthMap, + azimuthGrid.scaleDiv.ticks( QwtScaleDiv::MediumTick ) ); + } + if ( azimuthGrid.isVisible ) + { + painter->setPen( azimuthGrid.majorPen ); + + drawRays( painter, canvasRect, pole, radius, azimuthMap, + azimuthGrid.scaleDiv.ticks( QwtScaleDiv::MajorTick ) ); + } + painter->restore(); + + for ( int axisId = 0; axisId < QwtPolar::AxesCount; axisId++ ) + { + const AxisData& axis = m_data->axisData[axisId]; + if ( axis.isVisible ) + { + painter->save(); + drawAxis( painter, axisId ); + painter->restore(); + } + } +} + +/*! + Draw lines from the pole + + \param painter Painter + \param canvasRect Contents rect of the canvas in painter coordinates + \param pole Position of the pole in painter coordinates + \param radius Length of the lines in painter coordinates + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param values Azimuth values, indicating the direction of the lines + */ +void QwtPolarGrid::drawRays( + QPainter* painter, const QRectF& canvasRect, + const QPointF& pole, double radius, + const QwtScaleMap& azimuthMap, const QList< double >& values ) const +{ + for ( int i = 0; i < int( values.size() ); i++ ) + { + double azimuth = azimuthMap.transform( values[i] ); + azimuth = ::fmod( azimuth, 2 * M_PI ); + + bool skipLine = false; + if ( testDisplayFlag( SmartScaleDraw ) ) + { + const QwtAbstractScaleDraw::ScaleComponent bone = + QwtAbstractScaleDraw::Backbone; + if ( isClose( azimuth, 0.0 ) ) + { + const AxisData& axis = m_data->axisData[QwtPolar::AxisRight]; + if ( axis.isVisible && axis.scaleDraw->hasComponent( bone ) ) + skipLine = true; + } + else if ( isClose( azimuth, M_PI / 2 ) ) + { + const AxisData& axis = m_data->axisData[QwtPolar::AxisTop]; + if ( axis.isVisible && axis.scaleDraw->hasComponent( bone ) ) + skipLine = true; + } + else if ( isClose( azimuth, M_PI ) ) + { + const AxisData& axis = m_data->axisData[QwtPolar::AxisLeft]; + if ( axis.isVisible && axis.scaleDraw->hasComponent( bone ) ) + skipLine = true; + } + else if ( isClose( azimuth, 3 * M_PI / 2.0 ) ) + { + const AxisData& axis = m_data->axisData[QwtPolar::AxisBottom]; + if ( axis.isVisible && axis.scaleDraw->hasComponent( bone ) ) + skipLine = true; + } + } + if ( !skipLine ) + { + const QPointF pos = qwtPolar2Pos( pole, radius, azimuth ); + + /* + Qt4 is horrible slow, when painting primitives, + with coordinates far outside the visible area. + */ + + QPolygonF polygon( 2 ); + polygon[0] = pole.toPoint(); + polygon[1] = pos.toPoint(); + + if ( testDisplayFlag( ClipGridLines ) ) + QwtClipper::clipPolygonF( canvasRect, polygon ); + + QwtPainter::drawPolyline( painter, polygon ); + } + } +} + +/*! + Draw circles + + \param painter Painter + \param canvasRect Contents rect of the canvas in painter coordinates + \param pole Position of the pole in painter coordinates + \param radialMap Maps radius values into painter coordinates. + \param values Radial values, indicating the distances from the pole + */ +void QwtPolarGrid::drawCircles( + QPainter* painter, const QRectF& canvasRect, + const QPointF& pole, const QwtScaleMap& radialMap, + const QList< double >& values ) const +{ + for ( int i = 0; i < int( values.size() ); i++ ) + { + const double val = values[i]; + + const GridData& gridData = + m_data->gridData[QwtPolar::Radius]; + + bool skipLine = false; + if ( testDisplayFlag( SmartScaleDraw ) ) + { + const AxisData& axis = m_data->axisData[QwtPolar::AxisAzimuth]; + if ( axis.isVisible && + axis.scaleDraw->hasComponent( QwtAbstractScaleDraw::Backbone ) ) + { + if ( isClose( val, gridData.scaleDiv.upperBound() ) ) + skipLine = true; + } + } + + if ( isClose( val, gridData.scaleDiv.lowerBound() ) ) + skipLine = true; + + if ( !skipLine ) + { + const double radius = radialMap.transform( val ); + + QRectF outerRect( 0, 0, 2 * radius, 2 * radius ); + outerRect.moveCenter( pole ); + + if ( testDisplayFlag( ClipGridLines ) ) + { + /* + Qt4 is horrible slow, when painting primitives, + with coordinates far outside the visible area. + We need to clip. + */ + + const QVector< QwtInterval > angles = + QwtClipper::clipCircle( canvasRect, pole, radius ); + + for ( int j = 0; j < angles.size(); j++ ) + { + const QwtInterval intv = angles[j]; + + if ( intv.minValue() == 0 && intv.maxValue() == 2 * M_PI ) + { + QwtPainter::drawEllipse( painter, outerRect ); + } + else + { + const double from = qwtDegrees( intv.minValue() ); + const double to = qwtDegrees( intv.maxValue() ); + + double span = to - from; + if ( span < 0.0 ) + span += 360.0; + + painter->drawArc( outerRect, + qRound( from * 16 ), qRound( span * 16 ) ); + } + } + } + else + { + QwtPainter::drawEllipse( painter, outerRect ); + } + } + } +} + +/*! + Paint an axis + + \param painter Painter + \param axisId Axis id (QwtPolar::Axis) + */ +void QwtPolarGrid::drawAxis( QPainter* painter, int axisId ) const +{ + if ( axisId < 0 || axisId >= QwtPolar::AxesCount ) + return; + + AxisData& axis = m_data->axisData[axisId]; + + painter->setPen( axis.pen ); + painter->setFont( axis.font ); + + QPalette pal; + pal.setColor( QPalette::WindowText, axis.pen.color() ); + pal.setColor( QPalette::Text, axis.pen.color() ); + + axis.scaleDraw->draw( painter, pal ); +} + +/*! + Update the axis scale draw geometries + + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param radius Radius of the complete plot area in painter coordinates + + \sa updateScaleDiv() + */ +void QwtPolarGrid::updateScaleDraws( + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double radius ) const +{ + const QPoint p = pole.toPoint(); + + const QwtInterval interval = + m_data->gridData[QwtPolar::ScaleRadius].scaleDiv.interval(); + + const int min = radialMap.transform( interval.minValue() ); + const int max = radialMap.transform( interval.maxValue() ); + const int l = max - min; + + for ( int axisId = 0; axisId < QwtPolar::AxesCount; axisId++ ) + { + AxisData& axis = m_data->axisData[axisId]; + + if ( axisId == QwtPolar::AxisAzimuth ) + { + QwtRoundScaleDraw* scaleDraw = + static_cast< QwtRoundScaleDraw* >( axis.scaleDraw ); + + scaleDraw->setRadius( qRound( radius ) ); + scaleDraw->moveCenter( p ); + + double from = ::fmod( 90.0 - qwtDegrees( azimuthMap.p1() ), 360.0 ); + if ( from < 0.0 ) + from += 360.0; + + scaleDraw->setAngleRange( from, from - 360.0 ); + + const QwtTransform* transform = azimuthMap.transformation(); + if ( transform ) + scaleDraw->setTransformation( transform->copy() ); + else + scaleDraw->setTransformation( NULL ); + } + else + { + QwtScaleDraw* scaleDraw = + static_cast< QwtScaleDraw* >( axis.scaleDraw ); + + switch( axisId ) + { + case QwtPolar::AxisLeft: + { + scaleDraw->move( p.x() - min, p.y() ); + scaleDraw->setLength( -l ); + break; + } + case QwtPolar::AxisRight: + { + scaleDraw->move( p.x() + min, p.y() ); + scaleDraw->setLength( l ); + break; + } + case QwtPolar::AxisTop: + { + scaleDraw->move( p.x(), p.y() - max ); + scaleDraw->setLength( l ); + break; + } + case QwtPolar::AxisBottom: + { + scaleDraw->move( p.x(), p.y() + max ); + scaleDraw->setLength( -l ); + break; + } + } + const QwtTransform* transform = radialMap.transformation(); + if ( transform ) + scaleDraw->setTransformation( transform->copy() ); + else + scaleDraw->setTransformation( NULL ); + } + } +} + +/*! + \brief Update the item to changes of the axes scale division + + If AutoScaling is enabled the radial scale is calculated + from the interval, otherwise the scales are adopted to + the plot scales. + + \param azimuthScaleDiv Scale division of the azimuth-scale + \param radialScaleDiv Scale division of the radius-axis + \param interval The interval of the radius-axis, that is + visible on the canvas + + \sa QwtPolarPlot::setGridAttributes() + */ + +void QwtPolarGrid::updateScaleDiv( const QwtScaleDiv& azimuthScaleDiv, + const QwtScaleDiv& radialScaleDiv, const QwtInterval& interval ) +{ + GridData& radialGrid = m_data->gridData[QwtPolar::Radius]; + + const QwtPolarPlot* plt = plot(); + if ( plt && testGridAttribute( AutoScaling ) ) + { + const QwtScaleEngine* se = plt->scaleEngine( QwtPolar::Radius ); + radialGrid.scaleDiv = se->divideScale( + interval.minValue(), interval.maxValue(), + plt->scaleMaxMajor( QwtPolar::Radius ), + plt->scaleMaxMinor( QwtPolar::Radius ), 0 ); + } + else + { + if ( radialGrid.scaleDiv != radialScaleDiv ) + radialGrid.scaleDiv = radialScaleDiv; + } + + GridData& azimuthGrid = m_data->gridData[QwtPolar::Azimuth]; + if ( azimuthGrid.scaleDiv != azimuthScaleDiv ) + { + azimuthGrid.scaleDiv = azimuthScaleDiv; + } + + bool hasOrigin = false; + for ( int axisId = 0; axisId < QwtPolar::AxesCount; axisId++ ) + { + AxisData& axis = m_data->axisData[axisId]; + if ( axis.isVisible && axis.scaleDraw ) + { + if ( axisId == QwtPolar::AxisAzimuth ) + { + axis.scaleDraw->setScaleDiv( azimuthGrid.scaleDiv ); + if ( testDisplayFlag( SmartScaleDraw ) ) + { + axis.scaleDraw->enableComponent( + QwtAbstractScaleDraw::Ticks, !azimuthGrid.isVisible ); + } + } + else + { + QwtScaleDiv sd = radialGrid.scaleDiv; + + QList< double > ticks = sd.ticks( QwtScaleDiv::MajorTick ); + + if ( testDisplayFlag( SmartOriginLabel ) ) + { + bool skipOrigin = hasOrigin; + if ( !skipOrigin ) + { + if ( axisId == QwtPolar::AxisLeft + || axisId == QwtPolar::AxisRight ) + { + if ( m_data->axisData[QwtPolar::AxisBottom].isVisible ) + skipOrigin = true; + } + else + { + if ( m_data->axisData[QwtPolar::AxisLeft].isVisible ) + skipOrigin = true; + } + } + if ( ticks.size() > 0 && ticks.first() == sd.lowerBound() ) + { + if ( skipOrigin ) + ticks.removeFirst(); + else + hasOrigin = true; + } + } + + if ( testDisplayFlag( HideMaxRadiusLabel ) ) + { + if ( ticks.size() > 0 && ticks.last() == sd.upperBound() ) + ticks.removeLast(); + } + + sd.setTicks( QwtScaleDiv::MajorTick, ticks ); + axis.scaleDraw->setScaleDiv( sd ); + + if ( testDisplayFlag( SmartScaleDraw ) ) + { + axis.scaleDraw->enableComponent( + QwtAbstractScaleDraw::Ticks, !radialGrid.isVisible ); + } + + } + } + } +} + +/*! + \return Number of pixels, that are necessary to paint the azimuth scale + \sa QwtRoundScaleDraw::extent() + */ +int QwtPolarGrid::marginHint() const +{ + const AxisData& axis = m_data->axisData[QwtPolar::AxisAzimuth]; + if ( axis.isVisible ) + { + const int extent = axis.scaleDraw->extent( axis.font ); + return extent; + } + + return 0; +} + +/*! + Returns the scale draw of a specified axis + + \param axisId axis index ( QwtPolar::AxisLeft <= axisId <= QwtPolar::AxisBottom) + \return specified scaleDraw for axis, or NULL if axis is invalid. + \sa azimuthScaleDraw() + */ +const QwtScaleDraw* QwtPolarGrid::scaleDraw( int axisId ) const +{ + if ( axisId >= QwtPolar::AxisLeft && axisId <= QwtPolar::AxisBottom ) + return static_cast< QwtScaleDraw* >( m_data->axisData[axisId].scaleDraw ); + + return NULL; +} + +/*! + Returns the scale draw of a specified axis + + \param axisId axis index ( QwtPolar::AxisLeft <= axisId <= QwtPolar::AxisBottom) + \return specified scaleDraw for axis, or NULL if axis is invalid. + \sa setScaleDraw(), azimuthScaleDraw() + */ +QwtScaleDraw* QwtPolarGrid::scaleDraw( int axisId ) +{ + if ( axisId >= QwtPolar::AxisLeft && axisId <= QwtPolar::AxisBottom ) + return static_cast< QwtScaleDraw* >( m_data->axisData[axisId].scaleDraw ); + + return NULL; +} + +/*! + \brief Set a scale draw + + \param axisId axis index ( QwtPolar::AxisLeft <= axisId <= QwtPolar::AxisBottom) + \param scaleDraw object responsible for drawing scales. + + \sa scaleDraw(), setAzimuthScaleDraw() + */ +void QwtPolarGrid::setScaleDraw( int axisId, QwtScaleDraw* scaleDraw ) +{ + if ( axisId < QwtPolar::AxisLeft || axisId > QwtPolar::AxisBottom ) + return; + + AxisData& axisData = m_data->axisData[axisId]; + if ( axisData.scaleDraw != scaleDraw ) + { + delete axisData.scaleDraw; + axisData.scaleDraw = scaleDraw; + itemChanged(); + } +} + +/*! + \return Scale draw for the azimuth scale + \sa setAzimuthScaleDraw(), scaleDraw() + */ +const QwtRoundScaleDraw* QwtPolarGrid::azimuthScaleDraw() const +{ + return static_cast< QwtRoundScaleDraw* >( + m_data->axisData[QwtPolar::AxisAzimuth].scaleDraw ); +} + +/*! + \return Scale draw for the azimuth scale + \sa setAzimuthScaleDraw(), scaleDraw() + */ +QwtRoundScaleDraw* QwtPolarGrid::azimuthScaleDraw() +{ + return static_cast< QwtRoundScaleDraw* >( + m_data->axisData[QwtPolar::AxisAzimuth].scaleDraw ); +} + +/*! + \brief Set a scale draw for the azimuth scale + + \param scaleDraw object responsible for drawing scales. + \sa azimuthScaleDraw(), setScaleDraw() + */ +void QwtPolarGrid::setAzimuthScaleDraw( QwtRoundScaleDraw* scaleDraw ) +{ + AxisData& axisData = m_data->axisData[QwtPolar::AxisAzimuth]; + if ( axisData.scaleDraw != scaleDraw ) + { + delete axisData.scaleDraw; + axisData.scaleDraw = scaleDraw; + itemChanged(); + } +} diff --git a/libs/qwt/src/qwt_polar_grid.h b/libs/qwt/src/qwt_polar_grid.h new file mode 100644 index 00000000..561f9db5 --- /dev/null +++ b/libs/qwt/src/qwt_polar_grid.h @@ -0,0 +1,183 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_GRID_H +#define QWT_POLAR_GRID_H + +#include "qwt_global.h" +#include "qwt_polar.h" +#include "qwt_polar_item.h" +#include "qwt_polar_plot.h" + +class QPainter; +class QPen; +class QwtScaleMap; +class QwtScaleDiv; +class QwtRoundScaleDraw; +class QwtScaleDraw; + +/*! + \brief An item which draws scales and grid lines on a polar plot. + + The QwtPolarGrid class can be used to draw a coordinate grid. + A coordinate grid consists of major and minor gridlines. + The locations of the gridlines are determined by the azimuth and radial + scale divisions. + + QwtPolarGrid is also responsible for drawing the axis representing the + scales. It is possible to display 4 radial and one azimuth axis. + + Whenever the scale divisions of the plot widget changes the grid + is synchronized by updateScaleDiv(). + + \sa QwtPolarPlot, QwtPolar::Axis + */ + +class QWT_EXPORT QwtPolarGrid : public QwtPolarItem +{ + public: + /*! + Mysterious flags trying to avoid conflicts, when painting the + scales and grid lines. + + The default setting enables all flags. + + \sa setDisplayFlag(), testDisplayFlag() + */ + enum DisplayFlag + { + /*! + Try to avoid situations, where the label of the origin is + painted over another axis. + */ + SmartOriginLabel = 1, + + /*! + Often the outermost tick of the radial scale is close to the + canvas border. With HideMaxRadiusLabel enabled it is not painted. + */ + HideMaxRadiusLabel = 2, + + /*! + The tick labels of the radial scales might be hard to read, when + they are painted on top of the radial grid lines ( or on top + of a curve/spectrogram ). When ClipAxisBackground the bounding rect + of each label is added to the clip region. + */ + ClipAxisBackground = 4, + + /*! + Don't paint the backbone of the radial axes, when they are very close + to a line of the azimuth grid. + */ + SmartScaleDraw = 8, + + /*! + All grid lines are clipped against the plot area before being painted. + When the plot is zoomed in this will have an significant impact + on the performance of the painting code. + */ + ClipGridLines = 16 + }; + + Q_DECLARE_FLAGS( DisplayFlags, DisplayFlag ) + + /*! + \brief Grid attributes + \sa setGridAttributes(), testGridAttributes() + */ + enum GridAttribute + { + /*! + When AutoScaling is enabled, the radial axes will be adjusted + to the interval, that is currently visible on the canvas plot. + */ + AutoScaling = 0x01 + }; + + Q_DECLARE_FLAGS( GridAttributes, GridAttribute ) + + explicit QwtPolarGrid(); + virtual ~QwtPolarGrid(); + + virtual int rtti() const QWT_OVERRIDE; + + void setDisplayFlag( DisplayFlag, bool on = true ); + bool testDisplayFlag( DisplayFlag ) const; + + void setGridAttribute( GridAttribute, bool on = true ); + bool testGridAttribute( GridAttribute ) const; + + void showGrid( int scaleId, bool show = true ); + bool isGridVisible( int scaleId ) const; + + void showMinorGrid( int scaleId, bool show = true ); + bool isMinorGridVisible( int scaleId ) const; + + void showAxis( int axisId, bool show = true ); + bool isAxisVisible( int axisId ) const; + + void setPen( const QPen& p ); + void setFont( const QFont& ); + + void setMajorGridPen( const QPen& p ); + void setMajorGridPen( int scaleId, const QPen& p ); + QPen majorGridPen( int scaleId ) const; + + void setMinorGridPen( const QPen& p ); + void setMinorGridPen( int scaleId, const QPen& p ); + QPen minorGridPen( int scaleId ) const; + + void setAxisPen( int axisId, const QPen& p ); + QPen axisPen( int axisId ) const; + + void setAxisFont( int axisId, const QFont& p ); + QFont axisFont( int axisId ) const; + + void setScaleDraw( int axisId, QwtScaleDraw* ); + const QwtScaleDraw* scaleDraw( int axisId ) const; + QwtScaleDraw* scaleDraw( int axisId ); + + void setAzimuthScaleDraw( QwtRoundScaleDraw* ); + const QwtRoundScaleDraw* azimuthScaleDraw() const; + QwtRoundScaleDraw* azimuthScaleDraw(); + + virtual void draw( QPainter* p, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double radius, + const QRectF& rect ) const QWT_OVERRIDE; + + virtual void updateScaleDiv( const QwtScaleDiv& azimuthMap, + const QwtScaleDiv& radialMap, const QwtInterval& ) QWT_OVERRIDE; + + virtual int marginHint() const QWT_OVERRIDE; + + protected: + void drawRays( QPainter*, const QRectF&, + const QPointF& pole, double radius, + const QwtScaleMap& azimuthMap, const QList< double >& ) const; + void drawCircles( QPainter*, const QRectF&, + const QPointF& pole, const QwtScaleMap& radialMap, + const QList< double >& ) const; + + void drawAxis( QPainter*, int axisId ) const; + + private: + void updateScaleDraws( + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, const double radius ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPolarGrid::DisplayFlags ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPolarGrid::GridAttributes ) + +#endif diff --git a/libs/qwt/src/qwt_polar_item.cpp b/libs/qwt/src/qwt_polar_item.cpp new file mode 100644 index 00000000..9543de84 --- /dev/null +++ b/libs/qwt/src/qwt_polar_item.cpp @@ -0,0 +1,481 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_plot.h" +#include "qwt_polar_item.h" +#include "qwt_legend.h" +#include "qwt_scale_div.h" + +#include + +class QwtPolarItem::PrivateData +{ + public: + PrivateData() + : plot( NULL ) + , isVisible( true ) + , renderThreadCount( 1 ) + , z( 0.0 ) + , legendIconSize( 8, 8 ) + { + } + + mutable QwtPolarPlot* plot; + + bool isVisible; + QwtPolarItem::ItemAttributes attributes; + QwtPolarItem::RenderHints renderHints; + uint renderThreadCount; + + double z; + + QwtText title; + QSize legendIconSize; +}; + +/*! + Constructor + + \param title Item title, f.e used on a legend + + \sa setTitle() + */ +QwtPolarItem::QwtPolarItem( const QwtText& title ) +{ + m_data = new PrivateData; + m_data->title = title; +} + +//! Destroy the QwtPolarItem +QwtPolarItem::~QwtPolarItem() +{ + attach( NULL ); + delete m_data; +} + +/*! + \brief Attach the item to a plot. + + This method will attach a QwtPolarItem to the QwtPolarPlot argument. + It will first detach the QwtPolarItem from any plot from a previous + call to attach (if necessary). + If a NULL argument is passed, it will detach from any QwtPolarPlot it + was attached to. + + \param plot Plot widget + + \sa QwtPolarItem::detach() + */ +void QwtPolarItem::attach( QwtPolarPlot* plot ) +{ + if ( plot == m_data->plot ) + return; + + if ( m_data->plot ) + m_data->plot->attachItem( this, false ); + + m_data->plot = plot; + + if ( m_data->plot ) + m_data->plot->attachItem( this, true ); +} + +/*! + \brief This method detaches a QwtPolarItem from the QwtPolarPlot it + has been associated with. + + detach() is equivalent to calling attach( NULL ) + \sa attach() + */ +void QwtPolarItem::detach() +{ + attach( NULL ); +} + +/*! + Return rtti for the specific class represented. QwtPolarItem 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 QwtPolarItem::rtti() const +{ + return Rtti_PolarItem; +} + +//! \return Attached plot +QwtPolarPlot* QwtPolarItem::plot() const +{ + return m_data->plot; +} + +/*! + Plot items are painted in increasing z-order. + + \return Z value + \sa setZ(), QwtPolarItemDict::itemList() + */ +double QwtPolarItem::z() const +{ + return m_data->z; +} + +/*! + \brief Set the z value + + Plot items are painted in increasing z-order. + + \param z Z-value + \sa z(), QwtPolarItemDict::itemList() + */ +void QwtPolarItem::setZ( double z ) +{ + if ( m_data->z != z ) + { + if ( m_data->plot ) + m_data->plot->attachItem( this, false ); + + m_data->z = z; + + if ( m_data->plot ) + m_data->plot->attachItem( this, true ); + + itemChanged(); + } +} + +/*! + Set a new title + + \param title Title + \sa title() + */ +void QwtPolarItem::setTitle( const QString& title ) +{ + setTitle( QwtText( title ) ); +} + +/*! + Set a new title + + \param title Title + \sa title() + */ +void QwtPolarItem::setTitle( const QwtText& title ) +{ + if ( m_data->title != title ) + { + m_data->title = title; + itemChanged(); + } +} + +/*! + \return Title of the item + \sa setTitle() + */ +const QwtText& QwtPolarItem::title() const +{ + return m_data->title; +} + +/*! + Toggle an item attribute + + \param attribute Attribute type + \param on true/false + + \sa testItemAttribute(), ItemAttribute + */ +void QwtPolarItem::setItemAttribute( ItemAttribute attribute, bool on ) +{ + if ( bool( m_data->attributes & attribute ) != on ) + { + if ( on ) + m_data->attributes |= attribute; + else + m_data->attributes &= ~attribute; + + itemChanged(); + } +} + +/*! + Test an item attribute + + \param attribute Attribute type + \return true/false + \sa setItemAttribute(), ItemAttribute + */ +bool QwtPolarItem::testItemAttribute( ItemAttribute attribute ) const +{ + return m_data->attributes & attribute; +} + +/*! + Toggle an render hint + + \param hint Render hint + \param on true/false + + \sa testRenderHint(), RenderHint + */ +void QwtPolarItem::setRenderHint( RenderHint hint, bool on ) +{ + if ( ( ( m_data->renderHints & hint ) != 0 ) != on ) + { + if ( on ) + m_data->renderHints |= hint; + else + m_data->renderHints &= ~hint; + + itemChanged(); + } +} + +/*! + Test a render hint + + \param hint Render hint + \return true/false + \sa setRenderHint(), RenderHint + */ +bool QwtPolarItem::testRenderHint( RenderHint hint ) const +{ + return ( m_data->renderHints & hint ); +} + +/*! + On multi core systems rendering of certain plot item + ( f.e QwtPolarSpectrogram ) 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 QwtPolarItem::setRenderThreadCount( uint numThreads ) +{ + m_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 QwtPolarItem::renderThreadCount() const +{ + return m_data->renderThreadCount; +} + +/*! + Set the size of the legend icon + + The default setting is 8x8 pixels + + \param size Size + \sa legendIconSize(), legendIcon() + */ +void QwtPolarItem::setLegendIconSize( const QSize& size ) +{ + if ( m_data->legendIconSize != size ) + { + m_data->legendIconSize = size; + legendChanged(); + } +} + +/*! + \return Legend icon size + \sa setLegendIconSize(), legendIcon() + */ +QSize QwtPolarItem::legendIconSize() const +{ + return m_data->legendIconSize; +} + +//! Show the item +void QwtPolarItem::show() +{ + setVisible( true ); +} + +//! Hide the item +void QwtPolarItem::hide() +{ + setVisible( false ); +} + +/*! + Show/Hide the item + + \param on Show if true, otherwise hide + \sa isVisible(), show(), hide() + */ +void QwtPolarItem::setVisible( bool on ) +{ + if ( on != m_data->isVisible ) + { + m_data->isVisible = on; + itemChanged(); + } +} + +/*! + \return true if visible + \sa setVisible(), show(), hide() + */ +bool QwtPolarItem::isVisible() const +{ + return m_data->isVisible; +} + +/*! + Update the legend and call QwtPolarPlot::autoRefresh for the + parent plot. + + \sa updateLegend() + */ +void QwtPolarItem::itemChanged() +{ + if ( m_data->plot ) + m_data->plot->autoRefresh(); +} + +/*! + Update the legend of the parent plot. + \sa QwtPolarPlot::updateLegend(), itemChanged() + */ +void QwtPolarItem::legendChanged() +{ + if ( testItemAttribute( QwtPolarItem::Legend ) && m_data->plot ) + m_data->plot->updateLegend( this ); +} + +/*! + Interval, that is necessary to display the item + + This interval can be useful for operations like clipping or autoscaling + For items ( like the grid ), where a bounding interval makes no + sense an invalid interval is returned. + + \param scaleId Scale id ( QwtPolar::Scale ) + \return Bounding interval of the plot item for a specific scale + */ +QwtInterval QwtPolarItem::boundingInterval( int scaleId ) const +{ + Q_UNUSED( scaleId ); + + return QwtInterval(); // invalid +} + +/*! + \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 QwtPolarGrid()) have to reimplement + updateScaleDiv() + + \param azimuthScaleDiv Scale division of the azimuth-scale + \param radialScaleDiv Scale division of the radius-axis + \param interval The interval of the radius-axis, that is + visible on the canvas + + \sa QwtPolarPlot::updateAxes() + */ +void QwtPolarItem::updateScaleDiv( const QwtScaleDiv& azimuthScaleDiv, + const QwtScaleDiv& radialScaleDiv, const QwtInterval& interval ) +{ + Q_UNUSED( azimuthScaleDiv ); + Q_UNUSED( radialScaleDiv ); + Q_UNUSED( interval ); +} + +/*! + \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. + + 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(). + + \sa title(), legendIcon(), QwtLegend + */ +QList< QwtLegendData > QwtPolarItem::legendData() const +{ + QwtLegendData data; + + QwtText label = title(); + label.setRenderFlags( label.renderFlags() & Qt::AlignLeft ); + + data.setValue( QwtLegendData::TitleRole, + QVariant::fromValue( label ) ); + + const QwtGraphic graphic = legendIcon( 0, legendIconSize() ); + if ( !graphic.isNull() ) + { + data.setValue( QwtLegendData::IconRole, + QVariant::fromValue( graphic ) ); + } + + QList< QwtLegendData > list; + list += data; + + return list; +} + +/*! + \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 QwtPolarItem::legendIcon( + int index, const QSizeF& size ) const +{ + Q_UNUSED( index ) + Q_UNUSED( size ) + + return QwtGraphic(); +} + +/*! + Some items like to display something (f.e. the azimuth axis) outside + of the area of the interval of the radial scale. + The default implementation returns 0 pixels + + \return Hint for the margin + */ +int QwtPolarItem::marginHint() const +{ + return 0; +} diff --git a/libs/qwt/src/qwt_polar_item.h b/libs/qwt/src/qwt_polar_item.h new file mode 100644 index 00000000..d1b931d9 --- /dev/null +++ b/libs/qwt/src/qwt_polar_item.h @@ -0,0 +1,174 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_ITEM_H +#define QWT_POLAR_ITEM_H + +#include "qwt_global.h" +#include "qwt_text.h" +#include "qwt_legend_data.h" +#include "qwt_graphic.h" +#include "qwt_interval.h" + +class QString; +class QRect; +class QPointF; +class QPainter; +class QwtPolarPlot; +class QwtScaleMap; +class QwtScaleDiv; + +/*! + \brief Base class for items on a polar plot + + A QwtPolarItem is "something that can be painted on the canvas". + It is connected to the QwtPolar framework by a couple of virtual + methods, that are individually implemented in derived item classes. + + QwtPolar offers an implementation of the most common types of items, + but deriving from QwtPolarItem makes it easy to implement additional + types of items. + */ +class QWT_EXPORT QwtPolarItem +{ + 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_PolarItem = 0, + + //! For QwtPolarGrid + Rtti_PolarGrid, + + //! For QwtPolarMarker + Rtti_PolarMarker, + + //! For QwtPolarCurve + Rtti_PolarCurve, + + //! For QwtPolarSpectrogram + Rtti_PolarSpectrogram, + + /*! + Values >= Rtti_PolarUserItem are reserved for plot items + not implemented in the QwtPolar library. + */ + Rtti_PolarUserItem = 1000 + }; + + /*! + \brief Plot Item Attributes + \sa setItemAttribute(), testItemAttribute() + */ + enum ItemAttribute + { + //! The item is represented on the legend. + Legend = 0x01, + + /*! + The boundingRect() of the item is included in the + autoscaling calculation. + */ + AutoScale = 0x02 + }; + + Q_DECLARE_FLAGS( ItemAttributes, ItemAttribute ) + + /*! + \brief Render hints + \sa setRenderHint(), testRenderHint() + */ + enum RenderHint + { + //! Enable antialiasing + RenderAntialiased = 0x01 + }; + + Q_DECLARE_FLAGS( RenderHints, RenderHint ) + + explicit QwtPolarItem( const QwtText& title = QwtText() ); + virtual ~QwtPolarItem(); + + void attach( QwtPolarPlot* plot ); + void detach(); + + QwtPolarPlot* 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 setRenderHint( RenderHint, bool on = true ); + bool testRenderHint( RenderHint ) const; + + void setRenderThreadCount( uint numThreads ); + uint renderThreadCount() const; + + double z() const; + void setZ( double z ); + + void show(); + void hide(); + virtual void setVisible( bool ); + bool isVisible () const; + + virtual void itemChanged(); + virtual void legendChanged(); + + /*! + \brief Draw the item + + \param painter Painter + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param radius Radius of the complete plot area in painter coordinates + \param canvasRect Contents rect of the canvas in painter coordinates + */ + virtual void draw( QPainter* painter, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double radius, + const QRectF& canvasRect ) const = 0; + + virtual QwtInterval boundingInterval( int scaleId ) const; + + virtual void updateScaleDiv( const QwtScaleDiv&, + const QwtScaleDiv&, const QwtInterval& ); + + virtual int marginHint() const; + + void setLegendIconSize( const QSize& ); + QSize legendIconSize() const; + + virtual QList< QwtLegendData > legendData() const; + virtual QwtGraphic legendIcon( int index, const QSizeF& ) const; + + private: + Q_DISABLE_COPY( QwtPolarItem ) + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPolarItem::ItemAttributes ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPolarItem::RenderHints ) + +Q_DECLARE_METATYPE( QwtPolarItem* ) + +#endif diff --git a/libs/qwt/src/qwt_polar_itemdict.cpp b/libs/qwt/src/qwt_polar_itemdict.cpp new file mode 100644 index 00000000..6f1e3fc8 --- /dev/null +++ b/libs/qwt/src/qwt_polar_itemdict.cpp @@ -0,0 +1,171 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_itemdict.h" + +class QwtPolarItemDict::PrivateData +{ + public: + class ItemList : public QList< QwtPolarItem* > + { + public: + void insertItem( QwtPolarItem* item ) + { + if ( item == NULL ) + return; + + // Unfortunately there is no inSort operation + // for lists in Qt4. The implementation below + // is slow, but there shouldn't be many plot items. + + QList< QwtPolarItem* >::Iterator it; + for ( it = begin(); it != end(); ++it ) + { + if ( *it == item ) + return; + + if ( ( *it )->z() > item->z() ) + { + insert( it, item ); + return; + } + } + append( item ); + } + + void removeItem( QwtPolarItem* item ) + { + if ( item == NULL ) + return; + + int i = 0; + + QList< QwtPolarItem* >::Iterator it; + for ( it = begin(); it != end(); ++it ) + { + if ( item == *it ) + { + removeAt( i ); + return; + } + i++; + } + } + }; + + ItemList itemList; + bool autoDelete; +}; + +/*! + Constructor + + Auto deletion is enabled. + \sa setAutoDelete, attachItem + */ +QwtPolarItemDict::QwtPolarItemDict() +{ + m_data = new QwtPolarItemDict::PrivateData; + m_data->autoDelete = true; +} + +/*! + Destructor + + If autoDelete is on, all attached items will be deleted + \sa setAutoDelete, autoDelete, attachItem + */ +QwtPolarItemDict::~QwtPolarItemDict() +{ + detachItems( QwtPolarItem::Rtti_PolarItem, m_data->autoDelete ); + delete m_data; +} + +/*! + En/Disable Auto deletion + + If Auto deletion is on all attached plot items will be deleted + in the destructor of QwtPolarItemDict. The default value is on. + + \sa autoDelete, attachItem + */ +void QwtPolarItemDict::setAutoDelete( bool autoDelete ) +{ + m_data->autoDelete = autoDelete; +} + +/*! + \return true if auto deletion is enabled + \sa setAutoDelete, attachItem + */ +bool QwtPolarItemDict::autoDelete() const +{ + return m_data->autoDelete; +} + +/*! + Insert a plot item + + \param item PlotItem + \sa removeItem() + */ +void QwtPolarItemDict::insertItem( QwtPolarItem* item ) +{ + m_data->itemList.insertItem( item ); +} + +/*! + Remove a plot item + + \param item PlotItem + \sa insertItem() + */ +void QwtPolarItemDict::removeItem( QwtPolarItem* item ) +{ + m_data->itemList.removeItem( item ); +} + +/*! + Detach items from the dictionary + + \param rtti In case of QwtPolarItem::Rtti_PlotItem detach all items + otherwise only those items of the type rtti. + \param autoDelete If true, delete all detached items + */ +void QwtPolarItemDict::detachItems( int rtti, bool autoDelete ) +{ + PrivateData::ItemList list = m_data->itemList; + QwtPolarItemIterator it = list.constBegin(); + while ( it != list.constEnd() ) + { + QwtPolarItem* item = *it; + + ++it; // increment before removing item from the list + + if ( rtti == QwtPolarItem::Rtti_PolarItem || item->rtti() == rtti ) + { + item->attach( NULL ); + if ( autoDelete ) + delete item; + } + } +} + +/*! + \brief A QwtPolarItemList of all attached plot items. + + \return List of all attached plot items. + \note 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. + */ +const QwtPolarItemList& QwtPolarItemDict::itemList() const +{ + return m_data->itemList; +} diff --git a/libs/qwt/src/qwt_polar_itemdict.h b/libs/qwt/src/qwt_polar_itemdict.h new file mode 100644 index 00000000..80c0a54d --- /dev/null +++ b/libs/qwt/src/qwt_polar_itemdict.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_ITEMDICT_H +#define QWT_POLAR_ITEMDICT_H + +#include "qwt_global.h" +#include "qwt_polar_item.h" +#include + +typedef QList< QwtPolarItem* >::ConstIterator QwtPolarItemIterator; +typedef QList< QwtPolarItem* > QwtPolarItemList; + +/*! + \brief A dictionary for polar plot items + + QwtPolarItemDict organizes polar plot items in increasing z-order. + If autoDelete() is enabled, all attached items will be deleted + in the destructor of the dictionary. + + \sa QwtPolarItem::attach(), QwtPolarItem::detach(), QwtPolarItem::z() + */ +class QWT_EXPORT QwtPolarItemDict +{ + public: + explicit QwtPolarItemDict(); + ~QwtPolarItemDict(); + + void setAutoDelete( bool ); + bool autoDelete() const; + + const QwtPolarItemList& itemList() const; + + void detachItems( int rtti = QwtPolarItem::Rtti_PolarItem, + bool autoDelete = true ); + + protected: + void insertItem( QwtPolarItem* ); + void removeItem( QwtPolarItem* ); + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_polar_layout.cpp b/libs/qwt/src/qwt_polar_layout.cpp new file mode 100644 index 00000000..dccfc2bd --- /dev/null +++ b/libs/qwt/src/qwt_polar_layout.cpp @@ -0,0 +1,443 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_layout.h" +#include "qwt_polar_plot.h" +#include "qwt_polar_canvas.h" +#include "qwt_text.h" +#include "qwt_text_label.h" +#include "qwt_legend.h" + +class QwtPolarLayout::LayoutData +{ + public: + void init( const QwtPolarPlot*, const QRectF& rect ); + + struct t_legendData + { + int frameWidth; + int hScrollExtent; + int vScrollExtent; + QSizeF hint; + } legend; + + struct t_titleData + { + QwtText text; + int frameWidth; + } title; + + struct t_canvasData + { + int frameWidth; + } canvas; +}; + +void QwtPolarLayout::LayoutData::init( + const QwtPolarPlot* plot, const QRectF& rect ) +{ + // legend + + if ( plot->plotLayout()->legendPosition() != QwtPolarPlot::ExternalLegend + && plot->legend() ) + { + legend.frameWidth = plot->legend()->frameWidth(); + legend.hScrollExtent = + plot->legend()->scrollExtent( Qt::Horizontal ); + legend.vScrollExtent = + plot->legend()->scrollExtent( Qt::Vertical ); + + const QSizeF hint = plot->legend()->sizeHint(); + + double w = qMin( hint.width(), rect.width() ); + double h = plot->legend()->heightForWidth( w ); + if ( h == 0.0 ) + h = hint.height(); + + if ( h > rect.height() ) + w += legend.hScrollExtent; + + legend.hint = QSizeF( w, h ); + } + + // title + + title.frameWidth = 0; + title.text = QwtText(); + + if ( plot->titleLabel() ) + { + const QwtTextLabel* label = plot->titleLabel(); + title.text = label->text(); + if ( !( title.text.testPaintAttribute( QwtText::PaintUsingTextFont ) ) ) + title.text.setFont( label->font() ); + + title.frameWidth = plot->titleLabel()->frameWidth(); + } + + // canvas + + canvas.frameWidth = plot->canvas()->frameWidth(); +} + +class QwtPolarLayout::PrivateData +{ + public: + PrivateData() + : margin( 0 ) + , spacing( 0 ) + { + } + + QRectF titleRect; + QRectF legendRect; + QRectF canvasRect; + + QwtPolarLayout::LayoutData layoutData; + + QwtPolarPlot::LegendPosition legendPos; + double legendRatio; + + unsigned int margin; + unsigned int spacing; +}; + +/*! + \brief Constructor + */ + +QwtPolarLayout::QwtPolarLayout() +{ + m_data = new PrivateData; + + setLegendPosition( QwtPolarPlot::BottomLegend ); + invalidate(); +} + +//! Destructor +QwtPolarLayout::~QwtPolarLayout() +{ + delete m_data; +} + +/*! + \brief Specify the position of the legend + \param pos The legend's position. + \param ratio Ratio between legend and the bounding rect + 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 QwtPolarPlot::setLegendPosition() + */ + +void QwtPolarLayout::setLegendPosition( + QwtPolarPlot::LegendPosition pos, double ratio ) +{ + if ( ratio > 1.0 ) + ratio = 1.0; + + switch( pos ) + { + case QwtPolarPlot::TopLegend: + case QwtPolarPlot::BottomLegend: + { + if ( ratio <= 0.0 ) + ratio = 0.33; + m_data->legendRatio = ratio; + m_data->legendPos = pos; + break; + } + case QwtPolarPlot::LeftLegend: + case QwtPolarPlot::RightLegend: + { + if ( ratio <= 0.0 ) + ratio = 0.5; + m_data->legendRatio = ratio; + m_data->legendPos = pos; + break; + } + case QwtPolarPlot::ExternalLegend: + { + m_data->legendRatio = ratio; // meaningless + m_data->legendPos = pos; + break; + } + default: + break; + } +} + +/*! + \brief Specify the position of the legend + \param pos The legend's position. Valid values are + \c QwtPolarPlot::LeftLegend, \c QwtPolarPlot::RightLegend, + \c QwtPolarPlot::TopLegend, \c QwtPolarPlot::BottomLegend. + + \sa QwtPolarPlot::setLegendPosition() + */ +void QwtPolarLayout::setLegendPosition( QwtPolarPlot::LegendPosition pos ) +{ + setLegendPosition( pos, 0.0 ); +} + +/*! + \return Position of the legend + \sa setLegendPosition(), QwtPolarPlot::setLegendPosition(), + QwtPolarPlot::legendPosition() + */ +QwtPolarPlot::LegendPosition QwtPolarLayout::legendPosition() const +{ + return m_data->legendPos; +} + +/*! + Specify the relative size of the legend in the plot + \param ratio Ratio between legend and the bounding rect + 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. + */ +void QwtPolarLayout::setLegendRatio( double ratio ) +{ + setLegendPosition( legendPosition(), ratio ); +} + +/*! + \return The relative size of the legend in the plot. + \sa setLegendPosition() + */ +double QwtPolarLayout::legendRatio() const +{ + return m_data->legendRatio; +} + +/*! + \return Geometry for the title + \sa activate(), invalidate() + */ + +const QRectF& QwtPolarLayout::titleRect() const +{ + return m_data->titleRect; +} + +/*! + \return Geometry for the legend + \sa activate(), invalidate() + */ + +const QRectF& QwtPolarLayout::legendRect() const +{ + return m_data->legendRect; +} + +/*! + \return Geometry for the canvas + \sa activate(), invalidate() + */ +const QRectF& QwtPolarLayout::canvasRect() const +{ + return m_data->canvasRect; +} + +/*! + Invalidate the geometry of all components. + \sa activate() + */ +void QwtPolarLayout::invalidate() +{ + m_data->titleRect = m_data->legendRect = m_data->canvasRect = QRect(); +} + +/*! + Find the geometry for the legend + \param options Options how to layout the legend + \param rect Rectangle where to place the legend + \return Geometry for the legend + */ + +QRectF QwtPolarLayout::layoutLegend( Options options, QRectF& rect ) const +{ + const QSizeF hint( m_data->layoutData.legend.hint ); + + int dim; + if ( m_data->legendPos == QwtPolarPlot::LeftLegend + || m_data->legendPos == QwtPolarPlot::RightLegend ) + { + // We don't allow vertical legends to take more than + // half of the available space. + + dim = qMin( double( hint.width() ), rect.width() * m_data->legendRatio ); + + if ( !( options & IgnoreScrollbars ) ) + { + if ( hint.height() > rect.height() ) + { + // The legend will need additional + // space for the vertical scrollbar. + + dim += m_data->layoutData.legend.hScrollExtent; + } + } + } + else + { + dim = qMin( double( hint.height() ), rect.height() * m_data->legendRatio ); + dim = qMax( dim, m_data->layoutData.legend.vScrollExtent ); + } + + QRectF legendRect = rect; + switch( m_data->legendPos ) + { + case QwtPolarPlot::LeftLegend: + { + legendRect.setWidth( dim ); + rect.setLeft( legendRect.right() ); + break; + } + case QwtPolarPlot::RightLegend: + { + legendRect.setX( rect.right() - dim + 1 ); + legendRect.setWidth( dim ); + rect.setRight( legendRect.left() ); + break; + } + case QwtPolarPlot::TopLegend: + { + legendRect.setHeight( dim ); + rect.setTop( legendRect.bottom() ); + break; + } + case QwtPolarPlot::BottomLegend: + { + legendRect.setY( rect.bottom() - dim + 1 ); + legendRect.setHeight( dim ); + rect.setBottom( legendRect.top() ); + break; + } + case QwtPolarPlot::ExternalLegend: + break; + } + + return legendRect; +} + +/*! + \brief Recalculate the geometry of all components. + + \param plot Plot to be layout + \param boundingRect Rect where to place the components + \param options Options + + \sa invalidate(), titleRect(), legendRect(), canvasRect() + */ +void QwtPolarLayout::activate( const QwtPolarPlot* plot, + const QRectF& boundingRect, Options options ) +{ + invalidate(); + + QRectF rect( boundingRect ); // undistributed rest of the plot rect + rect.adjust( m_data->margin, m_data->margin, + -m_data->margin, -m_data->margin ); + + // We extract all layout relevant data from the widgets + // and save them to m_data->layoutData. + + m_data->layoutData.init( plot, rect ); + if ( !( options & IgnoreLegend ) + && m_data->legendPos != QwtPolarPlot::ExternalLegend + && plot->legend() && !plot->legend()->isEmpty() ) + { + m_data->legendRect = layoutLegend( options, rect ); + if ( m_data->layoutData.legend.frameWidth && + !( options & IgnoreFrames ) ) + { + // In case of a frame we have to insert a spacing. + // Otherwise the leading of the font separates + // legend and scale/canvas + + switch( m_data->legendPos ) + { + case QwtPolarPlot::LeftLegend: + rect.setLeft( rect.left() + m_data->spacing ); + break; + + case QwtPolarPlot::RightLegend: + rect.setRight( rect.right() - m_data->spacing ); + break; + + case QwtPolarPlot::TopLegend: + rect.setTop( rect.top() + m_data->spacing ); + break; + + case QwtPolarPlot::BottomLegend: + rect.setBottom( rect.bottom() - m_data->spacing ); + break; + + case QwtPolarPlot::ExternalLegend: + break; // suppress compiler warning + } + } + } + + if ( !( options & IgnoreTitle ) && + !m_data->layoutData.title.text.isEmpty() ) + { + int h = m_data->layoutData.title.text.heightForWidth( rect.width() ); + if ( !( options & IgnoreFrames ) ) + h += 2 * m_data->layoutData.title.frameWidth; + + m_data->titleRect = QRectF( rect.x(), rect.y(), rect.width(), h ); + + // subtract title + rect.setTop( rect.top() + h + m_data->spacing ); + } + + if ( plot->zoomPos().radius() > 0.0 || plot->zoomFactor() < 1.0 ) + { + // In zoomed state we have no idea about the geometry that + // is best for the plot. So we use the complete rectangle + // accepting, that there might a lot of space wasted + // around the plot. + + m_data->canvasRect = rect; + } + else + { + // In full state we know, that we want + // to display something circular. + + const int dim = qMin( rect.width(), rect.height() ); + + m_data->canvasRect.setX( rect.center().x() - dim / 2 ); + m_data->canvasRect.setY( rect.y() ); + m_data->canvasRect.setSize( QSize( dim, dim ) ); + } + + if ( !m_data->legendRect.isEmpty() ) + { + if ( m_data->legendPos == QwtPolarPlot::LeftLegend + || m_data->legendPos == QwtPolarPlot::RightLegend ) + { + // We prefer to align the legend to the canvas - not to + // the complete plot - if possible. + + if ( m_data->layoutData.legend.hint.height() + < m_data->canvasRect.height() ) + { + m_data->legendRect.setY( m_data->canvasRect.y() ); + m_data->legendRect.setHeight( m_data->canvasRect.height() ); + } + } + } +} diff --git a/libs/qwt/src/qwt_polar_layout.h b/libs/qwt/src/qwt_polar_layout.h new file mode 100644 index 00000000..67fdd0de --- /dev/null +++ b/libs/qwt/src/qwt_polar_layout.h @@ -0,0 +1,76 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_LAYOUT_H +#define QWT_POLAR_LAYOUT_H + +#include "qwt_global.h" +#include "qwt_polar_plot.h" + +/*! + \brief Layout class for QwtPolarPlot. + + Organizes the geometry for the different QwtPolarPlot components. + It is used by the QwtPolar widget to organize its internal widgets + or by QwtPolarRnderer to render its content to a QPaintDevice like + a QPrinter, QPixmap/QImage or QSvgRenderer. + */ +class QWT_EXPORT QwtPolarLayout +{ + public: + + //! \brief Options to configure the plot layout engine + enum Option + { + //! Ignore the dimension of the scrollbars. + IgnoreScrollbars = 0x01, + + //! Ignore all frames. + IgnoreFrames = 0x02, + + //! Ignore the title. + IgnoreTitle = 0x04, + + //! Ignore the legend. + IgnoreLegend = 0x08 + }; + + Q_DECLARE_FLAGS( Options, Option ) + + explicit QwtPolarLayout(); + virtual ~QwtPolarLayout(); + + void setLegendPosition( QwtPolarPlot::LegendPosition pos, double ratio ); + void setLegendPosition( QwtPolarPlot::LegendPosition pos ); + QwtPolarPlot::LegendPosition legendPosition() const; + + void setLegendRatio( double ratio ); + double legendRatio() const; + + virtual void activate( const QwtPolarPlot*, + const QRectF& rect, Options options = Options() ); + + virtual void invalidate(); + + const QRectF& titleRect() const; + const QRectF& legendRect() const; + const QRectF& canvasRect() const; + + class LayoutData; + + protected: + QRectF layoutLegend( Options options, QRectF& ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPolarLayout::Options ) + +#endif diff --git a/libs/qwt/src/qwt_polar_magnifier.cpp b/libs/qwt/src/qwt_polar_magnifier.cpp new file mode 100644 index 00000000..7a692401 --- /dev/null +++ b/libs/qwt/src/qwt_polar_magnifier.cpp @@ -0,0 +1,172 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_magnifier.h" +#include "qwt_polar_plot.h" +#include "qwt_polar_canvas.h" +#include "qwt_scale_div.h" +#include "qwt_point_polar.h" + +#include + +class QwtPolarMagnifier::PrivateData +{ + public: + PrivateData() + : unzoomKey( Qt::Key_Home ) + , unzoomKeyModifiers( Qt::NoModifier ) + { + } + + int unzoomKey; + int unzoomKeyModifiers; +}; + +/*! + Constructor + \param canvas Plot canvas to be magnified + */ +QwtPolarMagnifier::QwtPolarMagnifier( QwtPolarCanvas* canvas ) + : QwtMagnifier( canvas ) +{ + m_data = new PrivateData(); +} + +//! Destructor +QwtPolarMagnifier::~QwtPolarMagnifier() +{ + delete m_data; +} + +/*! + Assign key and modifiers, that are used for unzooming + The default combination is Qt::Key_Home + Qt::NoModifier. + + \param key Key code + \param modifiers Modifiers + \sa getUnzoomKey(), QwtPolarPlot::unzoom() + */ +void QwtPolarMagnifier::setUnzoomKey( int key, int modifiers ) +{ + m_data->unzoomKey = key; + m_data->unzoomKeyModifiers = modifiers; +} + +/*! + \return Key, and modifiers that are used for unzooming + + \param key Key code + \param modifiers Modifiers + \sa setUnzoomKey(), QwtPolarPlot::unzoom() + */ +void QwtPolarMagnifier::getUnzoomKey( int& key, int& modifiers ) const +{ + key = m_data->unzoomKey; + modifiers = m_data->unzoomKeyModifiers; +} + +//! \return Observed plot canvas +QwtPolarCanvas* QwtPolarMagnifier::canvas() +{ + return qobject_cast< QwtPolarCanvas* >( parent() ); +} + +//! \return Observed plot canvas +const QwtPolarCanvas* QwtPolarMagnifier::canvas() const +{ + return qobject_cast< QwtPolarCanvas* >( parent() ); +} + +//! \return Observed plot +QwtPolarPlot* QwtPolarMagnifier::plot() +{ + QwtPolarCanvas* c = canvas(); + if ( c ) + return c->plot(); + + return NULL; +} + +//! \return observed plot +const QwtPolarPlot* QwtPolarMagnifier::plot() const +{ + const QwtPolarCanvas* c = canvas(); + if ( c ) + return c->plot(); + + return NULL; +} + +/*! + Handle a key press event for the observed widget. + + \param event Key event + */ +void QwtPolarMagnifier::widgetKeyPressEvent( QKeyEvent* event ) +{ + const int key = event->key(); + const int state = event->modifiers(); + + if ( key == m_data->unzoomKey && + state == m_data->unzoomKeyModifiers ) + { + unzoom(); + return; + } + + QwtMagnifier::widgetKeyPressEvent( event ); +} + +/*! + Zoom in/out the zoomed area + \param factor A value < 1.0 zooms in, a value > 1.0 zooms out. + */ +void QwtPolarMagnifier::rescale( double factor ) +{ + factor = qAbs( factor ); + if ( factor == 1.0 || factor == 0.0 ) + return; + + QwtPolarPlot* plt = plot(); + if ( plt == NULL ) + return; + + QwtPointPolar zoomPos; + double newZoomFactor = plt->zoomFactor() * factor; + + if ( newZoomFactor >= 1.0 ) + newZoomFactor = 1.0; + else + zoomPos = plt->zoomPos(); + + const bool autoReplot = plt->autoReplot(); + plt->setAutoReplot( false ); + + plt->zoom( zoomPos, newZoomFactor ); + + plt->setAutoReplot( autoReplot ); + plt->replot(); +} + +//! Unzoom the plot widget +void QwtPolarMagnifier::unzoom() +{ + QwtPolarPlot* plt = plot(); + + const bool autoReplot = plt->autoReplot(); + plt->setAutoReplot( false ); + + plt->unzoom(); + + plt->setAutoReplot( autoReplot ); + plt->replot(); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_polar_magnifier.cpp" +#endif diff --git a/libs/qwt/src/qwt_polar_magnifier.h b/libs/qwt/src/qwt_polar_magnifier.h new file mode 100644 index 00000000..8e083b74 --- /dev/null +++ b/libs/qwt/src/qwt_polar_magnifier.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_MAGNIFIER_H +#define QWT_POLAR_MAGNIFIER_H + +#include "qwt_global.h" +#include "qwt_magnifier.h" + +class QwtPolarPlot; +class QwtPolarCanvas; + +/*! + \brief QwtPolarMagnifier provides zooming, by magnifying in steps. + + Using QwtPlotMagnifier a plot can be zoomed in/out in steps using + keys, the mouse wheel or moving a mouse button in vertical direction. + + Together with QwtPolarPanner it is possible to implement + an individual navigation of the plot canvas. + + \sa QwtPolarPanner, QwtPolarPlot, QwtPolarCanvas + */ + +class QWT_EXPORT QwtPolarMagnifier : public QwtMagnifier +{ + Q_OBJECT + + public: + explicit QwtPolarMagnifier( QwtPolarCanvas* ); + virtual ~QwtPolarMagnifier(); + + void setUnzoomKey( int key, int modifiers ); + void getUnzoomKey( int& key, int& modifiers ) const; + + QwtPolarPlot* plot(); + const QwtPolarPlot* plot() const; + + QwtPolarCanvas* canvas(); + const QwtPolarCanvas* canvas() const; + + public Q_SLOTS: + virtual void rescale( double factor ) QWT_OVERRIDE; + void unzoom(); + + protected: + virtual void widgetKeyPressEvent( QKeyEvent* ) QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_polar_marker.cpp b/libs/qwt/src/qwt_polar_marker.cpp new file mode 100644 index 00000000..bc40b525 --- /dev/null +++ b/libs/qwt/src/qwt_polar_marker.cpp @@ -0,0 +1,234 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_marker.h" +#include "qwt_polar.h" +#include "qwt_scale_map.h" +#include "qwt_symbol.h" +#include "qwt_text.h" + +#include + +static const int LabelDist = 2; + +class QwtPolarMarker::PrivateData +{ + public: + PrivateData() + : align( Qt::AlignCenter ) + { + symbol = new QwtSymbol(); + } + + ~PrivateData() + { + delete symbol; + } + + QwtText label; + Qt::Alignment align; + QPen pen; + const QwtSymbol* symbol; + + QwtPointPolar pos; +}; + +//! Sets alignment to Qt::AlignCenter, and style to NoLine +QwtPolarMarker::QwtPolarMarker() + : QwtPolarItem( QwtText( "Marker" ) ) +{ + m_data = new PrivateData; + + setItemAttribute( QwtPolarItem::AutoScale ); + setZ( 30.0 ); +} + +//! Destructor +QwtPolarMarker::~QwtPolarMarker() +{ + delete m_data; +} + +//! \return QwtPolarItem::Rtti_PlotMarker +int QwtPolarMarker::rtti() const +{ + return QwtPolarItem::Rtti_PolarMarker; +} + +//! \return Position of the marker +QwtPointPolar QwtPolarMarker::position() const +{ + return m_data->pos; +} + +//! Change the position of the marker +void QwtPolarMarker::setPosition( const QwtPointPolar& pos ) +{ + if ( m_data->pos != pos ) + { + m_data->pos = pos; + itemChanged(); + } +} + +/*! + Draw the marker + + \param painter Painter + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param radius Radius of the complete plot area in painter coordinates + \param canvasRect Contents rect of the canvas in painter coordinates + */ +void QwtPolarMarker::draw( QPainter* painter, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double radius, + const QRectF& canvasRect ) const +{ + Q_UNUSED( radius ); + Q_UNUSED( canvasRect ); + + const double r = radialMap.transform( m_data->pos.radius() ); + const double a = azimuthMap.transform( m_data->pos.azimuth() ); + + const QPointF pos = qwtPolar2Pos( pole, r, a ); + + + // draw symbol + QSize sSym( 0, 0 ); + if ( m_data->symbol->style() != QwtSymbol::NoSymbol ) + { + sSym = m_data->symbol->size(); + m_data->symbol->drawSymbol( painter, pos ); + } + + // draw label + if ( !m_data->label.isEmpty() ) + { + int xlw = qMax( int( m_data->pen.width() ), 1 ); + int ylw = xlw; + + int xlw1 = qMax( ( xlw + 1 ) / 2, ( sSym.width() + 1 ) / 2 ) + LabelDist; + xlw = qMax( xlw / 2, ( sSym.width() + 1 ) / 2 ) + LabelDist; + int ylw1 = qMax( ( ylw + 1 ) / 2, ( sSym.height() + 1 ) / 2 ) + LabelDist; + ylw = qMax( ylw / 2, ( sSym.height() + 1 ) / 2 ) + LabelDist; + + QRect tr( QPoint( 0, 0 ), m_data->label.textSize( painter->font() ).toSize() ); + tr.moveCenter( QPoint( 0, 0 ) ); + + int dx = pos.x(); + int dy = pos.y(); + + if ( m_data->align & Qt::AlignTop ) + dy += tr.y() - ylw1; + else if ( m_data->align & Qt::AlignBottom ) + dy -= tr.y() - ylw1; + + if ( m_data->align & Qt::AlignLeft ) + dx += tr.x() - xlw1; + else if ( m_data->align & Qt::AlignRight ) + dx -= tr.x() - xlw1; + + tr.translate( dx, dy ); + m_data->label.draw( painter, tr ); + } +} + +/*! + \brief Assign a symbol + \param symbol New symbol + \sa symbol() + */ +void QwtPolarMarker::setSymbol( const QwtSymbol* symbol ) +{ + if ( m_data->symbol != symbol ) + { + delete m_data->symbol; + m_data->symbol = symbol; + itemChanged(); + } +} + +/*! + \return the symbol + \sa setSymbol(), QwtSymbol + */ +const QwtSymbol* QwtPolarMarker::symbol() const +{ + return m_data->symbol; +} + +/*! + \brief Set the label + \param label label text + \sa label() + */ +void QwtPolarMarker::setLabel( const QwtText& label ) +{ + if ( label != m_data->label ) + { + m_data->label = label; + itemChanged(); + } +} + +/*! + \return the label + \sa setLabel() + */ +QwtText QwtPolarMarker::label() const +{ + return m_data->label; +} + +/*! + \brief Set the alignment of the label + + The alignment determines where the label is drawn relative to + the marker's position. + + \param align Alignment. A combination of AlignTop, AlignBottom, + AlignLeft, AlignRight, AlignCenter, AlgnHCenter, + AlignVCenter. + \sa labelAlignment() + */ +void QwtPolarMarker::setLabelAlignment( Qt::Alignment align ) +{ + if ( align == m_data->align ) + return; + + m_data->align = align; + itemChanged(); +} + +/*! + \return the label alignment + \sa setLabelAlignment() + */ +Qt::Alignment QwtPolarMarker::labelAlignment() const +{ + return m_data->align; +} + +/*! + Interval, that is necessary to display the item + This interval can be useful for operations like clipping or autoscaling + + \param scaleId Scale index + \return bounding interval ( == position ) + + \sa position() + */ +QwtInterval QwtPolarMarker::boundingInterval( int scaleId ) const +{ + const double v = ( scaleId == QwtPolar::ScaleRadius ) + ? m_data->pos.radius() : m_data->pos.azimuth(); + + return QwtInterval( v, v ); +} diff --git a/libs/qwt/src/qwt_polar_marker.h b/libs/qwt/src/qwt_polar_marker.h new file mode 100644 index 00000000..9291d683 --- /dev/null +++ b/libs/qwt/src/qwt_polar_marker.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_MARKER_H +#define QWT_POLAR_MARKER_H + +#include "qwt_global.h" +#include "qwt_polar_item.h" +#include "qwt_point_polar.h" + +class QRect; +class QwtText; +class QwtSymbol; + +/*! + \brief A class for drawing markers + + A marker can be a a symbol, a label or a combination of them, which can + be drawn around a center point inside a bounding rectangle. + + The setSymbol() member assigns a symbol to the marker. + The symbol is drawn at the specified point. + + With setLabel(), a label can be assigned to the marker. + The setLabelAlignment() member specifies where the label is + drawn. All the Align*-constants in Qt::AlignmentFlags (see Qt documentation) + are valid. The alignment refers to the center point of + the marker, which means, for example, that the label would be painted + left above the center point if the alignment was set to AlignLeft|AlignTop. + */ +class QWT_EXPORT QwtPolarMarker : public QwtPolarItem +{ + public: + explicit QwtPolarMarker(); + virtual ~QwtPolarMarker(); + + virtual int rtti() const QWT_OVERRIDE; + + void setPosition( const QwtPointPolar& ); + QwtPointPolar position() const; + + void setSymbol( const QwtSymbol* s ); + const QwtSymbol* symbol() const; + + void setLabel( const QwtText& ); + QwtText label() const; + + void setLabelAlignment( Qt::Alignment ); + Qt::Alignment labelAlignment() const; + + virtual void draw( QPainter* painter, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double radius, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + virtual QwtInterval boundingInterval( int scaleId ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_polar_panner.cpp b/libs/qwt/src/qwt_polar_panner.cpp new file mode 100644 index 00000000..0e3933f1 --- /dev/null +++ b/libs/qwt/src/qwt_polar_panner.cpp @@ -0,0 +1,120 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_panner.h" +#include "qwt_polar_plot.h" +#include "qwt_polar_canvas.h" +#include "qwt_scale_div.h" +#include "qwt_point_polar.h" + +//! Create a plot panner for a polar plot canvas +QwtPolarPanner::QwtPolarPanner( QwtPolarCanvas* canvas ) + : QwtPanner( canvas ) +{ + connect( this, SIGNAL(panned(int,int)), + SLOT(movePlot(int,int)) ); +} + +//! Destructor +QwtPolarPanner::~QwtPolarPanner() +{ +} + +//! \return observed plot canvas +QwtPolarCanvas* QwtPolarPanner::canvas() +{ + return qobject_cast< QwtPolarCanvas* >( parent() ); +} + +//! \return observed plot canvas +const QwtPolarCanvas* QwtPolarPanner::canvas() const +{ + return qobject_cast< const QwtPolarCanvas* >( parent() ); +} + +//! \return observed plot +QwtPolarPlot* QwtPolarPanner::plot() +{ + QwtPolarCanvas* c = canvas(); + if ( c ) + return c->plot(); + + return NULL; +} + +//! \return observed plot +const QwtPolarPlot* QwtPolarPanner::plot() const +{ + const QwtPolarCanvas* c = canvas(); + if ( c ) + return c->plot(); + + return NULL; +} + +/*! + Adjust the zoomed area according to dx/dy + + \param dx Pixel offset in x direction + \param dy Pixel offset in y direction + + \sa QwtPanner::panned(), QwtPolarPlot::zoom() + */ +void QwtPolarPanner::movePlot( int dx, int dy ) +{ + QwtPolarPlot* plot = QwtPolarPanner::plot(); + if ( plot == NULL || ( dx == 0 && dy == 0 ) ) + return; + + const QwtScaleMap map = plot->scaleMap( QwtPolar::Radius ); + + QwtPointPolar pos = plot->zoomPos(); + if ( map.s1() <= map.s2() ) + { + pos.setRadius( + map.transform( map.s1() + pos.radius() ) - map.p1() ); + pos.setPoint( pos.toPoint() - QPointF( dx, -dy ) ); + pos.setRadius( + map.invTransform( map.p1() + pos.radius() ) - map.s1() ); + } + else + { + pos.setRadius( + map.transform( map.s1() - pos.radius() ) - map.p1() ); + pos.setPoint( pos.toPoint() - QPointF( dx, -dy ) ); + pos.setRadius( + map.s1() - map.invTransform( map.p1() + pos.radius() ) ); + } + + const bool doAutoReplot = plot->autoReplot(); + plot->setAutoReplot( false ); + + plot->zoom( pos, plot->zoomFactor() ); + + plot->setAutoReplot( doAutoReplot ); + plot->replot(); +} + +/*! + Block panning when the plot zoom factor is >= 1.0. + + \param event Mouse event + */ +void QwtPolarPanner::widgetMousePressEvent( QMouseEvent* event ) +{ + const QwtPolarPlot* plot = QwtPolarPanner::plot(); + if ( plot ) + { + if ( plot->zoomFactor() < 1.0 ) + QwtPanner::widgetMousePressEvent( event ); + } +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_polar_panner.cpp" +#endif diff --git a/libs/qwt/src/qwt_polar_panner.h b/libs/qwt/src/qwt_polar_panner.h new file mode 100644 index 00000000..7cd4fa46 --- /dev/null +++ b/libs/qwt/src/qwt_polar_panner.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_PANNER_H +#define QWT_POLAR_PANNER_H + +#include "qwt_global.h" +#include "qwt_panner.h" + +class QwtPolarPlot; +class QwtPolarCanvas; + +/*! + \brief QwtPolarPanner provides panning of a polar plot canvas + + QwtPolarPanner is a panner for a QwtPolarCanvas, that + adjusts the visible area after dropping + the canvas on its new position. + + Together with QwtPolarMagnifier individual ways + of navigating on a QwtPolarPlot widget can be implemented easily. + + \sa QwtPolarMagnifier + */ +class QWT_EXPORT QwtPolarPanner : public QwtPanner +{ + Q_OBJECT + + public: + explicit QwtPolarPanner( QwtPolarCanvas* ); + virtual ~QwtPolarPanner(); + + QwtPolarPlot* plot(); + const QwtPolarPlot* plot() const; + + QwtPolarCanvas* canvas(); + const QwtPolarCanvas* canvas() const; + + public Q_SLOTS: + virtual void movePlot( int dx, int dy ); + + protected: + virtual void widgetMousePressEvent( QMouseEvent* ) QWT_OVERRIDE; +}; + +#endif diff --git a/libs/qwt/src/qwt_polar_picker.cpp b/libs/qwt/src/qwt_polar_picker.cpp new file mode 100644 index 00000000..5424b688 --- /dev/null +++ b/libs/qwt/src/qwt_polar_picker.cpp @@ -0,0 +1,244 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_picker.h" +#include "qwt_polar_plot.h" +#include "qwt_polar_canvas.h" +#include "qwt_scale_map.h" +#include "qwt_picker_machine.h" +#include "qwt_point_polar.h" + +class QwtPolarPicker::PrivateData +{ +}; + +/*! + \brief Create a polar plot picker + \param canvas Plot canvas to observe, also the parent object + */ + +QwtPolarPicker::QwtPolarPicker( QwtPolarCanvas* canvas ) + : QwtPicker( canvas ) + , m_data( nullptr ) +{ +} + +/*! + Create a plot picker + + \param rubberBand Rubberband style + \param trackerMode Tracker mode + \param canvas Plot canvas to observe, also the parent object + + \sa QwtPicker, QwtPicker::setSelectionFlags(), QwtPicker::setRubberBand(), + QwtPicker::setTrackerMode + + \sa QwtPolarPlot::autoReplot(), QwtPolarPlot::replot(), scaleRect() + */ +QwtPolarPicker::QwtPolarPicker( + RubberBand rubberBand, DisplayMode trackerMode, QwtPolarCanvas* canvas ) + : QwtPicker( rubberBand, trackerMode, canvas ) + , m_data( nullptr ) +{ +} + +//! Destructor +QwtPolarPicker::~QwtPolarPicker() +{ +} + +//! \return Observed plot canvas +QwtPolarCanvas* QwtPolarPicker::canvas() +{ + return qobject_cast< QwtPolarCanvas* >( parentWidget() ); +} + +//! \return Observed plot canvas +const QwtPolarCanvas* QwtPolarPicker::canvas() const +{ + return qobject_cast< const QwtPolarCanvas* >( parentWidget() ); +} + +//! \return Plot widget, containing the observed plot canvas +QwtPolarPlot* QwtPolarPicker::plot() +{ + QwtPolarCanvas* w = canvas(); + if ( w ) + return w->plot(); + + return NULL; +} + +//! \return Plot widget, containing the observed plot canvas +const QwtPolarPlot* QwtPolarPicker::plot() const +{ + const QwtPolarCanvas* w = canvas(); + if ( w ) + return w->plot(); + + return NULL; +} + +/*! + Translate a pixel position into a position string + + \param pos Position in pixel coordinates + \return Position string + */ +QwtText QwtPolarPicker::trackerText( const QPoint& pos ) const +{ + const QwtPointPolar polarPoint = invTransform( pos ); + return trackerTextPolar( polarPoint ); +} + +/*! + \brief Translate a position into a position string + + 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 double to string conversion is "%.4f". + + \param pos Position + \return Position string + */ +QwtText QwtPolarPicker::trackerTextPolar( const QwtPointPolar& pos ) const +{ + const QString text = QString::number( pos.radius(), 'f', 4 ) + + ", " + QString::number( pos.azimuth(), 'f', 4 ); + + return QwtText( text ); +} + +/*! + Append a point to the selection and update rubberband and tracker. + + \param pos Additional point + \sa isActive, begin(), end(), move(), appended() + + \note The appended(const QPoint &), appended(const QDoublePoint &) + signals are emitted. + */ +void QwtPolarPicker::append( const QPoint& pos ) +{ + QwtPicker::append( pos ); + Q_EMIT appended( invTransform( pos ) ); +} + +/*! + Move the last point of the selection + + \param pos New position + \sa isActive, begin(), end(), append() + + \note The moved(const QPoint &), moved(const QDoublePoint &) + signals are emitted. + */ +void QwtPolarPicker::move( const QPoint& pos ) +{ + QwtPicker::move( pos ); + Q_EMIT moved( invTransform( pos ) ); +} + +/*! + Close a selection setting the state to inactive. + + \param ok If true, complete the selection and emit selected signals + otherwise discard the selection. + \return true if the selection is accepted, false otherwise + */ + +bool QwtPolarPicker::end( bool ok ) +{ + ok = QwtPicker::end( ok ); + if ( !ok ) + return false; + + QwtPolarPlot* plot = QwtPolarPicker::plot(); + if ( !plot ) + return false; + + const QPolygon points = selection(); + if ( points.count() == 0 ) + return false; + + QwtPickerMachine::SelectionType selectionType = + QwtPickerMachine::NoSelection; + + if ( stateMachine() ) + selectionType = stateMachine()->selectionType(); + + switch ( selectionType ) + { + case QwtPickerMachine::PointSelection: + { + const QwtPointPolar pos = invTransform( points[0] ); + Q_EMIT selected( pos ); + break; + } + case QwtPickerMachine::RectSelection: + case QwtPickerMachine::PolygonSelection: + { + QVector< QwtPointPolar > polarPoints( points.count() ); + for ( int i = 0; i < points.count(); i++ ) + polarPoints[i] = invTransform( points[i] ); + + Q_EMIT selected( polarPoints ); + } + default: + break; + } + + return true; +} + +/*! + Translate a point from widget into plot coordinates + + \param pos Point in widget coordinates of the plot canvas + \return Point in plot coordinates + \sa transform(), canvas() + */ +QwtPointPolar QwtPolarPicker::invTransform( const QPoint& pos ) const +{ + QwtPointPolar polarPos; + if ( canvas() == NULL ) + return QwtPointPolar(); + + return canvas()->invTransform( pos ); +} + +/*! + \return Bounding rectangle of the region, where picking is + supported. + */ +QRect QwtPolarPicker::pickRect() const +{ + const QRect cr = canvas()->contentsRect(); + const QRect pr = plot()->plotRect( cr ).toRect(); + + return cr & pr; +} + +QPainterPath QwtPolarPicker::pickArea() const +{ + const QRect cr = canvas()->contentsRect(); + + QPainterPath crPath; + crPath.addRect( cr ); + + QPainterPath prPath; + prPath.addEllipse( plot()->plotRect( cr ) ); + + return crPath.intersected( prPath ); +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_polar_picker.cpp" +#endif diff --git a/libs/qwt/src/qwt_polar_picker.h b/libs/qwt/src/qwt_polar_picker.h new file mode 100644 index 00000000..27c734d1 --- /dev/null +++ b/libs/qwt/src/qwt_polar_picker.h @@ -0,0 +1,98 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_PICKER_H +#define QWT_POLAR_PICKER_H + +#include "qwt_global.h" +#include "qwt_picker.h" + +#include +#include + +class QwtPolarPlot; +class QwtPolarCanvas; +class QwtPointPolar; + +/*! + \brief QwtPolarPicker provides selections on a plot canvas + + QwtPolarPicker is a QwtPicker tailored for selections on + a polar plot canvas. + */ +class QWT_EXPORT QwtPolarPicker : public QwtPicker +{ + Q_OBJECT + + public: + explicit QwtPolarPicker( QwtPolarCanvas* ); + virtual ~QwtPolarPicker(); + + explicit QwtPolarPicker( + RubberBand rubberBand, DisplayMode trackerMode, + QwtPolarCanvas* ); + + QwtPolarPlot* plot(); + const QwtPolarPlot* plot() const; + + QwtPolarCanvas* canvas(); + const QwtPolarCanvas* canvas() const; + + virtual QRect pickRect() const; + + Q_SIGNALS: + + /*! + A signal emitted in case of selectionFlags() & PointSelection. + \param pos Selected point + */ + void selected( const QwtPointPolar& pos ); + + /*! + A signal emitting the selected points, + at the end of a selection. + + \param points Selected points + */ + void selected( const QVector< QwtPointPolar >& points ); + + /*! + 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 QwtPointPolar& 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 QwtPointPolar& pos ); + + protected: + QwtPointPolar invTransform( const QPoint& ) const; + + virtual QwtText trackerText( const QPoint& ) const QWT_OVERRIDE; + virtual QwtText trackerTextPolar( const QwtPointPolar& ) const; + + virtual void move( const QPoint& ) QWT_OVERRIDE; + virtual void append( const QPoint& ) QWT_OVERRIDE; + virtual bool end( bool ok = true ) QWT_OVERRIDE; + + private: + virtual QPainterPath pickArea() const QWT_OVERRIDE; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_polar_plot.cpp b/libs/qwt/src/qwt_polar_plot.cpp new file mode 100644 index 00000000..b20d8af6 --- /dev/null +++ b/libs/qwt/src/qwt_polar_plot.cpp @@ -0,0 +1,1367 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_plot.h" +#include "qwt_polar_canvas.h" +#include "qwt_polar_layout.h" +#include "qwt_painter.h" +#include "qwt_scale_engine.h" +#include "qwt_scale_div.h" +#include "qwt_text_label.h" +#include "qwt_round_scale_draw.h" +#include "qwt_legend.h" +#include "qwt_dyngrid_layout.h" + +#include +#include +#include +#include + +static inline double qwtDistance( + const QPointF& p1, const QPointF& p2 ) +{ + double dx = p2.x() - p1.x(); + double dy = p2.y() - p1.y(); + return qSqrt( dx * dx + dy * dy ); +} + +namespace +{ + class ScaleData + { + public: + ScaleData() + : isValid( false ) + , scaleEngine( NULL ) + { + } + + ~ScaleData() + { + delete scaleEngine; + } + + bool doAutoScale; + + double minValue; + double maxValue; + double stepSize; + + int maxMajor; + int maxMinor; + + bool isValid; + + QwtScaleDiv scaleDiv; + QwtScaleEngine* scaleEngine; + }; +} + +class QwtPolarPlot::PrivateData +{ + public: + QBrush canvasBrush; + + bool autoReplot; + + QwtPointPolar zoomPos; + double zoomFactor; + + ScaleData scaleData[QwtPolar::ScaleCount]; + QPointer< QwtTextLabel > titleLabel; + QPointer< QwtPolarCanvas > canvas; + QPointer< QwtAbstractLegend > legend; + double azimuthOrigin; + + QwtPolarLayout* layout; +}; + +/*! + Constructor + \param parent Parent widget + */ +QwtPolarPlot::QwtPolarPlot( QWidget* parent ) + : QFrame( parent ) +{ + initPlot( QwtText() ); +} + +/*! + Constructor + \param title Title text + \param parent Parent widget + */ +QwtPolarPlot::QwtPolarPlot( const QwtText& title, QWidget* parent ) + : QFrame( parent ) +{ + initPlot( title ); +} + +//! Destructor +QwtPolarPlot::~QwtPolarPlot() +{ + detachItems( QwtPolarItem::Rtti_PolarItem, autoDelete() ); + + delete m_data->layout; + delete m_data; +} + +/*! + Change the plot's title + \param title New title + */ +void QwtPolarPlot::setTitle( const QString& title ) +{ + if ( title != m_data->titleLabel->text().text() ) + { + m_data->titleLabel->setText( title ); + if ( !title.isEmpty() ) + m_data->titleLabel->show(); + else + m_data->titleLabel->hide(); + } +} + +/*! + Change the plot's title + \param title New title + */ +void QwtPolarPlot::setTitle( const QwtText& title ) +{ + if ( title != m_data->titleLabel->text() ) + { + m_data->titleLabel->setText( title ); + if ( !title.isEmpty() ) + m_data->titleLabel->show(); + else + m_data->titleLabel->hide(); + } +} + +//! \return the plot's title +QwtText QwtPolarPlot::title() const +{ + return m_data->titleLabel->text(); +} + +//! \return the plot's title +QwtTextLabel* QwtPolarPlot::titleLabel() +{ + return m_data->titleLabel; +} + +//! \return the plot's title label. +const QwtTextLabel* QwtPolarPlot::titleLabel() const +{ + return m_data->titleLabel; +} + +/*! + \brief Insert a legend + + If the position legend is \c QwtPolarPlot::LeftLegend or \c QwtPolarPlot::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. + + If pos != QwtPolarPlot::ExternalLegend the plot widget will become + parent of the legend. It will be deleted when the plot is deleted, + or another legend is set with insertLegend(). + + \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 rect + 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(), QwtPolarLayout::legendPosition(), + QwtPolarLayout::setLegendPosition() + */ +void QwtPolarPlot::insertLegend( QwtAbstractLegend* legend, + QwtPolarPlot::LegendPosition pos, double ratio ) +{ + m_data->layout->setLegendPosition( pos, ratio ); + + if ( legend != m_data->legend ) + { + if ( m_data->legend && m_data->legend->parent() == this ) + delete m_data->legend; + + m_data->legend = legend; + + if ( m_data->legend ) + { + connect( this, + SIGNAL(legendDataChanged( + const QVariant&,const QList&)), + m_data->legend, + SLOT(updateLegend( + const QVariant&,const QList&)) + ); + + if ( m_data->legend->parent() != this ) + m_data->legend->setParent( this ); + + updateLegend(); + + QwtLegend* lgd = qobject_cast< QwtLegend* >( legend ); + if ( lgd ) + { + switch ( m_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; + } + } + + } + } + + updateLayout(); +} + +/*! + Emit legendDataChanged() for all plot item + + \sa QwtPlotItem::legendData(), legendDataChanged() + */ +void QwtPolarPlot::updateLegend() +{ + const QwtPolarItemList& itmList = itemList(); + for ( QwtPolarItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + updateLegend( *it ); + } +} + +/*! + Emit legendDataChanged() for a plot item + + \param plotItem Plot item + \sa QwtPlotItem::legendData(), legendDataChanged() + */ +void QwtPolarPlot::updateLegend( const QwtPolarItem* plotItem ) +{ + if ( plotItem == NULL ) + return; + + QList< QwtLegendData > legendData; + + if ( plotItem->testItemAttribute( QwtPolarItem::Legend ) ) + legendData = plotItem->legendData(); + + const QVariant itemInfo = itemToInfo( const_cast< QwtPolarItem* >( plotItem ) ); + Q_EMIT legendDataChanged( itemInfo, legendData ); +} + +/*! + \return the plot's legend + \sa insertLegend() + */ +QwtAbstractLegend* QwtPolarPlot::legend() +{ + return m_data->legend; +} + +/*! + \return the plot's legend + \sa insertLegend() + */ +const QwtAbstractLegend* QwtPolarPlot::legend() const +{ + return m_data->legend; +} + +/*! + \brief Set the background of the plot area + + The plot area is the circle around the pole. It's radius + is defined by the radial scale. + + \param brush Background Brush + \sa plotBackground(), plotArea() + */ +void QwtPolarPlot::setPlotBackground( const QBrush& brush ) +{ + if ( brush != m_data->canvasBrush ) + { + m_data->canvasBrush = brush; + autoRefresh(); + } +} + +/*! + \return plot background brush + \sa plotBackground(), plotArea() + */ +const QBrush& QwtPolarPlot::plotBackground() const +{ + return m_data->canvasBrush; +} + +/*! + \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 enable \c true or \c false. Defaults to \c true. + \sa replot() + */ +void QwtPolarPlot::setAutoReplot( bool enable ) +{ + m_data->autoReplot = enable; +} + +//! \return true if the autoReplot option is set. +bool QwtPolarPlot::autoReplot() const +{ + return m_data->autoReplot; +} + +/*! + \brief Enable autoscaling + + This member function is used to switch back to autoscaling mode + after a fixed scale has been set. Autoscaling calculates a useful + scale division from the bounding interval of all plot items with + the QwtPolarItem::AutoScale attribute. + + Autoscaling is only supported for the radial scale and enabled as default. + + \param scaleId Scale index + + \sa hasAutoScale(), setScale(), setScaleDiv(), + QwtPolarItem::boundingInterval() + */ +void QwtPolarPlot::setAutoScale( int scaleId ) +{ + if ( scaleId != QwtPolar::ScaleRadius ) + return; + + ScaleData& scaleData = m_data->scaleData[scaleId]; + if ( !scaleData.doAutoScale ) + { + scaleData.doAutoScale = true; + autoRefresh(); + } +} + +/*! + \return \c true if autoscaling is enabled + \param scaleId Scale index + \sa setAutoScale() + */ +bool QwtPolarPlot::hasAutoScale( int scaleId ) const +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return false; + + return m_data->scaleData[scaleId].doAutoScale; +} + +/*! + Set the maximum number of major scale intervals for a specified scale + + \param scaleId Scale index + \param maxMinor maximum number of minor steps + \sa scaleMaxMajor() + */ +void QwtPolarPlot::setScaleMaxMinor( int scaleId, int maxMinor ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return; + + maxMinor = qBound( 0, maxMinor, 100 ); + + ScaleData& scaleData = m_data->scaleData[scaleId]; + + if ( maxMinor != scaleData.maxMinor ) + { + scaleData.maxMinor = maxMinor; + scaleData.isValid = false; + autoRefresh(); + } +} + +/*! + \return the maximum number of minor ticks for a specified axis + \param scaleId Scale index + \sa setScaleMaxMinor() + */ +int QwtPolarPlot::scaleMaxMinor( int scaleId ) const +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return 0; + + return m_data->scaleData[scaleId].maxMinor; +} + +/*! + Set the maximum number of major scale intervals for a specified scale + + \param scaleId Scale index + \param maxMajor maximum number of major steps + \sa scaleMaxMajor() + */ +void QwtPolarPlot::setScaleMaxMajor( int scaleId, int maxMajor ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return; + + maxMajor = qBound( 1, maxMajor, 10000 ); + + ScaleData& scaleData = m_data->scaleData[scaleId]; + if ( maxMajor != scaleData.maxMinor ) + { + scaleData.maxMajor = maxMajor; + scaleData.isValid = false; + autoRefresh(); + } +} + +/*! + \return the maximum number of major ticks for a specified axis + \param scaleId Scale index + + \sa setScaleMaxMajor() + */ +int QwtPolarPlot::scaleMaxMajor( int scaleId ) const +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return 0; + + return m_data->scaleData[scaleId].maxMajor; +} + +/*! + Change the scale engine for an axis + + \param scaleId Scale index + \param scaleEngine Scale engine + + \sa axisScaleEngine() + */ +void QwtPolarPlot::setScaleEngine( int scaleId, QwtScaleEngine* scaleEngine ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return; + + ScaleData& scaleData = m_data->scaleData[scaleId]; + if ( scaleEngine == NULL || scaleEngine == scaleData.scaleEngine ) + return; + + delete scaleData.scaleEngine; + scaleData.scaleEngine = scaleEngine; + + scaleData.isValid = false; + + autoRefresh(); +} + +/*! + \return Scale engine for a specific scale + + \param scaleId Scale index + \sa setScaleEngine() + */ +QwtScaleEngine* QwtPolarPlot::scaleEngine( int scaleId ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return NULL; + + return m_data->scaleData[scaleId].scaleEngine; +} + +/*! + \return Scale engine for a specific scale + + \param scaleId Scale index + \sa setScaleEngine() + */ +const QwtScaleEngine* QwtPolarPlot::scaleEngine( int scaleId ) const +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return NULL; + + return m_data->scaleData[scaleId].scaleEngine; +} + +/*! + \brief Disable autoscaling and specify a fixed scale for a selected scale. + \param scaleId Scale index + \param min + \param max minimum and maximum of the scale + \param stepSize Major step size. If step == 0, the step size is + calculated automatically using the maxMajor setting. + \sa setScaleMaxMajor(), setAutoScale() + */ +void QwtPolarPlot::setScale( int scaleId, + double min, double max, double stepSize ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return; + + ScaleData& scaleData = m_data->scaleData[scaleId]; + + scaleData.isValid = false; + + scaleData.minValue = min; + scaleData.maxValue = max; + scaleData.stepSize = stepSize; + scaleData.doAutoScale = false; + + autoRefresh(); +} + +/*! + \brief Disable autoscaling and specify a fixed scale for a selected scale. + \param scaleId Scale index + \param scaleDiv Scale division + \sa setScale(), setAutoScale() + */ +void QwtPolarPlot::setScaleDiv( int scaleId, const QwtScaleDiv& scaleDiv ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return; + + ScaleData& scaleData = m_data->scaleData[scaleId]; + + scaleData.scaleDiv = scaleDiv; + scaleData.isValid = true; + scaleData.doAutoScale = false; + + autoRefresh(); +} + +/*! + \brief Return the scale division of a specified scale + + scaleDiv(scaleId)->lBound(), scaleDiv(scaleId)->hBound() + are the current limits of the scale. + + \param scaleId Scale index + \return Scale division + + \sa QwtScaleDiv, setScaleDiv(), setScale() + */ +const QwtScaleDiv* QwtPolarPlot::scaleDiv( int scaleId ) const +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return NULL; + + return &m_data->scaleData[scaleId].scaleDiv; +} + +/*! + \brief Return the scale division of a specified scale + + scaleDiv(scaleId)->lBound(), scaleDiv(scaleId)->hBound() + are the current limits of the scale. + + \param scaleId Scale index + \return Scale division + + \sa QwtScaleDiv, setScaleDiv(), setScale() + */ +QwtScaleDiv* QwtPolarPlot::scaleDiv( int scaleId ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return NULL; + + return &m_data->scaleData[scaleId].scaleDiv; +} + +/*! + \brief Change the origin of the azimuth scale + + The azimuth origin is the angle where the azimuth scale + shows the value 0.0. The default origin is 0.0. + + \param origin New origin + \sa azimuthOrigin() + */ +void QwtPolarPlot::setAzimuthOrigin( double origin ) +{ + origin = ::fmod( origin, 2 * M_PI ); + if ( origin != m_data->azimuthOrigin ) + { + m_data->azimuthOrigin = origin; + autoRefresh(); + } +} + +/*! + The azimuth origin is the angle where the azimuth scale + shows the value 0.0. + + \return Origin of the azimuth scale + \sa setAzimuthOrigin() + */ +double QwtPolarPlot::azimuthOrigin() const +{ + return m_data->azimuthOrigin; +} + +/*! + \brief Translate and in/decrease the zoom factor + + In zoom mode the zoom position is in the center of the + canvas. The radius of the circle depends on the size of the plot canvas, + that is divided by the zoom factor. Thus a factor < 1.0 zoom in. + + Setting an invalid zoom position disables zooming. + + \param zoomPos Center of the translation + \param zoomFactor Zoom factor + + \sa unzoom(), zoomPos(), zoomFactor() + */ +void QwtPolarPlot::zoom( const QwtPointPolar& zoomPos, double zoomFactor ) +{ + zoomFactor = qAbs( zoomFactor ); + if ( zoomPos != m_data->zoomPos || + zoomFactor != m_data->zoomFactor ) + { + m_data->zoomPos = zoomPos; + m_data->zoomFactor = zoomFactor; + updateLayout(); + autoRefresh(); + } +} + +/*! + Unzoom the plot + \sa zoom() + */ +void QwtPolarPlot::unzoom() +{ + if ( m_data->zoomFactor != 1.0 || m_data->zoomPos.isValid() ) + { + m_data->zoomFactor = 1.0; + m_data->zoomPos = QwtPointPolar(); + autoRefresh(); + } +} + +/*! + \return Zoom position + \sa zoom(), zoomFactor() + */ +QwtPointPolar QwtPolarPlot::zoomPos() const +{ + return m_data->zoomPos; +} + +/*! + \return Zoom factor + \sa zoom(), zoomPos() + */ +double QwtPolarPlot::zoomFactor() const +{ + return m_data->zoomFactor; +} + +/*! + Build a scale map + + The azimuth map translates between the scale values and angles from + [0.0, 2 * PI[. The radial map translates scale values into the distance + from the pole. The radial map is calculated from the current geometry + of the canvas. + + \param scaleId Scale index + \return Map for the scale on the canvas. With this map pixel coordinates can + translated to plot coordinates and vice versa. + + \sa QwtScaleMap, transform(), invTransform() + */ +QwtScaleMap QwtPolarPlot::scaleMap( int scaleId ) const +{ + const QRectF pr = plotRect(); + return scaleMap( scaleId, pr.width() / 2.0 ); +} + +/*! + Build a scale map + + The azimuth map translates between the scale values and angles from + [0.0, 2 * PI[. The radial map translates scale values into the distance + from the pole. + + \param scaleId Scale index + \param radius Radius of the plot are in pixels + \return Map for the scale on the canvas. With this map pixel coordinates can + translated to plot coordinates and vice versa. + + \sa QwtScaleMap, transform(), invTransform() + */ +QwtScaleMap QwtPolarPlot::scaleMap( int scaleId, const double radius ) const +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return QwtScaleMap(); + + QwtScaleMap map; + map.setTransformation( scaleEngine( scaleId )->transformation() ); + + const QwtScaleDiv* sd = scaleDiv( scaleId ); + map.setScaleInterval( sd->lowerBound(), sd->upperBound() ); + + if ( scaleId == QwtPolar::Azimuth ) + { + map.setPaintInterval( m_data->azimuthOrigin, + m_data->azimuthOrigin + 2 * M_PI ); + } + else + { + map.setPaintInterval( 0.0, radius ); + } + + return map; +} + +/*! + \brief Qt event handler + + Handles QEvent::LayoutRequest and QEvent::PolishRequest + + \param e Qt Event + \return True, when the event was processed + */ +bool QwtPolarPlot::event( QEvent* e ) +{ + bool ok = QWidget::event( e ); + switch( e->type() ) + { + case QEvent::LayoutRequest: + { + updateLayout(); + break; + } + case QEvent::PolishRequest: + { + updateLayout(); + replot(); + break; + } + default:; + } + return ok; +} + +//! Resize and update internal layout +void QwtPolarPlot::resizeEvent( QResizeEvent* e ) +{ + QFrame::resizeEvent( e ); + updateLayout(); +} + +void QwtPolarPlot::initPlot( const QwtText& title ) +{ + m_data = new PrivateData; + m_data->layout = new QwtPolarLayout; + + QwtText text( title ); + text.setRenderFlags( Qt::AlignCenter | Qt::TextWordWrap ); + + m_data->titleLabel = new QwtTextLabel( text, this ); + m_data->titleLabel->setFont( QFont( fontInfo().family(), 14, QFont::Bold ) ); + if ( !text.isEmpty() ) + m_data->titleLabel->show(); + else + m_data->titleLabel->hide(); + + m_data->canvas = new QwtPolarCanvas( this ); + + m_data->autoReplot = false; + m_data->canvasBrush = QBrush( Qt::white ); + + for ( int scaleId = 0; scaleId < QwtPolar::ScaleCount; scaleId++ ) + { + ScaleData& scaleData = m_data->scaleData[scaleId]; + + if ( scaleId == QwtPolar::Azimuth ) + { + scaleData.minValue = 0.0; + scaleData.maxValue = 360.0; + scaleData.stepSize = 30.0; + } + else + { + scaleData.minValue = 0.0; + scaleData.maxValue = 1000.0; + scaleData.stepSize = 0.0; + } + + scaleData.doAutoScale = true; + + scaleData.maxMinor = 5; + scaleData.maxMajor = 8; + + scaleData.isValid = false; + + scaleData.scaleEngine = new QwtLinearScaleEngine; + } + m_data->zoomFactor = 1.0; + m_data->azimuthOrigin = 0.0; + + setSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding ); + + for ( int scaleId = 0; scaleId < QwtPolar::ScaleCount; scaleId++ ) + updateScale( scaleId ); +} + +//! Replots the plot if QwtPlot::autoReplot() is \c true. +void QwtPolarPlot::autoRefresh() +{ + if ( m_data->autoReplot ) + replot(); +} + +//! Rebuild the layout +void QwtPolarPlot::updateLayout() +{ + m_data->layout->activate( this, contentsRect() ); + + // resize and show the visible widgets + if ( m_data->titleLabel ) + { + if ( !m_data->titleLabel->text().isEmpty() ) + { + m_data->titleLabel->setGeometry( m_data->layout->titleRect().toRect() ); + if ( !m_data->titleLabel->isVisible() ) + m_data->titleLabel->show(); + } + else + m_data->titleLabel->hide(); + } + + if ( m_data->legend ) + { + if ( m_data->legend->isEmpty() ) + { + m_data->legend->hide(); + } + else + { + const QRectF legendRect = m_data->layout->legendRect(); + m_data->legend->setGeometry( legendRect.toRect() ); + m_data->legend->show(); + } + } + + m_data->canvas->setGeometry( m_data->layout->canvasRect().toRect() ); + Q_EMIT layoutChanged(); +} + +/*! + \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 setAutoReplot() + \warning Calls canvas()->repaint, take care of infinite recursions + */ +void QwtPolarPlot::replot() +{ + bool doAutoReplot = autoReplot(); + setAutoReplot( false ); + + for ( int scaleId = 0; scaleId < QwtPolar::ScaleCount; scaleId++ ) + updateScale( scaleId ); + + m_data->canvas->invalidateBackingStore(); + m_data->canvas->repaint(); + + setAutoReplot( doAutoReplot ); +} + +//! \return the plot's canvas +QwtPolarCanvas* QwtPolarPlot::canvas() +{ + return m_data->canvas; +} + +//! \return the plot's canvas +const QwtPolarCanvas* QwtPolarPlot::canvas() const +{ + return m_data->canvas; +} + +/*! + Redraw the canvas. + \param painter Painter used for drawing + \param canvasRect Contents rect of the canvas + */ +void QwtPolarPlot::drawCanvas( QPainter* painter, + const QRectF& canvasRect ) const +{ + const QRectF cr = canvasRect; + const QRectF pr = plotRect( cr ); + + const double radius = pr.width() / 2.0; + + if ( m_data->canvasBrush.style() != Qt::NoBrush ) + { + painter->save(); + painter->setPen( Qt::NoPen ); + painter->setBrush( m_data->canvasBrush ); + + if ( qwtDistance( pr.center(), cr.topLeft() ) < radius && + qwtDistance( pr.center(), cr.topRight() ) < radius && + qwtDistance( pr.center(), cr.bottomRight() ) < radius && + qwtDistance( pr.center(), cr.bottomLeft() ) < radius ) + { + QwtPainter::drawRect( painter, cr ); + } + else + { + painter->setRenderHint( QPainter::Antialiasing, true ); + QwtPainter::drawEllipse( painter, pr ); + } + painter->restore(); + } + + drawItems( painter, + scaleMap( QwtPolar::Azimuth, radius ), + scaleMap( QwtPolar::Radius, radius ), + pr.center(), radius, canvasRect ); +} + +/*! + Redraw the canvas items. + + \param painter Painter used for drawing + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param radius Radius of the complete plot area in painter coordinates + \param canvasRect Contents rect of the canvas in painter coordinates + */ +void QwtPolarPlot::drawItems( QPainter* painter, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double radius, + const QRectF& canvasRect ) const +{ + const QRectF pr = plotRect( canvasRect ); + + const QwtPolarItemList& itmList = itemList(); + for ( QwtPolarItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPolarItem* item = *it; + if ( item && item->isVisible() ) + { + painter->save(); + + // Unfortunately circular clipping slows down + // painting a lot. So we better try to avoid it. + + bool doClipping = false; + if ( item->rtti() != QwtPolarItem::Rtti_PolarGrid ) + { + const QwtInterval intv = + item->boundingInterval( QwtPolar::Radius ); + + if ( !intv.isValid() ) + doClipping = true; + else + { + if ( radialMap.s1() < radialMap.s2() ) + doClipping = intv.maxValue() > radialMap.s2(); + else + doClipping = intv.minValue() < radialMap.s2(); + } + } + + if ( doClipping ) + { + const int margin = item->marginHint(); + + const QRectF clipRect = pr.adjusted( + -margin, -margin, margin, margin ); + if ( !clipRect.contains( canvasRect ) ) + { + QRegion clipRegion( clipRect.toRect(), QRegion::Ellipse ); + painter->setClipRegion( clipRegion, Qt::IntersectClip ); + } + } + + painter->setRenderHint( QPainter::Antialiasing, + item->testRenderHint( QwtPolarItem::RenderAntialiased ) ); + + item->draw( painter, azimuthMap, radialMap, + pole, radius, canvasRect ); + + painter->restore(); + } + } +} + +/*! + Rebuild the scale + \param scaleId Scale index + */ + +void QwtPolarPlot::updateScale( int scaleId ) +{ + if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount ) + return; + + ScaleData& d = m_data->scaleData[scaleId]; + + double minValue = d.minValue; + double maxValue = d.maxValue; + double stepSize = d.stepSize; + + if ( scaleId == QwtPolar::ScaleRadius && d.doAutoScale ) + { + QwtInterval interval; + + const QwtPolarItemList& itmList = itemList(); + for ( QwtPolarItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + const QwtPolarItem* item = *it; + if ( item->testItemAttribute( QwtPolarItem::AutoScale ) ) + interval |= item->boundingInterval( scaleId ); + } + + minValue = interval.minValue(); + maxValue = interval.maxValue(); + + d.scaleEngine->autoScale( d.maxMajor, + minValue, maxValue, stepSize ); + d.isValid = false; + } + + if ( !d.isValid ) + { + d.scaleDiv = d.scaleEngine->divideScale( + minValue, maxValue, d.maxMajor, d.maxMinor, stepSize ); + d.isValid = true; + } + + const QwtInterval interval = visibleInterval(); + + const QwtPolarItemList& itmList = itemList(); + for ( QwtPolarItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPolarItem* item = *it; + item->updateScaleDiv( *scaleDiv( QwtPolar::Azimuth ), + *scaleDiv( QwtPolar::Radius ), interval ); + } +} + +/*! + \return Maximum of all item margin hints. + \sa QwtPolarItem::marginHint() + */ +int QwtPolarPlot::plotMarginHint() const +{ + int margin = 0; + const QwtPolarItemList& itmList = itemList(); + for ( QwtPolarItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPolarItem* item = *it; + if ( item && item->isVisible() ) + { + const int hint = item->marginHint(); + if ( hint > margin ) + margin = hint; + } + } + return margin; +} + +/*! + The plot area depends on the size of the canvas + and the zoom parameters. + + \return Bounding rect of the plot area + + */ +QRectF QwtPolarPlot::plotRect() const +{ + return plotRect( canvas()->contentsRect() ); +} + +/*! + \brief Calculate the bounding rect of the plot area + + The plot area depends on the zoom parameters. + + \param canvasRect Rectangle of the canvas + \return Rectangle for displaying 100% of the plot + */ +QRectF QwtPolarPlot::plotRect( const QRectF& canvasRect ) const +{ + const QwtScaleDiv* sd = scaleDiv( QwtPolar::Radius ); + const QwtScaleEngine* se = scaleEngine( QwtPolar::Radius ); + + const int margin = plotMarginHint(); + const QRectF cr = canvasRect; + const int radius = qMin( cr.width(), cr.height() ) / 2 - margin; + + QwtScaleMap map; + map.setTransformation( se->transformation() ); + map.setPaintInterval( 0.0, radius / m_data->zoomFactor ); + map.setScaleInterval( sd->lowerBound(), sd->upperBound() ); + + double v = map.s1(); + if ( map.s1() <= map.s2() ) + v += m_data->zoomPos.radius(); + else + v -= m_data->zoomPos.radius(); + v = map.transform( v ); + + const QPointF off = + QwtPointPolar( m_data->zoomPos.azimuth(), v ).toPoint(); + + QPointF center( cr.center().x(), cr.top() + margin + radius ); + center -= QPointF( off.x(), -off.y() ); + + QRectF rect( 0, 0, 2 * map.p2(), 2 * map.p2() ); + rect.moveCenter( center ); + + return rect; +} + +/*! + \return Bounding interval of the radial scale that is + visible on the canvas. + */ +QwtInterval QwtPolarPlot::visibleInterval() const +{ + const QwtScaleDiv* sd = scaleDiv( QwtPolar::Radius ); + + const QRectF cRect = canvas()->contentsRect(); + const QRectF pRect = plotRect( cRect ); + if ( cRect.contains( pRect ) || !cRect.intersects( pRect ) ) + { + return QwtInterval( sd->lowerBound(), sd->upperBound() ); + } + + const QPointF pole = pRect.center(); + const QRectF scaleRect = pRect & cRect; + + const QwtScaleMap map = scaleMap( QwtPolar::Radius ); + + double dmin = 0.0; + double dmax = 0.0; + if ( scaleRect.contains( pole ) ) + { + dmin = 0.0; + + QPointF corners[4]; + corners[0] = scaleRect.bottomRight(); + corners[1] = scaleRect.topRight(); + corners[2] = scaleRect.topLeft(); + corners[3] = scaleRect.bottomLeft(); + + dmax = 0.0; + for ( int i = 0; i < 4; i++ ) + { + const double dist = qwtDistance( pole, corners[i] ); + if ( dist > dmax ) + dmax = dist; + } + } + else + { + if ( pole.x() < scaleRect.left() ) + { + if ( pole.y() < scaleRect.top() ) + { + dmin = qwtDistance( pole, scaleRect.topLeft() ); + dmax = qwtDistance( pole, scaleRect.bottomRight() ); + } + else if ( pole.y() > scaleRect.bottom() ) + { + dmin = qwtDistance( pole, scaleRect.bottomLeft() ); + dmax = qwtDistance( pole, scaleRect.topRight() ); + } + else + { + dmin = scaleRect.left() - pole.x(); + dmax = qMax( qwtDistance( pole, scaleRect.bottomRight() ), + qwtDistance( pole, scaleRect.topRight() ) ); + } + } + else if ( pole.x() > scaleRect.right() ) + { + if ( pole.y() < scaleRect.top() ) + { + dmin = qwtDistance( pole, scaleRect.topRight() ); + dmax = qwtDistance( pole, scaleRect.bottomLeft() ); + } + else if ( pole.y() > scaleRect.bottom() ) + { + dmin = qwtDistance( pole, scaleRect.bottomRight() ); + dmax = qwtDistance( pole, scaleRect.topLeft() ); + } + else + { + dmin = pole.x() - scaleRect.right(); + dmax = qMax( qwtDistance( pole, scaleRect.bottomLeft() ), + qwtDistance( pole, scaleRect.topLeft() ) ); + } + } + else if ( pole.y() < scaleRect.top() ) + { + dmin = scaleRect.top() - pole.y(); + dmax = qMax( qwtDistance( pole, scaleRect.bottomLeft() ), + qwtDistance( pole, scaleRect.bottomRight() ) ); + } + else if ( pole.y() > scaleRect.bottom() ) + { + dmin = pole.y() - scaleRect.bottom(); + dmax = qMax( qwtDistance( pole, scaleRect.topLeft() ), + qwtDistance( pole, scaleRect.topRight() ) ); + } + } + + const double radius = pRect.width() / 2.0; + if ( dmax > radius ) + dmax = radius; + + QwtInterval interval; + interval.setMinValue( map.invTransform( dmin ) ); + interval.setMaxValue( map.invTransform( dmax ) ); + + return interval; +} + +/*! + \return Layout, responsible for the geometry of the plot components + */ +QwtPolarLayout* QwtPolarPlot::plotLayout() +{ + return m_data->layout; +} + +/*! + \return Layout, responsible for the geometry of the plot components + */ +const QwtPolarLayout* QwtPolarPlot::plotLayout() const +{ + return m_data->layout; +} + +/*! + \brief Attach/Detach a plot item + + \param plotItem Plot item + \param on When true attach the item, otherwise detach it + */ +void QwtPolarPlot::attachItem( QwtPolarItem* plotItem, bool on ) +{ + if ( on ) + insertItem( plotItem ); + else + removeItem( plotItem ); + + Q_EMIT itemAttached( plotItem, on ); + + if ( plotItem->testItemAttribute( QwtPolarItem::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< QwtLegendData >() ); + } + } + + 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 + \sa infoToItem() + */ +QVariant QwtPolarPlot::itemToInfo( QwtPolarItem* plotItem ) const +{ + return QVariant::fromValue( plotItem ); +} + +/*! + \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() + */ +QwtPolarItem* QwtPolarPlot::infoToItem( const QVariant& itemInfo ) const +{ + if ( itemInfo.canConvert< QwtPolarItem* >() ) + return qvariant_cast< QwtPolarItem* >( itemInfo ); + + return NULL; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_polar_plot.cpp" +#endif diff --git a/libs/qwt/src/qwt_polar_plot.h b/libs/qwt/src/qwt_polar_plot.h new file mode 100644 index 00000000..1161641f --- /dev/null +++ b/libs/qwt/src/qwt_polar_plot.h @@ -0,0 +1,219 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_PLOT_H +#define QWT_POLAR_PLOT_H + +#include "qwt_global.h" +#include "qwt_polar.h" +#include "qwt_polar_itemdict.h" +#include "qwt_interval.h" +#include "qwt_scale_map.h" +#include "qwt_point_polar.h" +#include + +class QwtRoundScaleDraw; +class QwtScaleEngine; +class QwtScaleDiv; +class QwtTextLabel; +class QwtPolarCanvas; +class QwtPolarLayout; +class QwtAbstractLegend; + +/*! + \brief A plotting widget, displaying a polar coordinate system + + An unlimited number of plot items can be displayed on + its canvas. Plot items might be curves (QwtPolarCurve), markers + (QwtPolarMarker), the grid (QwtPolarGrid), or anything else derived + from QwtPolarItem. + + The coordinate system is defined by a radial and a azimuth scale. + 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. Autoscaling is supported + for the radial scale. + + In opposite to QwtPlot the scales might be different from the + view, that is displayed on the canvas. The view can be changed by + zooming - f.e. by using QwtPolarPanner or QwtPolarMaginfier. + */ +class QWT_EXPORT QwtPolarPlot : public QFrame, public QwtPolarItemDict +{ + Q_OBJECT + + Q_PROPERTY( QBrush plotBackground READ plotBackground WRITE setPlotBackground ) + Q_PROPERTY( double azimuthOrigin READ azimuthOrigin WRITE setAzimuthOrigin ) + + + public: + /*! + Position of the legend, relative to the canvas. + \sa insertLegend() + */ + enum LegendPosition + { + //! The legend will be left from the canvas. + LeftLegend, + + //! The legend will be right from the canvas. + RightLegend, + + //! The legend will be below the canvas. + BottomLegend, + + //! The legend will be between canvas and title. + TopLegend, + + /*! + External means that only the content of the legend + will be handled by QwtPlot, but not its geometry. + This might be interesting if an application wants to + have a legend in an external window ( or on the canvas ). + + \note The legend is not painted by QwtPolarRenderer + */ + ExternalLegend + }; + + explicit QwtPolarPlot( QWidget* parent = NULL ); + QwtPolarPlot( const QwtText& title, QWidget* parent = NULL ); + + virtual ~QwtPolarPlot(); + + void setTitle( const QString& ); + void setTitle( const QwtText& ); + + QwtText title() const; + + QwtTextLabel* titleLabel(); + const QwtTextLabel* titleLabel() const; + + void setAutoReplot( bool tf = true ); + bool autoReplot() const; + + void setAutoScale( int scaleId ); + bool hasAutoScale( int scaleId ) const; + + void setScaleMaxMinor( int scaleId, int maxMinor ); + int scaleMaxMinor( int scaleId ) const; + + int scaleMaxMajor( int scaleId ) const; + void setScaleMaxMajor( int scaleId, int maxMajor ); + + QwtScaleEngine* scaleEngine( int scaleId ); + const QwtScaleEngine* scaleEngine( int scaleId ) const; + void setScaleEngine( int scaleId, QwtScaleEngine* ); + + void setScale( int scaleId, double min, double max, double step = 0 ); + + void setScaleDiv( int scaleId, const QwtScaleDiv& ); + const QwtScaleDiv* scaleDiv( int scaleId ) const; + QwtScaleDiv* scaleDiv( int scaleId ); + + QwtScaleMap scaleMap( int scaleId, double radius ) const; + QwtScaleMap scaleMap( int scaleId ) const; + + void updateScale( int scaleId ); + + double azimuthOrigin() const; + + void zoom( const QwtPointPolar&, double factor ); + void unzoom(); + + QwtPointPolar zoomPos() const; + double zoomFactor() const; + + // Canvas + + QwtPolarCanvas* canvas(); + const QwtPolarCanvas* canvas() const; + + void setPlotBackground ( const QBrush& c ); + const QBrush& plotBackground() const; + + virtual void drawCanvas( QPainter*, const QRectF& ) const; + + // Legend + + void insertLegend( QwtAbstractLegend*, + LegendPosition = RightLegend, double ratio = -1.0 ); + + QwtAbstractLegend* legend(); + const QwtAbstractLegend* legend() const; + + void updateLegend(); + void updateLegend( const QwtPolarItem* ); + + // Layout + QwtPolarLayout* plotLayout(); + const QwtPolarLayout* plotLayout() const; + + QwtInterval visibleInterval() const; + QRectF plotRect() const; + QRectF plotRect( const QRectF& ) const; + + int plotMarginHint() const; + + virtual QVariant itemToInfo( QwtPolarItem* ) const; + virtual QwtPolarItem* infoToItem( const QVariant& ) const; + + Q_SIGNALS: + /*! + A signal indicating, that an item has been attached/detached + + \param plotItem Plot item + \param on Attached/Detached + */ + void itemAttached( QwtPolarItem* plotItem, bool on ); + + /*! + A signal with the attributes how to update + the legend entries for a plot item. + + \param itemInfo Info about a plot, 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< QwtLegendData >& data ); + + /*! + A signal that is emitted, whenever the layout of the plot + has been recalculated. + */ + void layoutChanged(); + + public Q_SLOTS: + virtual void replot(); + void autoRefresh(); + void setAzimuthOrigin( double ); + + protected: + virtual bool event( QEvent* ) QWT_OVERRIDE; + virtual void resizeEvent( QResizeEvent* ) QWT_OVERRIDE; + + virtual void updateLayout(); + + virtual void drawItems( QPainter* painter, + const QwtScaleMap& radialMap, const QwtScaleMap& azimuthMap, + const QPointF& pole, double radius, + const QRectF& canvasRect ) const; + + private: + friend class QwtPolarItem; + void attachItem( QwtPolarItem*, bool ); + + void initPlot( const QwtText& ); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_polar_renderer.cpp b/libs/qwt/src/qwt_polar_renderer.cpp new file mode 100644 index 00000000..e04eed23 --- /dev/null +++ b/libs/qwt/src/qwt_polar_renderer.cpp @@ -0,0 +1,485 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_renderer.h" +#include "qwt_polar_plot.h" +#include "qwt_polar_layout.h" +#include "qwt_legend.h" +#include "qwt_dyngrid_layout.h" +#include "qwt_text_label.h" +#include "qwt_text.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef QWT_NO_SVG +#ifdef QT_SVG_LIB +#define QWT_FORMAT_SVG 1 +#endif +#endif + +#ifndef QT_NO_PRINTER +#define QWT_FORMAT_PDF 1 +#endif + +#ifndef QT_NO_PDF + +// QPdfWriter::setResolution() has been introduced with +// Qt 5.3. Guess it is o.k. to stay with QPrinter for older +// versions. + +#if QT_VERSION >= 0x050300 + +#ifndef QWT_FORMAT_PDF +#define QWT_FORMAT_PDF 1 +#endif + +#define QWT_PDF_WRITER 1 + +#endif +#endif + +#ifndef QT_NO_PRINTER +// postscript support has been dropped in Qt5 +#if QT_VERSION < 0x050000 +#define QWT_FORMAT_POSTSCRIPT 1 +#endif +#endif + +#if QWT_FORMAT_SVG +#include +#endif + +#if QWT_PDF_WRITER +#include +#endif + +static inline double qwtDistance( + const QPointF& p1, const QPointF& p2 ) +{ + double dx = p2.x() - p1.x(); + double dy = p2.y() - p1.y(); + return qSqrt( dx * dx + dy * dy ); +} + +class QwtPolarRenderer::PrivateData +{ + public: + PrivateData() + : plot( NULL ) + { + } + + QwtPolarPlot* plot; +}; + +/*! + Constructor + \param parent Parent object + */ +QwtPolarRenderer::QwtPolarRenderer( QObject* parent ) + : QObject( parent ) +{ + m_data = new PrivateData; +} + +//! Destructor +QwtPolarRenderer::~QwtPolarRenderer() +{ + delete m_data; +} + +/*! + Render a polar plot to a file + + The format of the document will be autodetected from the + suffix of the filename. + + \param plot Plot widget + \param fileName Path of the file, where the document will be stored + \param sizeMM Size for the document in millimeters. + \param resolution Resolution in dots per Inch (dpi) + */ +void QwtPolarRenderer::renderDocument( QwtPolarPlot* plot, + const QString& fileName, const QSizeF& sizeMM, int resolution ) +{ + renderDocument( plot, fileName, + QFileInfo( fileName ).suffix(), sizeMM, resolution ); +} + +/*! + Render a plot to a file + + Supported formats are: + + - pdf\n + - ps\n + - svg\n + - all image formats supported by Qt, see QImageWriter::supportedImageFormats() + + \param plot Plot widget + \param fileName Path of the file, where the document will be stored + \param format Format for the document + \param sizeMM Size for the document in millimeters. + \param resolution Resolution in dots per Inch (dpi) + + \sa renderTo(), render(), QwtPainter::setRoundingAlignment() + */ +void QwtPolarRenderer::renderDocument( QwtPolarPlot* plot, + const QString& fileName, const QString& format, + const QSizeF& sizeMM, int resolution ) +{ + if ( plot == NULL || sizeMM.isEmpty() || resolution <= 0 ) + return; + + QString title = plot->title().text(); + if ( title.isEmpty() ) + title = "Plot Document"; + + const double mmToInch = 1.0 / 25.4; + const QSizeF size = sizeMM * mmToInch * resolution; + + const QRectF documentRect( 0.0, 0.0, size.width(), size.height() ); + + const QString fmt = format.toLower(); + if ( format == "pdf" ) + { +#if QWT_FORMAT_PDF +#if QWT_PDF_WRITER + QPdfWriter pdfWriter( fileName ); + pdfWriter.setPageSize( QPageSize( sizeMM, QPageSize::Millimeter ) ); + pdfWriter.setTitle( title ); + pdfWriter.setPageMargins( QMarginsF() ); + pdfWriter.setResolution( resolution ); + + QPainter painter( &pdfWriter ); + render( plot, &painter, documentRect ); + +#else + QPrinter printer; + printer.setOutputFormat( QPrinter::PdfFormat ); + printer.setColorMode( QPrinter::Color ); + printer.setFullPage( true ); + printer.setPaperSize( sizeMM, QPrinter::Millimeter ); + printer.setDocName( title ); + printer.setOutputFileName( fileName ); + printer.setResolution( resolution ); + + QPainter painter( &printer ); + render( plot, &painter, documentRect ); +#endif +#endif + } + else if ( format == "ps" ) + { +#if QWT_FORMAT_POSTSCRIPT + QPrinter printer; + printer.setColorMode( QPrinter::Color ); + printer.setFullPage( true ); + printer.setPaperSize( sizeMM, QPrinter::Millimeter ); + printer.setDocName( title ); + printer.setOutputFileName( fileName ); + printer.setOutputFormat( QPrinter::PostScriptFormat ); + printer.setResolution( resolution ); + + QPainter painter( &printer ); + render( plot, &painter, documentRect ); +#endif + } + else if ( format == "svg" ) + { +#ifdef QWT_FORMAT_SVG + QSvgGenerator generator; + generator.setTitle( title ); + generator.setFileName( fileName ); + generator.setResolution( resolution ); + generator.setViewBox( documentRect ); + + QPainter painter( &generator ); + render( plot, &painter, documentRect ); +#endif + } + else + { + if ( QImageWriter::supportedImageFormats().indexOf( + format.toLatin1() ) >= 0 ) + { + const QRect imageRect = documentRect.toRect(); + const int dotsPerMeter = qRound( resolution * mmToInch * 1000.0 ); + + QImage image( imageRect.size(), QImage::Format_ARGB32 ); + image.setDotsPerMeterX( dotsPerMeter ); + image.setDotsPerMeterY( dotsPerMeter ); + image.fill( QColor( Qt::white ).rgb() ); + + QPainter painter( &image ); + render( plot, &painter, imageRect ); + painter.end(); + + image.save( fileName, format.toLatin1() ); + } + } +} + +/*! + \brief Render the plot to a \c QPaintDevice + + This function renders the contents of a QwtPolarPlot instance to + \c QPaintDevice object. The target rectangle is derived from + its device metrics. + + \param plot Plot to be rendered + \param paintDevice device to paint on, f.e a QImage + + \sa renderDocument(), render(), QwtPainter::setRoundingAlignment() + */ + +void QwtPolarRenderer::renderTo( + QwtPolarPlot* plot, QPaintDevice& paintDevice ) const +{ + int w = paintDevice.width(); + int h = paintDevice.height(); + + QPainter p( &paintDevice ); + render( plot, &p, QRectF( 0, 0, w, h ) ); +} + + +/*! + \brief Render the plot to a QPrinter + + This function renders the contents of a QwtPolarPlot instance to + \c QPaintDevice object. The size is derived from the printer + metrics. + + \param plot Plot to be rendered + \param printer Printer to paint on + + \sa renderDocument(), render(), QwtPainter::setRoundingAlignment() + */ + +#ifndef QT_NO_PRINTER + +void QwtPolarRenderer::renderTo( + QwtPolarPlot* plot, QPrinter& printer ) const +{ + int w = printer.width(); + int h = printer.height(); + + QRectF rect( 0, 0, w, h ); + double aspect = rect.width() / rect.height(); + if ( ( aspect < 1.0 ) ) + rect.setHeight( aspect * rect.width() ); + + QPainter p( &printer ); + render( plot, &p, rect ); +} + +#endif + +#ifdef QWT_FORMAT_SVG + +/*! + \brief Render the plot to a QSvgGenerator + + If the generator has a view box, the plot will be rendered into it. + If it has no viewBox but a valid size the target coordinates + will be (0, 0, generator.width(), generator.height()). Otherwise + the target rectangle will be QRectF(0, 0, 800, 600); + + \param plot Plot to be rendered + \param generator SVG generator + */ +void QwtPolarRenderer::renderTo( + QwtPolarPlot* plot, QSvgGenerator& generator ) const +{ + QRectF rect = generator.viewBoxF(); + if ( rect.isEmpty() ) + rect.setRect( 0, 0, generator.width(), generator.height() ); + + if ( rect.isEmpty() ) + rect.setRect( 0, 0, 800, 600 ); // something + + QPainter p( &generator ); + render( plot, &p, rect ); +} + +#endif + +/*! + \brief Render the plot to a given rectangle ( f.e QPrinter, QSvgRenderer ) + + \param plot Plot widget to be rendered + \param painter Painter + \param plotRect Bounding rectangle for the plot + */ +void QwtPolarRenderer::render( QwtPolarPlot* plot, + QPainter* painter, const QRectF& plotRect ) const +{ + if ( plot == NULL || painter == NULL || !painter->isActive() || + !plotRect.isValid() || plot->size().isNull() ) + { + return; + } + + m_data->plot = plot; + + /* + The layout engine uses the same methods as they are used + by the Qt layout system. Therefore we need to calculate the + layout in screen coordinates and paint with a scaled painter. + */ + QTransform transform; + transform.scale( + double( painter->device()->logicalDpiX() ) / plot->logicalDpiX(), + double( painter->device()->logicalDpiY() ) / plot->logicalDpiY() ); + + const QRectF layoutRect = transform.inverted().mapRect( plotRect ); + + QwtPolarLayout* layout = plot->plotLayout(); + + // All paint operations need to be scaled according to + // the paint device metrics. + + QwtPolarLayout::Options layoutOptions = + QwtPolarLayout::IgnoreScrollbars | QwtPolarLayout::IgnoreFrames; + + layout->activate( plot, layoutRect, layoutOptions ); + + painter->save(); + painter->setWorldTransform( transform, true ); + + painter->save(); + renderTitle( painter, layout->titleRect() ); + painter->restore(); + + painter->save(); + renderLegend( plot, painter, layout->legendRect() ); + painter->restore(); + + const QRectF canvasRect = layout->canvasRect(); + + painter->save(); + painter->setClipRect( canvasRect ); + plot->drawCanvas( painter, canvasRect ); + painter->restore(); + + painter->restore(); + + layout->invalidate(); + + m_data->plot = NULL; +} + +/*! + Render the title into a given rectangle. + + \param painter Painter + \param rect Bounding rectangle + */ + +void QwtPolarRenderer::renderTitle( QPainter* painter, const QRectF& rect ) const +{ + QwtTextLabel* title = m_data->plot->titleLabel(); + + painter->setFont( title->font() ); + + const QColor color = title->palette().color( + QPalette::Active, QPalette::Text ); + + painter->setPen( color ); + title->text().draw( painter, rect ); +} + +/*! + Render the legend into a given rectangle. + + \param plot Plot widget + \param painter Painter + \param rect Bounding rectangle + */ +void QwtPolarRenderer::renderLegend( const QwtPolarPlot* plot, + QPainter* painter, const QRectF& rect ) const +{ + if ( plot->legend() ) + plot->legend()->renderLegend( painter, rect, true ); +} + +/*! + \brief Execute a file dialog and render the plot to the selected file + + The document will be rendered in 85 dpi for a size 30x30 cm + + \param plot Plot widget + \param documentName Default document name + \param sizeMM Size for the document in millimeters. + \param resolution Resolution in dots per Inch (dpi) + + \sa renderDocument() + */ +bool QwtPolarRenderer::exportTo( QwtPolarPlot* plot, + const QString& documentName, const QSizeF& sizeMM, int resolution ) +{ + if ( plot == NULL ) + return false; + + QString fileName = documentName; + + // What about translation + +#ifndef QT_NO_FILEDIALOG + const QList< QByteArray > imageFormats = + QImageWriter::supportedImageFormats(); + + QStringList filter; +#ifndef QT_NO_PRINTER + filter += QString( "PDF " ) + tr( "Documents" ) + " (*.pdf)"; +#endif +#ifndef QWT_NO_SVG + filter += QString( "SVG " ) + tr( "Documents" ) + " (*.svg)"; +#endif +#ifndef QT_NO_PRINTER + filter += QString( "Postscript " ) + tr( "Documents" ) + " (*.ps)"; +#endif + + if ( imageFormats.size() > 0 ) + { + QString imageFilter( tr( "Images" ) ); + imageFilter += " ("; + for ( int i = 0; i < imageFormats.size(); i++ ) + { + if ( i > 0 ) + imageFilter += " "; + imageFilter += "*."; + imageFilter += imageFormats[i]; + } + imageFilter += ")"; + + filter += imageFilter; + } + + fileName = QFileDialog::getSaveFileName( + NULL, tr( "Export File Name" ), fileName, + filter.join( ";;" ), NULL, QFileDialog::DontConfirmOverwrite ); +#endif + if ( fileName.isEmpty() ) + return false; + + renderDocument( plot, fileName, sizeMM, resolution ); + + return true; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_polar_renderer.cpp" +#endif diff --git a/libs/qwt/src/qwt_polar_renderer.h b/libs/qwt/src/qwt_polar_renderer.h new file mode 100644 index 00000000..c877386a --- /dev/null +++ b/libs/qwt/src/qwt_polar_renderer.h @@ -0,0 +1,78 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_RENDERER_H +#define QWT_POLAR_RENDERER_H + +#include "qwt_global.h" +#include +#include + +class QwtPolarPlot; +class QRectF; +class QPainter; +class QPaintDevice; + +#ifndef QT_NO_PRINTER +class QPrinter; +#endif + +#ifndef QWT_NO_SVG +#ifdef QT_SVG_LIB +class QSvgGenerator; +#endif +#endif + +/*! + \brief Renderer for exporting a polar plot to a document, a printer + or anything else, that is supported by QPainter/QPaintDevice + */ +class QWT_EXPORT QwtPolarRenderer : public QObject +{ + Q_OBJECT + + public: + explicit QwtPolarRenderer( QObject* parent = NULL ); + virtual ~QwtPolarRenderer(); + + void renderDocument( QwtPolarPlot*, const QString& format, + const QSizeF& sizeMM, int resolution = 85 ); + + void renderDocument( QwtPolarPlot*, + const QString& title, const QString& format, + const QSizeF& sizeMM, int resolution = 85 ); + +#ifndef QWT_NO_SVG +#ifdef QT_SVG_LIB + void renderTo( QwtPolarPlot*, QSvgGenerator& ) const; +#endif +#endif + +#ifndef QT_NO_PRINTER + void renderTo( QwtPolarPlot*, QPrinter& ) const; +#endif + + void renderTo( QwtPolarPlot*, QPaintDevice& ) const; + + virtual void render( QwtPolarPlot*, + QPainter*, const QRectF& rect ) const; + + bool exportTo( QwtPolarPlot*, const QString& documentName, + const QSizeF& sizeMM = QSizeF( 200, 200 ), int resolution = 85 ); + + virtual void renderTitle( QPainter*, const QRectF& ) const; + + virtual void renderLegend( + const QwtPolarPlot*, QPainter*, const QRectF& ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_polar_spectrogram.cpp b/libs/qwt/src/qwt_polar_spectrogram.cpp new file mode 100644 index 00000000..a5a668f5 --- /dev/null +++ b/libs/qwt/src/qwt_polar_spectrogram.cpp @@ -0,0 +1,449 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_polar_spectrogram.h" +#include "qwt_polar.h" +#include "qwt_polar_plot.h" +#include "qwt_color_map.h" +#include "qwt_scale_map.h" +#include "qwt_raster_data.h" +#include "qwt_math.h" +#include "qwt_clipper.h" + +#include +#include +#include +#include +#include + +#if QT_VERSION < 0x050000 +#include +#endif + +class QwtPolarSpectrogram::TileInfo +{ + public: + QPoint imagePos; + QRect rect; + QImage* image; +}; + +class QwtPolarSpectrogram::PrivateData +{ + public: + PrivateData() + : data( NULL ) + { + colorMap = new QwtLinearColorMap(); + } + + ~PrivateData() + { + delete data; + delete colorMap; + } + + QwtRasterData* data; + QwtColorMap* colorMap; + + QwtPolarSpectrogram::PaintAttributes paintAttributes; +}; + +//! Constructor +QwtPolarSpectrogram::QwtPolarSpectrogram() + : QwtPolarItem( QwtText( "Spectrogram" ) ) +{ + m_data = new PrivateData; + + setItemAttribute( QwtPolarItem::AutoScale ); + setItemAttribute( QwtPolarItem::Legend, false ); + + setZ( 20.0 ); +} + +//! Destructor +QwtPolarSpectrogram::~QwtPolarSpectrogram() +{ + delete m_data; +} + +//! \return QwtPolarItem::Rtti_PolarSpectrogram +int QwtPolarSpectrogram::rtti() const +{ + return QwtPolarItem::Rtti_PolarSpectrogram; +} + +/*! + Set the data to be displayed + + \param data Spectrogram Data + \sa data() + + \warning QwtRasterData::initRaster() is called each time before the + image is rendered, but without any useful parameters. + Also QwtRasterData::rasterHint() is not used. + */ +void QwtPolarSpectrogram::setData( QwtRasterData* data ) +{ + if ( data != m_data->data ) + { + delete m_data->data; + m_data->data = data; + + itemChanged(); + } +} + +/*! + \return Spectrogram data + \sa setData() + */ +const QwtRasterData* QwtPolarSpectrogram::data() const +{ + return m_data->data; +} + +/*! + Change the color map + + Often it is useful to display the mapping between intensities and + colors as an additional plot axis, showing a color bar. + + \param colorMap Color Map + + \sa colorMap(), QwtScaleWidget::setColorBarEnabled(), + QwtScaleWidget::setColorMap() + */ +void QwtPolarSpectrogram::setColorMap( QwtColorMap* colorMap ) +{ + if ( m_data->colorMap != colorMap ) + { + delete m_data->colorMap; + m_data->colorMap = colorMap; + } + + itemChanged(); +} + +/*! + \return Color Map used for mapping the intensity values to colors + \sa setColorMap() + */ +const QwtColorMap* QwtPolarSpectrogram::colorMap() const +{ + return m_data->colorMap; +} + +/*! + Specify an attribute how to draw the curve + + \param attribute Paint attribute + \param on On/Off + \sa testPaintAttribute() + */ +void QwtPolarSpectrogram::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( on ) + m_data->paintAttributes |= attribute; + else + m_data->paintAttributes &= ~attribute; +} + +/*! + \param attribute Paint attribute + \return True, when attribute has been set + \sa setPaintAttribute() + */ +bool QwtPolarSpectrogram::testPaintAttribute( PaintAttribute attribute ) const +{ + return ( m_data->paintAttributes & attribute ); +} + +/*! + Draw the spectrogram + + \param painter Painter + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param radius Radius of the complete plot area in painter coordinates + \param canvasRect Contents rect of the canvas in painter coordinates + */ +void QwtPolarSpectrogram::draw( QPainter* painter, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double, + const QRectF& canvasRect ) const +{ + const QRectF plotRect = plot()->plotRect( canvasRect.toRect() ); + QRect imageRect = canvasRect.toRect(); + + painter->save(); + + painter->setClipRect( canvasRect ); + + QPainterPath clipPathCanvas; + clipPathCanvas.addEllipse( plotRect ); + painter->setClipPath( clipPathCanvas, Qt::IntersectClip ); + + imageRect &= plotRect.toAlignedRect(); // outer rect + + const QwtInterval radialInterval = boundingInterval( QwtPolar::ScaleRadius ); + if ( radialInterval.isValid() ) + { + const double radius = radialMap.transform( radialInterval.maxValue() ) - + radialMap.transform( radialInterval.minValue() ); + + QRectF clipRect( 0, 0, 2 * radius, 2 * radius ); + clipRect.moveCenter( pole ); + + imageRect &= clipRect.toRect(); // inner rect, we don't have points outside + + QPainterPath clipPathRadial; + clipPathRadial.addEllipse( clipRect ); + painter->setClipPath( clipPathRadial, Qt::IntersectClip ); + } + + const QImage image = renderImage( azimuthMap, radialMap, pole, imageRect ); + painter->drawImage( imageRect, image ); + + painter->restore(); +} + +/*! + \brief Render an image from the data and color map. + + The area is translated into a rect of the paint device. + For each pixel of this rect the intensity is mapped + into a color. + + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param rect Target rectangle of the image in painter coordinates + + \return A QImage::Format_Indexed8 or QImage::Format_ARGB32 depending + on the color map. + + \sa QwtRasterData::intensity(), QwtColorMap::rgb(), + QwtColorMap::colorIndex() + */ +QImage QwtPolarSpectrogram::renderImage( + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, const QRect& rect ) const +{ + if ( m_data->data == NULL || m_data->colorMap == NULL ) + return QImage(); + + QImage image( rect.size(), m_data->colorMap->format() == QwtColorMap::RGB + ? QImage::Format_ARGB32 : QImage::Format_Indexed8 ); + + const QwtInterval intensityRange = m_data->data->interval( Qt::ZAxis ); + if ( !intensityRange.isValid() ) + return image; + + if ( m_data->colorMap->format() == QwtColorMap::Indexed ) + image.setColorTable( m_data->colorMap->colorTable256() ); + + /* + For the moment we only announce the composition of the image by + calling initRaster(), but we don't pass any useful parameters. + ( How to map rect into something, that is useful to initialize a matrix + of values in polar coordinates ? ) + */ + m_data->data->initRaster( QRectF(), QSize() ); + + +#if !defined( QT_NO_QFUTURE ) + uint numThreads = renderThreadCount(); + + if ( numThreads <= 0 ) + numThreads = QThread::idealThreadCount(); + + if ( numThreads <= 0 ) + numThreads = 1; + + const int numRows = rect.height() / numThreads; + + + QVector< TileInfo > tileInfos; + for ( uint i = 0; i < numThreads; i++ ) + { + QRect tile( rect.x(), rect.y() + i * numRows, rect.width(), numRows ); + if ( i == numThreads - 1 ) + tile.setHeight( rect.height() - i * numRows ); + + TileInfo tileInfo; + tileInfo.imagePos = rect.topLeft(); + tileInfo.rect = tile; + tileInfo.image = ℑ + + tileInfos += tileInfo; + } + + QVector< QFuture< void > > futures; + for ( int i = 0; i < tileInfos.size(); i++ ) + { + if ( i == tileInfos.size() - 1 ) + { + renderTileInfo( azimuthMap, radialMap, pole, &tileInfos[i] ); + } + else + { + futures += QtConcurrent::run( +#if QT_VERSION >= 0x060000 + &QwtPolarSpectrogram::renderTileInfo, this, +#else + this, &QwtPolarSpectrogram::renderTileInfo, +#endif + azimuthMap, radialMap, pole, &tileInfos[i] ); + } + } + + for ( int i = 0; i < futures.size(); i++ ) + futures[i].waitForFinished(); + +#else + renderTile( azimuthMap, radialMap, pole, rect.topLeft(), rect, &image ); +#endif + + m_data->data->discardRaster(); + + return image; +} + +void QwtPolarSpectrogram::renderTileInfo( + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, TileInfo* tileInfo ) const +{ + renderTile( azimuthMap, radialMap, pole, + tileInfo->imagePos, tileInfo->rect, tileInfo->image ); +} + +/*! + \brief Render a sub-rectangle of an image + + renderTile() is called by renderImage() to render different parts + of the image by concurrent threads. + + \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI + \param radialMap Maps radius values into painter coordinates. + \param pole Position of the pole in painter coordinates + \param imagePos Top/left position of the image in painter coordinates + \param tile Sub-rectangle of the tile in painter coordinates + \param image Image to be rendered + + \sa setRenderThreadCount() + \note renderTile needs to be reentrant + */ +void QwtPolarSpectrogram::renderTile( + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, const QPoint& imagePos, + const QRect& tile, QImage* image ) const +{ + const QwtInterval intensityRange = m_data->data->interval( Qt::ZAxis ); + if ( !intensityRange.isValid() ) + return; + + const bool doFastAtan = testPaintAttribute( ApproximatedAtan ); + + const int y0 = imagePos.y(); + const int y1 = tile.top(); + const int y2 = tile.bottom(); + + const int x0 = imagePos.x(); + const int x1 = tile.left(); + const int x2 = tile.right(); + + if ( m_data->colorMap->format() == QwtColorMap::RGB ) + { + for ( int y = y1; y <= y2; y++ ) + { + const double dy = pole.y() - y; + const double dy2 = qwtSqr( dy ); + + QRgb* line = reinterpret_cast< QRgb* >( image->scanLine( y - y0 ) ); + line += x1 - x0; + + for ( int x = x1; x <= x2; x++ ) + { + const double dx = x - pole.x(); + + double a = doFastAtan ? qwtFastAtan2( dy, dx ) : qAtan2( dy, dx ); + + if ( a < 0.0 ) + a += 2 * M_PI; + + if ( a < azimuthMap.p1() ) + a += 2 * M_PI; + + const double r = qSqrt( qwtSqr( dx ) + dy2 ); + + const double azimuth = azimuthMap.invTransform( a ); + const double radius = radialMap.invTransform( r ); + + const double value = m_data->data->value( azimuth, radius ); + if ( qIsNaN( value ) ) + { + *line++ = 0u; + } + else + { + *line++ = m_data->colorMap->rgb( intensityRange, value ); + } + } + } + } + else if ( m_data->colorMap->format() == QwtColorMap::Indexed ) + { + for ( int y = y1; y <= y2; y++ ) + { + const double dy = pole.y() - y; + const double dy2 = qwtSqr( dy ); + + unsigned char* line = image->scanLine( y - y0 ); + line += x1 - x0; + for ( int x = x1; x <= x2; x++ ) + { + const double dx = x - pole.x(); + + double a = doFastAtan ? qwtFastAtan2( dy, dx ) : qAtan2( dy, dx ); + if ( a < 0.0 ) + a += 2 * M_PI; + if ( a < azimuthMap.p1() ) + a += 2 * M_PI; + + const double r = qSqrt( qwtSqr( dx ) + dy2 ); + + const double azimuth = azimuthMap.invTransform( a ); + const double radius = radialMap.invTransform( r ); + + const double value = m_data->data->value( azimuth, radius ); + + const uint index = m_data->colorMap->colorIndex( 256, intensityRange, value ); + *line++ = static_cast< unsigned char >( index ); + } + } + } +} + +/*! + Interval, that is necessary to display the item + This interval can be useful for operations like clipping or autoscaling + + \param scaleId Scale index + \return bounding interval ( == position ) + + \sa position() + */ +QwtInterval QwtPolarSpectrogram::boundingInterval( int scaleId ) const +{ + if ( scaleId == QwtPolar::ScaleRadius ) + return m_data->data->interval( Qt::YAxis ); + + return QwtPolarItem::boundingInterval( scaleId ); +} diff --git a/libs/qwt/src/qwt_polar_spectrogram.h b/libs/qwt/src/qwt_polar_spectrogram.h new file mode 100644 index 00000000..eab3b5a1 --- /dev/null +++ b/libs/qwt/src/qwt_polar_spectrogram.h @@ -0,0 +1,91 @@ +/****************************************************************************** + * QwtPolar Widget Library + * Copyright (C) 2008 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_POLAR_SPECTROGRAM_H +#define QWT_POLAR_SPECTROGRAM_H + +#include "qwt_global.h" +#include "qwt_polar_item.h" +#include + +class QwtRasterData; +class QwtColorMap; + +/*! + \brief An item, which displays a spectrogram + + A spectrogram displays 3-dimensional data, where the 3rd dimension + ( the intensity ) is displayed using colors. The colors are calculated + from the values using a color map. + + \sa QwtRasterData, QwtColorMap + */ +class QWT_EXPORT QwtPolarSpectrogram : public QwtPolarItem +{ + public: + /*! + Attributes to modify the drawing algorithm. + The default setting disables ApproximatedAtan + + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + /*! + Use qwtFastAtan2 instead of atan2 for translating + widget into polar coordinates. + */ + + ApproximatedAtan = 0x01 + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + explicit QwtPolarSpectrogram(); + virtual ~QwtPolarSpectrogram(); + + void setData( QwtRasterData* data ); + const QwtRasterData* data() const; + + void setColorMap( QwtColorMap* ); + const QwtColorMap* colorMap() const; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + virtual int rtti() const QWT_OVERRIDE; + + virtual void draw( QPainter* painter, + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, double radius, + const QRectF& canvasRect ) const QWT_OVERRIDE; + + virtual QwtInterval boundingInterval( int scaleId ) const QWT_OVERRIDE; + + protected: + virtual QImage renderImage( + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, const QRect& rect ) const; + + virtual void renderTile( + const QwtScaleMap& azimuthMap, const QwtScaleMap& radialMap, + const QPointF& pole, const QPoint& imagePos, + const QRect& tile, QImage* image ) const; + + private: + class TileInfo; + void renderTileInfo( const QwtScaleMap&, const QwtScaleMap&, + const QPointF& pole, TileInfo* ) const; + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPolarSpectrogram::PaintAttributes ) + +#endif diff --git a/libs/qwt/src/qwt_raster_data.cpp b/libs/qwt/src/qwt_raster_data.cpp new file mode 100644 index 00000000..afbf267f --- /dev/null +++ b/libs/qwt/src/qwt_raster_data.cpp @@ -0,0 +1,429 @@ +/****************************************************************************** + * 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_raster_data.h" +#include "qwt_point_3d.h" +#include "qwt_interval.h" + +#include +#include +#include +#include +#include + +class QwtRasterData::ContourPlane +{ + public: + explicit inline ContourPlane( double z ): + m_z( z ) + { + } + + inline bool intersect( const QwtPoint3D vertex[3], + QPointF line[2], bool ignoreOnPlane ) const; + + inline double z() const { return m_z; } + + private: + inline int compare( double z ) const; + inline QPointF intersection( + const QwtPoint3D& p1, const QwtPoint3D& p2 ) const; + + double m_z; +}; + +inline bool QwtRasterData::ContourPlane::intersect( + const QwtPoint3D vertex[3], QPointF line[2], + bool ignoreOnPlane ) const +{ + bool found = true; + + // Are the vertices below (-1), on (0) or above (1) the plan ? + const int eq1 = compare( vertex[0].z() ); + const int eq2 = compare( vertex[1].z() ); + const int eq3 = compare( vertex[2].z() ); + + /* + (a) All the vertices lie below the contour level. + (b) Two vertices lie below and one on the contour level. + (c) Two vertices lie below and one above the contour level. + (d) One vertex lies below and two on the contour level. + (e) One vertex lies below, one on and one above the contour level. + (f) One vertex lies below and two above the contour level. + (g) Three vertices lie on the contour level. + (h) Two vertices lie on and one above the contour level. + (i) One vertex lies on and two above the contour level. + (j) All the vertices lie above the contour level. + */ + + static const int tab[3][3][3] = + { + // jump table to avoid nested case statements + { { 0, 0, 8 }, { 0, 2, 5 }, { 7, 6, 9 } }, + { { 0, 3, 4 }, { 1, 10, 1 }, { 4, 3, 0 } }, + { { 9, 6, 7 }, { 5, 2, 0 }, { 8, 0, 0 } } + }; + + const int edgeType = tab[eq1 + 1][eq2 + 1][eq3 + 1]; + switch ( edgeType ) + { + case 1: + // d(0,0,-1), h(0,0,1) + line[0] = vertex[0].toPoint(); + line[1] = vertex[1].toPoint(); + break; + case 2: + // d(-1,0,0), h(1,0,0) + line[0] = vertex[1].toPoint(); + line[1] = vertex[2].toPoint(); + break; + case 3: + // d(0,-1,0), h(0,1,0) + line[0] = vertex[2].toPoint(); + line[1] = vertex[0].toPoint(); + break; + case 4: + // e(0,-1,1), e(0,1,-1) + line[0] = vertex[0].toPoint(); + line[1] = intersection( vertex[1], vertex[2] ); + break; + case 5: + // e(-1,0,1), e(1,0,-1) + line[0] = vertex[1].toPoint(); + line[1] = intersection( vertex[2], vertex[0] ); + break; + case 6: + // e(-1,1,0), e(1,0,-1) + line[0] = vertex[2].toPoint(); + line[1] = intersection( vertex[0], vertex[1] ); + break; + case 7: + // c(-1,1,-1), f(1,1,-1) + line[0] = intersection( vertex[0], vertex[1] ); + line[1] = intersection( vertex[1], vertex[2] ); + break; + case 8: + // c(-1,-1,1), f(1,1,-1) + line[0] = intersection( vertex[1], vertex[2] ); + line[1] = intersection( vertex[2], vertex[0] ); + break; + case 9: + // f(-1,1,1), c(1,-1,-1) + line[0] = intersection( vertex[2], vertex[0] ); + line[1] = intersection( vertex[0], vertex[1] ); + break; + case 10: + // g(0,0,0) + // The CONREC algorithm has no satisfying solution for + // what to do, when all vertices are on the plane. + + if ( ignoreOnPlane ) + found = false; + else + { + line[0] = vertex[2].toPoint(); + line[1] = vertex[0].toPoint(); + } + break; + default: + found = false; + } + + return found; +} + +inline int QwtRasterData::ContourPlane::compare( double z ) const +{ + if ( z > m_z ) + return 1; + + if ( z < m_z ) + return -1; + + return 0; +} + +inline QPointF QwtRasterData::ContourPlane::intersection( + const QwtPoint3D& p1, const QwtPoint3D& p2 ) const +{ + const double h1 = p1.z() - m_z; + const double h2 = p2.z() - m_z; + + const double x = ( h2 * p1.x() - h1 * p2.x() ) / ( h2 - h1 ); + const double y = ( h2 * p1.y() - h1 * p2.y() ) / ( h2 - h1 ); + + return QPointF( x, y ); +} + +class QwtRasterData::PrivateData +{ + public: + QwtRasterData::Attributes attributes; +}; + +//! Constructor +QwtRasterData::QwtRasterData() +{ + m_data = new PrivateData(); +} + +//! Destructor +QwtRasterData::~QwtRasterData() +{ + delete m_data; +} + +/*! + Specify an attribute of the data + + \param attribute Attribute + \param on On/Off + /sa Attribute, testAttribute() + */ +void QwtRasterData::setAttribute( Attribute attribute, bool on ) +{ + if ( on ) + m_data->attributes |= attribute; + else + m_data->attributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa Attribute, setAttribute() + */ +bool QwtRasterData::testAttribute( Attribute attribute ) const +{ + return m_data->attributes & attribute; +} + +/*! + \brief Initialize a raster + + Before the composition of an image QwtPlotSpectrogram calls initRaster(), + announcing the area and its resolution that will be requested. + + The default implementation does nothing, but for data sets that + are stored in files, it might be good idea to reimplement initRaster(), + where the data is resampled and loaded into memory. + + \param area Area of the raster + \param raster Number of horizontal and vertical pixels + + \sa initRaster(), value() + */ +void QwtRasterData::initRaster( const QRectF& area, const QSize& raster ) +{ + Q_UNUSED( area ); + Q_UNUSED( raster ); +} + +/*! + \brief Discard a raster + + After the composition of an image QwtPlotSpectrogram calls discardRaster(). + + The default implementation does nothing, but if data has been loaded + in initRaster(), it could deleted now. + + \sa initRaster(), value() + */ +void QwtRasterData::discardRaster() +{ +} + +/*! + \brief 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. + + Width and height of the hint need to be the horizontal + and vertical distances between 2 neighbored points. + The center of the hint has to be the position of any point + ( it doesn't matter which one ). + + An empty hint indicates, that there are values for any detail level. + + Limiting the resolution of the image might significantly improve + the performance and heavily reduce the amount of memory when rendering + a QImage from the raster data. + + The default implementation returns an empty rectangle recommending + to render in target device ( f.e. screen ) resolution. + + \param area In most implementations the resolution of the data doesn't + depend on the requested area. + + \return Bounding rectangle of a pixel + */ +QRectF QwtRasterData::pixelHint( const QRectF& area ) const +{ + Q_UNUSED( area ); + return QRectF(); +} + +/*! + Calculate contour lines + + \param rect Bounding rectangle for the contour lines + \param raster Number of data pixels of the raster data + \param levels List of limits, where to insert contour lines + \param flags Flags to customize the contouring algorithm + + \return Calculated contour lines + + An adaption of CONREC, a simple contouring algorithm. + http://local.wasp.uwa.edu.au/~pbourke/papers/conrec/ + */ +QwtRasterData::ContourLines QwtRasterData::contourLines( + const QRectF& rect, const QSize& raster, + const QList< double >& levels, ConrecFlags flags ) const +{ + ContourLines contourLines; + + if ( levels.size() == 0 || !rect.isValid() || !raster.isValid() ) + return contourLines; + + const double dx = rect.width() / raster.width(); + const double dy = rect.height() / raster.height(); + + const bool ignoreOnPlane = + flags & QwtRasterData::IgnoreAllVerticesOnLevel; + + const QwtInterval range = interval( Qt::ZAxis ); + bool ignoreOutOfRange = false; + if ( range.isValid() ) + ignoreOutOfRange = flags & IgnoreOutOfRange; + + QwtRasterData* that = const_cast< QwtRasterData* >( this ); + that->initRaster( rect, raster ); + + for ( int y = 0; y < raster.height() - 1; y++ ) + { + enum Position + { + Center, + + TopLeft, + TopRight, + BottomRight, + BottomLeft, + + NumPositions + }; + + QwtPoint3D xy[NumPositions]; + + for ( int x = 0; x < raster.width() - 1; x++ ) + { + const QPointF pos( rect.x() + x * dx, rect.y() + y * dy ); + + if ( x == 0 ) + { + xy[TopRight].setX( pos.x() ); + xy[TopRight].setY( pos.y() ); + xy[TopRight].setZ( + value( xy[TopRight].x(), xy[TopRight].y() ) + ); + + xy[BottomRight].setX( pos.x() ); + xy[BottomRight].setY( pos.y() + dy ); + xy[BottomRight].setZ( + value( xy[BottomRight].x(), xy[BottomRight].y() ) + ); + } + + xy[TopLeft] = xy[TopRight]; + xy[BottomLeft] = xy[BottomRight]; + + xy[TopRight].setX( pos.x() + dx ); + xy[TopRight].setY( pos.y() ); + xy[BottomRight].setX( pos.x() + dx ); + xy[BottomRight].setY( pos.y() + dy ); + + xy[TopRight].setZ( + value( xy[TopRight].x(), xy[TopRight].y() ) + ); + xy[BottomRight].setZ( + value( xy[BottomRight].x(), xy[BottomRight].y() ) + ); + + double zMin = xy[TopLeft].z(); + double zMax = zMin; + double zSum = zMin; + + for ( int i = TopRight; i <= BottomLeft; i++ ) + { + const double z = xy[i].z(); + + zSum += z; + if ( z < zMin ) + zMin = z; + if ( z > zMax ) + zMax = z; + } + + if ( qIsNaN( zSum ) ) + { + // one of the points is NaN + continue; + } + + if ( ignoreOutOfRange ) + { + if ( !range.contains( zMin ) || !range.contains( zMax ) ) + continue; + } + + if ( zMax < levels[0] || + zMin > levels[levels.size() - 1] ) + { + continue; + } + + xy[Center].setX( pos.x() + 0.5 * dx ); + xy[Center].setY( pos.y() + 0.5 * dy ); + xy[Center].setZ( 0.25 * zSum ); + + const int numLevels = levels.size(); + for ( int l = 0; l < numLevels; l++ ) + { + const double level = levels[l]; + if ( level < zMin || level > zMax ) + continue; + QPolygonF& lines = contourLines[level]; + const ContourPlane plane( level ); + + QPointF line[2]; + QwtPoint3D vertex[3]; + + for ( int m = TopLeft; m < NumPositions; m++ ) + { + vertex[0] = xy[m]; + vertex[1] = xy[0]; + vertex[2] = xy[m != BottomLeft ? m + 1 : TopLeft]; + + const bool intersects = + plane.intersect( vertex, line, ignoreOnPlane ); + if ( intersects ) + { + lines += line[0]; + lines += line[1]; + } + } + } + } + } + + that->discardRaster(); + + return contourLines; +} diff --git a/libs/qwt/src/qwt_raster_data.h b/libs/qwt/src/qwt_raster_data.h new file mode 100644 index 00000000..0bbaaa9d --- /dev/null +++ b/libs/qwt/src/qwt_raster_data.h @@ -0,0 +1,129 @@ +/****************************************************************************** + * 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_RASTER_DATA_H +#define QWT_RASTER_DATA_H + +#include "qwt_global.h" +#include + +class QwtInterval; +class QPolygonF; +class QRectF; +class QSize; +template< typename T > class QList; +template< class Key, class T > class QMap; + +/*! + \brief QwtRasterData defines an interface to any type of raster data. + + QwtRasterData is an abstract interface, that is used by + QwtPlotRasterItem to find the values at the pixels of its raster. + + Gaps inside the bounding rectangle of the data can be indicated by NaN + values ( when WithoutGaps is disabled ). + + Often a raster item is used to display values from a matrix. Then the + derived raster data class needs to implement some sort of resampling, + that maps the raster of the matrix into the requested raster of + the raster item ( depending on resolution and scales of the canvas ). + + QwtMatrixRasterData implements raster data, that returns values from + a given 2D matrix. + + \sa QwtMatrixRasterData + */ +class QWT_EXPORT QwtRasterData +{ + public: + //! Contour lines + typedef QMap< double, QPolygonF > ContourLines; + + /*! + \brief Raster data attributes + + Additional information that is used to improve processing + of the data. + */ + enum Attribute + { + /*! + The bounding rectangle of the data is spanned by + the interval(Qt::XAxis) and interval(Qt::YAxis). + + WithoutGaps indicates, that the data has no gaps + ( unknown values ) in this area and the result of + value() does not need to be checked for NaN values. + + Enabling this flag will have an positive effect on + the performance of rendering a QwtPlotSpectrogram. + + The default setting is false. + + \note NaN values indicate an undefined value + */ + WithoutGaps = 0x01 + }; + + Q_DECLARE_FLAGS( Attributes, Attribute ) + + //! Flags to modify the contour algorithm + enum ConrecFlag + { + //! Ignore all vertices on the same level + IgnoreAllVerticesOnLevel = 0x01, + + //! Ignore all values, that are out of range + IgnoreOutOfRange = 0x02 + }; + + Q_DECLARE_FLAGS( ConrecFlags, ConrecFlag ) + + QwtRasterData(); + virtual ~QwtRasterData(); + + void setAttribute( Attribute, bool on = true ); + bool testAttribute( Attribute ) const; + + /*! + \return Bounding interval for an axis + \sa setInterval + */ + virtual QwtInterval interval( Qt::Axis ) const = 0; + + virtual QRectF pixelHint( const QRectF& ) const; + + virtual void initRaster( const QRectF&, const QSize& raster ); + virtual void discardRaster(); + + /*! + \return the value at a raster position + \param x X value in plot coordinates + \param y Y value in plot coordinates + */ + virtual double value( double x, double y ) const = 0; + + virtual ContourLines contourLines( const QRectF& rect, + const QSize& raster, const QList< double >& levels, + ConrecFlags ) const; + + class Contour3DPoint; + class ContourPlane; + + private: + Q_DISABLE_COPY(QwtRasterData) + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtRasterData::ConrecFlags ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtRasterData::Attributes ) + +#endif diff --git a/libs/qwt/src/qwt_round_scale_draw.cpp b/libs/qwt/src/qwt_round_scale_draw.cpp new file mode 100644 index 00000000..f048430a --- /dev/null +++ b/libs/qwt/src/qwt_round_scale_draw.cpp @@ -0,0 +1,312 @@ +/****************************************************************************** + * 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_round_scale_draw.h" +#include "qwt_painter.h" +#include "qwt_scale_div.h" +#include "qwt_scale_map.h" +#include "qwt_text.h" +#include "qwt_math.h" + +#include + +class QwtRoundScaleDraw::PrivateData +{ + public: + PrivateData() + : center( 50.0, 50.0 ) + , radius( 50.0 ) + , startAngle( -135.0 ) + , endAngle( 135.0 ) + { + } + + QPointF center; + double radius; + + double startAngle; + double endAngle; +}; + +/*! + \brief Constructor + + The range of the scale is initialized to [0, 100], + The center is set to (50, 50) with a radius of 50. + The angle range is set to [-135, 135]. + */ +QwtRoundScaleDraw::QwtRoundScaleDraw() +{ + m_data = new QwtRoundScaleDraw::PrivateData; + + setRadius( 50 ); + scaleMap().setPaintInterval( m_data->startAngle, m_data->endAngle ); +} + +//! Destructor +QwtRoundScaleDraw::~QwtRoundScaleDraw() +{ + delete m_data; +} + +/*! + Change of radius the scale + + Radius is the radius of the backbone without ticks and labels. + + \param radius New Radius + \sa moveCenter() + */ +void QwtRoundScaleDraw::setRadius( double radius ) +{ + m_data->radius = radius; +} + +/*! + Get the radius + + Radius is the radius of the backbone without ticks and labels. + + \return Radius of the scale + \sa setRadius(), extent() + */ +double QwtRoundScaleDraw::radius() const +{ + return m_data->radius; +} + +/*! + Move the center of the scale draw, leaving the radius unchanged + + \param center New center + \sa setRadius() + */ +void QwtRoundScaleDraw::moveCenter( const QPointF& center ) +{ + m_data->center = center; +} + +//! Get the center of the scale +QPointF QwtRoundScaleDraw::center() const +{ + return m_data->center; +} + +/*! + \brief Adjust the baseline circle segment for round scales. + + The baseline will be drawn from min(angle1,angle2) to max(angle1, angle2). + The default setting is [ -135, 135 ]. + An angle of 0 degrees corresponds to the 12 o'clock position, + and positive angles count in a clockwise direction. + \param angle1 + \param angle2 boundaries of the angle interval in degrees. + \warning
    +
  • The angle range is limited to [-360, 360] degrees. Angles exceeding + this range will be clipped. +
  • For angles more or equal than 360 degrees above or below min(angle1, angle2), + scale marks will not be drawn. +
  • If you need a counterclockwise scale, use QwtScaleDiv::setInterval() +
+ */ +void QwtRoundScaleDraw::setAngleRange( double angle1, double angle2 ) +{ +#if 0 + angle1 = qBound( -360.0, angle1, 360.0 ); + angle2 = qBound( -360.0, angle2, 360.0 ); +#endif + + m_data->startAngle = angle1; + m_data->endAngle = angle2; + + if ( m_data->startAngle == m_data->endAngle ) + { + m_data->startAngle -= 1; + m_data->endAngle += 1; + } + + scaleMap().setPaintInterval( m_data->startAngle, m_data->endAngle ); +} + +/*! + Draws the label for a major scale tick + + \param painter Painter + \param value Value + + \sa drawTick(), drawBackbone() + */ +void QwtRoundScaleDraw::drawLabel( QPainter* painter, double value ) const +{ + const double tval = scaleMap().transform( value ); + if ( ( tval >= m_data->startAngle + 360.0 ) + || ( tval <= m_data->startAngle - 360.0 ) ) + { + return; + } + + const QwtText label = tickLabel( painter->font(), value ); + if ( label.isEmpty() ) + return; + + double radius = m_data->radius; + if ( hasComponent( QwtAbstractScaleDraw::Ticks ) || + hasComponent( QwtAbstractScaleDraw::Backbone ) ) + { + radius += spacing(); + } + + if ( hasComponent( QwtAbstractScaleDraw::Ticks ) ) + radius += tickLength( QwtScaleDiv::MajorTick ); + + const QSizeF sz = label.textSize( painter->font() ); + const double arc = qwtRadians( tval ); + + const double x = m_data->center.x() + + ( radius + sz.width() / 2.0 ) * std::sin( arc ); + const double y = m_data->center.y() - + ( radius + sz.height() / 2.0 ) * std::cos( arc ); + + const QRectF r( x - sz.width() / 2, y - sz.height() / 2, + sz.width(), sz.height() ); + label.draw( painter, r ); +} + +/*! + Draw a tick + + \param painter Painter + \param value Value of the tick + \param len Length of the tick + + \sa drawBackbone(), drawLabel() + */ +void QwtRoundScaleDraw::drawTick( QPainter* painter, double value, double len ) const +{ + if ( len <= 0 ) + return; + + const double tval = scaleMap().transform( value ); + + const double cx = m_data->center.x(); + const double cy = m_data->center.y(); + const double radius = m_data->radius; + + if ( ( tval < m_data->startAngle + 360.0 ) + && ( tval > m_data->startAngle - 360.0 ) ) + { + const double arc = qwtRadians( tval ); + + const double sinArc = std::sin( arc ); + const double cosArc = std::cos( arc ); + + const double x1 = cx + radius * sinArc; + const double x2 = cx + ( radius + len ) * sinArc; + const double y1 = cy - radius * cosArc; + const double y2 = cy - ( radius + len ) * cosArc; + + QwtPainter::drawLine( painter, x1, y1, x2, y2 ); + } +} + +/*! + Draws the baseline of the scale + \param painter Painter + + \sa drawTick(), drawLabel() + */ +void QwtRoundScaleDraw::drawBackbone( QPainter* painter ) const +{ + const double deg1 = scaleMap().p1(); + const double deg2 = scaleMap().p2(); + + const int a1 = qRound( qwtMinF( deg1, deg2 ) - 90 ); + const int a2 = qRound( qwtMaxF( deg1, deg2 ) - 90 ); + + const double radius = m_data->radius; + const double x = m_data->center.x() - radius; + const double y = m_data->center.y() - radius; + + painter->drawArc( QRectF( x, y, 2 * radius, 2 * radius ), + -a2 * 16, ( a2 - a1 + 1 ) * 16 ); // counterclockwise +} + +/*! + Calculate the extent of the scale + + The extent is the distance between the baseline to the outermost + pixel of the scale draw. radius() + extent() is an upper limit + for the radius of the bounding circle. + + \param font Font used for painting the labels + \return Calculated extent + + \sa setMinimumExtent(), minimumExtent() + \warning The implemented algorithm is not too smart and + calculates only an upper limit, that might be a + few pixels too large + */ +double QwtRoundScaleDraw::extent( const QFont& font ) const +{ + double d = 0.0; + + if ( hasComponent( QwtAbstractScaleDraw::Labels ) ) + { + const QwtScaleDiv& sd = scaleDiv(); + const QList< double >& ticks = sd.ticks( QwtScaleDiv::MajorTick ); + for ( int i = 0; i < ticks.count(); i++ ) + { + const double value = ticks[i]; + if ( !sd.contains( value ) ) + continue; + + const double tval = scaleMap().transform( value ); + if ( ( tval < m_data->startAngle + 360 ) + && ( tval > m_data->startAngle - 360 ) ) + { + const QwtText label = tickLabel( font, value ); + if ( label.isEmpty() ) + continue; + + const double arc = qwtRadians( tval ); + + const QSizeF sz = label.textSize( font ); + const double off = qMax( sz.width(), sz.height() ); + + double x = off * std::sin( arc ); + double y = off * std::cos( arc ); + + const double dist = std::sqrt( x * x + y * y ); + if ( dist > d ) + d = dist; + } + } + } + + if ( hasComponent( QwtAbstractScaleDraw::Ticks ) ) + { + d += maxTickLength(); + } + + if ( hasComponent( QwtAbstractScaleDraw::Backbone ) ) + { + d += qwtMaxF( penWidthF(), 1.0 ); + } + + if ( hasComponent( QwtAbstractScaleDraw::Labels ) && + ( hasComponent( QwtAbstractScaleDraw::Ticks ) || + hasComponent( QwtAbstractScaleDraw::Backbone ) ) ) + { + d += spacing(); + } + + d = qwtMaxF( d, minimumExtent() ); + + return d; +} diff --git a/libs/qwt/src/qwt_round_scale_draw.h b/libs/qwt/src/qwt_round_scale_draw.h new file mode 100644 index 00000000..8a52c7be --- /dev/null +++ b/libs/qwt/src/qwt_round_scale_draw.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * 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_ROUND_SCALE_DRAW_H +#define QWT_ROUND_SCALE_DRAW_H + +#include "qwt_global.h" +#include "qwt_abstract_scale_draw.h" + +#include + +/*! + \brief A class for drawing round scales + + QwtRoundScaleDraw can be used to draw round scales. + The circle segment can be adjusted by setAngleRange(). + The geometry of the scale can be specified with + moveCenter() and setRadius(). + + After a scale division has been specified as a QwtScaleDiv object + using QwtAbstractScaleDraw::setScaleDiv(const QwtScaleDiv &s), + the scale can be drawn with the QwtAbstractScaleDraw::draw() member. + */ + +class QWT_EXPORT QwtRoundScaleDraw : public QwtAbstractScaleDraw +{ + public: + QwtRoundScaleDraw(); + virtual ~QwtRoundScaleDraw(); + + void setRadius( double radius ); + double radius() const; + + void moveCenter( double x, double y ); + void moveCenter( const QPointF& ); + QPointF center() const; + + void setAngleRange( double angle1, double angle2 ); + + virtual double extent( const QFont& ) const QWT_OVERRIDE; + + protected: + virtual void drawTick( QPainter*, + double value, double len ) const QWT_OVERRIDE; + + virtual void drawBackbone( + QPainter* ) const QWT_OVERRIDE; + + virtual void drawLabel( + QPainter*, double value ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +//! Move the center of the scale draw, leaving the radius unchanged +inline void QwtRoundScaleDraw::moveCenter( double x, double y ) +{ + moveCenter( QPointF( x, y ) ); +} + +#endif diff --git a/libs/qwt/src/qwt_samples.h b/libs/qwt/src/qwt_samples.h new file mode 100644 index 00000000..424b8269 --- /dev/null +++ b/libs/qwt/src/qwt_samples.h @@ -0,0 +1,314 @@ +/****************************************************************************** + * 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_SAMPLES_H +#define QWT_SAMPLES_H + +#include "qwt_global.h" +#include "qwt_interval.h" + +#include +#include + +//! \brief A sample of the types (x1-x2, y) or (x, y1-y2) +class QWT_EXPORT QwtIntervalSample +{ + public: + QwtIntervalSample(); + QwtIntervalSample( double, const QwtInterval& ); + QwtIntervalSample( double value, double min, double max ); + + bool operator==( const QwtIntervalSample& ) const; + bool operator!=( const QwtIntervalSample& ) const; + + //! Value + double value; + + //! Interval + QwtInterval interval; +}; + +/*! + Constructor + The value is set to 0.0, the interval is invalid + */ +inline QwtIntervalSample::QwtIntervalSample() + : value( 0.0 ) +{ +} + +//! Constructor +inline QwtIntervalSample::QwtIntervalSample( double v, const QwtInterval& intv ) + : value( v ) + , interval( intv ) +{ +} + +//! Constructor +inline QwtIntervalSample::QwtIntervalSample( double v, double min, double max ) + : value( v ) + , interval( min, max ) +{ +} + +//! Compare operator +inline bool QwtIntervalSample::operator==( const QwtIntervalSample& other ) const +{ + return value == other.value && interval == other.interval; +} + +//! Compare operator +inline bool QwtIntervalSample::operator!=( const QwtIntervalSample& other ) const +{ + return !( *this == other ); +} + +//! \brief A sample of the types (x1...xn, y) or (x, y1..yn) +class QWT_EXPORT QwtSetSample +{ + public: + QwtSetSample(); + explicit QwtSetSample( double, const QVector< double >& = QVector< double >( ) ); + + bool operator==( const QwtSetSample& other ) const; + bool operator!=( const QwtSetSample& other ) const; + + double added() const; + + //! value + double value; + + //! Vector of values associated to value + QVector< double > set; +}; + +/*! + Constructor + The value is set to 0.0 + */ +inline QwtSetSample::QwtSetSample() + : value( 0.0 ) +{ +} + +/*! + Constructor + + \param v Value + \param s Set of values + */ +inline QwtSetSample::QwtSetSample( double v, const QVector< double >& s ) + : value( v ) + , set( s ) +{ +} + +//! Compare operator +inline bool QwtSetSample::operator==( const QwtSetSample& other ) const +{ + return value == other.value && set == other.set; +} + +//! Compare operator +inline bool QwtSetSample::operator!=( const QwtSetSample& other ) const +{ + return !( *this == other ); +} + +//! \return All values of the set added +inline double QwtSetSample::added() const +{ + double y = 0.0; + for ( int i = 0; i < set.size(); i++ ) + y += set[i]; + + return y; +} + +/*! + \brief Open-High-Low-Close sample used in financial charts + + In financial charts the movement of a price in a time interval is often + represented by the opening/closing prices and the lowest/highest prices + in this interval. + + \sa QwtTradingChartData + */ +class QWT_EXPORT QwtOHLCSample +{ + public: + QwtOHLCSample( double time = 0.0, + double open = 0.0, double high = 0.0, + double low = 0.0, double close = 0.0 ); + + QwtInterval boundingInterval() const; + + bool isValid() const; + + /*! + Time of the sample, usually a number representing + a specific interval - like a day. + */ + double time; + + //! Opening price + double open; + + //! Highest price + double high; + + //! Lowest price + double low; + + //! Closing price + double close; +}; + +/*! + Constructor + + \param t Time value + \param o Open value + \param h High value + \param l Low value + \param c Close value + */ +inline QwtOHLCSample::QwtOHLCSample( + double t, double o, double h, double l, double c ) + : time( t ) + , open( o ) + , high( h ) + , low( l ) + , close( c ) +{ +} + +/*! + \brief Check if a sample is valid + + A sample is valid, when all of the following checks are true: + + - low <= high + - low <= open <= high + - low <= close <= high + + \return True, when the sample is valid + */ +inline bool QwtOHLCSample::isValid() const +{ + return ( low <= high ) + && ( open >= low ) + && ( open <= high ) + && ( close >= low ) + && ( close <= high ); +} + +/*! + \brief Calculate the bounding interval of the OHLC values + + For valid samples the limits of this interval are always low/high. + + \return Bounding interval + \sa isValid() + */ +inline QwtInterval QwtOHLCSample::boundingInterval() const +{ + double minY = open; + minY = qMin( minY, high ); + minY = qMin( minY, low ); + minY = qMin( minY, close ); + + double maxY = open; + maxY = qMax( maxY, high ); + maxY = qMax( maxY, low ); + maxY = qMax( maxY, close ); + + return QwtInterval( minY, maxY ); +} + +/*! + \brief Sample used in vector fields + + A vector field sample is a position and a vector - usually + representing some direction and magnitude - attached to this position. + + \sa QwtVectorFieldData + */ +class QWT_EXPORT QwtVectorFieldSample +{ + public: + QwtVectorFieldSample( double x = 0.0, double y = 0.0, + double vx = 0.0, double vy = 0.0 ); + + QwtVectorFieldSample( const QPointF& pos, + double vx = 0.0, double vy = 0.0 ); + + QPointF pos() const; + + bool isNull() const; + + //! x coordinate of the position + double x; + + //! y coordinate of the position + double y; + + //! x coordinate of the vector + double vx; + + //! y coordinate of the vector + double vy; +}; + +/*! + \brief Constructor + + \param posX x coordinate of the position + \param posY y coordinate of the position + \param vectorX x coordinate of the vector + \param vectorY y coordinate of the vector + */ +inline QwtVectorFieldSample::QwtVectorFieldSample( + double posX, double posY, double vectorX, double vectorY ) + : x( posX ) + , y( posY ) + , vx( vectorX ) + , vy( vectorY ) +{ +} + +/*! + \brief Constructor + + \param pos Position + \param vectorX x coordinate of the vector + \param vectorY y coordinate of the vector + */ +inline QwtVectorFieldSample::QwtVectorFieldSample( + const QPointF& pos, double vectorX, double vectorY ) + : x( pos.x() ) + , y( pos.y() ) + , vx( vectorX ) + , vy( vectorY ) +{ +} + +//! \return x/y coordinates as QPointF +inline QPointF QwtVectorFieldSample::pos() const +{ + return QPointF( x, y ); +} + +//! \return true, if vx and vy are 0 +inline bool QwtVectorFieldSample::isNull() const +{ + return ( vx == 0.0 ) && ( vy == 0.0 ); +} + +#endif diff --git a/libs/qwt/src/qwt_sampling_thread.cpp b/libs/qwt/src/qwt_sampling_thread.cpp new file mode 100644 index 00000000..8230bb7c --- /dev/null +++ b/libs/qwt/src/qwt_sampling_thread.cpp @@ -0,0 +1,112 @@ +/****************************************************************************** + * 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_sampling_thread.h" +#include + +class QwtSamplingThread::PrivateData +{ + public: + QElapsedTimer timer; + double msecsInterval; +}; + +//! Constructor +QwtSamplingThread::QwtSamplingThread( QObject* parent ) + : QThread( parent ) +{ + m_data = new PrivateData; + m_data->msecsInterval = 1e3; // 1 second +} + +//! Destructor +QwtSamplingThread::~QwtSamplingThread() +{ + delete m_data; +} + +/*! + Change the interval (in ms), when sample() is called. + The default interval is 1000.0 ( = 1s ) + + \param msecs Interval + \sa interval() + */ +void QwtSamplingThread::setInterval( double msecs ) +{ + if ( msecs < 0.0 ) + msecs = 0.0; + + m_data->msecsInterval = msecs; +} + +/*! + \return Interval (in ms), between 2 calls of sample() + \sa setInterval() + */ +double QwtSamplingThread::interval() const +{ + return m_data->msecsInterval; +} + +/*! + \return Time (in ms) since the thread was started + \sa QThread::start(), run() + */ +double QwtSamplingThread::elapsed() const +{ + if ( m_data->timer.isValid() ) + return m_data->timer.nsecsElapsed() / 1e6; + + return 0.0; +} + +/*! + Terminate the collecting thread + \sa QThread::start(), run() + */ +void QwtSamplingThread::stop() +{ + m_data->timer.invalidate(); +} + +/*! + Loop collecting samples started from QThread::start() + \sa stop() + */ +void QwtSamplingThread::run() +{ + m_data->timer.start(); + + /* + We should have all values in nsecs/qint64, but + this would break existing code. TODO ... + Anyway - for QThread::usleep we even need microseconds( usecs ) + */ + while ( m_data->timer.isValid() ) + { + const qint64 timestamp = m_data->timer.nsecsElapsed(); + sample( timestamp / 1e9 ); // seconds + + if ( m_data->msecsInterval > 0.0 ) + { + const double interval = m_data->msecsInterval * 1e3; + const double elapsed = ( m_data->timer.nsecsElapsed() - timestamp ) / 1e3; + + const double usecs = interval - elapsed; + + if ( usecs > 0.0 ) + QThread::usleep( qRound( usecs ) ); + } + } +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_sampling_thread.cpp" +#endif diff --git a/libs/qwt/src/qwt_sampling_thread.h b/libs/qwt/src/qwt_sampling_thread.h new file mode 100644 index 00000000..1cafbb4f --- /dev/null +++ b/libs/qwt/src/qwt_sampling_thread.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * 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_SAMPLING_THREAD_H +#define QWT_SAMPLING_THREAD_H + +#include "qwt_global.h" +#include + +/*! + \brief A thread collecting samples at regular intervals. + + Continuous signals are converted into a discrete signal by + collecting samples at regular intervals. A discrete signal + can be displayed by a QwtPlotSeriesItem on a QwtPlot widget. + + QwtSamplingThread starts a thread calling periodically sample(), + to collect and store ( or emit ) a single sample. + + \sa QwtPlotCurve, QwtPlotSeriesItem + */ +class QWT_EXPORT QwtSamplingThread : public QThread +{ + Q_OBJECT + + public: + virtual ~QwtSamplingThread(); + + double interval() const; + double elapsed() const; + + public Q_SLOTS: + void setInterval( double interval ); + void stop(); + + protected: + explicit QwtSamplingThread( QObject* parent = NULL ); + + virtual void run() QWT_OVERRIDE; + + /*! + Collect a sample + + \param elapsed Time since the thread was started in seconds + \note Due to a bug in previous version elapsed was passed as + seconds instead of miliseconds. To avoid breaking existing + code we stay with seconds for now. + */ + virtual void sample( double elapsed ) = 0; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_scale_div.cpp b/libs/qwt/src/qwt_scale_div.cpp new file mode 100644 index 00000000..b9e43ab7 --- /dev/null +++ b/libs/qwt/src/qwt_scale_div.cpp @@ -0,0 +1,332 @@ +/****************************************************************************** + * 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_scale_div.h" +#include "qwt_interval.h" + +/*! + Construct a division without ticks + + \param lowerBound First boundary + \param upperBound Second boundary + + \note lowerBound might be greater than upperBound for inverted scales + */ +QwtScaleDiv::QwtScaleDiv( double lowerBound, double upperBound ) + : m_lowerBound( lowerBound ) + , m_upperBound( upperBound ) +{ +} + +/*! + Construct a scale division + + \param interval Interval + \param ticks List of major, medium and minor ticks + */ +QwtScaleDiv::QwtScaleDiv( const QwtInterval& interval, + QList< double > ticks[NTickTypes] ) + : m_lowerBound( interval.minValue() ) + , m_upperBound( interval.maxValue() ) +{ + for ( int i = 0; i < NTickTypes; i++ ) + m_ticks[i] = ticks[i]; +} + +/*! + Construct a scale division + + \param lowerBound First boundary + \param upperBound Second boundary + \param ticks List of major, medium and minor ticks + + \note lowerBound might be greater than upperBound for inverted scales + */ +QwtScaleDiv::QwtScaleDiv( double lowerBound, double upperBound, + QList< double > ticks[NTickTypes] ) + : m_lowerBound( lowerBound ) + , m_upperBound( upperBound ) +{ + for ( int i = 0; i < NTickTypes; i++ ) + m_ticks[i] = ticks[i]; +} + +/*! + Construct a scale division + + \param lowerBound First boundary + \param upperBound Second boundary + \param minorTicks List of minor ticks + \param mediumTicks List medium ticks + \param majorTicks List of major ticks + + \note lowerBound might be greater than upperBound for inverted scales + */ +QwtScaleDiv::QwtScaleDiv( double lowerBound, double upperBound, + const QList< double >& minorTicks, + const QList< double >& mediumTicks, + const QList< double >& majorTicks ) + : m_lowerBound( lowerBound ) + , m_upperBound( upperBound ) +{ + m_ticks[ MinorTick ] = minorTicks; + m_ticks[ MediumTick ] = mediumTicks; + m_ticks[ MajorTick ] = majorTicks; +} + +/*! + Change the interval + + \param lowerBound First boundary + \param upperBound Second boundary + + \note lowerBound might be greater than upperBound for inverted scales + */ +void QwtScaleDiv::setInterval( double lowerBound, double upperBound ) +{ + m_lowerBound = lowerBound; + m_upperBound = upperBound; +} + +/*! + Change the interval + + \param interval Interval + */ +void QwtScaleDiv::setInterval( const QwtInterval& interval ) +{ + m_lowerBound = interval.minValue(); + m_upperBound = interval.maxValue(); +} + +/*! + \return lowerBound -> upperBound + */ +QwtInterval QwtScaleDiv::interval() const +{ + return QwtInterval( m_lowerBound, m_upperBound ); +} + +/*! + Set the first boundary + + \param lowerBound First boundary + \sa lowerBound(), setUpperBound() + */ +void QwtScaleDiv::setLowerBound( double lowerBound ) +{ + m_lowerBound = lowerBound; +} + +/*! + \return First boundary + \sa upperBound() + */ +double QwtScaleDiv::lowerBound() const +{ + return m_lowerBound; +} + +/*! + Set the second boundary + + \param upperBound Second boundary + \sa upperBound(), setLowerBound() + */ +void QwtScaleDiv::setUpperBound( double upperBound ) +{ + m_upperBound = upperBound; +} + +/*! + \return upper bound + \sa lowerBound() + */ +double QwtScaleDiv::upperBound() const +{ + return m_upperBound; +} + +/*! + \return upperBound() - lowerBound() + */ +double QwtScaleDiv::range() const +{ + return m_upperBound - m_lowerBound; +} + +/*! + \brief Equality operator + \return true if this instance is equal to other + */ +bool QwtScaleDiv::operator==( const QwtScaleDiv& other ) const +{ + if ( m_lowerBound != other.m_lowerBound || + m_upperBound != other.m_upperBound ) + { + return false; + } + + for ( int i = 0; i < NTickTypes; i++ ) + { + if ( m_ticks[i] != other.m_ticks[i] ) + return false; + } + + return true; +} + +/*! + \brief Inequality + \return true if this instance is not equal to other + */ +bool QwtScaleDiv::operator!=( const QwtScaleDiv& other ) const +{ + return ( !( *this == other ) ); +} + +//! Check if the scale division is empty( lowerBound() == upperBound() ) +bool QwtScaleDiv::isEmpty() const +{ + return ( m_lowerBound == m_upperBound ); +} + +//! Check if the scale division is increasing( lowerBound() <= upperBound() ) +bool QwtScaleDiv::isIncreasing() const +{ + return m_lowerBound <= m_upperBound; +} + +/*! + Return if a value is between lowerBound() and upperBound() + + \param value Value + \return true/false + */ +bool QwtScaleDiv::contains( double value ) const +{ + const double min = qMin( m_lowerBound, m_upperBound ); + const double max = qMax( m_lowerBound, m_upperBound ); + + return value >= min && value <= max; +} + +/*! + Invert the scale division + \sa inverted() + */ +void QwtScaleDiv::invert() +{ + qSwap( m_lowerBound, m_upperBound ); + + for ( int i = 0; i < NTickTypes; i++ ) + { + QList< double >& ticks = m_ticks[i]; + + const int size = ticks.count(); + const int size2 = size / 2; + + for ( int j = 0; j < size2; j++ ) + qSwap( ticks[j], ticks[size - 1 - j] ); + } +} + +/*! + \return A scale division with inverted boundaries and ticks + \sa invert() + */ +QwtScaleDiv QwtScaleDiv::inverted() const +{ + QwtScaleDiv other = *this; + other.invert(); + + return other; +} + +/*! + Return a scale division with an interval [lowerBound, upperBound] + where all ticks outside this interval are removed + + \param lowerBound Lower bound + \param upperBound Upper bound + + \return Scale division with all ticks inside of the given interval + + \note lowerBound might be greater than upperBound for inverted scales + */ +QwtScaleDiv QwtScaleDiv::bounded( + double lowerBound, double upperBound ) const +{ + const double min = qMin( lowerBound, upperBound ); + const double max = qMax( lowerBound, upperBound ); + + QwtScaleDiv sd; + sd.setInterval( lowerBound, upperBound ); + + for ( int tickType = 0; tickType < QwtScaleDiv::NTickTypes; tickType++ ) + { + const QList< double >& ticks = m_ticks[ tickType ]; + + QList< double > boundedTicks; + for ( int i = 0; i < ticks.size(); i++ ) + { + const double tick = ticks[i]; + if ( tick >= min && tick <= max ) + boundedTicks += tick; + } + + sd.setTicks( tickType, boundedTicks ); + } + + return sd; + +} + +/*! + Assign ticks + + \param tickType MinorTick, MediumTick or MajorTick + \param ticks Values of the tick positions + */ +void QwtScaleDiv::setTicks( int tickType, const QList< double >& ticks ) +{ + if ( tickType >= 0 && tickType < NTickTypes ) + m_ticks[tickType] = ticks; +} + +/*! + Return a list of ticks + + \param tickType MinorTick, MediumTick or MajorTick + \return Tick list + */ +QList< double > QwtScaleDiv::ticks( int tickType ) const +{ + if ( tickType >= 0 && tickType < NTickTypes ) + return m_ticks[tickType]; + + return QList< double >(); +} + +#ifndef QT_NO_DEBUG_STREAM + +#include + +QDebug operator<<( QDebug debug, const QwtScaleDiv& scaleDiv ) +{ + debug << scaleDiv.lowerBound() << "<->" << scaleDiv.upperBound(); + debug << "Major: " << scaleDiv.ticks( QwtScaleDiv::MajorTick ); + debug << "Medium: " << scaleDiv.ticks( QwtScaleDiv::MediumTick ); + debug << "Minor: " << scaleDiv.ticks( QwtScaleDiv::MinorTick ); + + return debug; +} + +#endif + diff --git a/libs/qwt/src/qwt_scale_div.h b/libs/qwt/src/qwt_scale_div.h new file mode 100644 index 00000000..8b37da5f --- /dev/null +++ b/libs/qwt/src/qwt_scale_div.h @@ -0,0 +1,107 @@ +/****************************************************************************** + * 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_SCALE_DIV_H +#define QWT_SCALE_DIV_H + +#include "qwt_global.h" +#include + +class QwtInterval; + +/*! + \brief A class representing a scale division + + A Qwt scale is defined by its boundaries and 3 list + for the positions of the major, medium and minor ticks. + + The upperBound() might be smaller than the lowerBound() + to indicate inverted scales. + + Scale divisions can be calculated from a QwtScaleEngine. + + \sa QwtScaleEngine::divideScale(), QwtPlot::setAxisScaleDiv(), + QwtAbstractSlider::setScaleDiv() + */ + +class QWT_EXPORT QwtScaleDiv +{ + public: + //! Scale tick types + enum TickType + { + //! No ticks + NoTick = -1, + + //! Minor ticks + MinorTick, + + //! Medium ticks + MediumTick, + + //! Major ticks + MajorTick, + + //! Number of valid tick types + NTickTypes + }; + + explicit QwtScaleDiv( double lowerBound = 0.0, + double upperBound = 0.0 ); + + explicit QwtScaleDiv( const QwtInterval&, QList< double >[NTickTypes] ); + + explicit QwtScaleDiv( double lowerBound, double upperBound, + QList< double >[NTickTypes] ); + + explicit QwtScaleDiv( double lowerBound, double upperBound, + const QList< double >& minorTicks, const QList< double >& mediumTicks, + const QList< double >& majorTicks ); + + bool operator==( const QwtScaleDiv& ) const; + bool operator!=( const QwtScaleDiv& ) const; + + void setInterval( double lowerBound, double upperBound ); + void setInterval( const QwtInterval& ); + QwtInterval interval() const; + + void setLowerBound( double ); + double lowerBound() const; + + void setUpperBound( double ); + double upperBound() const; + + double range() const; + + bool contains( double value ) const; + + void setTicks( int tickType, const QList< double >& ); + QList< double > ticks( int tickType ) const; + + bool isEmpty() const; + bool isIncreasing() const; + + void invert(); + QwtScaleDiv inverted() const; + + QwtScaleDiv bounded( double lowerBound, double upperBound ) const; + + private: + double m_lowerBound; + double m_upperBound; + QList< double > m_ticks[NTickTypes]; +}; + +Q_DECLARE_TYPEINFO( QwtScaleDiv, Q_MOVABLE_TYPE ); + +#ifndef QT_NO_DEBUG_STREAM +QWT_EXPORT QDebug operator<<( QDebug, const QwtScaleDiv& ); +#endif + +#endif diff --git a/libs/qwt/src/qwt_scale_draw.cpp b/libs/qwt/src/qwt_scale_draw.cpp new file mode 100644 index 00000000..9267a091 --- /dev/null +++ b/libs/qwt/src/qwt_scale_draw.cpp @@ -0,0 +1,1043 @@ +/****************************************************************************** + * 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_scale_draw.h" +#include "qwt_scale_div.h" +#include "qwt_scale_map.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_text.h" + +#include +#include +#include + +static inline double qwtEffectivePenWidth( const QwtAbstractScaleDraw* scaleDraw ) +{ + return qwtMaxF( scaleDraw->penWidthF(), 1.0 ); +} + +namespace QwtScaleRendererReal +{ + inline qreal penWidth( const QPainter* painter, const QwtScaleDraw* scaleDraw ) + { + qreal width = scaleDraw->penWidthF(); +#if 1 + if ( width <= 0.0 ) + width = 1.0; +#endif + + if ( painter->pen().isCosmetic() ) + { + const QTransform& transform = painter->transform(); + + switch ( scaleDraw->alignment() ) + { + case QwtScaleDraw::LeftScale: + case QwtScaleDraw::RightScale: + { + width /= transform.m11(); + break; + } + case QwtScaleDraw::TopScale: + case QwtScaleDraw::BottomScale: + { + width /= transform.m22(); + break; + } + } + } + + return width; + } + + inline void drawBackbone( QPainter* painter, const QwtScaleDraw* scaleDraw ) + { + const qreal pw2 = 0.5 * penWidth( painter, scaleDraw ); + + const QPointF pos = scaleDraw->pos(); + const qreal length = scaleDraw->length(); + + switch ( scaleDraw->alignment() ) + { + case QwtScaleDraw::LeftScale: + { + const qreal x = pos.x() + 1.0 - pw2; + QwtPainter::drawLine( painter, x, pos.y(), x, pos.y() + length ); + + break; + } + case QwtScaleDraw::RightScale: + { + const qreal x = pos.x() - 1.0 + pw2; + QwtPainter::drawLine( painter, x, pos.y(), x, pos.y() + length ); + + break; + } + case QwtScaleDraw::TopScale: + { + const qreal y = pos.y() + 1.0 - pw2; + QwtPainter::drawLine( painter, pos.x(), y, pos.x() + length, y ); + + break; + } + case QwtScaleDraw::BottomScale: + { + const qreal y = pos.y() - 1.0 + pw2; + QwtPainter::drawLine( painter, pos.x(), y, pos.x() + length, y ); + + break; + } + } + } + + inline void drawTick( QPainter* painter, + const QwtScaleDraw* scaleDraw, qreal tickPos, qreal tickLength ) + { + const QPointF pos = scaleDraw->pos(); + + qreal pw = 0.0; + + if ( scaleDraw->hasComponent( QwtScaleDraw::Backbone ) ) + pw = penWidth( painter, scaleDraw ); + + const qreal length = tickLength + pw; + + /* + Those correction offsets have been found by try and error. + They need to be understood and replaced by a calculation, + that makes sense. TODO ... + */ + const qreal off1 = 1.0; + const qreal off2 = ( scaleDraw->penWidthF() <= 0.0 ) ? 0.5 : 0.0; + + switch ( scaleDraw->alignment() ) + { + case QwtScaleDraw::LeftScale: + { + const qreal x = pos.x() + off1 - off2; + QwtPainter::drawLine( painter, x, tickPos, x - length, tickPos ); + + break; + } + case QwtScaleDraw::RightScale: + { + const qreal x = pos.x() - off1 + off2; + QwtPainter::drawLine( painter, x, tickPos, x + length, tickPos ); + break; + } + case QwtScaleDraw::TopScale: + { + const qreal y = pos.y() + off1 - 2 * off2; + QwtPainter::drawLine( painter, tickPos, y, tickPos, y - length ); + + break; + } + case QwtScaleDraw::BottomScale: + { + const qreal y = pos.y() - off1 + off2; + QwtPainter::drawLine( painter, tickPos, y, tickPos, y + length ); + + break; + } + } + } +} + +namespace QwtScaleRendererInt +{ + inline void drawBackbone( QPainter* painter, const QwtScaleDraw* scaleDraw ) + { + const int pw = qMax( qRound( scaleDraw->penWidthF() ), 1 ); + + const qreal length = scaleDraw->length(); + const QPointF pos = scaleDraw->pos(); + + switch ( scaleDraw->alignment() ) + { + case QwtScaleDraw::LeftScale: + { + const qreal x = qRound( pos.x() - ( pw - 1 ) / 2 ); + QwtPainter::drawLine( painter, x, pos.y(), x, pos.y() + length ); + + break; + } + case QwtScaleDraw::RightScale: + { + const qreal x = qRound( pos.x() + pw / 2 ); + QwtPainter::drawLine( painter, x, pos.y(), x, pos.y() + length ); + + break; + } + case QwtScaleDraw::TopScale: + { + const qreal y = qRound( pos.y() - ( pw - 1 ) / 2 ); + QwtPainter::drawLine( painter, pos.x(), y, pos.x() + length, y ); + + break; + } + case QwtScaleDraw::BottomScale: + { + const qreal y = qRound( pos.y() + pw / 2 ); + QwtPainter::drawLine( painter, pos.x(), y, pos.x() + length, y ); + + break; + } + } + } + + inline void drawTick( QPainter* painter, + const QwtScaleDraw* scaleDraw, qreal tickPos, qreal tickLength ) + { + const QPointF pos = scaleDraw->pos(); + tickPos = qRound( tickPos ); + + int pw = 0; + if ( scaleDraw->hasComponent( QwtScaleDraw::Backbone ) ) + pw = qMax( qRound( scaleDraw->penWidthF() ), 1 ); + + int len = qMax( qRound( tickLength ), 1 ); + + // the width of ticks at the borders might extent the backbone + len += pw; + + if ( painter->pen().capStyle() == Qt::FlatCap ) + len++; // the end point is not rendered + + qreal off = 0.0; + + if ( painter->paintEngine()->type() == QPaintEngine::X11 ) + { + if ( pw == 1 ) + { + // In opposite to raster, X11 paints the end point + off = 1.0; + } + } + + switch ( scaleDraw->alignment() ) + { + case QwtScaleDraw::LeftScale: + { + const qreal x1 = qRound( pos.x() ) + 1; + const qreal x2 = x1 - len + 1; + + QwtPainter::drawLine( painter, x2, tickPos, x1 - off, tickPos ); + + break; + } + case QwtScaleDraw::RightScale: + { + const qreal x1 = qRound( pos.x() ); + const qreal x2 = x1 + len - 1; + + QwtPainter::drawLine( painter, x1, tickPos, x2 - off, tickPos ); + + break; + } + case QwtScaleDraw::BottomScale: + { + const qreal y1 = qRound( pos.y() ); + const qreal y2 = y1 + len - 1; + + QwtPainter::drawLine( painter, tickPos, y1, tickPos, y2 - off ); + + break; + } + case QwtScaleDraw::TopScale: + { + const qreal y1 = qRound( pos.y() ); + const qreal y2 = y1 - len + 1; + + QwtPainter::drawLine( painter, tickPos, y2 + 1, tickPos, y1 + 1 - off ); + + break; + } + } + } +} + +class QwtScaleDraw::PrivateData +{ + public: + PrivateData() + : len( 0 ) + , alignment( QwtScaleDraw::BottomScale ) + , labelRotation( 0.0 ) + { + } + + QPointF pos; + double len; + + Alignment alignment; + + Qt::Alignment labelAlignment; + double labelRotation; +}; + +/*! + \brief Constructor + + The range of the scale is initialized to [0, 100], + The position is at (0, 0) with a length of 100. + The orientation is QwtAbstractScaleDraw::Bottom. + */ +QwtScaleDraw::QwtScaleDraw() +{ + m_data = new QwtScaleDraw::PrivateData; + setLength( 100 ); +} + +//! Destructor +QwtScaleDraw::~QwtScaleDraw() +{ + delete m_data; +} + +/*! + Return alignment of the scale + \sa setAlignment() + \return Alignment of the scale + */ +QwtScaleDraw::Alignment QwtScaleDraw::alignment() const +{ + return m_data->alignment; +} + +/*! + Set the alignment of the scale + + \param align Alignment of the scale + + The default alignment is QwtScaleDraw::BottomScale + \sa alignment() + */ +void QwtScaleDraw::setAlignment( Alignment align ) +{ + m_data->alignment = align; +} + +/*! + Return the orientation + + TopScale, BottomScale are horizontal (Qt::Horizontal) scales, + LeftScale, RightScale are vertical (Qt::Vertical) scales. + + \return Orientation of the scale + + \sa alignment() + */ +Qt::Orientation QwtScaleDraw::orientation() const +{ + switch ( m_data->alignment ) + { + case TopScale: + case BottomScale: + return Qt::Horizontal; + case LeftScale: + case RightScale: + default: + return Qt::Vertical; + } +} + +/*! + \brief Determine the minimum border distance + + This member function returns the minimum space + needed to draw the mark labels at the scale's endpoints. + + \param font Font + \param start Start border distance + \param end End border distance + */ +void QwtScaleDraw::getBorderDistHint( + const QFont& font, int& start, int& end ) const +{ + start = 0; + end = 1.0; + + if ( !hasComponent( QwtAbstractScaleDraw::Labels ) ) + return; + + const QList< double >& ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick ); + if ( ticks.count() == 0 ) + return; + + // Find the ticks, that are mapped to the borders. + // minTick is the tick, that is mapped to the top/left-most position + // in widget coordinates. + + double minTick = ticks[0]; + double minPos = scaleMap().transform( minTick ); + double maxTick = minTick; + double maxPos = minPos; + + for ( int i = 1; i < ticks.count(); i++ ) + { + const double tickPos = scaleMap().transform( ticks[i] ); + if ( tickPos < minPos ) + { + minTick = ticks[i]; + minPos = tickPos; + } + if ( tickPos > scaleMap().transform( maxTick ) ) + { + maxTick = ticks[i]; + maxPos = tickPos; + } + } + + double e = 0.0; + double s = 0.0; + if ( orientation() == Qt::Vertical ) + { + s = -labelRect( font, minTick ).top(); + s -= qAbs( minPos - qRound( scaleMap().p2() ) ); + + e = labelRect( font, maxTick ).bottom(); + e -= qAbs( maxPos - scaleMap().p1() ); + } + else + { + s = -labelRect( font, minTick ).left(); + s -= qAbs( minPos - scaleMap().p1() ); + + e = labelRect( font, maxTick ).right(); + e -= qAbs( maxPos - scaleMap().p2() ); + } + + if ( s < 0.0 ) + s = 0.0; + if ( e < 0.0 ) + e = 0.0; + + start = qwtCeil( s ); + end = qwtCeil( e ); +} + +/*! + Determine the minimum distance between two labels, that is necessary + that the texts don't overlap. + + \param font Font + \return The maximum width of a label + + \sa getBorderDistHint() + */ + +int QwtScaleDraw::minLabelDist( const QFont& font ) const +{ + if ( !hasComponent( QwtAbstractScaleDraw::Labels ) ) + return 0; + + const QList< double >& ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick ); + if ( ticks.isEmpty() ) + return 0; + + const QFontMetrics fm( font ); + + const bool vertical = ( orientation() == Qt::Vertical ); + + QRectF bRect1; + QRectF bRect2 = labelRect( font, ticks[0] ); + if ( vertical ) + { + bRect2.setRect( -bRect2.bottom(), 0.0, bRect2.height(), bRect2.width() ); + } + + double maxDist = 0.0; + + for ( int i = 1; i < ticks.count(); i++ ) + { + bRect1 = bRect2; + bRect2 = labelRect( font, ticks[i] ); + if ( vertical ) + { + bRect2.setRect( -bRect2.bottom(), 0.0, + bRect2.height(), bRect2.width() ); + } + + double dist = fm.leading(); // space between the labels + if ( bRect1.right() > 0 ) + dist += bRect1.right(); + if ( bRect2.left() < 0 ) + dist += -bRect2.left(); + + if ( dist > maxDist ) + maxDist = dist; + } + + double angle = qwtRadians( labelRotation() ); + if ( vertical ) + angle += M_PI / 2; + + const double sinA = qFastSin( angle ); // qreal -> double + if ( qFuzzyCompare( sinA + 1.0, 1.0 ) ) + return qCeil( maxDist ); + + const int fmHeight = fm.ascent() - 2; + + // The distance we need until there is + // the height of the label font. This height is needed + // for the neighbored label. + + double labelDist = fmHeight / qFastSin( angle ) * qFastCos( angle ); + if ( labelDist < 0 ) + labelDist = -labelDist; + + // For text orientations close to the scale orientation + + if ( labelDist > maxDist ) + labelDist = maxDist; + + // For text orientations close to the opposite of the + // scale orientation + + if ( labelDist < fmHeight ) + labelDist = fmHeight; + + return qCeil( labelDist ); +} + +/*! + Calculate the width/height that is needed for a + vertical/horizontal scale. + + The extent is calculated from the pen width of the backbone, + the major tick length, the spacing and the maximum width/height + of the labels. + + \param font Font used for painting the labels + \return Extent + + \sa minLength() + */ +double QwtScaleDraw::extent( const QFont& font ) const +{ + double d = 0; + + if ( hasComponent( QwtAbstractScaleDraw::Labels ) ) + { + if ( orientation() == Qt::Vertical ) + d = maxLabelWidth( font ); + else + d = maxLabelHeight( font ); + + if ( d > 0 ) + d += spacing(); + } + + if ( hasComponent( QwtAbstractScaleDraw::Ticks ) ) + { + d += maxTickLength(); + } + + if ( hasComponent( QwtAbstractScaleDraw::Backbone ) ) + { + d += qwtEffectivePenWidth( this ); + } + + d = qwtMaxF( d, minimumExtent() ); + return d; +} + +/*! + Calculate the minimum length that is needed to draw the scale + + \param font Font used for painting the labels + \return Minimum length that is needed to draw the scale + + \sa extent() + */ +int QwtScaleDraw::minLength( const QFont& font ) const +{ + int startDist, endDist; + getBorderDistHint( font, startDist, endDist ); + + const QwtScaleDiv& sd = scaleDiv(); + + const uint minorCount = + sd.ticks( QwtScaleDiv::MinorTick ).count() + + sd.ticks( QwtScaleDiv::MediumTick ).count(); + const uint majorCount = + sd.ticks( QwtScaleDiv::MajorTick ).count(); + + int lengthForLabels = 0; + if ( hasComponent( QwtAbstractScaleDraw::Labels ) ) + lengthForLabels = minLabelDist( font ) * majorCount; + + int lengthForTicks = 0; + if ( hasComponent( QwtAbstractScaleDraw::Ticks ) ) + { + const double pw = qwtEffectivePenWidth( this ); + lengthForTicks = qCeil( ( majorCount + minorCount ) * ( pw + 1.0 ) ); + } + + return startDist + endDist + qMax( lengthForLabels, lengthForTicks ); +} + +/*! + Find the position, where to paint a label + + The position has a distance that depends on the length of the ticks + in direction of the alignment(). + + \param value Value + \return Position, where to paint a label + */ +QPointF QwtScaleDraw::labelPosition( double value ) const +{ + const double tval = scaleMap().transform( value ); + double dist = spacing(); + if ( hasComponent( QwtAbstractScaleDraw::Backbone ) ) + dist += qwtEffectivePenWidth( this ); + + if ( hasComponent( QwtAbstractScaleDraw::Ticks ) ) + dist += tickLength( QwtScaleDiv::MajorTick ); + + double px = 0; + double py = 0; + + switch ( alignment() ) + { + case RightScale: + { + px = m_data->pos.x() + dist; + py = tval; + break; + } + case LeftScale: + { + px = m_data->pos.x() - dist; + py = tval; + break; + } + case BottomScale: + { + px = tval; + py = m_data->pos.y() + dist; + break; + } + case TopScale: + { + px = tval; + py = m_data->pos.y() - dist; + break; + } + } + + return QPointF( px, py ); +} + +/*! + Draw a tick + + \param painter Painter + \param value Value of the tick + \param len Length of the tick + + \sa drawBackbone(), drawLabel() + */ +void QwtScaleDraw::drawTick( QPainter* painter, double value, double len ) const +{ + if ( len <= 0 ) + return; + + const double tval = scaleMap().transform( value ); + + if ( QwtPainter::roundingAlignment( painter ) ) + QwtScaleRendererInt::drawTick( painter, this, tval, len ); + else + QwtScaleRendererReal::drawTick( painter, this, tval, len ); +} + +/*! + Draws the baseline of the scale + \param painter Painter + + \sa drawTick(), drawLabel() + */ +void QwtScaleDraw::drawBackbone( QPainter* painter ) const +{ + if ( QwtPainter::roundingAlignment( painter ) ) + QwtScaleRendererInt::drawBackbone( painter, this ); + else + QwtScaleRendererReal::drawBackbone( painter, this ); +} + +/*! + \brief Move the position of the scale + + The meaning of the parameter pos depends on the alignment: +
+
QwtScaleDraw::LeftScale +
The origin is the topmost point of the + backbone. The backbone is a vertical line. + Scale marks and labels are drawn + at the left of the backbone. +
QwtScaleDraw::RightScale +
The origin is the topmost point of the + backbone. The backbone is a vertical line. + Scale marks and labels are drawn + at the right of the backbone. +
QwtScaleDraw::TopScale +
The origin is the leftmost point of the + backbone. The backbone is a horizontal line. + Scale marks and labels are drawn + above the backbone. +
QwtScaleDraw::BottomScale +
The origin is the leftmost point of the + backbone. The backbone is a horizontal line + Scale marks and labels are drawn + below the backbone. +
+ + \param pos Origin of the scale + + \sa pos(), setLength() + */ +void QwtScaleDraw::move( const QPointF& pos ) +{ + m_data->pos = pos; + updateMap(); +} + +/*! + \return Origin of the scale + \sa move(), length() + */ +QPointF QwtScaleDraw::pos() const +{ + return m_data->pos; +} + +/*! + Set the length of the backbone. + + The length doesn't include the space needed for + overlapping labels. + + \param length Length of the backbone + + \sa move(), minLabelDist() + */ +void QwtScaleDraw::setLength( double length ) +{ +#if 0 + if ( length >= 0 && length < 10 ) + length = 10; + + // why should we accept negative lengths ??? + if ( length < 0 && length > -10 ) + length = -10; +#else + length = qwtMaxF( length, 10.0 ); +#endif + + m_data->len = length; + updateMap(); +} + +/*! + \return the length of the backbone + \sa setLength(), pos() + */ +double QwtScaleDraw::length() const +{ + return m_data->len; +} + +/*! + Draws the label for a major scale tick + + \param painter Painter + \param value Value + + \sa drawTick(), drawBackbone(), boundingLabelRect() + */ +void QwtScaleDraw::drawLabel( QPainter* painter, double value ) const +{ + QwtText lbl = tickLabel( painter->font(), value ); + if ( lbl.isEmpty() ) + return; + + QPointF pos = labelPosition( value ); + + QSizeF labelSize = lbl.textSize( painter->font() ); + + const QTransform transform = labelTransformation( pos, labelSize ); + + painter->save(); + painter->setWorldTransform( transform, true ); + + lbl.draw ( painter, QRect( QPoint( 0, 0 ), labelSize.toSize() ) ); + + painter->restore(); +} + +/*! + \brief Find the bounding rectangle for the label. + + The coordinates of the rectangle are absolute ( calculated from pos() ). + in direction of the tick. + + \param font Font used for painting + \param value Value + + \return Bounding rectangle + \sa labelRect() + */ +QRect QwtScaleDraw::boundingLabelRect( const QFont& font, double value ) const +{ + QwtText lbl = tickLabel( font, value ); + if ( lbl.isEmpty() ) + return QRect(); + + const QPointF pos = labelPosition( value ); + QSizeF labelSize = lbl.textSize( font ); + + const QTransform transform = labelTransformation( pos, labelSize ); + return transform.mapRect( QRect( QPoint( 0, 0 ), labelSize.toSize() ) ); +} + +/*! + Calculate the transformation that is needed to paint a label + depending on its alignment and rotation. + + \param pos Position where to paint the label + \param size Size of the label + + \return Transformation matrix + \sa setLabelAlignment(), setLabelRotation() + */ +QTransform QwtScaleDraw::labelTransformation( + const QPointF& pos, const QSizeF& size ) const +{ + QTransform transform; + transform.translate( pos.x(), pos.y() ); + transform.rotate( labelRotation() ); + + int flags = labelAlignment(); + if ( flags == 0 ) + { + switch ( alignment() ) + { + case RightScale: + { + if ( flags == 0 ) + flags = Qt::AlignRight | Qt::AlignVCenter; + break; + } + case LeftScale: + { + if ( flags == 0 ) + flags = Qt::AlignLeft | Qt::AlignVCenter; + break; + } + case BottomScale: + { + if ( flags == 0 ) + flags = Qt::AlignHCenter | Qt::AlignBottom; + break; + } + case TopScale: + { + if ( flags == 0 ) + flags = Qt::AlignHCenter | Qt::AlignTop; + break; + } + } + } + + double x, y; + + if ( flags & Qt::AlignLeft ) + x = -size.width(); + else if ( flags & Qt::AlignRight ) + x = 0.0; + else // Qt::AlignHCenter + x = -( 0.5 * size.width() ); + + if ( flags & Qt::AlignTop ) + y = -size.height(); + else if ( flags & Qt::AlignBottom ) + y = 0; + else // Qt::AlignVCenter + y = -( 0.5 * size.height() ); + + transform.translate( x, y ); + + return transform; +} + +/*! + Find the bounding rectangle for the label. The coordinates of + the rectangle are relative to spacing + tick length from the backbone + in direction of the tick. + + \param font Font used for painting + \param value Value + + \return Bounding rectangle that is needed to draw a label + */ +QRectF QwtScaleDraw::labelRect( const QFont& font, double value ) const +{ + QwtText lbl = tickLabel( font, value ); + if ( lbl.isEmpty() ) + return QRectF( 0.0, 0.0, 0.0, 0.0 ); + + const QPointF pos = labelPosition( value ); + + const QSizeF labelSize = lbl.textSize( font ); + const QTransform transform = labelTransformation( pos, labelSize ); + + QRectF br = transform.mapRect( QRectF( QPointF( 0, 0 ), labelSize ) ); + br.translate( -pos.x(), -pos.y() ); + + return br; +} + +/*! + Calculate the size that is needed to draw a label + + \param font Label font + \param value Value + + \return Size that is needed to draw a label + */ +QSizeF QwtScaleDraw::labelSize( const QFont& font, double value ) const +{ + return labelRect( font, value ).size(); +} + +/*! + Rotate all labels. + + When changing the rotation, it might be necessary to + adjust the label flags too. Finding a useful combination is + often the result of try and error. + + \param rotation Angle in degrees. When changing the label rotation, + the label flags often needs to be adjusted too. + + \sa setLabelAlignment(), labelRotation(), labelAlignment(). + + */ +void QwtScaleDraw::setLabelRotation( double rotation ) +{ + m_data->labelRotation = rotation; +} + +/*! + \return the label rotation + \sa setLabelRotation(), labelAlignment() + */ +double QwtScaleDraw::labelRotation() const +{ + return m_data->labelRotation; +} + +/*! + \brief Change the label flags + + Labels are aligned to the point tick length + spacing away from the backbone. + + The alignment is relative to the orientation of the label text. + In case of an flags of 0 the label will be aligned + depending on the orientation of the scale: + + QwtScaleDraw::TopScale: Qt::AlignHCenter | Qt::AlignTop\n + QwtScaleDraw::BottomScale: Qt::AlignHCenter | Qt::AlignBottom\n + QwtScaleDraw::LeftScale: Qt::AlignLeft | Qt::AlignVCenter\n + QwtScaleDraw::RightScale: Qt::AlignRight | Qt::AlignVCenter\n + + Changing the alignment is often necessary for rotated labels. + + \param alignment Or'd Qt::AlignmentFlags see + + \sa setLabelRotation(), labelRotation(), labelAlignment() + \warning The various alignments might be confusing. + The alignment of the label is not the alignment + of the scale and is not the alignment of the flags + ( QwtText::flags() ) returned from QwtAbstractScaleDraw::label(). + */ + +void QwtScaleDraw::setLabelAlignment( Qt::Alignment alignment ) +{ + m_data->labelAlignment = alignment; +} + +/*! + \return the label flags + \sa setLabelAlignment(), labelRotation() + */ +Qt::Alignment QwtScaleDraw::labelAlignment() const +{ + return m_data->labelAlignment; +} + +/*! + \param font Font + \return the maximum width of a label + */ +int QwtScaleDraw::maxLabelWidth( const QFont& font ) const +{ + double maxWidth = 0.0; + + const QList< double >& ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick ); + for ( int i = 0; i < ticks.count(); i++ ) + { + const double v = ticks[i]; + if ( scaleDiv().contains( v ) ) + { + const double w = labelSize( font, ticks[i] ).width(); + if ( w > maxWidth ) + maxWidth = w; + } + } + + return qCeil( maxWidth ); +} + +/*! + \param font Font + \return the maximum height of a label + */ +int QwtScaleDraw::maxLabelHeight( const QFont& font ) const +{ + double maxHeight = 0.0; + + const QList< double >& ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick ); + for ( int i = 0; i < ticks.count(); i++ ) + { + const double v = ticks[i]; + if ( scaleDiv().contains( v ) ) + { + const double h = labelSize( font, ticks[i] ).height(); + if ( h > maxHeight ) + maxHeight = h; + } + } + + return qCeil( maxHeight ); +} + +void QwtScaleDraw::updateMap() +{ + const QPointF pos = m_data->pos; + double len = m_data->len; + + QwtScaleMap& sm = scaleMap(); + if ( orientation() == Qt::Vertical ) + sm.setPaintInterval( pos.y() + len, pos.y() ); + else + sm.setPaintInterval( pos.x(), pos.x() + len ); +} diff --git a/libs/qwt/src/qwt_scale_draw.h b/libs/qwt/src/qwt_scale_draw.h new file mode 100644 index 00000000..5bc23ada --- /dev/null +++ b/libs/qwt/src/qwt_scale_draw.h @@ -0,0 +1,123 @@ +/****************************************************************************** + * 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_SCALE_DRAW_H +#define QWT_SCALE_DRAW_H + +#include "qwt_global.h" +#include "qwt_abstract_scale_draw.h" + +#include + +class QTransform; +class QSizeF; +class QRectF; +class QRect; + +/*! + \brief A class for drawing scales + + QwtScaleDraw can be used to draw linear or logarithmic scales. + A scale has a position, an alignment and a length, which can be specified . + The labels can be rotated and aligned + to the ticks using setLabelRotation() and setLabelAlignment(). + + After a scale division has been specified as a QwtScaleDiv object + using QwtAbstractScaleDraw::setScaleDiv(const QwtScaleDiv &s), + the scale can be drawn with the QwtAbstractScaleDraw::draw() member. + */ +class QWT_EXPORT QwtScaleDraw : public QwtAbstractScaleDraw +{ + public: + /*! + Alignment of the scale draw + \sa setAlignment(), alignment() + */ + enum Alignment + { + //! The scale is below + BottomScale, + + //! The scale is above + TopScale, + + //! The scale is left + LeftScale, + + //! The scale is right + RightScale + }; + + QwtScaleDraw(); + virtual ~QwtScaleDraw(); + + void getBorderDistHint( const QFont&, int& start, int& end ) const; + int minLabelDist( const QFont& ) const; + + int minLength( const QFont& ) const; + virtual double extent( const QFont& ) const QWT_OVERRIDE; + + void move( double x, double y ); + void move( const QPointF& ); + void setLength( double length ); + + Alignment alignment() const; + void setAlignment( Alignment ); + + Qt::Orientation orientation() const; + + QPointF pos() const; + double length() const; + + void setLabelAlignment( Qt::Alignment ); + Qt::Alignment labelAlignment() const; + + void setLabelRotation( double rotation ); + double labelRotation() const; + + int maxLabelHeight( const QFont& ) const; + int maxLabelWidth( const QFont& ) const; + + QPointF labelPosition( double value ) const; + + QRectF labelRect( const QFont&, double value ) const; + QSizeF labelSize( const QFont&, double value ) const; + + QRect boundingLabelRect( const QFont&, double value ) const; + + protected: + QTransform labelTransformation( const QPointF&, const QSizeF& ) const; + + virtual void drawTick( QPainter*, + double value, double len ) const QWT_OVERRIDE; + + virtual void drawBackbone( QPainter* ) const QWT_OVERRIDE; + virtual void drawLabel( QPainter*, double value ) const QWT_OVERRIDE; + + private: + void updateMap(); + + class PrivateData; + PrivateData* m_data; +}; + +/*! + Move the position of the scale + + \param x X coordinate + \param y Y coordinate + + \sa move(const QPointF &) + */ +inline void QwtScaleDraw::move( double x, double y ) +{ + move( QPointF( x, y ) ); +} + +#endif diff --git a/libs/qwt/src/qwt_scale_engine.cpp b/libs/qwt/src/qwt_scale_engine.cpp new file mode 100644 index 00000000..21ede709 --- /dev/null +++ b/libs/qwt/src/qwt_scale_engine.cpp @@ -0,0 +1,1120 @@ +/****************************************************************************** + * 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_scale_engine.h" +#include "qwt_math.h" +#include "qwt_interval.h" +#include "qwt_transform.h" + +#include + +#include + +static inline double qwtLog( double base, double value ) +{ + return std::log( value ) / std::log( base ); +} + +static inline QwtInterval qwtLogInterval( double base, const QwtInterval& interval ) +{ + return QwtInterval( qwtLog( base, interval.minValue() ), + qwtLog( base, interval.maxValue() ) ); +} + +static inline QwtInterval qwtPowInterval( double base, const QwtInterval& interval ) +{ + return QwtInterval( std::pow( base, interval.minValue() ), + std::pow( base, interval.maxValue() ) ); +} + +#if 1 + +// this version often doesn't find the best ticks: f.e for 15: 5, 10 +static double qwtStepSize( double intervalSize, int maxSteps, uint base ) +{ + const double minStep = + QwtScaleArithmetic::divideInterval( intervalSize, maxSteps, base ); + + if ( minStep != 0.0 ) + { + // # ticks per interval + const int numTicks = qwtCeil( qAbs( intervalSize / minStep ) ) - 1; + + // Do the minor steps fit into the interval? + if ( qwtFuzzyCompare( ( numTicks + 1 ) * qAbs( minStep ), + qAbs( intervalSize ), intervalSize ) > 0 ) + { + // The minor steps doesn't fit into the interval + return 0.5 * intervalSize; + } + } + + return minStep; +} + +#else + +static double qwtStepSize( double intervalSize, int maxSteps, uint base ) +{ + if ( maxSteps <= 0 ) + return 0.0; + + if ( maxSteps > 2 ) + { + for ( int numSteps = maxSteps; numSteps > 1; numSteps-- ) + { + const double stepSize = intervalSize / numSteps; + + const double p = std::floor( std::log( stepSize ) / std::log( base ) ); + const double fraction = std::pow( base, p ); + + for ( uint n = base; n > 1; n /= 2 ) + { + if ( qFuzzyCompare( stepSize, n * fraction ) ) + return stepSize; + + if ( n == 3 && ( base % 2 ) == 0 ) + { + if ( qFuzzyCompare( stepSize, 2 * fraction ) ) + return stepSize; + } + } + } + } + + return intervalSize * 0.5; +} + +#endif + +static const double _eps = 1.0e-6; + +/*! + Ceil a value, relative to an interval + + \param value Value to be ceiled + \param intervalSize Interval size + + \return Rounded value + + \sa floorEps() + */ +double QwtScaleArithmetic::ceilEps( double value, + double intervalSize ) +{ + const double eps = _eps * intervalSize; + + value = ( value - eps ) / intervalSize; + return std::ceil( value ) * intervalSize; +} + +/*! + Floor a value, relative to an interval + + \param value Value to be floored + \param intervalSize Interval size + + \return Rounded value + \sa floorEps() + */ +double QwtScaleArithmetic::floorEps( double value, double intervalSize ) +{ + const double eps = _eps * intervalSize; + + value = ( value + eps ) / intervalSize; + return std::floor( value ) * intervalSize; +} + +/*! + \brief Divide an interval into steps + + \f$stepSize = (intervalSize - intervalSize * 10e^{-6}) / numSteps\f$ + + \param intervalSize Interval size + \param numSteps Number of steps + \return Step size + */ +double QwtScaleArithmetic::divideEps( double intervalSize, double numSteps ) +{ + if ( numSteps == 0.0 || intervalSize == 0.0 ) + return 0.0; + + return ( intervalSize - ( _eps * intervalSize ) ) / numSteps; +} + +/*! + Calculate a step size for a given interval + + \param intervalSize Interval size + \param numSteps Number of steps + \param base Base for the division ( usually 10 ) + + \return Calculated step size + */ +double QwtScaleArithmetic::divideInterval( + double intervalSize, int numSteps, uint base ) +{ + if ( numSteps <= 0 ) + return 0.0; + + const double v = QwtScaleArithmetic::divideEps( intervalSize, numSteps ); + if ( v == 0.0 ) + return 0.0; + + const double lx = qwtLog( base, std::fabs( v ) ); + const double p = std::floor( lx ); + + const double fraction = std::pow( base, lx - p ); + + uint n = base; + while ( ( n > 1 ) && ( fraction <= n / 2 ) ) + n /= 2; + + double stepSize = n * std::pow( base, p ); + if ( v < 0 ) + stepSize = -stepSize; + + return stepSize; +} + +class QwtScaleEngine::PrivateData +{ + public: + PrivateData(): + attributes( QwtScaleEngine::NoAttribute ), + lowerMargin( 0.0 ), + upperMargin( 0.0 ), + referenceValue( 0.0 ), + base( 10 ), + transform( NULL ) + { + } + + ~PrivateData() + { + delete transform; + } + + QwtScaleEngine::Attributes attributes; + + double lowerMargin; + double upperMargin; + + double referenceValue; + + uint base; + + QwtTransform* transform; +}; + +/*! + Constructor + + \param base Base of the scale engine + \sa setBase() + */ +QwtScaleEngine::QwtScaleEngine( uint base ) +{ + m_data = new PrivateData; + setBase( base ); +} + + +//! Destructor +QwtScaleEngine::~QwtScaleEngine () +{ + delete m_data; +} + +/*! + Assign a transformation + + \param transform Transformation + + The transformation object is used as factory for clones + that are returned by transformation() + + The scale engine takes ownership of the transformation. + + \sa QwtTransform::copy(), transformation() + + */ +void QwtScaleEngine::setTransformation( QwtTransform* transform ) +{ + if ( transform != m_data->transform ) + { + delete m_data->transform; + m_data->transform = transform; + } +} + +/*! + Create and return a clone of the transformation + of the engine. When the engine has no special transformation + NULL is returned, indicating no transformation. + + \return A clone of the transformation + \sa setTransformation() + */ +QwtTransform* QwtScaleEngine::transformation() const +{ + QwtTransform* transform = NULL; + if ( m_data->transform ) + transform = m_data->transform->copy(); + + return transform; +} + +/*! + \return the margin at the lower end of the scale + The default margin is 0. + + \sa setMargins() + */ +double QwtScaleEngine::lowerMargin() const +{ + return m_data->lowerMargin; +} + +/*! + \return the margin at the upper end of the scale + The default margin is 0. + + \sa setMargins() + */ +double QwtScaleEngine::upperMargin() const +{ + return m_data->upperMargin; +} + +/*! + \brief Specify margins at the scale's endpoints + \param lower minimum distance between the scale's lower boundary and the + smallest enclosed value + \param upper minimum distance between the scale's upper boundary and the + greatest enclosed value + + Margins can be used to leave a minimum amount of space between + the enclosed intervals and the boundaries of the scale. + + \warning + \li QwtLogScaleEngine measures the margins in decades. + + \sa upperMargin(), lowerMargin() + */ + +void QwtScaleEngine::setMargins( double lower, double upper ) +{ + m_data->lowerMargin = qwtMaxF( lower, 0.0 ); + m_data->upperMargin = qwtMaxF( upper, 0.0 ); +} + +/*! + Calculate a step size for an interval size + + \param intervalSize Interval size + \param numSteps Number of steps + + \return Step size + */ +double QwtScaleEngine::divideInterval( + double intervalSize, int numSteps ) const +{ + return QwtScaleArithmetic::divideInterval( + intervalSize, numSteps, m_data->base ); +} + +/*! + Check if an interval "contains" a value + + \param interval Interval + \param value Value + + \return True, when the value is inside the interval + */ +bool QwtScaleEngine::contains( + const QwtInterval& interval, double value ) const +{ + if ( !interval.isValid() ) + return false; + + if ( qwtFuzzyCompare( value, interval.minValue(), interval.width() ) < 0 ) + return false; + + if ( qwtFuzzyCompare( value, interval.maxValue(), interval.width() ) > 0 ) + return false; + + return true; +} + +/*! + Remove ticks from a list, that are not inside an interval + + \param ticks Tick list + \param interval Interval + + \return Stripped tick list + */ +QList< double > QwtScaleEngine::strip( const QList< double >& ticks, + const QwtInterval& interval ) const +{ + if ( !interval.isValid() || ticks.count() == 0 ) + return QList< double >(); + + if ( contains( interval, ticks.first() ) + && contains( interval, ticks.last() ) ) + { + return ticks; + } + + QList< double > strippedTicks; + for ( int i = 0; i < ticks.count(); i++ ) + { + if ( contains( interval, ticks[i] ) ) + strippedTicks += ticks[i]; + } + return strippedTicks; +} + +/*! + \brief Build an interval around a value + + In case of v == 0.0 the interval is [-0.5, 0.5], + otherwise it is [0.5 * v, 1.5 * v] + + \param value Initial value + \return Calculated interval + */ + +QwtInterval QwtScaleEngine::buildInterval( double value ) const +{ + const double delta = ( value == 0.0 ) ? 0.5 : qAbs( 0.5 * value ); + const double max = std::numeric_limits< double >::max(); + + if ( max - delta < value ) + return QwtInterval( max - delta, max ); + + if ( -max + delta > value ) + return QwtInterval( -max, -max + delta ); + + return QwtInterval( value - delta, value + delta ); +} + +/*! + Change a scale attribute + + \param attribute Attribute to change + \param on On/Off + + \sa Attribute, testAttribute() + */ +void QwtScaleEngine::setAttribute( Attribute attribute, bool on ) +{ + if ( on ) + m_data->attributes |= attribute; + else + m_data->attributes &= ~attribute; +} + +/*! + \return True, if attribute is enabled. + + \param attribute Attribute to be tested + \sa Attribute, setAttribute() + */ +bool QwtScaleEngine::testAttribute( Attribute attribute ) const +{ + return ( m_data->attributes & attribute ); +} + +/*! + Change the scale attribute + + \param attributes Set scale attributes + \sa Attribute, attributes() + */ +void QwtScaleEngine::setAttributes( Attributes attributes ) +{ + m_data->attributes = attributes; +} + +/*! + \return Scale attributes + \sa Attribute, setAttributes(), testAttribute() + */ +QwtScaleEngine::Attributes QwtScaleEngine::attributes() const +{ + return m_data->attributes; +} + +/*! + \brief Specify a reference point + \param reference New reference value + + The reference point is needed if options IncludeReference or + Symmetric are active. Its default value is 0.0. + + \sa Attribute + */ +void QwtScaleEngine::setReference( double reference ) +{ + m_data->referenceValue = reference; +} + +/*! + \return the reference value + \sa setReference(), setAttribute() + */ +double QwtScaleEngine::reference() const +{ + return m_data->referenceValue; +} + +/*! + Set the base of the scale engine + + While a base of 10 is what 99.9% of all applications need + certain scales might need a different base: f.e 2 + + The default setting is 10 + + \param base Base of the engine + + \sa base() + */ +void QwtScaleEngine::setBase( uint base ) +{ + m_data->base = qMax( base, 2U ); +} + +/*! + \return base Base of the scale engine + \sa setBase() + */ +uint QwtScaleEngine::base() const +{ + return m_data->base; +} + +/*! + Constructor + + \param base Base of the scale engine + \sa setBase() + */ +QwtLinearScaleEngine::QwtLinearScaleEngine( uint base ): + QwtScaleEngine( base ) +{ +} + +//! Destructor +QwtLinearScaleEngine::~QwtLinearScaleEngine() +{ +} + +/*! + Align and divide an interval + + \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 setAttribute() + */ +void QwtLinearScaleEngine::autoScale( int maxNumSteps, + double& x1, double& x2, double& stepSize ) const +{ + 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() ); + + stepSize = QwtScaleArithmetic::divideInterval( + interval.width(), qMax( maxNumSteps, 1 ), base() ); + + if ( !testAttribute( QwtScaleEngine::Floating ) ) + interval = align( interval, stepSize ); + + x1 = interval.minValue(); + x2 = interval.maxValue(); + + if ( testAttribute( QwtScaleEngine::Inverted ) ) + { + qSwap( x1, x2 ); + stepSize = -stepSize; + } +} + +/*! + \brief Calculate a scale division for an 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 engine + calculates one. + + \return Calculated scale division + */ +QwtScaleDiv QwtLinearScaleEngine::divideScale( double x1, double x2, + int maxMajorSteps, int maxMinorSteps, double stepSize ) const +{ + QwtInterval interval = QwtInterval( x1, x2 ).normalized(); + + if ( interval.widthL() > std::numeric_limits< double >::max() ) + { + qWarning() << "QwtLinearScaleEngine::divideScale: overflow"; + return QwtScaleDiv(); + } + + if ( interval.width() <= 0 ) + return QwtScaleDiv(); + + stepSize = qAbs( stepSize ); + if ( stepSize == 0.0 ) + { + if ( maxMajorSteps < 1 ) + maxMajorSteps = 1; + + stepSize = QwtScaleArithmetic::divideInterval( + interval.width(), maxMajorSteps, base() ); + } + + QwtScaleDiv scaleDiv; + + if ( stepSize != 0.0 ) + { + QList< double > ticks[QwtScaleDiv::NTickTypes]; + buildTicks( interval, stepSize, maxMinorSteps, ticks ); + + scaleDiv = QwtScaleDiv( interval, ticks ); + } + + if ( x1 > x2 ) + scaleDiv.invert(); + + return scaleDiv; +} + +/*! + \brief Calculate ticks for an interval + + \param interval Interval + \param stepSize Step size + \param maxMinorSteps Maximum number of minor steps + \param ticks Arrays to be filled with the calculated ticks + + \sa buildMajorTicks(), buildMinorTicks + */ +void QwtLinearScaleEngine::buildTicks( + const QwtInterval& interval, double stepSize, int maxMinorSteps, + QList< double > ticks[QwtScaleDiv::NTickTypes] ) const +{ + const QwtInterval boundingInterval = align( interval, stepSize ); + + ticks[QwtScaleDiv::MajorTick] = + buildMajorTicks( boundingInterval, stepSize ); + + if ( maxMinorSteps > 0 ) + { + buildMinorTicks( ticks[QwtScaleDiv::MajorTick], maxMinorSteps, stepSize, + ticks[QwtScaleDiv::MinorTick], ticks[QwtScaleDiv::MediumTick] ); + } + + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + { + ticks[i] = strip( ticks[i], interval ); + + // ticks very close to 0.0 are explicitly set to 0.0 + + for ( int j = 0; j < ticks[i].count(); j++ ) + { + if ( qwtFuzzyCompare( ticks[i][j], 0.0, stepSize ) == 0 ) + ticks[i][j] = 0.0; + } + } +} + +/*! + \brief Calculate major ticks for an interval + + \param interval Interval + \param stepSize Step size + + \return Calculated ticks + */ +QList< double > QwtLinearScaleEngine::buildMajorTicks( + const QwtInterval& interval, double stepSize ) const +{ + int numTicks = qRound( interval.width() / stepSize ) + 1; + if ( numTicks > 10000 ) + numTicks = 10000; + + QList< double > ticks; + ticks.reserve( numTicks ); + + ticks += interval.minValue(); + for ( int i = 1; i < numTicks - 1; i++ ) + ticks += interval.minValue() + i * stepSize; + ticks += interval.maxValue(); + + return ticks; +} + +/*! + \brief Calculate minor/medium ticks for major ticks + + \param majorTicks Major ticks + \param maxMinorSteps Maximum number of minor steps + \param stepSize Step size + \param minorTicks Array to be filled with the calculated minor ticks + \param mediumTicks Array to be filled with the calculated medium ticks + + */ +void QwtLinearScaleEngine::buildMinorTicks( + const QList< double >& majorTicks, + int maxMinorSteps, double stepSize, + QList< double >& minorTicks, + QList< double >& mediumTicks ) const +{ + double minStep = qwtStepSize( stepSize, maxMinorSteps, base() ); + if ( minStep == 0.0 ) + return; + + // # ticks per interval + const int numTicks = qwtCeil( qAbs( stepSize / minStep ) ) - 1; + + int medIndex = -1; + if ( numTicks % 2 ) + medIndex = numTicks / 2; + + // calculate minor ticks + + for ( int i = 0; i < majorTicks.count(); i++ ) + { + double val = majorTicks[i]; + for ( int k = 0; k < numTicks; k++ ) + { + val += minStep; + + double alignedValue = val; + if ( qwtFuzzyCompare( val, 0.0, stepSize ) == 0 ) + alignedValue = 0.0; + + if ( k == medIndex ) + mediumTicks += alignedValue; + else + minorTicks += alignedValue; + } + } +} + +/*! + \brief Align an interval to a step size + + The limits of an interval are aligned that both are integer + multiples of the step size. + + \param interval Interval + \param stepSize Step size + + \return Aligned interval + */ +QwtInterval QwtLinearScaleEngine::align( + const QwtInterval& interval, double stepSize ) const +{ + double x1 = interval.minValue(); + double x2 = interval.maxValue(); + + // when there is no rounding beside some effect, when + // calculating with doubles, we keep the original value + + const double eps = 0.000000000001; // since Qt 4.8: qFuzzyIsNull + const double max = std::numeric_limits< double >::max(); + + if ( -max + stepSize <= x1 ) + { + const double x = QwtScaleArithmetic::floorEps( x1, stepSize ); + if ( qAbs(x) <= eps || !qFuzzyCompare( x1, x ) ) + x1 = x; + } + + if ( max - stepSize >= x2 ) + { + const double x = QwtScaleArithmetic::ceilEps( x2, stepSize ); + if ( qAbs(x) <= eps || !qFuzzyCompare( x2, x ) ) + x2 = x; + } + + return QwtInterval( x1, x2 ); +} + +/*! + Constructor + + \param base Base of the scale engine + \sa setBase() + */ +QwtLogScaleEngine::QwtLogScaleEngine( uint base ): + QwtScaleEngine( base ) +{ + setTransformation( new QwtLogTransform() ); +} + +//! Destructor +QwtLogScaleEngine::~QwtLogScaleEngine() +{ +} + +/*! + Align and divide an interval + + \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 QwtLogScaleEngine::autoScale( int maxNumSteps, + double& x1, double& x2, double& stepSize ) const +{ + if ( x1 > x2 ) + qSwap( x1, x2 ); + + const double logBase = base(); + + QwtInterval interval( x1 / std::pow( logBase, lowerMargin() ), + x2 * std::pow( logBase, upperMargin() ) ); + + if ( interval.maxValue() / interval.minValue() < logBase ) + { + // scale width is less than one step -> try to build a linear scale + + QwtLinearScaleEngine linearScaler; + linearScaler.setAttributes( attributes() ); + linearScaler.setReference( reference() ); + linearScaler.setMargins( lowerMargin(), upperMargin() ); + + linearScaler.autoScale( maxNumSteps, x1, x2, stepSize ); + + QwtInterval linearInterval = QwtInterval( x1, x2 ).normalized(); + linearInterval = linearInterval.limited( + QwtLogTransform::LogMin, QwtLogTransform::LogMax ); + + if ( linearInterval.maxValue() / linearInterval.minValue() < logBase ) + { + stepSize = 0.0; + return; + } + } + + double logRef = 1.0; + if ( reference() > QwtLogTransform::LogMin / 2 ) + logRef = qwtMinF( reference(), QwtLogTransform::LogMax / 2 ); + + if ( testAttribute( QwtScaleEngine::Symmetric ) ) + { + const double delta = qwtMaxF( interval.maxValue() / logRef, + logRef / interval.minValue() ); + interval.setInterval( logRef / delta, logRef * delta ); + } + + if ( testAttribute( QwtScaleEngine::IncludeReference ) ) + interval = interval.extend( logRef ); + + interval = interval.limited( QwtLogTransform::LogMin, QwtLogTransform::LogMax ); + + if ( interval.width() == 0.0 ) + interval = buildInterval( interval.minValue() ); + + stepSize = divideInterval( qwtLogInterval( logBase, interval ).width(), + qMax( maxNumSteps, 1 ) ); + if ( stepSize < 1.0 ) + stepSize = 1.0; + + if ( !testAttribute( QwtScaleEngine::Floating ) ) + interval = align( interval, stepSize ); + + x1 = interval.minValue(); + x2 = interval.maxValue(); + + if ( testAttribute( QwtScaleEngine::Inverted ) ) + { + qSwap( x1, x2 ); + stepSize = -stepSize; + } +} + +/*! + \brief Calculate a scale division for an 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 engine + calculates one. + + \return Calculated scale division + */ +QwtScaleDiv QwtLogScaleEngine::divideScale( double x1, double x2, + int maxMajorSteps, int maxMinorSteps, double stepSize ) const +{ + QwtInterval interval = QwtInterval( x1, x2 ).normalized(); + interval = interval.limited( QwtLogTransform::LogMin, QwtLogTransform::LogMax ); + + if ( interval.width() <= 0 ) + return QwtScaleDiv(); + + const double logBase = base(); + + if ( interval.maxValue() / interval.minValue() < logBase ) + { + // scale width is less than one decade -> build linear scale + + QwtLinearScaleEngine linearScaler; + linearScaler.setAttributes( attributes() ); + linearScaler.setReference( reference() ); + linearScaler.setMargins( lowerMargin(), upperMargin() ); + + return linearScaler.divideScale( x1, x2, + maxMajorSteps, maxMinorSteps, 0.0 ); + } + + stepSize = qAbs( stepSize ); + if ( stepSize == 0.0 ) + { + if ( maxMajorSteps < 1 ) + maxMajorSteps = 1; + + stepSize = divideInterval( + qwtLogInterval( logBase, interval ).width(), maxMajorSteps ); + if ( stepSize < 1.0 ) + stepSize = 1.0; // major step must be >= 1 decade + } + + QwtScaleDiv scaleDiv; + if ( stepSize != 0.0 ) + { + QList< double > ticks[QwtScaleDiv::NTickTypes]; + buildTicks( interval, stepSize, maxMinorSteps, ticks ); + + scaleDiv = QwtScaleDiv( interval, ticks ); + } + + if ( x1 > x2 ) + scaleDiv.invert(); + + return scaleDiv; +} + +/*! + \brief Calculate ticks for an interval + + \param interval Interval + \param maxMinorSteps Maximum number of minor steps + \param stepSize Step size + \param ticks Arrays to be filled with the calculated ticks + + \sa buildMajorTicks(), buildMinorTicks + */ +void QwtLogScaleEngine::buildTicks( + const QwtInterval& interval, double stepSize, int maxMinorSteps, + QList< double > ticks[QwtScaleDiv::NTickTypes] ) const +{ + const QwtInterval boundingInterval = align( interval, stepSize ); + + ticks[QwtScaleDiv::MajorTick] = + buildMajorTicks( boundingInterval, stepSize ); + + if ( maxMinorSteps > 0 ) + { + buildMinorTicks( ticks[QwtScaleDiv::MajorTick], maxMinorSteps, stepSize, + ticks[QwtScaleDiv::MinorTick], ticks[QwtScaleDiv::MediumTick] ); + } + + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + ticks[i] = strip( ticks[i], interval ); +} + +/*! + \brief Calculate major ticks for an interval + + \param interval Interval + \param stepSize Step size + + \return Calculated ticks + */ +QList< double > QwtLogScaleEngine::buildMajorTicks( + const QwtInterval& interval, double stepSize ) const +{ + double width = qwtLogInterval( base(), interval ).width(); + + int numTicks = qRound( width / stepSize ) + 1; + if ( numTicks > 10000 ) + numTicks = 10000; + + const double lxmin = std::log( interval.minValue() ); + const double lxmax = std::log( interval.maxValue() ); + const double lstep = ( lxmax - lxmin ) / double( numTicks - 1 ); + + QList< double > ticks; + ticks.reserve( numTicks ); + + ticks += interval.minValue(); + + for ( int i = 1; i < numTicks - 1; i++ ) + ticks += std::exp( lxmin + double( i ) * lstep ); + + ticks += interval.maxValue(); + + return ticks; +} + +/*! + \brief Calculate minor/medium ticks for major ticks + + \param majorTicks Major ticks + \param maxMinorSteps Maximum number of minor steps + \param stepSize Step size + \param minorTicks Array to be filled with the calculated minor ticks + \param mediumTicks Array to be filled with the calculated medium ticks + */ +void QwtLogScaleEngine::buildMinorTicks( + const QList< double >& majorTicks, + int maxMinorSteps, double stepSize, + QList< double >& minorTicks, + QList< double >& mediumTicks ) const +{ + const double logBase = base(); + + if ( stepSize < 1.1 ) // major step width is one base + { + double minStep = divideInterval( stepSize, maxMinorSteps + 1 ); + if ( minStep == 0.0 ) + return; + + const int numSteps = qRound( stepSize / minStep ); + + int mediumTickIndex = -1; + if ( ( numSteps > 2 ) && ( numSteps % 2 == 0 ) ) + mediumTickIndex = numSteps / 2; + + for ( int i = 0; i < majorTicks.count() - 1; i++ ) + { + const double v = majorTicks[i]; + const double s = logBase / numSteps; + + if ( s >= 1.0 ) + { + if ( !qFuzzyCompare( s, 1.0 ) ) + minorTicks += v * s; + + for ( int j = 2; j < numSteps; j++ ) + { + minorTicks += v * j * s; + } + } + else + { + for ( int j = 1; j < numSteps; j++ ) + { + const double tick = v + j * v * ( logBase - 1 ) / numSteps; + if ( j == mediumTickIndex ) + mediumTicks += tick; + else + minorTicks += tick; + } + } + } + } + else + { + double minStep = divideInterval( stepSize, maxMinorSteps ); + if ( minStep == 0.0 ) + return; + + if ( minStep < 1.0 ) + minStep = 1.0; + + // # subticks per interval + int numTicks = qRound( stepSize / minStep ) - 1; + + // Do the minor steps fit into the interval? + if ( qwtFuzzyCompare( ( numTicks + 1 ) * minStep, + stepSize, stepSize ) > 0 ) + { + numTicks = 0; + } + + if ( numTicks < 1 ) + return; + + int mediumTickIndex = -1; + if ( ( numTicks > 2 ) && ( numTicks % 2 ) ) + mediumTickIndex = numTicks / 2; + + // substep factor = base^substeps + const qreal minFactor = qwtMaxF( std::pow( logBase, minStep ), logBase ); + + for ( int i = 0; i < majorTicks.count(); i++ ) + { + double tick = majorTicks[i]; + for ( int j = 0; j < numTicks; j++ ) + { + tick *= minFactor; + + if ( j == mediumTickIndex ) + mediumTicks += tick; + else + minorTicks += tick; + } + } + } +} + +/*! + \brief Align an interval to a step size + + The limits of an interval are aligned that both are integer + multiples of the step size. + + \param interval Interval + \param stepSize Step size + + \return Aligned interval + */ +QwtInterval QwtLogScaleEngine::align( + const QwtInterval& interval, double stepSize ) const +{ + const QwtInterval intv = qwtLogInterval( base(), interval ); + + double x1 = QwtScaleArithmetic::floorEps( intv.minValue(), stepSize ); + if ( qwtFuzzyCompare( interval.minValue(), x1, stepSize ) == 0 ) + x1 = interval.minValue(); + + double x2 = QwtScaleArithmetic::ceilEps( intv.maxValue(), stepSize ); + if ( qwtFuzzyCompare( interval.maxValue(), x2, stepSize ) == 0 ) + x2 = interval.maxValue(); + + return qwtPowInterval( base(), QwtInterval( x1, x2 ) ); +} diff --git a/libs/qwt/src/qwt_scale_engine.h b/libs/qwt/src/qwt_scale_engine.h new file mode 100644 index 00000000..303dab71 --- /dev/null +++ b/libs/qwt/src/qwt_scale_engine.h @@ -0,0 +1,221 @@ +/****************************************************************************** + * 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_SCALE_ENGINE_H +#define QWT_SCALE_ENGINE_H + +#include "qwt_global.h" +#include "qwt_scale_div.h" + +class QwtInterval; +class QwtTransform; + +/*! + \brief Arithmetic including a tolerance + */ +class QWT_EXPORT QwtScaleArithmetic +{ + public: + static double ceilEps( double value, double intervalSize ); + static double floorEps( double value, double intervalSize ); + + static double divideEps( double intervalSize, double numSteps ); + + static double divideInterval( double intervalSize, + int numSteps, uint base ); +}; + +/*! + \brief Base class for scale engines. + + A scale engine tries to find "reasonable" ranges and step sizes + for scales. + + The layout of the scale can be varied with setAttribute(). + + Qwt offers implementations for logarithmic and linear scales. + */ + +class QWT_EXPORT QwtScaleEngine +{ + public: + /*! + Layout attributes + \sa setAttribute(), testAttribute(), reference(), + lowerMargin(), upperMargin() + */ + + enum Attribute + { + //! No attributes + NoAttribute = 0x00, + + //! Build a scale which includes the reference() value. + IncludeReference = 0x01, + + //! Build a scale which is symmetric to the reference() value. + Symmetric = 0x02, + + /*! + The endpoints of the scale are supposed to be equal the + outmost included values plus the specified margins + (see setMargins()). + If this attribute is *not* set, the endpoints of the scale will + be integer multiples of the step size. + */ + Floating = 0x04, + + //! Turn the scale upside down. + Inverted = 0x08 + }; + + Q_DECLARE_FLAGS( Attributes, Attribute ) + + explicit QwtScaleEngine( uint base = 10 ); + virtual ~QwtScaleEngine(); + + void setBase( uint base ); + uint base() const; + + void setAttribute( Attribute, bool on = true ); + bool testAttribute( Attribute ) const; + + void setAttributes( Attributes ); + Attributes attributes() const; + + void setReference( double ); + double reference() const; + + void setMargins( double lower, double upper ); + double lowerMargin() const; + double upperMargin() const; + + /*! + Align and divide an interval + + \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 (Return value) + */ + virtual void autoScale( int maxNumSteps, + double& x1, double& x2, double& stepSize ) const = 0; + + /*! + \brief Calculate a scale division + + \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.0, the scaleEngine + calculates one. + + \return Calculated scale division + */ + virtual QwtScaleDiv divideScale( double x1, double x2, + int maxMajorSteps, int maxMinorSteps, + double stepSize = 0.0 ) const = 0; + + void setTransformation( QwtTransform* ); + QwtTransform* transformation() const; + + protected: + bool contains( const QwtInterval&, double value ) const; + QList< double > strip( const QList< double >&, const QwtInterval& ) const; + + double divideInterval( double intervalSize, int numSteps ) const; + + QwtInterval buildInterval( double value ) const; + + private: + Q_DISABLE_COPY(QwtScaleEngine) + + class PrivateData; + PrivateData* m_data; +}; + +/*! + \brief A scale engine for linear scales + + The step size will fit into the pattern + \f$\left\{ 1,2,5\right\} \cdot 10^{n}\f$, where n is an integer. + */ + +class QWT_EXPORT QwtLinearScaleEngine : public QwtScaleEngine +{ + public: + explicit QwtLinearScaleEngine( uint base = 10 ); + virtual ~QwtLinearScaleEngine(); + + virtual void autoScale( int maxNumSteps, + double& x1, double& x2, double& stepSize ) const QWT_OVERRIDE; + + virtual QwtScaleDiv divideScale( double x1, double x2, + int maxMajorSteps, int maxMinorSteps, + double stepSize = 0.0 ) const QWT_OVERRIDE; + + + protected: + QwtInterval align( const QwtInterval&, double stepSize ) const; + + void buildTicks( + const QwtInterval&, double stepSize, int maxMinorSteps, + QList< double > ticks[QwtScaleDiv::NTickTypes] ) const; + + QList< double > buildMajorTicks( + const QwtInterval& interval, double stepSize ) const; + + void buildMinorTicks( const QList< double >& majorTicks, + int maxMinorSteps, double stepSize, + QList< double >& minorTicks, QList< double >& mediumTicks ) const; +}; + +/*! + \brief A scale engine for logarithmic scales + + The step size is measured in *decades* + and the major step size will be adjusted to fit the pattern + \f$\left\{ 1,2,3,5\right\} \cdot 10^{n}\f$, where n is a natural number + including zero. + + \warning the step size as well as the margins are measured in *decades*. + */ + +class QWT_EXPORT QwtLogScaleEngine : public QwtScaleEngine +{ + public: + explicit QwtLogScaleEngine( uint base = 10 ); + virtual ~QwtLogScaleEngine(); + + virtual void autoScale( int maxNumSteps, + double& x1, double& x2, double& stepSize ) const QWT_OVERRIDE; + + virtual QwtScaleDiv divideScale( double x1, double x2, + int maxMajorSteps, int maxMinorSteps, + double stepSize = 0.0 ) const QWT_OVERRIDE; + + protected: + QwtInterval align( const QwtInterval&, double stepSize ) const; + + void buildTicks( + const QwtInterval&, double stepSize, int maxMinorSteps, + QList< double > ticks[QwtScaleDiv::NTickTypes] ) const; + + QList< double > buildMajorTicks( + const QwtInterval& interval, double stepSize ) const; + + void buildMinorTicks( const QList< double >& majorTicks, + int maxMinorSteps, double stepSize, + QList< double >& minorTicks, QList< double >& mediumTicks ) const; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtScaleEngine::Attributes ) + +#endif diff --git a/libs/qwt/src/qwt_scale_map.cpp b/libs/qwt/src/qwt_scale_map.cpp new file mode 100644 index 00000000..8be2f6fe --- /dev/null +++ b/libs/qwt/src/qwt_scale_map.cpp @@ -0,0 +1,249 @@ +/****************************************************************************** + * 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_scale_map.h" +#include "qwt_math.h" + +#include +#include + +/*! + \brief Constructor + + The scale and paint device intervals are both set to [0,1]. + */ +QwtScaleMap::QwtScaleMap() + : m_s1( 0.0 ) + , m_s2( 1.0 ) + , m_p1( 0.0 ) + , m_p2( 1.0 ) + , m_cnv( 1.0 ) + , m_ts1( 0.0 ) + , m_transform( NULL ) +{ +} + +//! Copy constructor +QwtScaleMap::QwtScaleMap( const QwtScaleMap& other ) + : m_s1( other.m_s1 ) + , m_s2( other.m_s2 ) + , m_p1( other.m_p1 ) + , m_p2( other.m_p2 ) + , m_cnv( other.m_cnv ) + , m_ts1( other.m_ts1 ) + , m_transform( NULL ) +{ + if ( other.m_transform ) + m_transform = other.m_transform->copy(); +} + +/*! + Destructor + */ +QwtScaleMap::~QwtScaleMap() +{ + delete m_transform; +} + +//! Assignment operator +QwtScaleMap& QwtScaleMap::operator=( const QwtScaleMap& other ) +{ + m_s1 = other.m_s1; + m_s2 = other.m_s2; + m_p1 = other.m_p1; + m_p2 = other.m_p2; + m_cnv = other.m_cnv; + m_ts1 = other.m_ts1; + + delete m_transform; + m_transform = NULL; + + if ( other.m_transform ) + m_transform = other.m_transform->copy(); + + return *this; +} + +/*! + Initialize the map with a transformation + */ +void QwtScaleMap::setTransformation( QwtTransform* transform ) +{ + if ( transform != m_transform ) + { + delete m_transform; + m_transform = transform; + } + + setScaleInterval( m_s1, m_s2 ); +} + +//! Get the transformation +const QwtTransform* QwtScaleMap::transformation() const +{ + return m_transform; +} + +/*! + \brief Specify the borders of the scale interval + \param s1 first border + \param s2 second border + \warning scales might be aligned to + transformation depending boundaries + */ +void QwtScaleMap::setScaleInterval( double s1, double s2 ) +{ + m_s1 = s1; + m_s2 = s2; + + if ( m_transform ) + { + m_s1 = m_transform->bounded( m_s1 ); + m_s2 = m_transform->bounded( m_s2 ); + } + + updateFactor(); +} + +/*! + \brief Specify the borders of the paint device interval + \param p1 first border + \param p2 second border + */ +void QwtScaleMap::setPaintInterval( double p1, double p2 ) +{ + m_p1 = p1; + m_p2 = p2; + + updateFactor(); +} + +void QwtScaleMap::updateFactor() +{ + m_ts1 = m_s1; + double ts2 = m_s2; + + if ( m_transform ) + { + m_ts1 = m_transform->transform( m_ts1 ); + ts2 = m_transform->transform( ts2 ); + } + + m_cnv = 1.0; + if ( m_ts1 != ts2 ) + m_cnv = ( m_p2 - m_p1 ) / ( ts2 - m_ts1 ); +} + +/*! + Transform a rectangle from scale to paint coordinates + + \param xMap X map + \param yMap Y map + \param rect Rectangle in scale coordinates + \return Rectangle in paint coordinates + + \sa invTransform() + */ +QRectF QwtScaleMap::transform( const QwtScaleMap& xMap, + const QwtScaleMap& yMap, const QRectF& rect ) +{ + double x1 = xMap.transform( rect.left() ); + double x2 = xMap.transform( rect.right() ); + double y1 = yMap.transform( rect.top() ); + double y2 = yMap.transform( rect.bottom() ); + + if ( x2 < x1 ) + qSwap( x1, x2 ); + if ( y2 < y1 ) + qSwap( y1, y2 ); + + if ( qwtFuzzyCompare( x1, 0.0, x2 - x1 ) == 0 ) + x1 = 0.0; + if ( qwtFuzzyCompare( x2, 0.0, x2 - x1 ) == 0 ) + x2 = 0.0; + if ( qwtFuzzyCompare( y1, 0.0, y2 - y1 ) == 0 ) + y1 = 0.0; + if ( qwtFuzzyCompare( y2, 0.0, y2 - y1 ) == 0 ) + y2 = 0.0; + + return QRectF( x1, y1, x2 - x1 + 1, y2 - y1 + 1 ); +} + +/*! + Transform a rectangle from paint to scale coordinates + + \param xMap X map + \param yMap Y map + \param pos Position in paint coordinates + \return Position in scale coordinates + \sa transform() + */ +QPointF QwtScaleMap::invTransform( const QwtScaleMap& xMap, + const QwtScaleMap& yMap, const QPointF& pos ) +{ + return QPointF( + xMap.invTransform( pos.x() ), + yMap.invTransform( pos.y() ) + ); +} + +/*! + Transform a point from scale to paint coordinates + + \param xMap X map + \param yMap Y map + \param pos Position in scale coordinates + \return Position in paint coordinates + + \sa invTransform() + */ +QPointF QwtScaleMap::transform( const QwtScaleMap& xMap, + const QwtScaleMap& yMap, const QPointF& pos ) +{ + return QPointF( + xMap.transform( pos.x() ), + yMap.transform( pos.y() ) + ); +} + +/*! + Transform a rectangle from paint to scale coordinates + + \param xMap X map + \param yMap Y map + \param rect Rectangle in paint coordinates + \return Rectangle in scale coordinates + \sa transform() + */ +QRectF QwtScaleMap::invTransform( const QwtScaleMap& xMap, + const QwtScaleMap& yMap, const QRectF& rect ) +{ + const double x1 = xMap.invTransform( rect.left() ); + const double x2 = xMap.invTransform( rect.right() - 1 ); + const double y1 = yMap.invTransform( rect.top() ); + const double y2 = yMap.invTransform( rect.bottom() - 1 ); + + const QRectF r( x1, y1, x2 - x1, y2 - y1 ); + return r.normalized(); +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<( QDebug debug, const QwtScaleMap& map ) +{ + debug.nospace() << "QwtScaleMap(" + << map.transformation() + << ", s:" << map.s1() << "->" << map.s2() + << ", p:" << map.p1() << "->" << map.p2() + << ")"; + + return debug.space(); +} + +#endif diff --git a/libs/qwt/src/qwt_scale_map.h b/libs/qwt/src/qwt_scale_map.h new file mode 100644 index 00000000..7f9e038a --- /dev/null +++ b/libs/qwt/src/qwt_scale_map.h @@ -0,0 +1,173 @@ +/****************************************************************************** + * 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_SCALE_MAP_H +#define QWT_SCALE_MAP_H + +#include "qwt_global.h" +#include "qwt_transform.h" + +class QPointF; +class QRectF; + +/*! + \brief A scale map + + QwtScaleMap offers transformations from the coordinate system + of a scale into the linear coordinate system of a paint device + and vice versa. + */ +class QWT_EXPORT QwtScaleMap +{ + public: + QwtScaleMap(); + QwtScaleMap( const QwtScaleMap& ); + + ~QwtScaleMap(); + + QwtScaleMap& operator=( const QwtScaleMap& ); + + void setTransformation( QwtTransform* ); + const QwtTransform* transformation() const; + + void setPaintInterval( double p1, double p2 ); + void setScaleInterval( double s1, double s2 ); + + double transform( double s ) const; + double invTransform( double p ) const; + + double p1() const; + double p2() const; + + double s1() const; + double s2() const; + + double pDist() const; + double sDist() const; + + static QRectF transform( const QwtScaleMap&, + const QwtScaleMap&, const QRectF& ); + + static QRectF invTransform( const QwtScaleMap&, + const QwtScaleMap&, const QRectF& ); + + static QPointF transform( const QwtScaleMap&, + const QwtScaleMap&, const QPointF& ); + + static QPointF invTransform( const QwtScaleMap&, + const QwtScaleMap&, const QPointF& ); + + bool isInverting() const; + + private: + void updateFactor(); + + double m_s1, m_s2; // scale interval boundaries + double m_p1, m_p2; // paint device interval boundaries + + double m_cnv; // conversion factor + double m_ts1; + + QwtTransform* m_transform; +}; + +/*! + \return First border of the scale interval + */ +inline double QwtScaleMap::s1() const +{ + return m_s1; +} + +/*! + \return Second border of the scale interval + */ +inline double QwtScaleMap::s2() const +{ + return m_s2; +} + +/*! + \return First border of the paint interval + */ +inline double QwtScaleMap::p1() const +{ + return m_p1; +} + +/*! + \return Second border of the paint interval + */ +inline double QwtScaleMap::p2() const +{ + return m_p2; +} + +/*! + \return qwtAbs(p2() - p1()) + */ +inline double QwtScaleMap::pDist() const +{ + return qAbs( m_p2 - m_p1 ); +} + +/*! + \return qwtAbs(s2() - s1()) + */ +inline double QwtScaleMap::sDist() const +{ + return qAbs( m_s2 - m_s1 ); +} + +/*! + Transform a point related to the scale interval into an point + related to the interval of the paint device + + \param s Value relative to the coordinates of the scale + \return Transformed value + + \sa invTransform() + */ +inline double QwtScaleMap::transform( double s ) const +{ + if ( m_transform ) + s = m_transform->transform( s ); + + return m_p1 + ( s - m_ts1 ) * m_cnv; +} + +/*! + Transform an paint device value into a value in the + interval of the scale. + + \param p Value relative to the coordinates of the paint device + \return Transformed value + + \sa transform() + */ +inline double QwtScaleMap::invTransform( double p ) const +{ + double s = m_ts1 + ( p - m_p1 ) / m_cnv; + if ( m_transform ) + s = m_transform->invTransform( s ); + + return s; +} + +//! \return True, when ( p1() < p2() ) != ( s1() < s2() ) +inline bool QwtScaleMap::isInverting() const +{ + return ( ( m_p1 < m_p2 ) != ( m_s1 < m_s2 ) ); +} + +#ifndef QT_NO_DEBUG_STREAM +QWT_EXPORT QDebug operator<<( QDebug, const QwtScaleMap& ); +#endif + +#endif diff --git a/libs/qwt/src/qwt_scale_widget.cpp b/libs/qwt/src/qwt_scale_widget.cpp new file mode 100644 index 00000000..3ce6e693 --- /dev/null +++ b/libs/qwt/src/qwt_scale_widget.cpp @@ -0,0 +1,981 @@ +/****************************************************************************** + * 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_scale_widget.h" +#include "qwt_painter.h" +#include "qwt_color_map.h" +#include "qwt_scale_map.h" +#include "qwt_math.h" +#include "qwt_scale_div.h" +#include "qwt_text.h" +#include "qwt_interval.h" +#include "qwt_scale_engine.h" + +#include +#include +#include +#include +#include +#include + +class QwtScaleWidget::PrivateData +{ + public: + PrivateData() + : scaleDraw( NULL ) + { + colorBar.colorMap = NULL; + } + + ~PrivateData() + { + delete scaleDraw; + delete colorBar.colorMap; + } + + QwtScaleDraw* scaleDraw; + + int borderDist[2]; + int minBorderDist[2]; + int scaleLength; + int margin; + + int titleOffset; + int spacing; + QwtText title; + + QwtScaleWidget::LayoutFlags layoutFlags; + + struct t_colorBar + { + bool isEnabled; + int width; + QwtInterval interval; + QwtColorMap* colorMap; + } colorBar; +}; + +/*! + \brief Create a scale with the position QwtScaleWidget::Left + \param parent Parent widget + */ +QwtScaleWidget::QwtScaleWidget( QWidget* parent ) + : QWidget( parent ) +{ + initScale( QwtScaleDraw::LeftScale ); +} + +/*! + \brief Constructor + \param align Alignment. + \param parent Parent widget + */ +QwtScaleWidget::QwtScaleWidget( QwtScaleDraw::Alignment align, QWidget* parent ) + : QWidget( parent ) +{ + initScale( align ); +} + +//! Destructor +QwtScaleWidget::~QwtScaleWidget() +{ + delete m_data; +} + +//! Initialize the scale +void QwtScaleWidget::initScale( QwtScaleDraw::Alignment align ) +{ + m_data = new PrivateData; + + if ( align == QwtScaleDraw::RightScale ) + m_data->layoutFlags |= TitleInverted; + + m_data->borderDist[0] = 0; + m_data->borderDist[1] = 0; + m_data->minBorderDist[0] = 0; + m_data->minBorderDist[1] = 0; + m_data->margin = 4; + m_data->titleOffset = 0; + m_data->spacing = 2; + + m_data->scaleDraw = new QwtScaleDraw; + m_data->scaleDraw->setAlignment( align ); + m_data->scaleDraw->setLength( 10 ); + + m_data->scaleDraw->setScaleDiv( + QwtLinearScaleEngine().divideScale( 0.0, 100.0, 10, 5 ) ); + + m_data->colorBar.colorMap = new QwtLinearColorMap(); + m_data->colorBar.isEnabled = false; + m_data->colorBar.width = 10; + + const int flags = Qt::AlignHCenter + | Qt::TextExpandTabs | Qt::TextWordWrap; + m_data->title.setRenderFlags( flags ); + m_data->title.setFont( font() ); + + QSizePolicy policy( QSizePolicy::MinimumExpanding, + QSizePolicy::Fixed ); + if ( m_data->scaleDraw->orientation() == Qt::Vertical ) + policy.transpose(); + + setSizePolicy( policy ); + + setAttribute( Qt::WA_WState_OwnSizePolicy, false ); +} + +/*! + Toggle an layout flag + + \param flag Layout flag + \param on true/false + + \sa testLayoutFlag(), LayoutFlag + */ +void QwtScaleWidget::setLayoutFlag( LayoutFlag flag, bool on ) +{ + if ( ( ( m_data->layoutFlags & flag ) != 0 ) != on ) + { + if ( on ) + m_data->layoutFlags |= flag; + else + m_data->layoutFlags &= ~flag; + + update(); + } +} + +/*! + Test a layout flag + + \param flag Layout flag + \return true/false + \sa setLayoutFlag(), LayoutFlag + */ +bool QwtScaleWidget::testLayoutFlag( LayoutFlag flag ) const +{ + return ( m_data->layoutFlags & flag ); +} + +/*! + Give title new text contents + + \param title New title + \sa title(), setTitle(const QwtText &); + */ +void QwtScaleWidget::setTitle( const QString& title ) +{ + if ( m_data->title.text() != title ) + { + m_data->title.setText( title ); + layoutScale(); + } +} + +/*! + Give title new text contents + + \param title New title + \sa title() + \warning The title flags are interpreted in + direction of the label, AlignTop, AlignBottom can't be set + as the title will always be aligned to the scale. + */ +void QwtScaleWidget::setTitle( const QwtText& title ) +{ + QwtText t = title; + const int flags = title.renderFlags() & ~( Qt::AlignTop | Qt::AlignBottom ); + t.setRenderFlags( flags ); + + if ( t != m_data->title ) + { + m_data->title = t; + layoutScale(); + } +} + +/*! + Change the alignment + + \param alignment New alignment + \sa alignment() + */ +void QwtScaleWidget::setAlignment( QwtScaleDraw::Alignment alignment ) +{ + if ( m_data->scaleDraw ) + m_data->scaleDraw->setAlignment( alignment ); + + if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) ) + { + QSizePolicy policy( QSizePolicy::MinimumExpanding, + QSizePolicy::Fixed ); + if ( m_data->scaleDraw->orientation() == Qt::Vertical ) + policy.transpose(); + + setSizePolicy( policy ); + + setAttribute( Qt::WA_WState_OwnSizePolicy, false ); + } + + layoutScale(); +} + + +/*! + \return position + \sa setPosition() + */ +QwtScaleDraw::Alignment QwtScaleWidget::alignment() const +{ + if ( !scaleDraw() ) + return QwtScaleDraw::LeftScale; + + return scaleDraw()->alignment(); +} + +/*! + Specify distances of the scale's endpoints from the + widget's borders. The actual borders will never be less + than minimum border distance. + \param dist1 Left or top Distance + \param dist2 Right or bottom distance + \sa borderDist() + */ +void QwtScaleWidget::setBorderDist( int dist1, int dist2 ) +{ + if ( dist1 != m_data->borderDist[0] || dist2 != m_data->borderDist[1] ) + { + m_data->borderDist[0] = dist1; + m_data->borderDist[1] = dist2; + layoutScale(); + } +} + +/*! + \brief Specify the margin to the colorBar/base line. + \param margin Margin + \sa margin() + */ +void QwtScaleWidget::setMargin( int margin ) +{ + margin = qMax( 0, margin ); + if ( margin != m_data->margin ) + { + m_data->margin = margin; + layoutScale(); + } +} + +/*! + \brief Specify the distance between color bar, scale and title + \param spacing Spacing + \sa spacing() + */ +void QwtScaleWidget::setSpacing( int spacing ) +{ + spacing = qMax( 0, spacing ); + if ( spacing != m_data->spacing ) + { + m_data->spacing = spacing; + layoutScale(); + } +} + +/*! + \brief Change the alignment for the labels. + + \sa QwtScaleDraw::setLabelAlignment(), setLabelRotation() + */ +void QwtScaleWidget::setLabelAlignment( Qt::Alignment alignment ) +{ + m_data->scaleDraw->setLabelAlignment( alignment ); + layoutScale(); +} + +/*! + \brief Change the rotation for the labels. + See QwtScaleDraw::setLabelRotation(). + + \param rotation Rotation + \sa QwtScaleDraw::setLabelRotation(), setLabelFlags() + */ +void QwtScaleWidget::setLabelRotation( double rotation ) +{ + m_data->scaleDraw->setLabelRotation( rotation ); + layoutScale(); +} + +/*! + Set a scale draw + + scaleDraw has to be created with new and will be deleted in + ~QwtScaleWidget() or the next call of setScaleDraw(). + scaleDraw will be initialized with the attributes of + the previous scaleDraw object. + + \param scaleDraw ScaleDraw object + \sa scaleDraw() + */ +void QwtScaleWidget::setScaleDraw( QwtScaleDraw* scaleDraw ) +{ + if ( ( scaleDraw == NULL ) || ( scaleDraw == m_data->scaleDraw ) ) + return; + + const QwtScaleDraw* sd = m_data->scaleDraw; + if ( sd ) + { + scaleDraw->setAlignment( sd->alignment() ); + scaleDraw->setScaleDiv( sd->scaleDiv() ); + + QwtTransform* transform = NULL; + if ( sd->scaleMap().transformation() ) + transform = sd->scaleMap().transformation()->copy(); + + scaleDraw->setTransformation( transform ); + } + + delete m_data->scaleDraw; + m_data->scaleDraw = scaleDraw; + + layoutScale(); +} + +/*! + \return scaleDraw of this scale + \sa setScaleDraw(), QwtScaleDraw::setScaleDraw() + */ +const QwtScaleDraw* QwtScaleWidget::scaleDraw() const +{ + return m_data->scaleDraw; +} + +/*! + \return scaleDraw of this scale + \sa QwtScaleDraw::setScaleDraw() + */ +QwtScaleDraw* QwtScaleWidget::scaleDraw() +{ + return m_data->scaleDraw; +} + +/*! + \return title + \sa setTitle() + */ +QwtText QwtScaleWidget::title() const +{ + return m_data->title; +} + +/*! + \return start border distance + \sa setBorderDist() + */ +int QwtScaleWidget::startBorderDist() const +{ + return m_data->borderDist[0]; +} + +/*! + \return end border distance + \sa setBorderDist() + */ +int QwtScaleWidget::endBorderDist() const +{ + return m_data->borderDist[1]; +} + +/*! + \return margin + \sa setMargin() + */ +int QwtScaleWidget::margin() const +{ + return m_data->margin; +} + +/*! + \return distance between scale and title + \sa setMargin() + */ +int QwtScaleWidget::spacing() const +{ + return m_data->spacing; +} + +/*! + \brief paintEvent + */ +void QwtScaleWidget::paintEvent( QPaintEvent* event ) +{ + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QStyleOption opt; + opt.initFrom(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); + + draw( &painter ); +} + +/*! + \brief draw the scale + */ +void QwtScaleWidget::draw( QPainter* painter ) const +{ + m_data->scaleDraw->draw( painter, palette() ); + + if ( m_data->colorBar.isEnabled && m_data->colorBar.width > 0 && + m_data->colorBar.interval.isValid() ) + { + drawColorBar( painter, colorBarRect( contentsRect() ) ); + } + + QRect r = contentsRect(); + if ( m_data->scaleDraw->orientation() == Qt::Horizontal ) + { + r.setLeft( r.left() + m_data->borderDist[0] ); + r.setWidth( r.width() - m_data->borderDist[1] ); + } + else + { + r.setTop( r.top() + m_data->borderDist[0] ); + r.setHeight( r.height() - m_data->borderDist[1] ); + } + + if ( !m_data->title.isEmpty() ) + drawTitle( painter, m_data->scaleDraw->alignment(), r ); +} + +/*! + Calculate the the rectangle for the color bar + + \param rect Bounding rectangle for all components of the scale + \return Rectangle for the color bar + */ +QRectF QwtScaleWidget::colorBarRect( const QRectF& rect ) const +{ + QRectF cr = rect; + + if ( m_data->scaleDraw->orientation() == Qt::Horizontal ) + { + cr.setLeft( cr.left() + m_data->borderDist[0] ); + cr.setWidth( cr.width() - m_data->borderDist[1] + 1 ); + } + else + { + cr.setTop( cr.top() + m_data->borderDist[0] ); + cr.setHeight( cr.height() - m_data->borderDist[1] + 1 ); + } + + switch ( m_data->scaleDraw->alignment() ) + { + case QwtScaleDraw::LeftScale: + { + cr.setLeft( cr.right() - m_data->margin + - m_data->colorBar.width ); + cr.setWidth( m_data->colorBar.width ); + break; + } + + case QwtScaleDraw::RightScale: + { + cr.setLeft( cr.left() + m_data->margin ); + cr.setWidth( m_data->colorBar.width ); + break; + } + + case QwtScaleDraw::BottomScale: + { + cr.setTop( cr.top() + m_data->margin ); + cr.setHeight( m_data->colorBar.width ); + break; + } + + case QwtScaleDraw::TopScale: + { + cr.setTop( cr.bottom() - m_data->margin + - m_data->colorBar.width ); + cr.setHeight( m_data->colorBar.width ); + break; + } + } + + return cr; +} + +/*! + Change Event handler + \param event Change event + + Invalidates internal caches if necessary + */ +void QwtScaleWidget::changeEvent( QEvent* event ) +{ + if ( event->type() == QEvent::LocaleChange ) + { + m_data->scaleDraw->invalidateCache(); + } + + QWidget::changeEvent( event ); +} + +/*! + Event handler for resize events + \param event Resize event + */ +void QwtScaleWidget::resizeEvent( QResizeEvent* event ) +{ + Q_UNUSED( event ); + layoutScale( false ); +} + +/*! + Recalculate the scale's geometry and layout based on + the current geometry and fonts. + + \param update_geometry Notify the layout system and call update + to redraw the scale + */ + +void QwtScaleWidget::layoutScale( bool update_geometry ) +{ + int bd0, bd1; + getBorderDistHint( bd0, bd1 ); + if ( m_data->borderDist[0] > bd0 ) + bd0 = m_data->borderDist[0]; + if ( m_data->borderDist[1] > bd1 ) + bd1 = m_data->borderDist[1]; + + int colorBarWidth = 0; + if ( m_data->colorBar.isEnabled && m_data->colorBar.interval.isValid() ) + colorBarWidth = m_data->colorBar.width + m_data->spacing; + + const QRectF r = contentsRect(); + double x, y, length; + + if ( m_data->scaleDraw->orientation() == Qt::Vertical ) + { + y = r.top() + bd0; + length = r.height() - ( bd0 + bd1 ); + + if ( m_data->scaleDraw->alignment() == QwtScaleDraw::LeftScale ) + x = r.right() - 1.0 - m_data->margin - colorBarWidth; + else + x = r.left() + m_data->margin + colorBarWidth; + } + else + { + x = r.left() + bd0; + length = r.width() - ( bd0 + bd1 ); + + if ( m_data->scaleDraw->alignment() == QwtScaleDraw::BottomScale ) + y = r.top() + m_data->margin + colorBarWidth; + else + y = r.bottom() - 1.0 - m_data->margin - colorBarWidth; + } + + m_data->scaleDraw->move( x, y ); + m_data->scaleDraw->setLength( length ); + + const int extent = qwtCeil( m_data->scaleDraw->extent( font() ) ); + + m_data->titleOffset = + m_data->margin + m_data->spacing + colorBarWidth + extent; + + if ( update_geometry ) + { + updateGeometry(); + +#if 1 + /* + for some reason updateGeometry does not send a LayoutRequest event + when the parent is not visible and has no layout + */ + + if ( QWidget* w = parentWidget() ) + { + if ( !w->isVisible() && w->layout() == NULL ) + { + if ( w->testAttribute( Qt::WA_WState_Polished ) ) + QApplication::postEvent( w, new QEvent( QEvent::LayoutRequest ) ); + } + } +#endif + + update(); + } +} + +/*! + Draw the color bar of the scale widget + + \param painter Painter + \param rect Bounding rectangle for the color bar + + \sa setColorBarEnabled() + */ +void QwtScaleWidget::drawColorBar( QPainter* painter, const QRectF& rect ) const +{ + if ( !m_data->colorBar.interval.isValid() ) + return; + + const QwtScaleDraw* sd = m_data->scaleDraw; + + QwtPainter::drawColorBar( painter, *m_data->colorBar.colorMap, + m_data->colorBar.interval.normalized(), sd->scaleMap(), + sd->orientation(), rect ); +} + +/*! + Rotate and paint a title according to its position into a given rectangle. + + \param painter Painter + \param align Alignment + \param rect Bounding rectangle + */ + +void QwtScaleWidget::drawTitle( QPainter* painter, + QwtScaleDraw::Alignment align, const QRectF& rect ) const +{ + QRectF r = rect; + double angle; + int flags = m_data->title.renderFlags() & + ~( Qt::AlignTop | Qt::AlignBottom | Qt::AlignVCenter ); + + switch ( align ) + { + case QwtScaleDraw::LeftScale: + angle = -90.0; + flags |= Qt::AlignTop; + r.setRect( r.left(), r.bottom(), + r.height(), r.width() - m_data->titleOffset ); + break; + + case QwtScaleDraw::RightScale: + angle = -90.0; + flags |= Qt::AlignTop; + r.setRect( r.left() + m_data->titleOffset, r.bottom(), + r.height(), r.width() - m_data->titleOffset ); + break; + + case QwtScaleDraw::BottomScale: + angle = 0.0; + flags |= Qt::AlignBottom; + r.setTop( r.top() + m_data->titleOffset ); + break; + + case QwtScaleDraw::TopScale: + default: + angle = 0.0; + flags |= Qt::AlignTop; + r.setBottom( r.bottom() - m_data->titleOffset ); + break; + } + + if ( m_data->layoutFlags & TitleInverted ) + { + if ( align == QwtScaleDraw::LeftScale + || align == QwtScaleDraw::RightScale ) + { + angle = -angle; + r.setRect( r.x() + r.height(), r.y() - r.width(), + r.width(), r.height() ); + } + } + + painter->save(); + painter->setFont( font() ); + painter->setPen( palette().color( QPalette::Text ) ); + + painter->translate( r.x(), r.y() ); + if ( angle != 0.0 ) + painter->rotate( angle ); + + QwtText title = m_data->title; + title.setRenderFlags( flags ); + title.draw( painter, QRectF( 0.0, 0.0, r.width(), r.height() ) ); + + painter->restore(); +} + +/*! + \brief Notify a change of the scale + + This virtual function can be overloaded by derived + classes. The default implementation updates the geometry + and repaints the widget. + */ + +void QwtScaleWidget::scaleChange() +{ + layoutScale(); +} + +/*! + \return a size hint + */ +QSize QwtScaleWidget::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \return a minimum size hint + */ +QSize QwtScaleWidget::minimumSizeHint() const +{ + const Qt::Orientation o = m_data->scaleDraw->orientation(); + + // Border Distance cannot be less than the scale borderDistHint + // Note, the borderDistHint is already included in minHeight/minWidth + int length = 0; + int mbd1, mbd2; + getBorderDistHint( mbd1, mbd2 ); + length += qMax( 0, m_data->borderDist[0] - mbd1 ); + length += qMax( 0, m_data->borderDist[1] - mbd2 ); + length += m_data->scaleDraw->minLength( font() ); + + int dim = dimForLength( length, font() ); + if ( length < dim ) + { + // compensate for long titles + length = dim; + dim = dimForLength( length, font() ); + } + + QSize size( length + 2, dim ); + if ( o == Qt::Vertical ) + size.transpose(); + + const QMargins m = contentsMargins(); + return size + QSize( m.left() + m.right(), m.top() + m.bottom() ); +} + +/*! + \brief Find the height of the title for a given width. + \param width Width + \return height Height + */ + +int QwtScaleWidget::titleHeightForWidth( int width ) const +{ + return qwtCeil( m_data->title.heightForWidth( width, font() ) ); +} + +/*! + \brief Find the minimum dimension for a given length. + dim is the height, length the width seen in + direction of the title. + \param length width for horizontal, height for vertical scales + \param scaleFont Font of the scale + \return height for horizontal, width for vertical scales + */ + +int QwtScaleWidget::dimForLength( int length, const QFont& scaleFont ) const +{ + const int extent = qwtCeil( m_data->scaleDraw->extent( scaleFont ) ); + + int dim = m_data->margin + extent + 1; + + if ( !m_data->title.isEmpty() ) + dim += titleHeightForWidth( length ) + m_data->spacing; + + if ( m_data->colorBar.isEnabled && m_data->colorBar.interval.isValid() ) + dim += m_data->colorBar.width + m_data->spacing; + + return dim; +} + +/*! + \brief Calculate a hint for the border distances. + + This member function calculates the distance + of the scale's endpoints from the widget borders which + is required for the mark labels to fit into the widget. + The maximum of this distance an the minimum border distance + is returned. + + \param start Return parameter for the border width at + the beginning of the scale + \param end Return parameter for the border width at the + end of the scale + + \warning +
  • The minimum border distance depends on the font.
+ \sa setMinBorderDist(), getMinBorderDist(), setBorderDist() + */ +void QwtScaleWidget::getBorderDistHint( int& start, int& end ) const +{ + m_data->scaleDraw->getBorderDistHint( font(), start, end ); + + if ( start < m_data->minBorderDist[0] ) + start = m_data->minBorderDist[0]; + + if ( end < m_data->minBorderDist[1] ) + end = m_data->minBorderDist[1]; +} + +/*! + Set a minimum value for the distances of the scale's endpoints from + the widget borders. This is useful to avoid that the scales + are "jumping", when the tick labels or their positions change + often. + + \param start Minimum for the start border + \param end Minimum for the end border + \sa getMinBorderDist(), getBorderDistHint() + */ +void QwtScaleWidget::setMinBorderDist( int start, int end ) +{ + m_data->minBorderDist[0] = start; + m_data->minBorderDist[1] = end; +} + +/*! + Get the minimum value for the distances of the scale's endpoints from + the widget borders. + + \param start Return parameter for the border width at + the beginning of the scale + \param end Return parameter for the border width at the + end of the scale + + \sa setMinBorderDist(), getBorderDistHint() + */ +void QwtScaleWidget::getMinBorderDist( int& start, int& end ) const +{ + start = m_data->minBorderDist[0]; + end = m_data->minBorderDist[1]; +} + +/*! + \brief Assign a scale division + + The scale division determines where to set the tick marks. + + \param scaleDiv Scale Division + \sa For more information about scale divisions, see QwtScaleDiv. + */ +void QwtScaleWidget::setScaleDiv( const QwtScaleDiv& scaleDiv ) +{ + QwtScaleDraw* sd = m_data->scaleDraw; + if ( sd->scaleDiv() != scaleDiv ) + { + sd->setScaleDiv( scaleDiv ); + layoutScale(); + + Q_EMIT scaleDivChanged(); + } +} + +/*! + Set the transformation + + \param transformation Transformation + \sa QwtAbstractScaleDraw::scaleDraw(), QwtScaleMap + */ +void QwtScaleWidget::setTransformation( QwtTransform* transformation ) +{ + m_data->scaleDraw->setTransformation( transformation ); + layoutScale(); +} + +/*! + En/disable a color bar associated to the scale + \sa isColorBarEnabled(), setColorBarWidth() + */ +void QwtScaleWidget::setColorBarEnabled( bool on ) +{ + if ( on != m_data->colorBar.isEnabled ) + { + m_data->colorBar.isEnabled = on; + layoutScale(); + } +} + +/*! + \return true, when the color bar is enabled + \sa setColorBarEnabled(), setColorBarWidth() + */ +bool QwtScaleWidget::isColorBarEnabled() const +{ + return m_data->colorBar.isEnabled; +} + +/*! + Set the width of the color bar + + \param width Width + \sa colorBarWidth(), setColorBarEnabled() + */ +void QwtScaleWidget::setColorBarWidth( int width ) +{ + if ( width != m_data->colorBar.width ) + { + m_data->colorBar.width = width; + if ( isColorBarEnabled() ) + layoutScale(); + } +} + +/*! + \return Width of the color bar + \sa setColorBarEnabled(), setColorBarEnabled() + */ +int QwtScaleWidget::colorBarWidth() const +{ + return m_data->colorBar.width; +} + +/*! + \return Value interval for the color bar + \sa setColorMap(), colorMap() + */ +QwtInterval QwtScaleWidget::colorBarInterval() const +{ + return m_data->colorBar.interval; +} + +/*! + Set the color map and value interval, that are used for displaying + the color bar. + + \param interval Value interval + \param colorMap Color map + + \sa colorMap(), colorBarInterval() + */ +void QwtScaleWidget::setColorMap( + const QwtInterval& interval, QwtColorMap* colorMap ) +{ + m_data->colorBar.interval = interval; + + if ( colorMap != m_data->colorBar.colorMap ) + { + delete m_data->colorBar.colorMap; + m_data->colorBar.colorMap = colorMap; + } + + if ( isColorBarEnabled() ) + layoutScale(); +} + +/*! + \return Color map + \sa setColorMap(), colorBarInterval() + */ +const QwtColorMap* QwtScaleWidget::colorMap() const +{ + return m_data->colorBar.colorMap; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_scale_widget.cpp" +#endif diff --git a/libs/qwt/src/qwt_scale_widget.h b/libs/qwt/src/qwt_scale_widget.h new file mode 100644 index 00000000..02f5e6d4 --- /dev/null +++ b/libs/qwt/src/qwt_scale_widget.h @@ -0,0 +1,137 @@ +/****************************************************************************** + * 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_SCALE_WIDGET_H +#define QWT_SCALE_WIDGET_H + +#include "qwt_global.h" +#include "qwt_text.h" +#include "qwt_scale_draw.h" + +#include +#include +#include +#include + +class QPainter; +class QwtTransform; +class QwtScaleDiv; +class QwtColorMap; + +/*! + \brief A Widget which contains a scale + + This Widget can be used to decorate composite widgets with + a scale. + */ + +class QWT_EXPORT QwtScaleWidget : public QWidget +{ + Q_OBJECT + + public: + //! Layout flags of the title + enum LayoutFlag + { + /*! + The title of vertical scales is painted from top to bottom. + Otherwise it is painted from bottom to top. + */ + TitleInverted = 1 + }; + + Q_DECLARE_FLAGS( LayoutFlags, LayoutFlag ) + + explicit QwtScaleWidget( QWidget* parent = NULL ); + explicit QwtScaleWidget( QwtScaleDraw::Alignment, QWidget* parent = NULL ); + virtual ~QwtScaleWidget(); + + Q_SIGNALS: + //! Signal emitted, whenever the scale division changes + void scaleDivChanged(); + + public: + void setTitle( const QString& title ); + void setTitle( const QwtText& title ); + QwtText title() const; + + void setLayoutFlag( LayoutFlag, bool on ); + bool testLayoutFlag( LayoutFlag ) const; + + void setBorderDist( int dist1, int dist2 ); + int startBorderDist() const; + int endBorderDist() const; + + void getBorderDistHint( int& start, int& end ) const; + + void getMinBorderDist( int& start, int& end ) const; + void setMinBorderDist( int start, int end ); + + void setMargin( int ); + int margin() const; + + void setSpacing( int ); + int spacing() const; + + void setScaleDiv( const QwtScaleDiv& ); + void setTransformation( QwtTransform* ); + + void setScaleDraw( QwtScaleDraw* ); + const QwtScaleDraw* scaleDraw() const; + QwtScaleDraw* scaleDraw(); + + void setLabelAlignment( Qt::Alignment ); + void setLabelRotation( double rotation ); + + void setColorBarEnabled( bool ); + bool isColorBarEnabled() const; + + void setColorBarWidth( int ); + int colorBarWidth() const; + + void setColorMap( const QwtInterval&, QwtColorMap* ); + + QwtInterval colorBarInterval() const; + const QwtColorMap* colorMap() const; + + virtual QSize sizeHint() const QWT_OVERRIDE; + virtual QSize minimumSizeHint() const QWT_OVERRIDE; + + int titleHeightForWidth( int width ) const; + int dimForLength( int length, const QFont& scaleFont ) const; + + void drawColorBar( QPainter*, const QRectF& ) const; + void drawTitle( QPainter*, QwtScaleDraw::Alignment, + const QRectF& rect ) const; + + void setAlignment( QwtScaleDraw::Alignment ); + QwtScaleDraw::Alignment alignment() const; + + QRectF colorBarRect( const QRectF& ) const; + + protected: + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + virtual void resizeEvent( QResizeEvent* ) QWT_OVERRIDE; + virtual void changeEvent( QEvent* ) QWT_OVERRIDE; + + void draw( QPainter* ) const; + + void scaleChange(); + void layoutScale( bool update_geometry = true ); + + private: + void initScale( QwtScaleDraw::Alignment ); + + class PrivateData; + PrivateData* m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtScaleWidget::LayoutFlags ) + +#endif diff --git a/libs/qwt/src/qwt_series_data.cpp b/libs/qwt/src/qwt_series_data.cpp new file mode 100644 index 00000000..0efc97ef --- /dev/null +++ b/libs/qwt/src/qwt_series_data.cpp @@ -0,0 +1,397 @@ +/****************************************************************************** + * 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_series_data.h" +#include "qwt_point_polar.h" + +static inline QRectF qwtBoundingRect( const QPointF& sample ) +{ + return QRectF( sample.x(), sample.y(), 0.0, 0.0 ); +} + +static inline QRectF qwtBoundingRect( const QwtPoint3D& sample ) +{ + return QRectF( sample.x(), sample.y(), 0.0, 0.0 ); +} + +static inline QRectF qwtBoundingRect( const QwtPointPolar& sample ) +{ + return QRectF( sample.azimuth(), sample.radius(), 0.0, 0.0 ); +} + +static inline QRectF qwtBoundingRect( const QwtIntervalSample& sample ) +{ + return QRectF( sample.interval.minValue(), sample.value, + sample.interval.maxValue() - sample.interval.minValue(), 0.0 ); +} + +static inline QRectF qwtBoundingRect( const QwtSetSample& sample ) +{ + if ( sample.set.empty() ) + return QRectF( sample.value, 0.0, 0.0, -1.0 ); + + double minY = sample.set[0]; + double maxY = sample.set[0]; + + for ( int i = 1; i < sample.set.size(); i++ ) + { + if ( sample.set[i] < minY ) + minY = sample.set[i]; + + if ( sample.set[i] > maxY ) + maxY = sample.set[i]; + } + + return QRectF( sample.value, minY, 0.0, maxY - minY ); +} + +static inline QRectF qwtBoundingRect( const QwtOHLCSample& sample ) +{ + const QwtInterval interval = sample.boundingInterval(); + return QRectF( interval.minValue(), sample.time, interval.width(), 0.0 ); +} + +static inline QRectF qwtBoundingRect( const QwtVectorFieldSample& sample ) +{ + /* + When displaying a sample as an arrow its length will be + proportional to the magnitude - but not the same. + As the factor between length and magnitude is not known + we can't include vx/vy into the bounding rectangle. + */ + + return QRectF( sample.x, sample.y, 0, 0 ); +} + +/*! + \brief Calculate the bounding rectangle of a series subset + + Slow implementation, that iterates over the series. + + \param series Series + \param from Index of the first sample, <= 0 means from the beginning + \param to Index of the last sample, < 0 means to the end + + \return Bounding rectangle + */ + +template< class T > +QRectF qwtBoundingRectT( const QwtSeriesData< T >& series, int from, int to ) +{ + QRectF boundingRect( 1.0, 1.0, -2.0, -2.0 ); // invalid; + + if ( from < 0 ) + from = 0; + + if ( to < 0 ) + to = series.size() - 1; + + if ( to < from ) + return boundingRect; + + int i; + for ( i = from; i <= to; i++ ) + { + const QRectF rect = qwtBoundingRect( series.sample( i ) ); + if ( rect.width() >= 0.0 && rect.height() >= 0.0 ) + { + boundingRect = rect; + i++; + break; + } + } + + for ( ; i <= to; i++ ) + { + const QRectF rect = qwtBoundingRect( series.sample( i ) ); + if ( rect.width() >= 0.0 && rect.height() >= 0.0 ) + { + boundingRect.setLeft( qMin( boundingRect.left(), rect.left() ) ); + boundingRect.setRight( qMax( boundingRect.right(), rect.right() ) ); + boundingRect.setTop( qMin( boundingRect.top(), rect.top() ) ); + boundingRect.setBottom( qMax( boundingRect.bottom(), rect.bottom() ) ); + } + } + + return boundingRect; +} + +/*! + \brief Calculate the bounding rectangle of a series subset + + Slow implementation, that iterates over the series. + + \param series Series + \param from Index of the first sample, <= 0 means from the beginning + \param to Index of the last sample, < 0 means to the end + + \return Bounding rectangle + */ +QRectF qwtBoundingRect( const QwtSeriesData< QPointF >& series, int from, int to ) +{ + return qwtBoundingRectT< QPointF >( series, from, to ); +} + +/*! + \brief Calculate the bounding rectangle of a series subset + + Slow implementation, that iterates over the series. + + \param series Series + \param from Index of the first sample, <= 0 means from the beginning + \param to Index of the last sample, < 0 means to the end + + \return Bounding rectangle + */ +QRectF qwtBoundingRect( + const QwtSeriesData< QwtPoint3D >& series, int from, int to ) +{ + return qwtBoundingRectT< QwtPoint3D >( series, from, to ); +} + +/*! + \brief Calculate the bounding rectangle of a series subset + + The horizontal coordinates represent the azimuth, the + vertical coordinates the radius. + + Slow implementation, that iterates over the series. + + \param series Series + \param from Index of the first sample, <= 0 means from the beginning + \param to Index of the last sample, < 0 means to the end + + \return Bounding rectangle + */ +QRectF qwtBoundingRect( + const QwtSeriesData< QwtPointPolar >& series, int from, int to ) +{ + return qwtBoundingRectT< QwtPointPolar >( series, from, to ); +} + +/*! + \brief Calculate the bounding rectangle of a series subset + + Slow implementation, that iterates over the series. + + \param series Series + \param from Index of the first sample, <= 0 means from the beginning + \param to Index of the last sample, < 0 means to the end + + \return Bounding rectangle + */ +QRectF qwtBoundingRect( + const QwtSeriesData< QwtIntervalSample >& series, int from, int to ) +{ + return qwtBoundingRectT< QwtIntervalSample >( series, from, to ); +} + +/*! + \brief Calculate the bounding rectangle of a series subset + + Slow implementation, that iterates over the series. + + \param series Series + \param from Index of the first sample, <= 0 means from the beginning + \param to Index of the last sample, < 0 means to the end + + \return Bounding rectangle + */ +QRectF qwtBoundingRect( + const QwtSeriesData< QwtOHLCSample >& series, int from, int to ) +{ + return qwtBoundingRectT< QwtOHLCSample >( series, from, to ); +} + +/*! + \brief Calculate the bounding rectangle of a series subset + + Slow implementation, that iterates over the series. + + \param series Series + \param from Index of the first sample, <= 0 means from the beginning + \param to Index of the last sample, < 0 means to the end + + \return Bounding rectangle + */ +QRectF qwtBoundingRect( + const QwtSeriesData< QwtSetSample >& series, int from, int to ) +{ + return qwtBoundingRectT< QwtSetSample >( series, from, to ); +} + +/*! + \brief Calculate the bounding rectangle of a series subset + + Slow implementation, that iterates over the series. + + \param series Series + \param from Index of the first sample, <= 0 means from the beginning + \param to Index of the last sample, < 0 means to the end + + \return Bounding rectangle + */ +QRectF qwtBoundingRect( + const QwtSeriesData< QwtVectorFieldSample >& series, int from, int to ) +{ + return qwtBoundingRectT< QwtVectorFieldSample >( series, from, to ); +} + +/*! + Constructor + \param samples Samples + */ +QwtPointSeriesData::QwtPointSeriesData( const QVector< QPointF >& samples ) + : QwtArraySeriesData< QPointF >( samples ) +{ +} + +/*! + \brief Calculate the bounding rectangle + + The bounding rectangle is calculated once by iterating over all + points and is stored for all following requests. + + \return Bounding rectangle + */ +QRectF QwtPointSeriesData::boundingRect() const +{ + if ( cachedBoundingRect.width() < 0.0 ) + cachedBoundingRect = qwtBoundingRect( *this ); + + return cachedBoundingRect; +} + +/*! + Constructor + \param samples Samples + */ +QwtPoint3DSeriesData::QwtPoint3DSeriesData( + const QVector< QwtPoint3D >& samples ) + : QwtArraySeriesData< QwtPoint3D >( samples ) +{ +} + +/*! + \brief Calculate the bounding rectangle + + The bounding rectangle is calculated once by iterating over all + points and is stored for all following requests. + + \return Bounding rectangle + */ +QRectF QwtPoint3DSeriesData::boundingRect() const +{ + if ( cachedBoundingRect.width() < 0.0 ) + cachedBoundingRect = qwtBoundingRect( *this ); + + return cachedBoundingRect; +} + +/*! + Constructor + \param samples Samples + */ +QwtIntervalSeriesData::QwtIntervalSeriesData( + const QVector< QwtIntervalSample >& samples ) + : QwtArraySeriesData< QwtIntervalSample >( samples ) +{ +} + +/*! + \brief Calculate the bounding rectangle + + The bounding rectangle is calculated once by iterating over all + points and is stored for all following requests. + + \return Bounding rectangle + */ +QRectF QwtIntervalSeriesData::boundingRect() const +{ + if ( cachedBoundingRect.width() < 0.0 ) + cachedBoundingRect = qwtBoundingRect( *this ); + + return cachedBoundingRect; +} + +/*! + Constructor + \param samples Samples + */ +QwtVectorFieldData::QwtVectorFieldData( + const QVector< QwtVectorFieldSample >& samples ) + : QwtArraySeriesData< QwtVectorFieldSample >( samples ) +{ +} + +/*! + \brief Calculate the bounding rectangle + + The bounding rectangle is calculated once by iterating over all + points and is stored for all following requests. + + \return Bounding rectangle + */ +QRectF QwtVectorFieldData::boundingRect() const +{ + if ( cachedBoundingRect.width() < 0.0 ) + cachedBoundingRect = qwtBoundingRect( *this ); + + return cachedBoundingRect; +} + +/*! + Constructor + \param samples Samples + */ +QwtSetSeriesData::QwtSetSeriesData( const QVector< QwtSetSample >& samples ) + : QwtArraySeriesData< QwtSetSample >( samples ) +{ +} + +/*! + \brief Calculate the bounding rectangle + + The bounding rectangle is calculated once by iterating over all + points and is stored for all following requests. + + \return Bounding rectangle + */ +QRectF QwtSetSeriesData::boundingRect() const +{ + if ( cachedBoundingRect.width() < 0.0 ) + cachedBoundingRect = qwtBoundingRect( *this ); + + return cachedBoundingRect; +} + +/*! + Constructor + \param samples Samples + */ +QwtTradingChartData::QwtTradingChartData( const QVector< QwtOHLCSample >& samples ) + : QwtArraySeriesData< QwtOHLCSample >( samples ) +{ +} + +/*! + \brief Calculate the bounding rectangle + + The bounding rectangle is calculated once by iterating over all + points and is stored for all following requests. + + \return Bounding rectangle + */ +QRectF QwtTradingChartData::boundingRect() const +{ + if ( cachedBoundingRect.width() < 0.0 ) + cachedBoundingRect = qwtBoundingRect( *this ); + + return cachedBoundingRect; +} diff --git a/libs/qwt/src/qwt_series_data.h b/libs/qwt/src/qwt_series_data.h new file mode 100644 index 00000000..f0d65a80 --- /dev/null +++ b/libs/qwt/src/qwt_series_data.h @@ -0,0 +1,377 @@ +/****************************************************************************** + * 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_SERIES_DATA_H +#define QWT_SERIES_DATA_H + +#include "qwt_global.h" +#include "qwt_samples.h" +#include "qwt_point_3d.h" + +#include +#include + +class QwtPointPolar; + +/*! + \brief Abstract interface for iterating over samples + + Qwt offers several implementations of the QwtSeriesData API, + but in situations, where data of an application specific format + needs to be displayed, without having to copy it, it is recommended + to implement an individual data access. + + A subclass of QwtSeriesData must implement: + + - size()\n + Should return number of data points. + + - sample()\n + Should return values x and y values of the sample at specific position + as QPointF object. + + - boundingRect()\n + Should return the bounding rectangle of the data series. + It is used for autoscaling and might help certain algorithms for displaying + the data. You can use qwtBoundingRect() for an implementation + but often it is possible to implement a more efficient algorithm + depending on the characteristics of the series. + The member cachedBoundingRect is intended for caching the calculated rectangle. + + */ +template< typename T > +class QwtSeriesData +{ + public: + //! Constructor + QwtSeriesData(); + + //! Destructor + virtual ~QwtSeriesData(); + +#ifndef QWT_PYTHON_WRAPPER + + //! \return Number of samples + virtual size_t size() const = 0; + + /*! + Return a sample + \param i Index + \return Sample at position i + */ + virtual T sample( size_t i ) const = 0; + + /*! + Calculate the bounding rect of all samples + + The bounding rect is necessary for autoscaling and can be used + for a couple of painting optimizations. + + qwtBoundingRect(...) offers slow implementations iterating + over the samples. For large sets it is recommended to implement + something faster f.e. by caching the bounding rectangle. + + \return Bounding rectangle + */ + virtual QRectF boundingRect() const = 0; + +#else + // Needed for generating the python bindings, but not for using them ! + virtual size_t size() const { return 0; } + virtual T sample( size_t i ) const { return T(); } + virtual QRectF boundingRect() const { return cachedBoundingRect; } +#endif + + /*! + Set a the "rect of interest" + + QwtPlotSeriesItem defines the current area of the plot canvas + as "rectangle of interest" ( QwtPlotSeriesItem::updateScaleDiv() ). + It can be used to implement different levels of details. + + The default implementation does nothing. + + \param rect Rectangle of interest + */ + virtual void setRectOfInterest( const QRectF& rect ); + + protected: + //! Can be used to cache a calculated bounding rectangle + mutable QRectF cachedBoundingRect; + + private: + QwtSeriesData< T >& operator=( const QwtSeriesData< T >& ); +}; + +template< typename T > +QwtSeriesData< T >::QwtSeriesData() + : cachedBoundingRect( 0.0, 0.0, -1.0, -1.0 ) +{ +} + +template< typename T > +QwtSeriesData< T >::~QwtSeriesData() +{ +} + +template< typename T > +void QwtSeriesData< T >::setRectOfInterest( const QRectF& ) +{ +} + +/*! + \brief Template class for data, that is organized as QVector + + QVector uses implicit data sharing and can be + passed around as argument efficiently. + */ +template< typename T > +class QwtArraySeriesData : public QwtSeriesData< T > +{ + public: + //! Constructor + QwtArraySeriesData(); + + /*! + Constructor + \param samples Array of samples + */ + explicit QwtArraySeriesData( const QVector< T >& samples ); + + /*! + Assign an array of samples + \param samples Array of samples + */ + void setSamples( const QVector< T >& samples ); + + //! \return Array of samples + const QVector< T > samples() const; + + //! \return Number of samples + virtual size_t size() const QWT_OVERRIDE; + + /*! + \return Sample at a specific position + + \param index Index + \return Sample at position index + */ + virtual T sample( size_t index ) const QWT_OVERRIDE; + + protected: + //! Vector of samples + QVector< T > m_samples; +}; + +template< typename T > +QwtArraySeriesData< T >::QwtArraySeriesData() +{ +} + +template< typename T > +QwtArraySeriesData< T >::QwtArraySeriesData( const QVector< T >& samples ) + : m_samples( samples ) +{ +} + +template< typename T > +void QwtArraySeriesData< T >::setSamples( const QVector< T >& samples ) +{ + QwtSeriesData< T >::cachedBoundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); + m_samples = samples; +} + +template< typename T > +const QVector< T > QwtArraySeriesData< T >::samples() const +{ + return m_samples; +} + +template< typename T > +size_t QwtArraySeriesData< T >::size() const +{ + return m_samples.size(); +} + +template< typename T > +T QwtArraySeriesData< T >::sample( size_t i ) const +{ + return m_samples[ static_cast< int >( i ) ]; +} + +//! Interface for iterating over an array of points +class QWT_EXPORT QwtPointSeriesData : public QwtArraySeriesData< QPointF > +{ + public: + QwtPointSeriesData( + const QVector< QPointF >& = QVector< QPointF >( ) ); + + virtual QRectF boundingRect() const QWT_OVERRIDE; +}; + +//! Interface for iterating over an array of 3D points +class QWT_EXPORT QwtPoint3DSeriesData : public QwtArraySeriesData< QwtPoint3D > +{ + public: + QwtPoint3DSeriesData( + const QVector< QwtPoint3D >& = QVector< QwtPoint3D >( ) ); + + virtual QRectF boundingRect() const QWT_OVERRIDE; +}; + +//! Interface for iterating over an array of intervals +class QWT_EXPORT QwtIntervalSeriesData : public QwtArraySeriesData< QwtIntervalSample > +{ + public: + QwtIntervalSeriesData( + const QVector< QwtIntervalSample >& = QVector< QwtIntervalSample >( ) ); + + virtual QRectF boundingRect() const QWT_OVERRIDE; +}; + +//! Interface for iterating over an array of samples +class QWT_EXPORT QwtSetSeriesData : public QwtArraySeriesData< QwtSetSample > +{ + public: + QwtSetSeriesData( + const QVector< QwtSetSample >& = QVector< QwtSetSample >( ) ); + + virtual QRectF boundingRect() const QWT_OVERRIDE; +}; + +//! Interface for iterating over an array of vector field samples +class QWT_EXPORT QwtVectorFieldData : public QwtArraySeriesData< QwtVectorFieldSample > +{ + public: + QwtVectorFieldData( + const QVector< QwtVectorFieldSample >& = QVector< QwtVectorFieldSample >( ) ); + + virtual QRectF boundingRect() const QWT_OVERRIDE; +}; + +/*! + Interface for iterating over an array of OHLC samples + */ +class QWT_EXPORT QwtTradingChartData : public QwtArraySeriesData< QwtOHLCSample > +{ + public: + QwtTradingChartData( + const QVector< QwtOHLCSample >& = QVector< QwtOHLCSample >( ) ); + + virtual QRectF boundingRect() const QWT_OVERRIDE; +}; + +QWT_EXPORT QRectF qwtBoundingRect( + const QwtSeriesData< QPointF >&, int from = 0, int to = -1 ); + +QWT_EXPORT QRectF qwtBoundingRect( + const QwtSeriesData< QwtPoint3D >&, int from = 0, int to = -1 ); + +QWT_EXPORT QRectF qwtBoundingRect( + const QwtSeriesData< QwtPointPolar >&, int from = 0, int to = -1 ); + +QWT_EXPORT QRectF qwtBoundingRect( + const QwtSeriesData< QwtIntervalSample >&, int from = 0, int to = -1 ); + +QWT_EXPORT QRectF qwtBoundingRect( + const QwtSeriesData< QwtSetSample >&, int from = 0, int to = -1 ); + +QWT_EXPORT QRectF qwtBoundingRect( + const QwtSeriesData< QwtOHLCSample >&, int from = 0, int to = -1 ); + +QWT_EXPORT QRectF qwtBoundingRect( + const QwtSeriesData< QwtVectorFieldSample >&, int from = 0, int to = -1 ); + +/*! + Binary search for a sorted series of samples + + qwtUpperSampleIndex returns the index of sample that is the upper bound + of value. Is the the value smaller than the smallest value the return + value will be 0. Is the value greater or equal than the largest + value the return value will be -1. + + \par Example + The following example shows finds a point of curve from an x + coordinate + \code + #include + #include + + struct compareX + { + inline bool operator()( const double x, const QPointF &pos ) const + { + return ( x < pos.x() ); + } + }; + + QLineF curveLineAt( const QwtPlotCurve *curve, double x ) + { + 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; + } + + QLineF line; // invalid + if ( index > 0 ) + { + line.setP1( curve->sample( index - 1 ) ); + line.setP2( curve->sample( index ) ); + } + + return line; + } + + \endcode + \endpar + + \param series Series of samples + \param value Value + \param lessThan Compare operation + + \note The samples must be sorted according to the order specified + by the lessThan object + */ +template< typename T, typename LessThan > +inline int qwtUpperSampleIndex( const QwtSeriesData< T >& series, + double value, LessThan lessThan ) +{ + const int indexMax = series.size() - 1; + + if ( indexMax < 0 || !lessThan( value, series.sample( indexMax ) ) ) + return -1; + + int indexMin = 0; + int n = indexMax; + + while ( n > 0 ) + { + const int half = n >> 1; + const int indexMid = indexMin + half; + + if ( lessThan( value, series.sample( indexMid ) ) ) + { + n = half; + } + else + { + indexMin = indexMid + 1; + n -= half + 1; + } + } + + return indexMin; +} + +#endif diff --git a/libs/qwt/src/qwt_series_store.h b/libs/qwt/src/qwt_series_store.h new file mode 100644 index 00000000..6059ca72 --- /dev/null +++ b/libs/qwt/src/qwt_series_store.h @@ -0,0 +1,208 @@ +/****************************************************************************** + * 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_SERIES_STORE_H +#define QWT_SERIES_STORE_H + +#include "qwt_global.h" +#include "qwt_series_data.h" + +/*! + \brief Bridge between QwtSeriesStore and QwtPlotSeriesItem + + QwtAbstractSeriesStore is an abstract interface only + to make it possible to isolate the template based methods ( QwtSeriesStore ) + from the regular methods ( QwtPlotSeriesItem ) to make it possible + to derive from QwtPlotSeriesItem without any hassle with templates. + */ +class QwtAbstractSeriesStore +{ + public: + //! Destructor + virtual ~QwtAbstractSeriesStore() {} + + protected: +#ifndef QWT_PYTHON_WRAPPER + //! dataChanged() indicates, that the series has been changed. + virtual void dataChanged() = 0; + + /*! + Set a the "rectangle of interest" for the stored series + \sa QwtSeriesData::setRectOfInterest() + */ + virtual void setRectOfInterest( const QRectF& ) = 0; + + //! \return Bounding rectangle of the stored series + virtual QRectF dataRect() const = 0; + + //! \return Number of samples + virtual size_t dataSize() const = 0; +#else + // Needed for generating the python bindings, but not for using them ! + virtual void dataChanged() {} + virtual void setRectOfInterest( const QRectF& ) {} + virtual QRectF dataRect() const { return QRectF( 0.0, 0.0, -1.0, -1.0 ); } + virtual size_t dataSize() const { return 0; } +#endif +}; + +/*! + \brief Class storing a QwtSeriesData object + + QwtSeriesStore and QwtPlotSeriesItem are intended as base classes for all + plot items iterating over a series of samples. Both classes share + a virtual base class ( QwtAbstractSeriesStore ) to bridge between them. + + QwtSeriesStore offers the template based part for the plot item API, so + that QwtPlotSeriesItem can be derived without any hassle with templates. + */ +template< typename T > +class QwtSeriesStore : public virtual QwtAbstractSeriesStore +{ + public: + /*! + \brief Constructor + The store contains no series + */ + explicit QwtSeriesStore(); + + //! Destructor + ~QwtSeriesStore(); + + /*! + Assign a series of samples + + \param series Data + \warning The item takes ownership of the data object, deleting + it when its not used anymore. + */ + void setData( QwtSeriesData< T >* series ); + + //! \return the the series data + QwtSeriesData< T >* data(); + + //! \return the the series data + const QwtSeriesData< T >* data() const; + + /*! + \param index Index + \return Sample at position index + */ + T sample( int index ) const; + + /*! + \return Number of samples of the series + \sa setData(), QwtSeriesData::size() + */ + virtual size_t dataSize() const QWT_OVERRIDE; + + /*! + \return Bounding rectangle of the series + or an invalid rectangle, when no series is stored + + \sa QwtSeriesData::boundingRect() + */ + virtual QRectF dataRect() const QWT_OVERRIDE; + + /*! + Set a the "rect of interest" for the series + + \param rect Rectangle of interest + \sa QwtSeriesData::setRectOfInterest() + */ + virtual void setRectOfInterest( const QRectF& rect ) QWT_OVERRIDE; + + /*! + Replace a series without deleting the previous one + + \param series New series + \return Previously assigned series + */ + QwtSeriesData< T >* swapData( QwtSeriesData< T >* series ); + + private: + QwtSeriesData< T >* m_series; +}; + +template< typename T > +QwtSeriesStore< T >::QwtSeriesStore() + : m_series( NULL ) +{ +} + +template< typename T > +QwtSeriesStore< T >::~QwtSeriesStore() +{ + delete m_series; +} + +template< typename T > +inline QwtSeriesData< T >* QwtSeriesStore< T >::data() +{ + return m_series; +} + +template< typename T > +inline const QwtSeriesData< T >* QwtSeriesStore< T >::data() const +{ + return m_series; +} + +template< typename T > +inline T QwtSeriesStore< T >::sample( int index ) const +{ + return m_series ? m_series->sample( index ) : T(); +} + +template< typename T > +void QwtSeriesStore< T >::setData( QwtSeriesData< T >* series ) +{ + if ( m_series != series ) + { + delete m_series; + m_series = series; + dataChanged(); + } +} + +template< typename T > +size_t QwtSeriesStore< T >::dataSize() const +{ + if ( m_series == NULL ) + return 0; + + return m_series->size(); +} + +template< typename T > +QRectF QwtSeriesStore< T >::dataRect() const +{ + if ( m_series == NULL ) + return QRectF( 1.0, 1.0, -2.0, -2.0 ); // invalid + + return m_series->boundingRect(); +} + +template< typename T > +void QwtSeriesStore< T >::setRectOfInterest( const QRectF& rect ) +{ + if ( m_series ) + m_series->setRectOfInterest( rect ); +} + +template< typename T > +QwtSeriesData< T >* QwtSeriesStore< T >::swapData( QwtSeriesData< T >* series ) +{ + QwtSeriesData< T >* swappedSeries = m_series; + m_series = series; + + return swappedSeries; +} + +#endif diff --git a/libs/qwt/src/qwt_slider.cpp b/libs/qwt/src/qwt_slider.cpp new file mode 100644 index 00000000..030c2485 --- /dev/null +++ b/libs/qwt/src/qwt_slider.cpp @@ -0,0 +1,1021 @@ +/****************************************************************************** + * 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_slider.h" +#include "qwt_painter.h" +#include "qwt_scale_draw.h" +#include "qwt_scale_map.h" +#include "qwt_math.h" +#include "qwt.h" + +#include +#include +#include +#include +#include +#include + +static QSize qwtHandleSize( const QSize& size, + Qt::Orientation orientation, bool hasTrough ) +{ + QSize handleSize = size; + + if ( handleSize.isEmpty() ) + { + const int handleThickness = 16; + handleSize.setWidth( 2 * handleThickness ); + handleSize.setHeight( handleThickness ); + + if ( !hasTrough ) + handleSize.transpose(); + + if ( orientation == Qt::Vertical ) + handleSize.transpose(); + } + + return handleSize; +} + +static QwtScaleDraw::Alignment qwtScaleDrawAlignment( + Qt::Orientation orientation, QwtSlider::ScalePosition scalePos ) +{ + QwtScaleDraw::Alignment align; + + if ( orientation == Qt::Vertical ) + { + // NoScale lays out like Left + if ( scalePos == QwtSlider::LeadingScale ) + align = QwtScaleDraw::RightScale; + else + align = QwtScaleDraw::LeftScale; + } + else + { + // NoScale lays out like Bottom + if ( scalePos == QwtSlider::TrailingScale ) + align = QwtScaleDraw::TopScale; + else + align = QwtScaleDraw::BottomScale; + } + + return align; +} + +class QwtSlider::PrivateData +{ + public: + PrivateData() + : repeatTimerId( 0 ) + , updateInterval( 150 ) + , stepsIncrement( 0 ) + , pendingValueChange( false ) + , borderWidth( 2 ) + , spacing( 4 ) + , scalePosition( QwtSlider::TrailingScale ) + , hasTrough( true ) + , hasGroove( false ) + , mouseOffset( 0 ) + { + } + + int repeatTimerId; + bool timerTick; + int updateInterval; + int stepsIncrement; + bool pendingValueChange; + + QRect sliderRect; + + QSize handleSize; + int borderWidth; + int spacing; + + Qt::Orientation orientation; + QwtSlider::ScalePosition scalePosition; + + bool hasTrough; + bool hasGroove; + + int mouseOffset; + + mutable QSize sizeHintCache; +}; +/*! + Construct vertical slider in QwtSlider::Trough style + with a scale to the left. + + The scale is initialized to [0.0, 100.0] and the value set to 0.0. + + \param parent Parent widget + + \sa setOrientation(), setScalePosition(), setBackgroundStyle() + */ +QwtSlider::QwtSlider( QWidget* parent ) + : QwtAbstractSlider( parent ) +{ + initSlider( Qt::Vertical ); +} + +/*! + Construct a slider in QwtSlider::Trough style + + When orientation is Qt::Vertical the scale will be aligned to + the left - otherwise at the the top of the slider. + + The scale is initialized to [0.0, 100.0] and the value set to 0.0. + + \param parent Parent widget + \param orientation Orientation of the slider. + */ +QwtSlider::QwtSlider( Qt::Orientation orientation, QWidget* parent ) + : QwtAbstractSlider( parent ) +{ + initSlider( orientation ); +} + +//! Destructor +QwtSlider::~QwtSlider() +{ + delete m_data; +} + +void QwtSlider::initSlider( Qt::Orientation orientation ) +{ + if ( orientation == Qt::Vertical ) + setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Expanding ); + else + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + + setAttribute( Qt::WA_WState_OwnSizePolicy, false ); + + m_data = new QwtSlider::PrivateData; + + m_data->orientation = orientation; + + scaleDraw()->setAlignment( + qwtScaleDrawAlignment( orientation, m_data->scalePosition ) ); + scaleDraw()->setLength( 100 ); + + setScale( 0.0, 100.0 ); + setValue( 0.0 ); +} + +/*! + \brief Set the orientation. + \param orientation Allowed values are Qt::Horizontal and Qt::Vertical. + + \sa orientation(), scalePosition() + */ +void QwtSlider::setOrientation( Qt::Orientation orientation ) +{ + if ( orientation == m_data->orientation ) + return; + + m_data->orientation = orientation; + + scaleDraw()->setAlignment( + qwtScaleDrawAlignment( orientation, m_data->scalePosition ) ); + + if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) ) + { + QSizePolicy sp = sizePolicy(); + sp.transpose(); + setSizePolicy( sp ); + + setAttribute( Qt::WA_WState_OwnSizePolicy, false ); + } + + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutSlider( true ); +} + +/*! + \return Orientation + \sa setOrientation() + */ +Qt::Orientation QwtSlider::orientation() const +{ + return m_data->orientation; +} + +/*! + \brief Change the position of the scale + \param scalePosition Position of the scale. + + \sa ScalePosition, scalePosition() + */ +void QwtSlider::setScalePosition( ScalePosition scalePosition ) +{ + if ( m_data->scalePosition == scalePosition ) + return; + + m_data->scalePosition = scalePosition; + scaleDraw()->setAlignment( + qwtScaleDrawAlignment( m_data->orientation, scalePosition ) ); + + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutSlider( true ); +} + +/*! + \return Position of the scale + \sa setScalePosition() + */ +QwtSlider::ScalePosition QwtSlider::scalePosition() const +{ + return m_data->scalePosition; +} + +/*! + \brief Change the slider's border width + + The border width is used for drawing the slider handle and the + trough. + + \param width Border width + \sa borderWidth() + */ +void QwtSlider::setBorderWidth( int width ) +{ + if ( width < 0 ) + width = 0; + + if ( width != m_data->borderWidth ) + { + m_data->borderWidth = width; + + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutSlider( true ); + } +} + +/*! + \return the border width. + \sa setBorderWidth() + */ +int QwtSlider::borderWidth() const +{ + return m_data->borderWidth; +} + +/*! + \brief Change the spacing between trough and scale + + A spacing of 0 means, that the backbone of the scale is covered + by the trough. + + The default setting is 4 pixels. + + \param spacing Number of pixels + \sa spacing(); + */ +void QwtSlider::setSpacing( int spacing ) +{ + if ( spacing <= 0 ) + spacing = 0; + + if ( spacing != m_data->spacing ) + { + m_data->spacing = spacing; + + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutSlider( true ); + } +} + +/*! + \return Number of pixels between slider and scale + \sa setSpacing() + */ +int QwtSlider::spacing() const +{ + return m_data->spacing; +} + +/*! + \brief Set the slider's handle size + + When the size is empty the slider handle will be painted with a + default size depending on its orientation() and backgroundStyle(). + + \param size New size + + \sa handleSize() + */ +void QwtSlider::setHandleSize( const QSize& size ) +{ + if ( size != m_data->handleSize ) + { + m_data->handleSize = size; + + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutSlider( true ); + } +} + +/*! + \return Size of the handle. + \sa setHandleSize() + */ +QSize QwtSlider::handleSize() const +{ + return m_data->handleSize; +} + +/*! + \brief Set a scale draw + + For changing the labels of the scales, it + is necessary to derive from QwtScaleDraw and + overload QwtScaleDraw::label(). + + \param scaleDraw ScaleDraw object, that has to be created with + new and will be deleted in ~QwtSlider() or the next + call of setScaleDraw(). + + \sa scaleDraw() + */ +void QwtSlider::setScaleDraw( QwtScaleDraw* scaleDraw ) +{ + const QwtScaleDraw* previousScaleDraw = this->scaleDraw(); + if ( scaleDraw == NULL || scaleDraw == previousScaleDraw ) + return; + + if ( previousScaleDraw ) + scaleDraw->setAlignment( previousScaleDraw->alignment() ); + + setAbstractScaleDraw( scaleDraw ); + + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutSlider( true ); +} + +/*! + \return the scale draw of the slider + \sa setScaleDraw() + */ +const QwtScaleDraw* QwtSlider::scaleDraw() const +{ + return static_cast< const QwtScaleDraw* >( abstractScaleDraw() ); +} + +/*! + \return the scale draw of the slider + \sa setScaleDraw() + */ +QwtScaleDraw* QwtSlider::scaleDraw() +{ + return static_cast< QwtScaleDraw* >( abstractScaleDraw() ); +} + +//! Notify changed scale +void QwtSlider::scaleChange() +{ + QwtAbstractSlider::scaleChange(); + + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutSlider( true ); +} + +/*! + \brief Specify the update interval for automatic scrolling + + The minimal accepted value is 50 ms. + + \param interval Update interval in milliseconds + + \sa setUpdateInterval() + */ +void QwtSlider::setUpdateInterval( int interval ) +{ + m_data->updateInterval = qMax( interval, 50 ); +} + +/*! + \return Update interval in milliseconds for automatic scrolling + \sa setUpdateInterval() + */ +int QwtSlider::updateInterval() const +{ + return m_data->updateInterval; +} + +/*! + Draw the slider into the specified rectangle. + + \param painter Painter + \param sliderRect Bounding rectangle of the slider + */ +void QwtSlider::drawSlider( + QPainter* painter, const QRect& sliderRect ) const +{ + QRect innerRect( sliderRect ); + + if ( m_data->hasTrough ) + { + const int bw = m_data->borderWidth; + innerRect = sliderRect.adjusted( bw, bw, -bw, -bw ); + + painter->fillRect( innerRect, palette().brush( QPalette::Mid ) ); + qDrawShadePanel( painter, sliderRect, palette(), true, bw, NULL ); + } + + if ( m_data->hasGroove ) + { + const QSize handleSize = qwtHandleSize( m_data->handleSize, + m_data->orientation, m_data->hasTrough ); + + const int slotExtent = 4; + const int slotMargin = 4; + + QRect slotRect; + if ( orientation() == Qt::Horizontal ) + { + int slotOffset = qMax( 1, handleSize.width() / 2 - slotMargin ); + int slotHeight = slotExtent + ( innerRect.height() % 2 ); + + slotRect.setWidth( innerRect.width() - 2 * slotOffset ); + slotRect.setHeight( slotHeight ); + } + else + { + int slotOffset = qMax( 1, handleSize.height() / 2 - slotMargin ); + int slotWidth = slotExtent + ( innerRect.width() % 2 ); + + slotRect.setWidth( slotWidth ); + slotRect.setHeight( innerRect.height() - 2 * slotOffset ); + + } + + slotRect.moveCenter( innerRect.center() ); + + QBrush brush = palette().brush( QPalette::Dark ); + qDrawShadePanel( painter, slotRect, palette(), true, 1, &brush ); + } + + if ( isValid() ) + drawHandle( painter, handleRect(), transform( value() ) ); +} + +/*! + Draw the thumb at a position + + \param painter Painter + \param handleRect Bounding rectangle of the handle + \param pos Position of the handle marker in widget coordinates + */ +void QwtSlider::drawHandle( QPainter* painter, + const QRect& handleRect, int pos ) const +{ + const int bw = m_data->borderWidth; + + qDrawShadePanel( painter, + handleRect, palette(), false, bw, + &palette().brush( QPalette::Button ) ); + + pos++; // shade line points one pixel below + if ( orientation() == Qt::Horizontal ) + { + qDrawShadeLine( painter, pos, handleRect.top() + bw, + pos, handleRect.bottom() - bw, palette(), true, 1 ); + } + else // Vertical + { + qDrawShadeLine( painter, handleRect.left() + bw, pos, + handleRect.right() - bw, pos, palette(), true, 1 ); + } +} + +/*! + \brief Determine what to do when the user presses a mouse button. + + \param pos Mouse position + + \retval True, when handleRect() contains pos + \sa scrolledTo() + */ +bool QwtSlider::isScrollPosition( const QPoint& pos ) const +{ + if ( handleRect().contains( pos ) ) + { + const double v = ( orientation() == Qt::Horizontal ) + ? pos.x() : pos.y(); + + m_data->mouseOffset = v - transform( value() ); + 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 QwtSlider::scrolledTo( const QPoint& pos ) const +{ + int p = ( orientation() == Qt::Horizontal ) + ? pos.x() : pos.y(); + + p -= m_data->mouseOffset; + + int min = transform( lowerBound() ); + int max = transform( upperBound() ); + if ( min > max ) + qSwap( min, max ); + + p = qBound( min, p, max ); + + return scaleMap().invTransform( p ); +} + +/*! + Mouse press event handler + \param event Mouse event + */ +void QwtSlider::mousePressEvent( QMouseEvent* event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + const QPoint pos = event->pos(); + + if ( isValid() && m_data->sliderRect.contains( pos ) ) + { + if ( !handleRect().contains( pos ) ) + { + const int markerPos = transform( value() ); + + m_data->stepsIncrement = pageSteps(); + + if ( m_data->orientation == Qt::Horizontal ) + { + if ( pos.x() < markerPos ) + m_data->stepsIncrement = -m_data->stepsIncrement; + } + else + { + if ( pos.y() < markerPos ) + m_data->stepsIncrement = -m_data->stepsIncrement; + } + + if ( isInverted() ) + m_data->stepsIncrement = -m_data->stepsIncrement; + + const double v = value(); + incrementValue( m_data->stepsIncrement ); + + if ( v != value() ) + { + if ( isTracking() ) + Q_EMIT valueChanged( value() ); + else + m_data->pendingValueChange = true; + + Q_EMIT sliderMoved( value() ); + } + + m_data->timerTick = false; + m_data->repeatTimerId = startTimer( qMax( 250, 2 * updateInterval() ) ); + + return; + } + } + + QwtAbstractSlider::mousePressEvent( event ); +} + +/*! + Mouse release event handler + \param event Mouse event + */ +void QwtSlider::mouseReleaseEvent( QMouseEvent* event ) +{ + if ( m_data->repeatTimerId > 0 ) + { + killTimer( m_data->repeatTimerId ); + m_data->repeatTimerId = 0; + m_data->timerTick = false; + m_data->stepsIncrement = 0; + } + + if ( m_data->pendingValueChange ) + { + m_data->pendingValueChange = false; + Q_EMIT valueChanged( value() ); + } + + QwtAbstractSlider::mouseReleaseEvent( event ); +} + +/*! + Timer event handler + + Handles the timer, when the mouse stays pressed + inside the sliderRect(). + + \param event Mouse event + */ +void QwtSlider::timerEvent( QTimerEvent* event ) +{ + if ( event->timerId() != m_data->repeatTimerId ) + { + QwtAbstractSlider::timerEvent( event ); + return; + } + + if ( !isValid() ) + { + killTimer( m_data->repeatTimerId ); + m_data->repeatTimerId = 0; + return; + } + + const double v = value(); + incrementValue( m_data->stepsIncrement ); + + if ( v != value() ) + { + if ( isTracking() ) + Q_EMIT valueChanged( value() ); + else + m_data->pendingValueChange = true; + + Q_EMIT sliderMoved( value() ); + } + + if ( !m_data->timerTick ) + { + // restart the timer with a shorter interval + killTimer( m_data->repeatTimerId ); + m_data->repeatTimerId = startTimer( updateInterval() ); + + m_data->timerTick = true; + } +} + +/*! + Qt paint event handler + \param event Paint event + */ +void QwtSlider::paintEvent( QPaintEvent* event ) +{ + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QStyleOption opt; + opt.initFrom(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); + + if ( m_data->scalePosition != QwtSlider::NoScale ) + { + if ( !m_data->sliderRect.contains( event->rect() ) ) + scaleDraw()->draw( &painter, palette() ); + } + + drawSlider( &painter, m_data->sliderRect ); + + if ( hasFocus() ) + QwtPainter::drawFocusRect( &painter, this, m_data->sliderRect ); +} + +/*! + Qt resize event handler + \param event Resize event + */ +void QwtSlider::resizeEvent( QResizeEvent* event ) +{ + layoutSlider( false ); + QwtAbstractSlider::resizeEvent( event ); +} + +/*! + Qt event handler + \param event Event + + \return true, if event was recognized and processed + */ +bool QwtSlider::event( QEvent* event ) +{ + if ( event->type() == QEvent::PolishRequest ) + layoutSlider( false ); + + return QwtAbstractSlider::event( event ); +} + +/*! + Handles QEvent::StyleChange and QEvent::FontChange events + \param event Change event + */ +void QwtSlider::changeEvent( QEvent* event ) +{ + if ( event->type() == QEvent::StyleChange || + event->type() == QEvent::FontChange ) + { + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutSlider( true ); + } + + QwtAbstractSlider::changeEvent( event ); +} + +/*! + Recalculate the slider's geometry and layout based on + the current geometry and fonts. + + \param update_geometry notify the layout system and call update + to redraw the scale + */ +void QwtSlider::layoutSlider( bool update_geometry ) +{ + int bw = 0; + if ( m_data->hasTrough ) + bw = m_data->borderWidth; + + const QSize handleSize = qwtHandleSize( m_data->handleSize, + m_data->orientation, m_data->hasTrough ); + + QRect sliderRect = contentsRect(); + + /* + The marker line of the handle needs to be aligned to + the scale. But the marker is in the center + and we need space enough to display the rest of the handle. + + But the scale itself usually needs margins for displaying + the tick labels, that also might needs space beyond the + backbone. + + Now it depends on what needs more margins. If it is the + slider the scale gets shrunk, otherwise the slider. + */ + + int scaleMargin = 0; + if ( m_data->scalePosition != QwtSlider::NoScale ) + { + int d1, d2; + scaleDraw()->getBorderDistHint( font(), d1, d2 ); + + scaleMargin = qMax( d1, d2 ) - bw; + } + + int scaleX, scaleY, scaleLength; + + if ( m_data->orientation == Qt::Horizontal ) + { + const int handleMargin = handleSize.width() / 2 - 1; + if ( scaleMargin > handleMargin ) + { + int off = scaleMargin - handleMargin; + sliderRect.adjust( off, 0, -off, 0 ); + } + + scaleX = sliderRect.left() + bw + handleSize.width() / 2 - 1; + scaleLength = sliderRect.width() - handleSize.width(); + } + else + { + int handleMargin = handleSize.height() / 2 - 1; + if ( scaleMargin > handleMargin ) + { + int off = scaleMargin - handleMargin; + sliderRect.adjust( 0, off, 0, -off ); + } + + scaleY = sliderRect.top() + bw + handleSize.height() / 2 - 1; + scaleLength = sliderRect.height() - handleSize.height(); + } + + scaleLength -= 2 * bw; + + // now align slider and scale according to the ScalePosition + + if ( m_data->orientation == Qt::Horizontal ) + { + const int h = handleSize.height() + 2 * bw; + + if ( m_data->scalePosition == QwtSlider::TrailingScale ) + { + sliderRect.setTop( sliderRect.bottom() + 1 - h ); + scaleY = sliderRect.top() - m_data->spacing; + } + else + { + sliderRect.setHeight( h ); + scaleY = sliderRect.bottom() + 1 + m_data->spacing; + } + } + else // Qt::Vertical + { + const int w = handleSize.width() + 2 * bw; + + if ( m_data->scalePosition == QwtSlider::LeadingScale ) + { + sliderRect.setWidth( w ); + scaleX = sliderRect.right() + 1 + m_data->spacing; + } + else + { + sliderRect.setLeft( sliderRect.right() + 1 - w ); + scaleX = sliderRect.left() - m_data->spacing; + } + } + + m_data->sliderRect = sliderRect; + + scaleDraw()->move( scaleX, scaleY ); + scaleDraw()->setLength( scaleLength ); + + if ( update_geometry ) + { + m_data->sizeHintCache = QSize(); // invalidate + updateGeometry(); + update(); + } +} + +/*! + En/Disable the trough + + The slider can be customized by showing a trough for the + handle. + + \param on When true, the groove is visible + \sa hasTrough(), setGroove() + */ +void QwtSlider::setTrough( bool on ) +{ + if ( m_data->hasTrough != on ) + { + m_data->hasTrough = on; + + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutSlider( true ); + } +} + +/*! + \return True, when the trough is visible + \sa setTrough(), hasGroove() + */ +bool QwtSlider::hasTrough() const +{ + return m_data->hasTrough; +} + +/*! + En/Disable the groove + + The slider can be customized by showing a groove for the + handle. + + \param on When true, the groove is visible + \sa hasGroove(), setThrough() + */ +void QwtSlider::setGroove( bool on ) +{ + if ( m_data->hasGroove != on ) + { + m_data->hasGroove = on; + + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutSlider( true ); + } +} + +/*! + \return True, when the groove is visible + \sa setGroove(), hasTrough() + */ +bool QwtSlider::hasGroove() const +{ + return m_data->hasGroove; +} + +/*! + \return minimumSizeHint() + */ +QSize QwtSlider::sizeHint() const +{ + const QSize hint = minimumSizeHint(); + return qwtExpandedToGlobalStrut( hint ); +} + +/*! + \return Minimum size hint + \sa sizeHint() + */ +QSize QwtSlider::minimumSizeHint() const +{ + if ( !m_data->sizeHintCache.isEmpty() ) + return m_data->sizeHintCache; + + const QSize handleSize = qwtHandleSize( m_data->handleSize, + m_data->orientation, m_data->hasTrough ); + + int bw = 0; + if ( m_data->hasTrough ) + bw = m_data->borderWidth; + + int sliderLength = 0; + int scaleExtent = 0; + + if ( m_data->scalePosition != QwtSlider::NoScale ) + { + int d1, d2; + scaleDraw()->getBorderDistHint( font(), d1, d2 ); + + const int scaleBorderDist = 2 * ( qMax( d1, d2 ) - bw ); + + int handleBorderDist; + if ( m_data->orientation == Qt::Horizontal ) + handleBorderDist = handleSize.width(); + else + handleBorderDist = handleSize.height(); + + sliderLength = scaleDraw()->minLength( font() ); + if ( handleBorderDist > scaleBorderDist ) + { + // We need additional space for the overlapping handle + sliderLength += handleBorderDist - scaleBorderDist; + } + + scaleExtent += m_data->spacing; + scaleExtent += qwtCeil( scaleDraw()->extent( font() ) ); + } + + sliderLength = qMax( sliderLength, 84 ); // from QSlider + + int w = 0; + int h = 0; + + if ( m_data->orientation == Qt::Horizontal ) + { + w = sliderLength; + h = handleSize.height() + 2 * bw + scaleExtent; + } + else + { + w = handleSize.width() + 2 * bw + scaleExtent; + h = sliderLength; + } + + // finally add margins + const QMargins m = contentsMargins(); + + w += m.left() + m.right(); + h += m.top() + m.bottom(); + + m_data->sizeHintCache = QSize( w, h ); + return m_data->sizeHintCache; +} + +/*! + \return Bounding rectangle of the slider handle + */ +QRect QwtSlider::handleRect() const +{ + if ( !isValid() ) + return QRect(); + + const int markerPos = transform( value() ); + + QPoint center = m_data->sliderRect.center(); + if ( m_data->orientation == Qt::Horizontal ) + center.setX( markerPos ); + else + center.setY( markerPos ); + + QRect rect; + rect.setSize( qwtHandleSize( m_data->handleSize, + m_data->orientation, m_data->hasTrough ) ); + rect.moveCenter( center ); + + return rect; +} + +/*! + \return Bounding rectangle of the slider - without the scale + */ +QRect QwtSlider::sliderRect() const +{ + return m_data->sliderRect; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_slider.cpp" +#endif diff --git a/libs/qwt/src/qwt_slider.h b/libs/qwt/src/qwt_slider.h new file mode 100644 index 00000000..1306e010 --- /dev/null +++ b/libs/qwt/src/qwt_slider.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_SLIDER_H +#define QWT_SLIDER_H + +#include "qwt_global.h" +#include "qwt_abstract_slider.h" + +class QwtScaleDraw; + +/*! + \brief The Slider Widget + + QwtSlider is a slider widget which operates on an interval + of type double. Its position is related to a scale showing + the current value. + + The slider can be customized by having a through, a groove - or both. + + \image html sliders.png + */ + +class QWT_EXPORT QwtSlider : public QwtAbstractSlider +{ + Q_OBJECT + + Q_ENUMS( ScalePosition BackgroundStyle ) + + Q_PROPERTY( Qt::Orientation orientation + READ orientation WRITE setOrientation ) + Q_PROPERTY( ScalePosition scalePosition READ scalePosition + WRITE setScalePosition ) + + Q_PROPERTY( bool trough READ hasTrough WRITE setTrough ) + Q_PROPERTY( bool groove READ hasGroove WRITE setGroove ) + + Q_PROPERTY( QSize handleSize READ handleSize WRITE setHandleSize ) + Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth ) + Q_PROPERTY( int spacing READ spacing WRITE setSpacing ) + + public: + + /*! + Position of the scale + \sa QwtSlider(), setScalePosition(), setOrientation() + */ + enum ScalePosition + { + //! The slider has no scale + NoScale, + + //! The scale is right of a vertical or below a horizontal slider + LeadingScale, + + //! The scale is left of a vertical or above a horizontal slider + TrailingScale + }; + + explicit QwtSlider( QWidget* parent = NULL ); + explicit QwtSlider( Qt::Orientation, QWidget* parent = NULL ); + + virtual ~QwtSlider(); + + void setOrientation( Qt::Orientation ); + Qt::Orientation orientation() const; + + void setScalePosition( ScalePosition ); + ScalePosition scalePosition() const; + + void setTrough( bool ); + bool hasTrough() const; + + void setGroove( bool ); + bool hasGroove() const; + + void setHandleSize( const QSize& ); + QSize handleSize() const; + + void setBorderWidth( int ); + int borderWidth() const; + + void setSpacing( int ); + int spacing() const; + + virtual QSize sizeHint() const QWT_OVERRIDE; + virtual QSize minimumSizeHint() const QWT_OVERRIDE; + + void setScaleDraw( QwtScaleDraw* ); + const QwtScaleDraw* scaleDraw() const; + + void setUpdateInterval( int ); + int updateInterval() const; + + protected: + virtual double scrolledTo( const QPoint& ) const QWT_OVERRIDE; + virtual bool isScrollPosition( const QPoint& ) const QWT_OVERRIDE; + + virtual void drawSlider ( QPainter*, const QRect& ) const; + virtual void drawHandle( QPainter*, const QRect&, int pos ) const; + + virtual void mousePressEvent( QMouseEvent* ) QWT_OVERRIDE; + virtual void mouseReleaseEvent( QMouseEvent* ) QWT_OVERRIDE; + virtual void resizeEvent( QResizeEvent* ) QWT_OVERRIDE; + virtual void paintEvent ( QPaintEvent* ) QWT_OVERRIDE; + virtual void changeEvent( QEvent* ) QWT_OVERRIDE; + virtual void timerEvent( QTimerEvent* ) QWT_OVERRIDE; + + virtual bool event( QEvent* ) QWT_OVERRIDE; + + virtual void scaleChange() QWT_OVERRIDE; + + QRect sliderRect() const; + QRect handleRect() const; + + private: + QwtScaleDraw* scaleDraw(); + + void layoutSlider( bool ); + void initSlider( Qt::Orientation ); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_spline.cpp b/libs/qwt/src/qwt_spline.cpp new file mode 100644 index 00000000..b2f573c6 --- /dev/null +++ b/libs/qwt/src/qwt_spline.cpp @@ -0,0 +1,1401 @@ +/****************************************************************************** + * 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_spline.h" +#include "qwt_spline_parametrization.h" +#include "qwt_spline_polynomial.h" +#include "qwt_bezier.h" + +#include + +namespace QwtSplineC1P +{ + struct param + { + param( const QwtSplineParametrization* p ): + parameter( p ) + { + } + + inline double operator()( const QPointF& p1, const QPointF& p2 ) const + { + return parameter->valueIncrement( p1, p2 ); + } + + const QwtSplineParametrization* parameter; + }; + + struct paramY + { + inline double operator()( const QPointF& p1, const QPointF& p2 ) const + { + return QwtSplineParametrization::valueIncrementY( p1, p2 ); + } + }; + + struct paramUniform + { + inline double operator()( const QPointF& p1, const QPointF& p2 ) const + { + return QwtSplineParametrization::valueIncrementUniform( p1, p2 ); + } + }; + + struct paramCentripetal + { + inline double operator()( const QPointF& p1, const QPointF& p2 ) const + { + return QwtSplineParametrization::valueIncrementCentripetal( p1, p2 ); + } + }; + + struct paramChordal + { + inline double operator()( const QPointF& p1, const QPointF& p2 ) const + { + return QwtSplineParametrization::valueIncrementChordal( p1, p2 ); + } + }; + + struct paramManhattan + { + inline double operator()( const QPointF& p1, const QPointF& p2 ) const + { + return QwtSplineParametrization::valueIncrementManhattan( p1, p2 ); + } + }; + + class PathStore + { + public: + inline void init( int size ) + { + Q_UNUSED(size); + } + + inline void start( double x1, double y1 ) + { + path.moveTo( x1, y1 ); + } + + inline void addCubic( double cx1, double cy1, + double cx2, double cy2, double x2, double y2 ) + { + path.cubicTo( cx1, cy1, cx2, cy2, x2, y2 ); + } + + inline void end() + { + path.closeSubpath(); + } + + QPainterPath path; + }; + + class ControlPointsStore + { + public: + inline ControlPointsStore(): + m_cp( NULL ) + { + } + + inline void init( int size ) + { + controlPoints.resize( size ); + m_cp = controlPoints.data(); + } + + inline void start( double x1, double y1 ) + { + Q_UNUSED( x1 ); + Q_UNUSED( y1 ); + } + + inline void addCubic( double cx1, double cy1, + double cx2, double cy2, double x2, double y2 ) + { + Q_UNUSED( x2 ); + Q_UNUSED( y2 ); + + QLineF& l = *m_cp++; + l.setLine( cx1, cy1, cx2, cy2 ); + } + + inline void end() + { + } + + QVector< QLineF > controlPoints; + + private: + QLineF* m_cp; + }; + + double slopeBoundary( int boundaryCondition, double boundaryValue, + const QPointF& p1, const QPointF& p2, double slope1 ) + { + const double dx = p2.x() - p1.x(); + const double dy = p2.y() - p1.y(); + + double m = 0.0; + + switch( boundaryCondition ) + { + case QwtSpline::Clamped1: + { + m = boundaryValue; + break; + } + case QwtSpline::Clamped2: + { + const double c2 = 0.5 * boundaryValue; + const double c1 = slope1; + + m = 0.5 * ( 3.0 * dy / dx - c1 - c2 * dx ); + break; + } + case QwtSpline::Clamped3: + { + const double c3 = boundaryValue / 6.0; + m = c3 * dx * dx + 2 * dy / dx - slope1; + break; + } + case QwtSpline::LinearRunout: + { + const double s = dy / dx; + const double r = qBound( 0.0, boundaryValue, 1.0 ); + + m = s - r * ( s - slope1 ); + break; + } + default: + { + m = dy / dx; // something + } + } + + return m; + } +} + +template< class SplineStore > +static inline SplineStore qwtSplineC1PathParamX( + const QwtSplineC1* spline, const QPolygonF& points ) +{ + const int n = points.size(); + + const QVector< double > m = spline->slopes( points ); + if ( m.size() != n ) + return SplineStore(); + + const QPointF* pd = points.constData(); + const double* md = m.constData(); + + SplineStore store; + store.init( m.size() - 1 ); + store.start( pd[0].x(), pd[0].y() ); + + for ( int i = 0; i < n - 1; i++ ) + { + const double dx3 = ( pd[i + 1].x() - pd[i].x() ) / 3.0; + + store.addCubic( pd[i].x() + dx3, pd[i].y() + md[i] * dx3, + pd[i + 1].x() - dx3, pd[i + 1].y() - md[i + 1] * dx3, + pd[i + 1].x(), pd[i + 1].y() ); + } + + return store; +} + +template< class SplineStore > +static inline SplineStore qwtSplineC1PathParamY( + const QwtSplineC1* spline, const QPolygonF& points ) +{ + const int n = points.size(); + + QPolygonF pointsFlipped( n ); + for ( int i = 0; i < n; i++ ) + { + pointsFlipped[i].setX( points[i].y() ); + pointsFlipped[i].setY( points[i].x() ); + } + + const QVector< double > m = spline->slopes( pointsFlipped ); + if ( m.size() != n ) + return SplineStore(); + + const QPointF* pd = pointsFlipped.constData(); + const double* md = m.constData(); + + SplineStore store; + store.init( m.size() - 1 ); + store.start( pd[0].y(), pd[0].x() ); + + QVector< QLineF > lines( n ); + for ( int i = 0; i < n - 1; i++ ) + { + const double dx3 = ( pd[i + 1].x() - pd[i].x() ) / 3.0; + + store.addCubic( pd[i].y() + md[i] * dx3, pd[i].x() + dx3, + pd[i + 1].y() - md[i + 1] * dx3, pd[i + 1].x() - dx3, + pd[i + 1].y(), pd[i + 1].x() ); + } + + return store; +} + +template< class SplineStore, class Param > +static inline SplineStore qwtSplineC1PathParametric( + const QwtSplineC1* spline, const QPolygonF& points, Param param ) +{ + const bool isClosing = ( spline->boundaryType() == QwtSpline::ClosedPolygon ); + const int n = points.size(); + + QPolygonF pointsX, pointsY; + pointsX.resize( isClosing ? n + 1 : n ); + pointsY.resize( isClosing ? n + 1 : n ); + + QPointF* px = pointsX.data(); + QPointF* py = pointsY.data(); + const QPointF* p = points.constData(); + + double t = 0.0; + + px[0].rx() = py[0].rx() = t; + px[0].ry() = p[0].x(); + py[0].ry() = p[0].y(); + + int numParamPoints = 1; + for ( int i = 1; i < n; i++ ) + { + const double td = param( points[i - 1], points[i] ); + if ( td > 0.0 ) + { + t += td; + + px[numParamPoints].rx() = py[numParamPoints].rx() = t; + + px[numParamPoints].ry() = p[i].x(); + py[numParamPoints].ry() = p[i].y(); + + numParamPoints++; + } + } + + if ( isClosing ) + { + const double td = param( points[n - 1], points[0] ); + + if ( td > 0.0 ) + { + t += td; + + px[numParamPoints].rx() = py[numParamPoints].rx() = t; + + px[numParamPoints].ry() = p[0].x(); + py[numParamPoints].ry() = p[0].y(); + + numParamPoints++; + } + } + + if ( pointsX.size() != numParamPoints ) + { + pointsX.resize( numParamPoints ); + pointsY.resize( numParamPoints ); + } + + const QVector< double > slopesX = spline->slopes( pointsX ); + const QVector< double > slopesY = spline->slopes( pointsY ); + + const double* mx = slopesX.constData(); + const double* my = slopesY.constData(); + + // we don't need it anymore + pointsX.clear(); + pointsY.clear(); + + SplineStore store; + store.init( isClosing ? n : n - 1 ); + store.start( points[0].x(), points[0].y() ); + + int j = 0; + + for ( int i = 0; i < n - 1; i++ ) + { + const QPointF& p1 = p[i]; + const QPointF& p2 = p[i + 1]; + + const double td = param( p1, p2 ); + + if ( td != 0.0 ) + { + const double t3 = td / 3.0; + + const double cx1 = p1.x() + mx[j] * t3; + const double cy1 = p1.y() + my[j] * t3; + + const double cx2 = p2.x() - mx[j + 1] * t3; + const double cy2 = p2.y() - my[j + 1] * t3; + + store.addCubic( cx1, cy1, cx2, cy2, p2.x(), p2.y() ); + + j++; + } + else + { + // setting control points to the ends + store.addCubic( p1.x(), p1.y(), p2.x(), p2.y(), p2.x(), p2.y() ); + } + } + + if ( isClosing ) + { + const QPointF& p1 = p[n - 1]; + const QPointF& p2 = p[0]; + + const double td = param( p1, p2 ); + + if ( td != 0.0 ) + { + const double t3 = td / 3.0; + + const double cx1 = p1.x() + mx[j] * t3; + const double cy1 = p1.y() + my[j] * t3; + + const double cx2 = p2.x() - mx[0] * t3; + const double cy2 = p2.y() - my[0] * t3; + + store.addCubic( cx1, cy1, cx2, cy2, p2.x(), p2.y() ); + } + else + { + store.addCubic( p1.x(), p1.y(), p2.x(), p2.y(), p2.x(), p2.y() ); + } + + store.end(); + } + + return store; +} + +template< QwtSplinePolynomial toPolynomial( const QPointF&, double, const QPointF&, double ) > +static QPolygonF qwtPolygonParametric( double distance, + const QPolygonF& points, const QVector< double >& values, bool withNodes ) +{ + QPolygonF fittedPoints; + + const QPointF* p = points.constData(); + const double* v = values.constData(); + + fittedPoints += p[0]; + double t = distance; + + const int n = points.size(); + + for ( int i = 0; i < n - 1; i++ ) + { + const QPointF& p1 = p[i]; + const QPointF& p2 = p[i + 1]; + + const QwtSplinePolynomial polynomial = toPolynomial( p1, v[i], p2, v[i + 1] ); + + const double l = p2.x() - p1.x(); + + while ( t < l ) + { + fittedPoints += QPointF( p1.x() + t, p1.y() + polynomial.valueAt( t ) ); + t += distance; + } + + if ( withNodes ) + { + if ( qFuzzyCompare( fittedPoints.last().x(), p2.x() ) ) + fittedPoints.last() = p2; + else + fittedPoints += p2; + } + else + { + t -= l; + } + } + + return fittedPoints; +} + +class QwtSpline::PrivateData +{ + public: + PrivateData() + : boundaryType( QwtSpline::ConditionalBoundaries ) + { + parametrization = new QwtSplineParametrization( + QwtSplineParametrization::ParameterChordal ); + + // parabolic runout at both ends + + boundaryConditions[0].type = QwtSpline::Clamped3; + boundaryConditions[0].value = 0.0; + + boundaryConditions[1].type = QwtSpline::Clamped3; + boundaryConditions[1].value = 0.0; + } + + ~PrivateData() + { + delete parametrization; + } + + QwtSplineParametrization* parametrization; + QwtSpline::BoundaryType boundaryType; + + struct + { + int type; + double value; + + } boundaryConditions[2]; +}; + +/*! + \fn QPainterPath QwtSpline::painterPath( const QPolygonF &points ) const + + Approximates a polygon piecewise with cubic Bezier curves + and returns them as QPainterPath. + + \param points Control points + \return Painter path, that can be rendered by QPainter + + \sa polygon(), QwtBezier + */ + +/*! + \brief Interpolate a curve by a polygon + + Interpolates a polygon piecewise with Bezier curves + interpolating them in a 2nd pass by polygons. + + The interpolation is based on "Piecewise Linear Approximation of Bézier Curves" + by Roger Willcocks ( http://www.rops.org ) + + \param points Control points + \param tolerance Maximum for the accepted error of the approximation + + \return polygon approximating the interpolating polynomials + + \sa bezierControlLines(), QwtBezier + */ +QPolygonF QwtSpline::polygon( const QPolygonF& points, double tolerance ) const +{ + if ( tolerance <= 0.0 ) + return QPolygonF(); + + const QPainterPath path = painterPath( points ); + const int n = path.elementCount(); + if ( n == 0 ) + return QPolygonF(); + + const QPainterPath::Element el = path.elementAt( 0 ); + if ( el.type != QPainterPath::MoveToElement ) + return QPolygonF(); + + QPointF p1( el.x, el.y ); + + QPolygonF polygon; + QwtBezier bezier( tolerance ); + + for ( int i = 1; i < n; i += 3 ) + { + const QPainterPath::Element el1 = path.elementAt( i ); + const QPainterPath::Element el2 = path.elementAt( i + 1 ); + const QPainterPath::Element el3 = path.elementAt( i + 2 ); + + const QPointF cp1( el1.x, el1.y ); + const QPointF cp2( el2.x, el2.y ); + const QPointF p2( el3.x, el3.y ); + + bezier.appendToPolygon( p1, cp1, cp2, p2, polygon ); + + p1 = p2; + } + + return polygon; +} + +/*! + \brief Constructor + + The default setting is a non closing spline with chordal parametrization + + \sa setParametrization(), setBoundaryType() + */ +QwtSpline::QwtSpline() +{ + m_data = new PrivateData; +} + +//! Destructor +QwtSpline::~QwtSpline() +{ + delete m_data; +} + +/*! + The locality of an spline interpolation identifies how many adjacent + polynomials are affected, when changing the position of one point. + + A locality of 'n' means, that changing the coordinates of a point + has an effect on 'n' leading and 'n' following polynomials. + Those polynomials can be calculated from a local subpolygon. + + A value of 0 means, that the interpolation is not local and any modification + of the polygon requires to recalculate all polynomials ( f.e cubic splines ). + + \return Order of locality + */ +uint QwtSpline::locality() const +{ + return 0; +} + +/*! + Define the parametrization for a parametric spline approximation + The default setting is a chordal parametrization. + + \param type Type of parametrization, usually one of QwtSplineParametrization::Type + \sa parametrization() + */ +void QwtSpline::setParametrization( int type ) +{ + if ( m_data->parametrization->type() != type ) + { + delete m_data->parametrization; + m_data->parametrization = new QwtSplineParametrization( type ); + } +} + +/*! + Define the parametrization for a parametric spline approximation + The default setting is a chordal parametrization. + + \param parametrization Parametrization + \sa parametrization() + */ +void QwtSpline::setParametrization( QwtSplineParametrization* parametrization ) +{ + if ( ( parametrization != NULL ) && ( m_data->parametrization != parametrization ) ) + { + delete m_data->parametrization; + m_data->parametrization = parametrization; + } +} + +/*! + \return parametrization + \sa setParametrization() + */ +const QwtSplineParametrization* QwtSpline::parametrization() const +{ + return m_data->parametrization; +} + +/*! + Define the boundary type for the endpoints of the approximating + spline. + + \param boundaryType Boundary type + \sa boundaryType() + */ +void QwtSpline::setBoundaryType( BoundaryType boundaryType ) +{ + m_data->boundaryType = boundaryType; +} + +/*! + \return Boundary type + \sa setBoundaryType() + */ +QwtSpline::BoundaryType QwtSpline::boundaryType() const +{ + return m_data->boundaryType; +} + +/*! + \brief Define the condition for an endpoint of the spline + + \param position At the beginning or the end of the spline + \param condition Condition + + \sa BoundaryCondition, QwtSplineC2::BoundaryCondition, boundaryCondition() + */ +void QwtSpline::setBoundaryCondition( BoundaryPosition position, int condition ) +{ + if ( ( position == QwtSpline::AtBeginning ) || ( position == QwtSpline::AtEnd ) ) + m_data->boundaryConditions[position].type = condition; +} + +/*! + \return Condition for an endpoint of the spline + \param position At the beginning or the end of the spline + + \sa setBoundaryCondition(), boundaryValue(), setBoundaryConditions() + */ +int QwtSpline::boundaryCondition( BoundaryPosition position ) const +{ + if ( ( position == QwtSpline::AtBeginning ) || ( position == QwtSpline::AtEnd ) ) + return m_data->boundaryConditions[position].type; + + return m_data->boundaryConditions[0].type; // should never happen +} + +/*! + \brief Define the boundary value + + The boundary value is an parameter used in combination with + the boundary condition. Its meaning depends on the condition. + + \param position At the beginning or the end of the spline + \param value Value used for the condition at the end point + + \sa boundaryValue(), setBoundaryCondition() + */ +void QwtSpline::setBoundaryValue( BoundaryPosition position, double value ) +{ + if ( ( position == QwtSpline::AtBeginning ) || ( position == QwtSpline::AtEnd ) ) + m_data->boundaryConditions[position].value = value; +} + +/*! + \return Boundary value + \param position At the beginning or the end of the spline + + \sa setBoundaryValue(), boundaryCondition() + */ +double QwtSpline::boundaryValue( BoundaryPosition position ) const +{ + if ( ( position == QwtSpline::AtBeginning ) || ( position == QwtSpline::AtEnd ) ) + return m_data->boundaryConditions[position].value; + + return m_data->boundaryConditions[0].value; // should never happen +} + +/*! + \brief Define the condition at the endpoints of a spline + + \param condition Condition + \param valueBegin Used for the condition at the beginning of te spline + \param valueEnd Used for the condition at the end of te spline + + \sa BoundaryCondition, QwtSplineC2::BoundaryCondition, + testBoundaryCondition(), setBoundaryValue() + */ +void QwtSpline::setBoundaryConditions( + int condition, double valueBegin, double valueEnd ) +{ + setBoundaryCondition( QwtSpline::AtBeginning, condition ); + setBoundaryValue( QwtSpline::AtBeginning, valueBegin ); + + setBoundaryCondition( QwtSpline::AtEnd, condition ); + setBoundaryValue( QwtSpline::AtEnd, valueEnd ); +} + +//! \brief Constructor +QwtSplineInterpolating::QwtSplineInterpolating() +{ +} + +//! Destructor +QwtSplineInterpolating::~QwtSplineInterpolating() +{ +} + +/*! \fn QVector QwtSplineInterpolating::bezierControlLines( const QPolygonF &points ) const + + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns the 2 control points of each curve as QLineF. + + \param points Control points + \return Control points of the interpolating Bezier curves + */ + +/*! + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns them as QPainterPath. + + The implementation calculates the Bezier control lines first + and converts them into painter path elements in an additional loop. + + \param points Control points + \return Painter path, that can be rendered by QPainter + + \note Derived spline classes might overload painterPath() to avoid + the extra loops for converting results into a QPainterPath + + \sa bezierControlLines() + */ +QPainterPath QwtSplineInterpolating::painterPath( const QPolygonF& points ) const +{ + const int n = points.size(); + + QPainterPath path; + if ( n == 0 ) + return path; + + if ( n == 1 ) + { + path.moveTo( points[0] ); + return path; + } + + if ( n == 2 ) + { + path.addPolygon( points ); + return path; + } + + const QVector< QLineF > controlLines = bezierControlLines( points ); + if ( controlLines.size() < n - 1 ) + return path; + + const QPointF* p = points.constData(); + const QLineF* l = controlLines.constData(); + + path.moveTo( p[0] ); + for ( int i = 0; i < n - 1; i++ ) + path.cubicTo( l[i].p1(), l[i].p2(), p[i + 1] ); + + if ( ( boundaryType() == QwtSpline::ClosedPolygon ) + && ( controlLines.size() >= n ) ) + { + path.cubicTo( l[n - 1].p1(), l[n - 1].p2(), p[0] ); + path.closeSubpath(); + } + + return path; +} + +/*! + \brief Interpolate a curve by a polygon + + Interpolates a polygon piecewise with Bezier curves + approximating them by polygons. + + The approximation is based on "Piecewise Linear Approximation of Bézier Curves" + by Roger Willcocks ( http://www.rops.org ) + + \param points Control points + \param tolerance Maximum for the accepted error of the approximation + + \return polygon approximating the interpolating polynomials + + \sa bezierControlLines(), QwtSplineBezier::toPolygon() + */ +QPolygonF QwtSplineInterpolating::polygon( + const QPolygonF& points, double tolerance ) const +{ + if ( tolerance <= 0.0 ) + return QPolygonF(); + + const QVector< QLineF > controlLines = bezierControlLines( points ); + if ( controlLines.isEmpty() ) + return QPolygonF(); + + const bool isClosed = boundaryType() == QwtSpline::ClosedPolygon; + + QwtBezier bezier( tolerance ); + + const QPointF* p = points.constData(); + const QLineF* cl = controlLines.constData(); + + const int n = controlLines.size(); + + QPolygonF polygon; + + for ( int i = 0; i < n - 1; i++ ) + { + const QLineF& l = cl[i]; + bezier.appendToPolygon( p[i], l.p1(), l.p2(), p[i + 1], polygon ); + } + + const QPointF& pn = isClosed ? p[0] : p[n]; + const QLineF& l = cl[n - 1]; + + bezier.appendToPolygon( p[n - 1], l.p1(), l.p2(), pn, polygon ); + + return polygon; +} + +/*! + \brief Find an interpolated polygon with "equidistant" points + + When withNodes is disabled all points of the resulting polygon + will be equidistant according to the parametrization. + + When withNodes is enabled the resulting polygon will also include + the control points and the interpolated points are always aligned to + the control point before ( points[i] + i * distance ). + + The implementation calculates bezier curves first and calculates + the interpolated points in a second run. + + \param points Control nodes of the spline + \param distance Distance between 2 points according + to the parametrization + \param withNodes When true, also add the control + nodes ( even if not being equidistant ) + + \return Interpolating polygon + + \sa bezierControlLines() + */ +QPolygonF QwtSplineInterpolating::equidistantPolygon( const QPolygonF& points, + double distance, bool withNodes ) const +{ + if ( distance <= 0.0 ) + return QPolygonF(); + + const int n = points.size(); + if ( n <= 1 ) + return points; + + if ( n == 2 ) + { + // TODO + return points; + } + + QPolygonF path; + + const QVector< QLineF > controlLines = bezierControlLines( points ); + + if ( controlLines.size() < n - 1 ) + return path; + + path += points.first(); + double t = distance; + + const QPointF* p = points.constData(); + const QLineF* cl = controlLines.constData(); + + const QwtSplineParametrization* param = parametrization(); + + for ( int i = 0; i < n - 1; i++ ) + { + const double l = param->valueIncrement( p[i], p[i + 1] ); + + while ( t < l ) + { + path += QwtBezier::pointAt( p[i], cl[i].p1(), + cl[i].p2(), p[i + 1], t / l ); + + t += distance; + } + + if ( withNodes ) + { + if ( qFuzzyCompare( path.last().x(), p[i + 1].x() ) ) + path.last() = p[i + 1]; + else + path += p[i + 1]; + + t = distance; + } + else + { + t -= l; + } + } + + if ( ( boundaryType() == QwtSpline::ClosedPolygon ) + && ( controlLines.size() >= n ) ) + { + const double l = param->valueIncrement( p[n - 1], p[0] ); + + while ( t < l ) + { + path += QwtBezier::pointAt( p[n - 1], cl[n - 1].p1(), + cl[n - 1].p2(), p[0], t / l ); + + t += distance; + } + + if ( qFuzzyCompare( path.last().x(), p[0].x() ) ) + path.last() = p[0]; + else + path += p[0]; + } + + return path; +} + +//! Constructor +QwtSplineG1::QwtSplineG1() +{ +} + +//! Destructor +QwtSplineG1::~QwtSplineG1() +{ +} + +/*! + \brief Constructor + + The default setting is a non closing spline with no parametrization + ( QwtSplineParametrization::ParameterX ). + + \sa QwtSpline::setParametrization(), + QwtSpline::setBoundaryType() + */ +QwtSplineC1::QwtSplineC1() +{ + setParametrization( QwtSplineParametrization::ParameterX ); +} + +//! Destructor +QwtSplineC1::~QwtSplineC1() +{ +} + +/*! + \param points Control points + \param slopeNext Value of the first derivative at the second point + + \return value of the first derivative at the first point + \sa slopeAtEnd(), QwtSpline::boundaryCondition(), QwtSpline::boundaryValue() + */ +double QwtSplineC1::slopeAtBeginning( const QPolygonF& points, double slopeNext ) const +{ + if ( points.size() < 2 ) + return 0.0; + + return QwtSplineC1P::slopeBoundary( + boundaryCondition( QwtSpline::AtBeginning ), + boundaryValue( QwtSpline::AtBeginning ), + points[0], points[1], slopeNext ); +} + +/*! + \param points Control points + \param slopeBefore Value of the first derivative at the point before the last one + + \return value of the first derivative at the last point + \sa slopeAtBeginning(), QwtSpline::boundaryCondition(), QwtSpline::boundaryValue() + */ +double QwtSplineC1::slopeAtEnd( const QPolygonF& points, double slopeBefore ) const +{ + const int n = points.size(); + + const QPointF p1( points[n - 1].x(), -points[n - 1].y() ); + const QPointF p2( points[n - 2].x(), -points[n - 2].y() ); + + const int condition = boundaryCondition( QwtSpline::AtEnd ); + + double value = boundaryValue( QwtSpline::AtEnd ); + if ( condition != QwtSpline::LinearRunout ) + { + // beside LinearRunout the boundaryValue is a slope or curvature + // and needs to be inverted too + value = -value; + } + + const double slope = QwtSplineC1P::slopeBoundary( condition, value, p1, p2, -slopeBefore ); + return -slope; +} + +/*! \fn QVector QwtSplineC1::slopes( const QPolygonF &points ) const + + \brief Find the first derivative at the control points + + \param points Control nodes of the spline + \return Vector with the values of the 2nd derivate at the control points + + \note The x coordinates need to be increasing or decreasing + */ + +/*! + \brief Calculate an interpolated painter path + + Interpolates a polygon piecewise into cubic Bezier curves + and returns them as QPainterPath. + + The implementation calculates the slopes at the control points + and converts them into painter path elements in an additional loop. + + \param points Control points + \return QPainterPath Painter path, that can be rendered by QPainter + + \note Derived spline classes might overload painterPath() to avoid + the extra loops for converting results into a QPainterPath + */ +QPainterPath QwtSplineC1::painterPath( const QPolygonF& points ) const +{ + const int n = points.size(); + if ( n <= 2 ) + return QwtSplineInterpolating::painterPath( points ); + + using namespace QwtSplineC1P; + + PathStore store; + switch( parametrization()->type() ) + { + case QwtSplineParametrization::ParameterX: + { + store = qwtSplineC1PathParamX< PathStore >( this, points ); + break; + } + case QwtSplineParametrization::ParameterY: + { + store = qwtSplineC1PathParamY< PathStore >( this, points ); + break; + } + case QwtSplineParametrization::ParameterUniform: + { + store = qwtSplineC1PathParametric< PathStore >( + this, points, paramUniform() ); + break; + } + case QwtSplineParametrization::ParameterCentripetal: + { + store = qwtSplineC1PathParametric< PathStore >( + this, points, paramCentripetal() ); + break; + } + case QwtSplineParametrization::ParameterChordal: + { + store = qwtSplineC1PathParametric< PathStore >( + this, points, paramChordal() ); + break; + } + default: + { + store = qwtSplineC1PathParametric< PathStore >( + this, points, param( parametrization() ) ); + } + } + + return store.path; +} + +/*! + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns the 2 control points of each curve as QLineF. + + \param points Control points + \return Control points of the interpolating Bezier curves + */ +QVector< QLineF > QwtSplineC1::bezierControlLines( const QPolygonF& points ) const +{ + using namespace QwtSplineC1P; + + const int n = points.size(); + if ( n <= 2 ) + return QVector< QLineF >(); + + ControlPointsStore store; + switch( parametrization()->type() ) + { + case QwtSplineParametrization::ParameterX: + { + store = qwtSplineC1PathParamX< ControlPointsStore >( this, points ); + break; + } + case QwtSplineParametrization::ParameterY: + { + store = qwtSplineC1PathParamY< ControlPointsStore >( this, points ); + break; + } + case QwtSplineParametrization::ParameterUniform: + { + store = qwtSplineC1PathParametric< ControlPointsStore >( + this, points, paramUniform() ); + break; + } + case QwtSplineParametrization::ParameterCentripetal: + { + store = qwtSplineC1PathParametric< ControlPointsStore >( + this, points, paramCentripetal() ); + break; + } + case QwtSplineParametrization::ParameterChordal: + { + store = qwtSplineC1PathParametric< ControlPointsStore >( + this, points, paramChordal() ); + break; + } + default: + { + store = qwtSplineC1PathParametric< ControlPointsStore >( + this, points, param( parametrization() ) ); + } + } + + return store.controlPoints; +} + +/*! + \brief Find an interpolated polygon with "equidistant" points + + The implementation is optimzed for non parametric curves + ( QwtSplineParametrization::ParameterX ) and falls back to + QwtSpline::equidistantPolygon() otherwise. + + \param points Control nodes of the spline + \param distance Distance between 2 points according + to the parametrization + \param withNodes When true, also add the control + nodes ( even if not being equidistant ) + + \return Interpolating polygon + + \sa QwtSpline::equidistantPolygon() + */ +QPolygonF QwtSplineC1::equidistantPolygon( const QPolygonF& points, + double distance, bool withNodes ) const +{ + if ( parametrization()->type() == QwtSplineParametrization::ParameterX ) + { + if ( points.size() > 2 ) + { + const QVector< double > m = slopes( points ); + if ( m.size() != points.size() ) + return QPolygonF(); + + return qwtPolygonParametric< QwtSplinePolynomial::fromSlopes >( + distance, points, m, withNodes ); + } + } + + return QwtSplineInterpolating::equidistantPolygon( points, distance, withNodes ); +} + +/*! + \brief Calculate the interpolating polynomials for a non parametric spline + + C1 spline interpolations are based on finding values for the first + derivates at the control points. The interpolating polynomials can + be calculated from the the first derivates using QwtSplinePolynomial::fromSlopes(). + + The default implementation is a two pass calculation. In derived classes it + might be overloaded by a one pass implementation. + + \param points Control points + \return Interpolating polynomials + + \note The x coordinates need to be increasing or decreasing + */ +QVector< QwtSplinePolynomial > QwtSplineC1::polynomials( + const QPolygonF& points ) const +{ + QVector< QwtSplinePolynomial > polynomials; + + const QVector< double > m = slopes( points ); + if ( m.size() < 2 ) + return polynomials; + + polynomials.reserve( m.size() - 1 ); + for ( int i = 1; i < m.size(); i++ ) + { + polynomials += QwtSplinePolynomial::fromSlopes( + points[i - 1], m[i - 1], points[i], m[i] ); + } + + return polynomials; +} + +/*! + \brief Constructor + + The default setting is a non closing spline with no parametrization + ( QwtSplineParametrization::ParameterX ). + + \sa QwtSpline::setParametrization(), QwtSpline::setBoundaryType() + */ +QwtSplineC2::QwtSplineC2() +{ +} + +//! Destructor +QwtSplineC2::~QwtSplineC2() +{ +} + +/*! + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns them as QPainterPath. + + \param points Control points + \return Painter path, that can be rendered by QPainter + + \note The implementation simply calls QwtSplineC1::painterPath(), but is + intended to be replaced by a one pass calculation some day. + */ +QPainterPath QwtSplineC2::painterPath( const QPolygonF& points ) const +{ + // could be implemented from curvatures without the extra + // loop for calculating the slopes vector. TODO ... + + return QwtSplineC1::painterPath( points ); +} + +/*! + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns the 2 control points of each curve as QLineF. + + \param points Control points + \return Control points of the interpolating Bezier curves + + \note The implementation simply calls QwtSplineC1::bezierControlLines(), + but is intended to be replaced by a more efficient implementation + that builds the polynomials by the curvatures some day. + */ +QVector< QLineF > QwtSplineC2::bezierControlLines( const QPolygonF& points ) const +{ + // could be implemented from curvatures without the extra + // loop for calculating the slopes vector. TODO ... + + return QwtSplineC1::bezierControlLines( points ); +} + +/*! + \brief Find an interpolated polygon with "equidistant" points + + The implementation is optimzed for non parametric curves + ( QwtSplineParametrization::ParameterX ) and falls back to + QwtSpline::equidistantPolygon() otherwise. + + \param points Control nodes of the spline + \param distance Distance between 2 points according + to the parametrization + \param withNodes When true, also add the control + nodes ( even if not being equidistant ) + + \return Interpolating polygon + + \sa QwtSpline::equidistantPolygon() + */ +QPolygonF QwtSplineC2::equidistantPolygon( const QPolygonF& points, + double distance, bool withNodes ) const +{ + if ( parametrization()->type() == QwtSplineParametrization::ParameterX ) + { + if ( points.size() > 2 ) + { + const QVector< double > cv = curvatures( points ); + if ( cv.size() != points.size() ) + return QPolygonF(); + + return qwtPolygonParametric< QwtSplinePolynomial::fromCurvatures >( + distance, points, cv, withNodes ); + } + } + + return QwtSplineInterpolating::equidistantPolygon( points, distance, withNodes ); +} + +/*! \fn QVector QwtSplineC2::curvatures( const QPolygonF &points ) const + + \brief Find the second derivative at the control points + + \param points Control nodes of the spline + \return Vector with the values of the 2nd derivate at the control points + + \sa slopes() + \note The x coordinates need to be increasing or decreasing + */ + +/*! + \brief Find the first derivative at the control points + + An implementation calculating the 2nd derivatives and then building + the slopes in a 2nd loop. QwtSplineCubic overloads it with a more + performant implementation doing it in one loop. + + \param points Control nodes of the spline + \return Vector with the values of the 1nd derivate at the control points + + \sa curvatures() + + \note The x coordinates need to be increasing or decreasing + */ +QVector< double > QwtSplineC2::slopes( const QPolygonF& points ) const +{ + const QVector< double > curvatures = this->curvatures( points ); + if ( curvatures.size() < 2 ) + return QVector< double >(); + + QVector< double > slopes( curvatures.size() ); + + const double* cv = curvatures.constData(); + double* m = slopes.data(); + + const int n = points.size(); + const QPointF* p = points.constData(); + + QwtSplinePolynomial polynomial; + + for ( int i = 0; i < n - 1; i++ ) + { + polynomial = QwtSplinePolynomial::fromCurvatures( p[i], cv[i], p[i + 1], cv[i + 1] ); + m[i] = polynomial.c1; + } + + m[n - 1] = polynomial.slopeAt( p[n - 1].x() - p[n - 2].x() ); + + return slopes; +} + +/*! + \brief Calculate the interpolating polynomials for a non parametric spline + + C2 spline interpolations are based on finding values for the second + derivates of f at the control points. The interpolating polynomials can + be calculated from the the second derivates using QwtSplinePolynomial::fromCurvatures. + + The default implementation is a 2 pass calculation. In derived classes it + might be overloaded by a one pass implementation. + + \param points Control points + \return Interpolating polynomials + + \note The x coordinates need to be increasing or decreasing + */ +QVector< QwtSplinePolynomial > QwtSplineC2::polynomials( const QPolygonF& points ) const +{ + QVector< QwtSplinePolynomial > polynomials; + + const QVector< double > curvatures = this->curvatures( points ); + if ( curvatures.size() < 2 ) + return polynomials; + + const QPointF* p = points.constData(); + const double* cv = curvatures.constData(); + const int n = curvatures.size(); + polynomials.reserve( n - 1 ); + + for ( int i = 1; i < n; i++ ) + { + polynomials += QwtSplinePolynomial::fromCurvatures( + p[i - 1], cv[i - 1], p[i], cv[i] ); + } + + return polynomials; +} diff --git a/libs/qwt/src/qwt_spline.h b/libs/qwt/src/qwt_spline.h new file mode 100644 index 00000000..aaf9b3fe --- /dev/null +++ b/libs/qwt/src/qwt_spline.h @@ -0,0 +1,310 @@ +/****************************************************************************** + * 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_SPLINE_H +#define QWT_SPLINE_H + +#include "qwt_global.h" +#include "qwt_spline.h" + +class QwtSplineParametrization; +class QwtSplinePolynomial; +class QPainterPath; +class QLineF; +class QPolygonF; + +#if QT_VERSION < 0x060000 +template< typename T > class QVector; +#endif + +/*! + \brief Base class for all splines + + A spline is a curve represented by a sequence of polynomials. Spline approximation + is the process of finding polynomials for a given set of points. + When the algorithm preserves the initial points it is called interpolating. + + Splines can be classified according to conditions of the polynomials that + are met at the start/endpoints of the pieces: + + - Geometric Continuity + + - G0: polynomials are joined + - G1: first derivatives are proportional at the join point + The curve tangents thus have the same direction, but not necessarily the + same magnitude. i.e., C1'(1) = (a,b,c) and C2'(0) = (k*a, k*b, k*c). + - G2: first and second derivatives are proportional at join point + + - Parametric Continuity + + - C0: curves are joined + - C1: first derivatives equal + - C2: first and second derivatives are equal + + Geometric continuity requires the geometry to be continuous, while parametric + continuity requires that the underlying parameterization be continuous as well. + Parametric continuity of order n implies geometric continuity of order n, + but not vice-versa. + + QwtSpline is the base class for spline approximations of any continuity. + */ +class QWT_EXPORT QwtSpline +{ + public: + /*! + Boundary type specifying the spline at its endpoints + + \sa setBoundaryType(), boundaryType() + */ + enum BoundaryType + { + /*! + The polynomials at the start/endpoint depend on specific conditions + + \sa QwtSpline::BoundaryCondition + */ + ConditionalBoundaries, + + /*! + The polynomials at the start/endpoint are found by using + imaginary additional points. Additional points at the end + are found by translating points from the beginning or v.v. + */ + PeriodicPolygon, + + /*! + ClosedPolygon is similar to PeriodicPolygon beside, that + the interpolation includes the connection between the last + and the first control point. + + \note Only works for parametrizations, where the parameter increment + for the the final closing line is positive. + This excludes QwtSplineParametrization::ParameterX and + QwtSplineParametrization::ParameterY + */ + + ClosedPolygon + }; + + /*! + position of a boundary condition + \sa boundaryCondition(), boundaryValue() + */ + enum BoundaryPosition + { + //! the condition is at the beginning of the polynomial + AtBeginning, + + //! the condition is at the end of the polynomial + AtEnd + }; + + /*! + \brief Boundary condition + + A spline algorithm calculates polynomials by looking + a couple of points back/ahead ( locality() ). At the ends + additional rules are necessary to compensate the missing + points. + + \sa boundaryCondition(), boundaryValue() + \sa QwtSplineC2::BoundaryConditionC2 + */ + enum BoundaryCondition + { + /*! + The first derivative at the end point is given + \sa boundaryValue() + */ + Clamped1, + + /*! + The second derivative at the end point is given + + \sa boundaryValue() + \note a condition having a second derivative of 0 + is also called "natural". + */ + Clamped2, + + /*! + The third derivative at the end point is given + + \sa boundaryValue() + \note a condition having a third derivative of 0 + is also called "parabolic runout". + */ + Clamped3, + + /*! + The first derivate at the endpoint is related to the first derivative + at its neighbour by the boundary value. F,e when the boundary + value at the end is 1.0 then the slope at the last 2 points is + the same. + + \sa boundaryValue(). + */ + LinearRunout + }; + + QwtSpline(); + virtual ~QwtSpline(); + + void setParametrization( int type ); + void setParametrization( QwtSplineParametrization* ); + const QwtSplineParametrization* parametrization() const; + + void setBoundaryType( BoundaryType ); + BoundaryType boundaryType() const; + + void setBoundaryValue( BoundaryPosition, double value ); + double boundaryValue( BoundaryPosition ) const; + + void setBoundaryCondition( BoundaryPosition, int condition ); + int boundaryCondition( BoundaryPosition ) const; + + void setBoundaryConditions( int condition, + double valueBegin = 0.0, double valueEnd = 0.0 ); + + virtual QPolygonF polygon( const QPolygonF&, double tolerance ) const; + virtual QPainterPath painterPath( const QPolygonF& ) const = 0; + + virtual uint locality() const; + + private: + Q_DISABLE_COPY(QwtSpline) + + class PrivateData; + PrivateData* m_data; +}; + +/*! + \brief Base class for a spline interpolation + + Spline interpolation is the process of interpolating a set of points + piecewise with polynomials. The initial set of points is preserved. + */ +class QWT_EXPORT QwtSplineInterpolating : public QwtSpline +{ + public: + QwtSplineInterpolating(); + virtual ~QwtSplineInterpolating(); + + virtual QPolygonF equidistantPolygon( const QPolygonF&, + double distance, bool withNodes ) const; + + virtual QPolygonF polygon( + const QPolygonF&, double tolerance ) const QWT_OVERRIDE; + + virtual QPainterPath painterPath( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< QLineF > bezierControlLines( const QPolygonF& ) const = 0; + + private: + Q_DISABLE_COPY(QwtSplineInterpolating) +}; + +/*! + \brief Base class for spline interpolations providing a + first order geometric continuity ( G1 ) between adjoining curves + */ +class QWT_EXPORT QwtSplineG1 : public QwtSplineInterpolating +{ + public: + QwtSplineG1(); + virtual ~QwtSplineG1(); +}; + +/*! + \brief Base class for spline interpolations providing a + first order parametric continuity ( C1 ) between adjoining curves + + All interpolations with C1 continuity are based on rules for finding + the 1. derivate at some control points. + + In case of non parametric splines those points are the curve points, while + for parametric splines the calculation is done twice using a parameter value t. + + \sa QwtSplineParametrization + */ +class QWT_EXPORT QwtSplineC1 : public QwtSplineG1 +{ + public: + QwtSplineC1(); + virtual ~QwtSplineC1(); + + virtual QPainterPath painterPath( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< QLineF > bezierControlLines( const QPolygonF& ) const QWT_OVERRIDE; + + virtual QPolygonF equidistantPolygon( const QPolygonF&, + double distance, bool withNodes ) const QWT_OVERRIDE; + + // these methods are the non parametric part + virtual QVector< QwtSplinePolynomial > polynomials( const QPolygonF& ) const; + virtual QVector< double > slopes( const QPolygonF& ) const = 0; + + virtual double slopeAtBeginning( const QPolygonF&, double slopeNext ) const; + virtual double slopeAtEnd( const QPolygonF&, double slopeBefore ) const; +}; + +/*! + \brief Base class for spline interpolations providing a + second order parametric continuity ( C2 ) between adjoining curves + + All interpolations with C2 continuity are based on rules for finding + the 2. derivate at some control points. + + In case of non parametric splines those points are the curve points, while + for parametric splines the calculation is done twice using a parameter value t. + + \sa QwtSplineParametrization + */ +class QWT_EXPORT QwtSplineC2 : public QwtSplineC1 +{ + public: + /*! + Boundary condition that requires C2 continuity + + \sa QwtSpline::boundaryCondition, QwtSpline::BoundaryCondition + */ + enum BoundaryConditionC2 + { + /*! + The second derivate at the endpoint is related to the second derivatives + at the 2 neighbours: cv[0] := 2.0 * cv[1] - cv[2]. + + \note boundaryValue() is ignored + */ + CubicRunout = LinearRunout + 1, + + /*! + The 3rd derivate at the endpoint matches the 3rd derivate at its neighbours. + Or in other words: the first/last curve segment extents the polynomial of its + neighboured polynomial + + \note boundaryValue() is ignored + */ + NotAKnot + }; + + QwtSplineC2(); + virtual ~QwtSplineC2(); + + virtual QPainterPath painterPath( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< QLineF > bezierControlLines( const QPolygonF& ) const QWT_OVERRIDE; + + virtual QPolygonF equidistantPolygon( const QPolygonF&, + double distance, bool withNodes ) const QWT_OVERRIDE; + + // calculating the parametric equations + virtual QVector< QwtSplinePolynomial > polynomials( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< double > slopes( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< double > curvatures( const QPolygonF& ) const = 0; +}; + +#endif diff --git a/libs/qwt/src/qwt_spline_basis.cpp b/libs/qwt/src/qwt_spline_basis.cpp new file mode 100644 index 00000000..2837294b --- /dev/null +++ b/libs/qwt/src/qwt_spline_basis.cpp @@ -0,0 +1,270 @@ +/****************************************************************************** + * 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_spline_basis.h" +#include "qwt_spline_parametrization.h" +#include + +#if 0 +static QPolygonF qwtBasisUniformKnots( const QPolygonF& points ) +{ + const int n = points.size(); + + if ( n < 3 ) + return points; + + QVector< double > u( n - 2 ); + QVector< double > kx( n - 2 ); + QVector< double > ky( n - 2 ); + + u[n - 3] = 4.0; + kx[n - 3] = 6.0 * points[n - 2].x() - points[n - 1].x(); + ky[n - 3] = 6.0 * points[n - 2].y() - points[n - 1].y(); + + for ( int i = n - 4; i >= 0; i-- ) + { + u[i] = 4.0 - 1.0 / u[i + 1]; + kx[i] = 6.0 * points[i + 1].x() - kx[i + 1] / u[i + 1]; + ky[i] = 6.0 * points[i + 1].y() - ky[i + 1] / u[i + 1]; + } + + QVector< QPointF > knots( n ); + + knots[0] = points[0]; + + for ( int i = 1; i < n - 1; i++ ) + { + knots[i].rx() = ( kx[i - 1] - knots[i - 1].x() ) / u[i - 1]; + knots[i].ry() = ( ky[i - 1] - knots[i - 1].y() ) / u[i - 1]; + } + + knots[n - 1] = points[n - 1]; + + return knots; +} +#endif + +#if 0 +static inline void qwtSplineBezierControlPoints( const QwtSplineParametrization* param, + const QPointF& p1, const QPointF& p2, const QPointF& p3, const QPointF& p4, + QPointF& cp1, QPointF& cp2 ) +{ + const double t1 = param->valueIncrement( p1, p2 ); + const double t2 = param->valueIncrement( p2, p3 ); + const double t3 = param->valueIncrement( p3, p4 ); + + const double t123 = t1 + t2 + t3; + + cp1 = ( t2 + t3 ) / t123 * p2 + t1 / t123 * p3; + cp2 = ( t3 * p2 + ( t1 + t2 ) * p3 ) / t123; +} +#endif + +static QPainterPath qwtSplineBasisPathUniform( const QPolygonF& points, + QwtSpline::BoundaryType boundaryType ) +{ + const int n = points.size(); + const QPointF* pd = points.constData(); + + QPainterPath path; + + QPointF cp1 = ( 2.0 * pd[0] + pd[1] ) / 3.0;; + + if ( boundaryType == QwtSpline::ConditionalBoundaries ) + { + path.moveTo( pd[0] ); + } + else + { + const QPointF cpN = ( pd[n - 1] + 2.0 * pd[0] ) / 3.0; + path.moveTo( 0.5 * ( cpN + cp1 ) ); + } + + for ( int i = 1; i < n - 1; i++ ) + { + const QPointF cp2 = ( pd[i - 1] + 2.0 * pd[i] ) / 3.0; + const QPointF cp3 = ( 2.0 * pd[i] + pd[i + 1] ) / 3.0; + + path.cubicTo( cp1, cp2, 0.5 * ( cp2 + cp3 ) ); + + cp1 = cp3; + } + + if ( boundaryType == QwtSpline::ConditionalBoundaries ) + { + const QPointF cp2 = ( pd[n - 2] + 2.0 * pd[n - 1] ) / 3.0; + path.cubicTo( cp1, cp2, pd[n - 1] ); + } + else + { + const QPointF cp2 = ( pd[n - 2] + 2.0 * pd[n - 1] ) / 3.0; + const QPointF cp3 = ( 2.0 * pd[n - 1] + pd[0] ) / 3.0; + + path.cubicTo( cp1, cp2, 0.5 * ( cp2 + cp3 ) ); + + if ( boundaryType == QwtSpline::ClosedPolygon ) + { + const QPointF cp4 = ( pd[n - 1] + 2.0 * pd[0] ) / 3.0; + const QPointF cp5 = ( 2.0 * pd[0] + pd[1] ) / 3.0; + + path.cubicTo( cp3, cp4, 0.5 * ( cp4 + cp5 ) ); + } + } + + return path; +} + +static QPainterPath qwtSplineBasisPath( const QPolygonF& points, + const QwtSplineParametrization* param, + QwtSpline::BoundaryType boundaryType ) +{ + const int n = points.size(); + const QPointF* pd = points.constData(); + + QPointF p0; + + double t1 = param->valueIncrement( pd[0], pd[1] ); + double t2 = param->valueIncrement( pd[1], pd[2] ); + + double t0; + if ( boundaryType == QwtSpline::ConditionalBoundaries ) + t0 = t1; + else + t0 = param->valueIncrement( pd[n - 1], pd[0] ); + + double t012 = t0 + t1 + t2; + QPointF cp1 = ( ( t1 + t2 ) * pd[0] + t0 * pd[1] ) / t012; + + if ( boundaryType == QwtSpline::ConditionalBoundaries ) + { + p0 = pd[0]; + } + else + { + const double tN = param->valueIncrement( pd[n - 2], pd[n - 1] ); + const QPointF cpN = ( t1 * pd[n - 1] + ( tN + t0 ) * pd[0] ) / ( tN + t0 + t1 ); + + p0 = ( t1 * cpN + t0 * cp1 ) / ( t0 + t1 ); + } + + QPainterPath path; + path.moveTo( p0 ); + + for ( int i = 1; i < n - 2; i++ ) + { + const double t3 = param->valueIncrement( pd[i + 1], pd[i + 2] ); + const double t123 = t1 + t2 + t3; + + const QPointF cp2 = ( t2 * pd[i - 1] + ( t0 + t1 ) * pd[i] ) / t012; + const QPointF cp3 = ( ( t2 + t3 ) * pd[i] + t1 * pd[i + 1] ) / t123; + + const QPointF p2 = ( t2 * cp2 + t1 * cp3 ) / ( t1 + t2 ); + + path.cubicTo( cp1, cp2, p2 ); + + cp1 = cp3; + + t0 = t1; + t1 = t2; + t2 = t3; + t012 = t123; + } + + { + double t3; + if ( boundaryType == QwtSpline::ConditionalBoundaries ) + t3 = t2; + else + t3 = param->valueIncrement( pd[n - 1], pd[0] ); + + const double t123 = t1 + t2 + t3; + + const QPointF cp2 = ( t2 * pd[n - 3] + ( t0 + t1 ) * pd[n - 2] ) / t012; + const QPointF cp3 = ( ( t2 + t3 ) * pd[n - 2] + t1 * pd[n - 1] ) / t123; + + const QPointF p2 = ( t2 * cp2 + t1 * cp3 ) / ( t1 + t2 ); + + path.cubicTo( cp1, cp2, p2 ); + + cp1 = cp3; + + t0 = t1; + t1 = t2; + t2 = t3; + t012 = t123; + } + + const QPointF cp2 = ( t2 * pd[n - 2] + ( t0 + t1 ) * pd[n - 1] ) / t012; + + if ( boundaryType == QwtSpline::ConditionalBoundaries ) + { + path.cubicTo( cp1, cp2, pd[n - 1] ); + } + else + { + const double t3 = param->valueIncrement( pd[0], pd[1] ); + const double t123 = t1 + t2 + t3; + + const QPointF cp3 = ( t2 + t3 ) / t123 * pd[n - 1] + t1 / t123 * pd[0]; + const QPointF cp4 = ( t3 * pd[n - 1] + ( t1 + t2 ) * pd[0] ) / t123; + + const QPointF pN = ( t2 * cp2 + t1 * cp3 ) / ( t1 + t2 ); + + path.cubicTo( cp1, cp2, pN ); + path.cubicTo( cp3, cp4, p0 ); + } + + return path; +} + +//! Constructor +QwtSplineBasis::QwtSplineBasis() +{ +} + +//! Destructor +QwtSplineBasis::~QwtSplineBasis() +{ +} + +//! The locality is always 2 +uint QwtSplineBasis::locality() const +{ + return 2; +} + +/*! + Approximates a polygon piecewise with cubic Bezier curves + and returns them as QPainterPath. + + \param points Control points + \return Painter path, that can be rendered by QPainter + */ +QPainterPath QwtSplineBasis::painterPath( const QPolygonF& points ) const +{ + if ( points.size() < 4 ) + return QPainterPath(); + + QPainterPath path; + + switch( parametrization()->type() ) + { + case QwtSplineParametrization::ParameterUniform: + { + path = qwtSplineBasisPathUniform( points, boundaryType() ); + break; + } + default: + { + path = qwtSplineBasisPath( points, parametrization(), boundaryType() ); + } + } + + return path; +} diff --git a/libs/qwt/src/qwt_spline_basis.h b/libs/qwt/src/qwt_spline_basis.h new file mode 100644 index 00000000..f95e8c96 --- /dev/null +++ b/libs/qwt/src/qwt_spline_basis.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_SPLINE_BASIS_H +#define QWT_SPLINE_BASIS_H + +#include "qwt_global.h" +#include "qwt_spline.h" + +/*! + \brief An approximation using a basis spline + + QwtSplineBasis approximates a set of points by a polynomials with C2 continuity + ( = first and second derivatives are equal ) at the end points. + + The end points of the spline do not match the original points. + */ +class QWT_EXPORT QwtSplineBasis : public QwtSpline +{ + public: + QwtSplineBasis(); + virtual ~QwtSplineBasis(); + + virtual QPainterPath painterPath( const QPolygonF& ) const QWT_OVERRIDE; + virtual uint locality() const QWT_OVERRIDE; +}; + +#endif diff --git a/libs/qwt/src/qwt_spline_cubic.cpp b/libs/qwt/src/qwt_spline_cubic.cpp new file mode 100644 index 00000000..1da9d506 --- /dev/null +++ b/libs/qwt/src/qwt_spline_cubic.cpp @@ -0,0 +1,1171 @@ +/****************************************************************************** + * 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_spline_cubic.h" +#include "qwt_spline_polynomial.h" + +#include +#include + +#define SLOPES_INCREMENTAL 0 +#define KAHAN 0 + +namespace QwtSplineCubicP +{ + class KahanSum + { + public: + inline KahanSum( double value = 0.0 ): + m_sum( value ), + m_carry( 0.0 ) + { + } + + inline void reset() + { + m_sum = m_carry = 0.0; + } + + inline double value() const + { + return m_sum; + } + + inline void add( double value ) + { + const double y = value - m_carry; + const double t = m_sum + y; + + m_carry = ( t - m_sum ) - y; + m_sum = t; + } + + static inline double sum3( double d1, double d2, double d3 ) + { + KahanSum sum( d1 ); + sum.add( d2 ); + sum.add( d3 ); + + return sum.value(); + } + + static inline double sum4( double d1, double d2, double d3, double d4 ) + { + KahanSum sum( d1 ); + sum.add( d2 ); + sum.add( d3 ); + sum.add( d4 ); + + return sum.value(); + } + + + private: + double m_sum; + double m_carry; // The carry from the previous operation + }; + + class CurvatureStore + { + public: + inline void setup( int size ) + { + m_curvatures.resize( size ); + m_cv = m_curvatures.data(); + } + + inline void storeFirst( double, + const QPointF&, const QPointF&, double b1, double ) + { + m_cv[0] = 2.0 * b1; + } + + inline void storeNext( int index, double, + const QPointF&, const QPointF&, double, double b2 ) + { + m_cv[index] = 2.0 * b2; + } + + inline void storeLast( double, + const QPointF&, const QPointF&, double, double b2 ) + { + m_cv[m_curvatures.size() - 1] = 2.0 * b2; + } + + inline void storePrevious( int index, double, + const QPointF&, const QPointF&, double b1, double ) + { + m_cv[index] = 2.0 * b1; + } + + inline void closeR() + { + m_cv[0] = m_cv[m_curvatures.size() - 1]; + } + + QVector< double > curvatures() const { return m_curvatures; } + + private: + QVector< double > m_curvatures; + double* m_cv; + }; + + class SlopeStore + { + public: + inline void setup( int size ) + { + m_slopes.resize( size ); + m_m = m_slopes.data(); + } + + inline void storeFirst( double h, + const QPointF& p1, const QPointF& p2, double b1, double b2 ) + { + const double s = ( p2.y() - p1.y() ) / h; + m_m[0] = s - h * ( 2.0 * b1 + b2 ) / 3.0; +#if KAHAN + m_sum.add( m_m[0] ); +#endif + } + + inline void storeNext( int index, double h, + const QPointF& p1, const QPointF& p2, double b1, double b2 ) + { +#if SLOPES_INCREMENTAL + Q_UNUSED( p1 ) + Q_UNUSED( p2 ) +#if KAHAN + m_sum.add( ( b1 + b2 ) * h ); + m_m[index] = m_sum.value(); +#else + m_m[index] = m_m[index - 1] + ( b1 + b2 ) * h; +#endif +#else + const double s = ( p2.y() - p1.y() ) / h; + m_m[index] = s + h * ( b1 + 2.0 * b2 ) / 3.0; +#endif + } + + inline void storeLast( double h, + const QPointF& p1, const QPointF& p2, double b1, double b2 ) + { + const double s = ( p2.y() - p1.y() ) / h; + m_m[m_slopes.size() - 1] = s + h * ( b1 + 2.0 * b2 ) / 3.0; +#if KAHAN + m_sum.add( m_m[m_slopes.size() - 1] ); +#endif + } + + inline void storePrevious( int index, double h, + const QPointF& p1, const QPointF& p2, double b1, double b2 ) + { +#if SLOPES_INCREMENTAL + Q_UNUSED( p1 ) + Q_UNUSED( p2 ) +#if KAHAN + m_sum.add( -( b1 + b2 ) * h ); + m_m[index] = m_sum.value(); +#else + m_m[index] = m_m[index + 1] - ( b1 + b2 ) * h; +#endif + +#else + const double s = ( p2.y() - p1.y() ) / h; + m_m[index] = s - h * ( 2.0 * b1 + b2 ) / 3.0; +#endif + } + + inline void closeR() + { + m_m[0] = m_m[m_slopes.size() - 1]; + } + + QVector< double > slopes() const { return m_slopes; } + + private: + QVector< double > m_slopes; + double* m_m; +#if SLOPES_INCREMENTAL + KahanSum m_sum; +#endif + }; +} + +namespace QwtSplineCubicP +{ + class Equation2 + { + public: + inline Equation2() + { + } + + inline Equation2( double p0, double q0, double r0 ): + p( p0 ), + q( q0 ), + r( r0 ) + { + } + + inline void setup( double p0, double q0, double r0 ) + { + p = p0; + q = q0; + r = r0; + } + + inline Equation2 normalized() const + { + Equation2 c; + c.p = 1.0; + c.q = q / p; + c.r = r / p; + + return c; + } + + inline double resolved1( double x2 ) const + { + return ( r - q * x2 ) / p; + } + + inline double resolved2( double x1 ) const + { + return ( r - p * x1 ) / q; + } + + inline double resolved1( const Equation2& eq ) const + { + // find x1 + double k = q / eq.q; + return ( r - k * eq.r ) / ( p - k * eq.p ); + } + + inline double resolved2( const Equation2& eq ) const + { + // find x2 + const double k = p / eq.p; + return ( r - k * eq.r ) / ( q - k * eq.q ); + } + + // p * x1 + q * x2 = r + double p, q, r; + }; + + class Equation3 + { + public: + inline Equation3() + { + } + + inline Equation3( const QPointF& p1, const QPointF& p2, const QPointF& p3 ) + { + const double h1 = p2.x() - p1.x(); + const double s1 = ( p2.y() - p1.y() ) / h1; + + const double h2 = p3.x() - p2.x(); + const double s2 = ( p3.y() - p2.y() ) / h2; + + p = h1; + q = 2 * ( h1 + h2 ); + u = h2; + r = 3 * ( s2 - s1 ); + } + + inline Equation3( double cp, double cq, double du, double dr ): + p( cp ), + q( cq ), + u( du ), + r( dr ) + { + } + + inline bool operator==( const Equation3& c ) const + { + return ( p == c.p ) && ( q == c.q ) && + ( u == c.u ) && ( r == c.r ); + } + + inline void setup( double cp, double cq, double du, double dr ) + { + p = cp; + q = cq; + u = du; + r = dr; + } + + inline Equation3 normalized() const + { + Equation3 c; + c.p = 1.0; + c.q = q / p; + c.u = u / p; + c.r = r / p; + + return c; + } + + inline Equation2 substituted1( const Equation3& eq ) const + { + // eliminate x1 + const double k = p / eq.p; + return Equation2( q - k * eq.q, u - k * eq.u, r - k * eq.r ); + } + + inline Equation2 substituted2( const Equation3& eq ) const + { + // eliminate x2 + + const double k = q / eq.q; + return Equation2( p - k * eq.p, u - k * eq.u, r - k * eq.r ); + } + + inline Equation2 substituted3( const Equation3& eq ) const + { + // eliminate x3 + + const double k = u / eq.u; + return Equation2( p - k * eq.p, q - k * eq.q, r - k * eq.r ); + } + + inline Equation2 substituted1( const Equation2& eq ) const + { + // eliminate x1 + const double k = p / eq.p; + return Equation2( q - k * eq.q, u, r - k * eq.r ); + } + + inline Equation2 substituted3( const Equation2& eq ) const + { + // eliminate x3 + + const double k = u / eq.q; + return Equation2( p, q - k * eq.p, r - k * eq.r ); + } + + + inline double resolved1( double x2, double x3 ) const + { + return ( r - q * x2 - u * x3 ) / p; + } + + inline double resolved2( double x1, double x3 ) const + { + return ( r - u * x3 - p * x1 ) / q; + } + + inline double resolved3( double x1, double x2 ) const + { + return ( r - p * x1 - q * x2 ) / u; + } + + // p * x1 + q * x2 + u * x3 = r + double p, q, u, r; + }; +} + +#if 0 +static QDebug operator<<( QDebug debug, const QwtSplineCubicP::Equation2& eq ) +{ + debug.nospace() << "EQ2(" << eq.p << ", " << eq.q << ", " << eq.r << ")"; + return debug.space(); +} + +static QDebug operator<<( QDebug debug, const QwtSplineCubicP::Equation3& eq ) +{ + debug.nospace() << "EQ3(" << eq.p << ", " + << eq.q << ", " << eq.u << ", " << eq.r << ")"; + return debug.space(); +} +#endif + +namespace QwtSplineCubicP +{ + template< class T > + class EquationSystem + { + public: + void setStartCondition( double p, double q, double u, double r ) + { + m_conditionsEQ[0].setup( p, q, u, r ); + } + + void setEndCondition( double p, double q, double u, double r ) + { + m_conditionsEQ[1].setup( p, q, u, r ); + } + + const T& store() const + { + return m_store; + } + + void resolve( const QPolygonF& p ) + { + const int n = p.size(); + if ( n < 3 ) + return; + + if ( m_conditionsEQ[0].p == 0.0 || + ( m_conditionsEQ[0].q == 0.0 && m_conditionsEQ[0].u != 0.0 ) ) + { + return; + } + + if ( m_conditionsEQ[1].u == 0.0 || + ( m_conditionsEQ[1].q == 0.0 && m_conditionsEQ[1].p != 0.0 ) ) + { + return; + } + + const double h0 = p[1].x() - p[0].x(); + const double h1 = p[2].x() - p[1].x(); + const double hn = p[n - 1].x() - p[n - 2].x(); + + m_store.setup( n ); + + + if ( n == 3 ) + { + // For certain conditions the first/last point does not + // necessarily meet the spline equation and we would + // have many solutions. In this case we resolve using + // the spline equation - as for all other conditions. + + const Equation3 eqSpline0( p[0], p[1], p[2] ); // ??? + const Equation2 eq0 = m_conditionsEQ[0].substituted1( eqSpline0 ); + + // The equation system can be solved without substitution + // from the start/end conditions and eqSpline0 ( = eqSplineN ). + + double b1; + + if ( m_conditionsEQ[0].normalized() == m_conditionsEQ[1].normalized() ) + { + // When we have 3 points only and start/end conditions + // for 3 points mean the same condition the system + // is under-determined and has many solutions. + // We chose b1 = 0.0 + + b1 = 0.0; + } + else + { + const Equation2 eq = m_conditionsEQ[1].substituted1( eqSpline0 ); + b1 = eq0.resolved1( eq ); + } + + const double b2 = eq0.resolved2( b1 ); + const double b0 = eqSpline0.resolved1( b1, b2 ); + + m_store.storeFirst( h0, p[0], p[1], b0, b1 ); + m_store.storeNext( 1, h0, p[0], p[1], b0, b1 ); + m_store.storeNext( 2, h1, p[1], p[2], b1, b2 ); + + return; + } + + const Equation3 eqSplineN( p[n - 3], p[n - 2], p[n - 1] ); + const Equation2 eqN = m_conditionsEQ[1].substituted3( eqSplineN ); + + Equation2 eq = eqN; + if ( n > 4 ) + { + const Equation3 eqSplineR( p[n - 4], p[n - 3], p[n - 2] ); + eq = eqSplineR.substituted3( eq ); + eq = substituteSpline( p, eq ); + } + + const Equation3 eqSpline0( p[0], p[1], p[2] ); + + double b0, b1; + if ( m_conditionsEQ[0].u == 0.0 ) + { + eq = eqSpline0.substituted3( eq ); + + const Equation3& eq0 = m_conditionsEQ[0]; + b0 = Equation2( eq0.p, eq0.q, eq0.r ).resolved1( eq ); + b1 = eq.resolved2( b0 ); + } + else + { + const Equation2 eqX = m_conditionsEQ[0].substituted3( eq ); + const Equation2 eqY = eqSpline0.substituted3( eq ); + + b0 = eqY.resolved1( eqX ); + b1 = eqY.resolved2( b0 ); + } + + m_store.storeFirst( h0, p[0], p[1], b0, b1 ); + m_store.storeNext( 1, h0, p[0], p[1], b0, b1 ); + + const double bn2 = resolveSpline( p, b1 ); + + const double bn1 = eqN.resolved2( bn2 ); + const double bn0 = m_conditionsEQ[1].resolved3( bn2, bn1 ); + + const double hx = p[n - 2].x() - p[n - 3].x(); + m_store.storeNext( n - 2, hx, p[n - 3], p[n - 2], bn2, bn1 ); + m_store.storeNext( n - 1, hn, p[n - 2], p[n - 1], bn1, bn0 ); + } + + private: + Equation2 substituteSpline( const QPolygonF& points, const Equation2& eq ) + { + const int n = points.size(); + + m_eq.resize( n - 2 ); + m_eq[n - 3] = eq; + + // eq[i].resolved2( b[i-1] ) => b[i] + + double slope2 = ( points[n - 3].y() - points[n - 4].y() ) / eq.p; + + for ( int i = n - 4; i > 1; i-- ) + { + const Equation2& eq2 = m_eq[i + 1]; + Equation2& eq1 = m_eq[i]; + + eq1.p = points[i].x() - points[i - 1].x(); + const double slope1 = ( points[i].y() - points[i - 1].y() ) / eq1.p; + + const double v = eq2.p / eq2.q; + + eq1.q = 2.0 * ( eq1.p + eq2.p ) - v * eq2.p; + eq1.r = 3.0 * ( slope2 - slope1 ) - v * eq2.r; + + slope2 = slope1; + } + + return m_eq[2]; + } + + double resolveSpline( const QPolygonF& points, double b1 ) + { + const int n = points.size(); + const QPointF* p = points.constData(); + + for ( int i = 2; i < n - 2; i++ ) + { + // eq[i].resolved2( b[i-1] ) => b[i] + const double b2 = m_eq[i].resolved2( b1 ); + m_store.storeNext( i, m_eq[i].p, p[i - 1], p[i], b1, b2 ); + + b1 = b2; + } + + return b1; + } + + private: + Equation3 m_conditionsEQ[2]; + QVector< Equation2 > m_eq; + T m_store; + }; + + template< class T > + class EquationSystem2 + { + public: + const T& store() const + { + return m_store; + } + + void resolve( const QPolygonF& p ) + { + const int n = p.size(); + + if ( p[n - 1].y() != p[0].y() ) + { + // TODO ??? + } + + const double h0 = p[1].x() - p[0].x(); + const double s0 = ( p[1].y() - p[0].y() ) / h0; + + if ( n == 3 ) + { + const double h1 = p[2].x() - p[1].x(); + const double s1 = ( p[2].y() - p[1].y() ) / h1; + + const double b = 3.0 * ( s0 - s1 ) / ( h0 + h1 ); + + m_store.setup( 3 ); + m_store.storeLast( h1, p[1], p[2], -b, b ); + m_store.storePrevious( 1, h1, p[1], p[2], -b, b ); + m_store.closeR(); + + return; + } + + const double hn = p[n - 1].x() - p[n - 2].x(); + + Equation2 eqn, eqX; + substitute( p, eqn, eqX ); + + const double b0 = eqn.resolved2( eqX ); + const double bn = eqn.resolved1( b0 ); + + m_store.setup( n ); + m_store.storeLast( hn, p[n - 2], p[n - 1], bn, b0 ); + m_store.storePrevious( n - 2, hn, p[n - 2], p[n - 1], bn, b0 ); + + resolveSpline( p, b0, bn ); + + m_store.closeR(); + } + + private: + + void substitute( const QPolygonF& points, Equation2& eqn, Equation2& eqX ) + { + const int n = points.size(); + + const double hn = points[n - 1].x() - points[n - 2].x(); + + const Equation3 eqSpline0( points[0], points[1], points[2] ); + const Equation3 eqSplineN( + QPointF( points[0].x() - hn, points[n - 2].y() ), points[0], points[1] ); + + m_eq.resize( n - 1 ); + + double dq = 0; + double dr = 0; + + m_eq[1] = eqSpline0; + + double slope1 = ( points[2].y() - points[1].y() ) / m_eq[1].u; + + // a) p1 * b[0] + q1 * b[1] + u1 * b[2] = r1 + // b) p2 * b[n-2] + q2 * b[0] + u2 * b[1] = r2 + // c) pi * b[i-1] + qi * b[i] + ui * b[i+1] = ri + // + // Using c) we can substitute b[i] ( starting from 2 ) by b[i+1] + // until we reach n-1. As we know, that b[0] == b[n-1] we found + // an equation where only 2 coefficients ( for b[n-2], b[0] ) are left unknown. + // Each step we have an equation that depends on b[0], b[i] and b[i+1] + // that can also be used to substitute b[i] in b). Ding so we end up with another + // equation depending on b[n-2], b[0] only. + // Finally 2 equations with 2 coefficients can be solved. + + for ( int i = 2; i < n - 1; i++ ) + { + const Equation3& eq1 = m_eq[i - 1]; + Equation3& eq2 = m_eq[i]; + + dq += eq1.p * eq1.p / eq1.q; + dr += eq1.p * eq1.r / eq1.q; + + eq2.u = points[i + 1].x() - points[i].x(); + const double slope2 = ( points[i + 1].y() - points[i].y() ) / eq2.u; + + const double k = eq1.u / eq1.q; + + eq2.p = -eq1.p * k; + eq2.q = 2.0 * ( eq1.u + eq2.u ) - eq1.u * k; + eq2.r = 3.0 * ( slope2 - slope1 ) - eq1.r * k; + + slope1 = slope2; + } + + + // b[0] * m_p[n-2] + b[n-2] * m_q[n-2] + b[n-1] * pN = m_r[n-2] + eqn.setup( m_eq[n - 2].q, m_eq[n - 2].p + eqSplineN.p, m_eq[n - 2].r ); + + // b[n-2] * pN + b[0] * ( qN - dq ) + b[n-2] * m_p[n-2] = rN - dr + eqX.setup( m_eq[n - 2].p + eqSplineN.p, eqSplineN.q - dq, eqSplineN.r - dr ); + } + + void resolveSpline( const QPolygonF& points, double b0, double bi ) + { + const int n = points.size(); + + for ( int i = n - 3; i >= 1; i-- ) + { + const Equation3& eq = m_eq[i]; + + const double b = eq.resolved2( b0, bi ); + m_store.storePrevious( i, eq.u, points[i], points[i + 1], b, bi ); + + bi = b; + } + } + + void resolveSpline2( const QPolygonF& points, + double b0, double bi, QVector< double >& m ) + { + const int n = points.size(); + + bi = m_eq[0].resolved3( b0, bi ); + + for ( int i = 1; i < n - 2; i++ ) + { + const Equation3& eq = m_eq[i]; + + const double b = eq.resolved3( b0, bi ); + m[i + 1] = m[i] + ( b + bi ) * m_eq[i].u; + + bi = b; + } + } + + void resolveSpline3( const QPolygonF& points, + double b0, double b1, QVector< double >& m ) + { + const int n = points.size(); + + double h0 = ( points[1].x() - points[0].x() ); + double s0 = ( points[1].y() - points[0].y() ) / h0; + + m[1] = m[0] + ( b0 + b1 ) * h0; + + for ( int i = 1; i < n - 1; i++ ) + { + const double h1 = ( points[i + 1].x() - points[i].x() ); + const double s1 = ( points[i + 1].y() - points[i].y() ) / h1; + + const double r = 3.0 * ( s1 - s0 ); + + const double b2 = ( r - h0 * b0 - 2.0 * ( h0 + h1 ) * b1 ) / h1; + m[i + 1] = m[i] + ( b1 + b2 ) * h1; + + h0 = h1; + s0 = s1; + b0 = b1; + b1 = b2; + } + } + + void resolveSpline4( const QPolygonF& points, + double b2, double b1, QVector< double >& m ) + { + const int n = points.size(); + + double h2 = ( points[n - 1].x() - points[n - 2].x() ); + double s2 = ( points[n - 1].y() - points[n - 2].y() ) / h2; + + for ( int i = n - 2; i > 1; i-- ) + { + const double h1 = ( points[i].x() - points[i - 1].x() ); + const double s1 = ( points[i].y() - points[i - 1].y() ) / h1; + + const double r = 3.0 * ( s2 - s1 ); + const double k = 2.0 * ( h1 + h2 ); + + const double b0 = ( r - h2 * b2 - k * b1 ) / h1; + + m[i - 1] = m[i] - ( b0 + b1 ) * h1; + + h2 = h1; + s2 = s1; + b2 = b1; + b1 = b0; + } + } + + public: + QVector< Equation3 > m_eq; + T m_store; + }; +} + +static void qwtSetupEndEquations( + int conditionBegin, double valueBegin, int conditionEnd, double valueEnd, + const QPolygonF& points, QwtSplineCubicP::Equation3 eq[2] ) +{ + const int n = points.size(); + + const double h0 = points[1].x() - points[0].x(); + const double s0 = ( points[1].y() - points[0].y() ) / h0; + + const double hn = ( points[n - 1].x() - points[n - 2].x() ); + const double sn = ( points[n - 1].y() - points[n - 2].y() ) / hn; + + switch( conditionBegin ) + { + case QwtSpline::Clamped1: + { + // first derivative at end points given + + // 3 * a1 * h + b1 = b2 + // a1 * h * h + b1 * h + c1 = s + + // c1 = slopeBegin + // => b1 * ( 2 * h / 3.0 ) + b2 * ( h / 3.0 ) = s - slopeBegin + + // c2 = slopeEnd + // => b1 * ( 1.0 / 3.0 ) + b2 * ( 2.0 / 3.0 ) = ( slopeEnd - s ) / h; + + eq[0].setup( 2 * h0 / 3.0, h0 / 3.0, 0.0, s0 - valueBegin ); + break; + } + case QwtSpline::Clamped2: + { + // second derivative at end points given + + // b0 = 0.5 * cvStart + // => b0 * 1.0 + b1 * 0.0 = 0.5 * cvStart + + // b1 = 0.5 * cvEnd + // => b0 * 0.0 + b1 * 1.0 = 0.5 * cvEnd + + eq[0].setup( 1.0, 0.0, 0.0, 0.5 * valueBegin ); + break; + } + case QwtSpline::Clamped3: + { + // third derivative at end point given + + // 3 * a * h0 + b[0] = b[1] + + // a = marg_0 / 6.0 + // => b[0] * 1.0 + b[1] * ( -1.0 ) = -0.5 * v0 * h0 + + // a = marg_n / 6.0 + // => b[n-2] * 1.0 + b[n-1] * ( -1.0 ) = -0.5 * v1 * h5 + + eq[0].setup( 1.0, -1.0, 0.0, -0.5 * valueBegin * h0 ); + + break; + } + case QwtSpline::LinearRunout: + { + const double r0 = qBound( 0.0, valueBegin, 1.0 ); + if ( r0 == 0.0 ) + { + // clamping s0 + eq[0].setup( 2 * h0 / 3.0, h0 / 3.0, 0.0, 0.0 ); + } + else + { + eq[0].setup( 1.0 + 2.0 / r0, 2.0 + 1.0 / r0, 0.0, 0.0 ); + } + break; + } + case QwtSplineC2::NotAKnot: + case QwtSplineC2::CubicRunout: + { + // building one cubic curve from 3 points + + double v0; + + if ( conditionBegin == QwtSplineC2::CubicRunout ) + { + // first/last point are the endpoints of the curve + + // b0 = 2 * b1 - b2 + // => 1.0 * b0 - 2 * b1 + 1.0 * b2 = 0.0 + + v0 = 1.0; + } + else + { + // first/last points are on the curve, + // the imaginary endpoints have the same distance as h0/hn + + v0 = h0 / ( points[2].x() - points[1].x() ); + } + + eq[0].setup( 1.0, -( 1.0 + v0 ), v0, 0.0 ); + break; + } + default: + { + // a natural spline, where the + // second derivative at end points set to 0.0 + eq[0].setup( 1.0, 0.0, 0.0, 0.0 ); + break; + } + } + + switch( conditionEnd ) + { + case QwtSpline::Clamped1: + { + // first derivative at end points given + eq[1].setup( 0.0, 1.0 / 3.0 * hn, 2.0 / 3.0 * hn, valueEnd - sn ); + break; + } + case QwtSpline::Clamped2: + { + // second derivative at end points given + eq[1].setup( 0.0, 0.0, 1.0, 0.5 * valueEnd ); + break; + } + case QwtSpline::Clamped3: + { + // third derivative at end point given + eq[1].setup( 0.0, 1.0, -1.0, -0.5 * valueEnd * hn ); + break; + } + case QwtSpline::LinearRunout: + { + const double rn = qBound( 0.0, valueEnd, 1.0 ); + if ( rn == 0.0 ) + { + // clamping sn + eq[1].setup( 0.0, 1.0 / 3.0 * hn, 2.0 / 3.0 * hn, 0.0 ); + } + else + { + eq[1].setup( 0.0, 2.0 + 1.0 / rn, 1.0 + 2.0 / rn, 0.0 ); + } + + break; + } + case QwtSplineC2::NotAKnot: + case QwtSplineC2::CubicRunout: + { + // building one cubic curve from 3 points + + double vn; + + if ( conditionEnd == QwtSplineC2::CubicRunout ) + { + // last point is the endpoints of the curve + vn = 1.0; + } + else + { + // last points on the curve, + // the imaginary endpoints have the same distance as hn + + vn = hn / ( points[n - 2].x() - points[n - 3].x() ); + } + + eq[1].setup( vn, -( 1.0 + vn ), 1.0, 0.0 ); + break; + } + default: + { + // a natural spline, where the + // second derivative at end points set to 0.0 + eq[1].setup( 0.0, 0.0, 1.0, 0.0 ); + break; + } + } +} + +class QwtSplineCubic::PrivateData +{ +}; + +/*! + \brief Constructor + The default setting is a non closing natural spline with no parametrization. + */ +QwtSplineCubic::QwtSplineCubic() + : m_data( nullptr ) +{ + // a natural spline + + setBoundaryCondition( QwtSpline::AtBeginning, QwtSpline::Clamped2 ); + setBoundaryValue( QwtSpline::AtBeginning, 0.0 ); + + setBoundaryCondition( QwtSpline::AtEnd, QwtSpline::Clamped2 ); + setBoundaryValue( QwtSpline::AtEnd, 0.0 ); +} + +//! Destructor +QwtSplineCubic::~QwtSplineCubic() +{ +} + +/*! + A cubic spline is non local, where changing one point has em effect on all + polynomials. + + \return 0 + */ +uint QwtSplineCubic::locality() const +{ + return 0; +} + +/*! + \brief Find the first derivative at the control points + + In opposite to the implementation QwtSplineC2::slopes the first derivates + are calculated directly, without calculating the second derivates first. + + \param points Control nodes of the spline + \return Vector with the values of the 2nd derivate at the control points + + \sa curvatures(), QwtSplinePolynomial::fromCurvatures() + \note The x coordinates need to be increasing or decreasing + */ +QVector< double > QwtSplineCubic::slopes( const QPolygonF& points ) const +{ + using namespace QwtSplineCubicP; + + if ( points.size() <= 2 ) + return QVector< double >(); + + if ( ( boundaryType() == QwtSpline::PeriodicPolygon ) + || ( boundaryType() == QwtSpline::ClosedPolygon ) ) + { + EquationSystem2< SlopeStore > eqs; + eqs.resolve( points ); + + return eqs.store().slopes(); + } + + if ( points.size() == 3 ) + { + if ( boundaryCondition( QwtSpline::AtBeginning ) == QwtSplineCubic::NotAKnot + || boundaryCondition( QwtSpline::AtEnd ) == QwtSplineCubic::NotAKnot ) + { +#if 0 + const double h0 = points[1].x() - points[0].x(); + const double h1 = points[2].x() - points[1].x(); + + const double s0 = ( points[1].y() - points[0].y() ) / h0; + const double s1 = ( points[2].y() - points[1].y() ) / h1; + + /* + the system is under-determined and we only + compute a quadratic spline. + */ + + const double b = ( s1 - s0 ) / ( h0 + h1 ); + + QVector< double > m( 3 ); + m[0] = s0 - h0 * b; + m[1] = s1 - h1 * b; + m[2] = s1 + h1 * b; + + return m; +#else + return QVector< double >(); +#endif + } + } + + Equation3 eq[2]; + qwtSetupEndEquations( + boundaryCondition( QwtSpline::AtBeginning ), + boundaryValue( QwtSpline::AtBeginning ), + boundaryCondition( QwtSpline::AtEnd ), + boundaryValue( QwtSpline::AtEnd ), + points, eq ); + + EquationSystem< SlopeStore > eqs; + eqs.setStartCondition( eq[0].p, eq[0].q, eq[0].u, eq[0].r ); + eqs.setEndCondition( eq[1].p, eq[1].q, eq[1].u, eq[1].r ); + eqs.resolve( points ); + + return eqs.store().slopes(); +} + +/*! + \brief Find the second derivative at the control points + + \param points Control nodes of the spline + \return Vector with the values of the 2nd derivate at the control points + + \sa slopes() + \note The x coordinates need to be increasing or decreasing + */ +QVector< double > QwtSplineCubic::curvatures( const QPolygonF& points ) const +{ + using namespace QwtSplineCubicP; + + if ( points.size() <= 2 ) + return QVector< double >(); + + if ( ( boundaryType() == QwtSpline::PeriodicPolygon ) + || ( boundaryType() == QwtSpline::ClosedPolygon ) ) + { + EquationSystem2< CurvatureStore > eqs; + eqs.resolve( points ); + + return eqs.store().curvatures(); + } + + if ( points.size() == 3 ) + { + if ( boundaryCondition( QwtSpline::AtBeginning ) == QwtSplineC2::NotAKnot + || boundaryCondition( QwtSpline::AtEnd ) == QwtSplineC2::NotAKnot ) + { + return QVector< double >(); + } + } + + Equation3 eq[2]; + qwtSetupEndEquations( + boundaryCondition( QwtSpline::AtBeginning ), + boundaryValue( QwtSpline::AtBeginning ), + boundaryCondition( QwtSpline::AtEnd ), + boundaryValue( QwtSpline::AtEnd ), + points, eq ); + + EquationSystem< CurvatureStore > eqs; + eqs.setStartCondition( eq[0].p, eq[0].q, eq[0].u, eq[0].r ); + eqs.setEndCondition( eq[1].p, eq[1].q, eq[1].u, eq[1].r ); + eqs.resolve( points ); + + return eqs.store().curvatures(); +} + +/*! + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns them as QPainterPath. + + \param points Control points + \return Painter path, that can be rendered by QPainter + + \note The implementation simply calls QwtSplineC1::painterPath() + */ +QPainterPath QwtSplineCubic::painterPath( const QPolygonF& points ) const +{ + // as QwtSplineCubic can calculate slopes directly we can + // use the implementation of QwtSplineC1 without any performance loss. + + return QwtSplineC1::painterPath( points ); +} + +/*! + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns the 2 control points of each curve as QLineF. + + \param points Control points + \return Control points of the interpolating Bezier curves + + \note The implementation simply calls QwtSplineC1::bezierControlLines() + */ +QVector< QLineF > QwtSplineCubic::bezierControlLines( const QPolygonF& points ) const +{ + // as QwtSplineCubic can calculate slopes directly we can + // use the implementation of QwtSplineC1 without any performance loss. + + return QwtSplineC1::bezierControlLines( points ); +} + +/*! + \brief Calculate the interpolating polynomials for a non parametric spline + + \param points Control points + \return Interpolating polynomials + + \note The x coordinates need to be increasing or decreasing + \note The implementation simply calls QwtSplineC2::polynomials(), but is + intended to be replaced by a one pass calculation some day. + */ +QVector< QwtSplinePolynomial > QwtSplineCubic::polynomials( const QPolygonF& points ) const +{ + return QwtSplineC2::polynomials( points ); +} + diff --git a/libs/qwt/src/qwt_spline_cubic.h b/libs/qwt/src/qwt_spline_cubic.h new file mode 100644 index 00000000..5272aa0a --- /dev/null +++ b/libs/qwt/src/qwt_spline_cubic.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * 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_SPLINE_CUBIC_H +#define QWT_SPLINE_CUBIC_H + +#include "qwt_global.h" +#include "qwt_spline.h" + +/*! + \brief A cubic spline + + A cubic spline is a spline with C2 continuity at all control points. + It is a non local spline, what means that all polynomials are changing + when one control point has changed. + + The implementation is based on the fact, that the continuity condition + means an equation with 3 unknowns for 3 adjacent points. The equation + system can be resolved by defining start/end conditions, that allow + substituting of one of the unknowns for the start/end equations. + + Resolving the equation system is a 2 pass algorithm, requiring more CPU costs + than all other implemented type of splines. + + \todo The implementation is not numerical stable + */ +class QWT_EXPORT QwtSplineCubic : public QwtSplineC2 +{ + public: + QwtSplineCubic(); + virtual ~QwtSplineCubic(); + + virtual uint locality() const QWT_OVERRIDE; + + virtual QPainterPath painterPath( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< QLineF > bezierControlLines( const QPolygonF& points ) const QWT_OVERRIDE; + + // calculating the parametric equations + virtual QVector< QwtSplinePolynomial > polynomials( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< double > slopes( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< double > curvatures( const QPolygonF& ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_spline_curve_fitter.cpp b/libs/qwt/src/qwt_spline_curve_fitter.cpp new file mode 100644 index 00000000..f7e34d9b --- /dev/null +++ b/libs/qwt/src/qwt_spline_curve_fitter.cpp @@ -0,0 +1,102 @@ +/****************************************************************************** + * 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_spline_curve_fitter.h" +#include "qwt_spline_local.h" +#include "qwt_spline_parametrization.h" + +#include +#include + +//! Constructor +QwtSplineCurveFitter::QwtSplineCurveFitter() + : QwtCurveFitter( QwtCurveFitter::Path ) +{ + m_spline = new QwtSplineLocal( QwtSplineLocal::Cardinal ); + m_spline->setParametrization( QwtSplineParametrization::ParameterUniform ); +} + +//! Destructor +QwtSplineCurveFitter::~QwtSplineCurveFitter() +{ + delete m_spline; +} + +/*! + Assign a spline + + The spline needs to be allocated by new and will be deleted + in the destructor of the fitter. + + \param spline Spline + \sa spline() + */ +void QwtSplineCurveFitter::setSpline( QwtSpline* spline ) +{ + if ( m_spline == spline ) + return; + + delete m_spline; + m_spline = spline; +} + +/*! + \return Spline + \sa setSpline() + */ +const QwtSpline* QwtSplineCurveFitter::spline() const +{ + return m_spline; +} + +/*! + \return Spline + \sa setSpline() + */ +QwtSpline* QwtSplineCurveFitter::spline() +{ + return m_spline; +} + +/*! + Find a curve which has the best fit to a series of data points + + \param points Series of data points + \return Fitted Curve + + \sa fitCurvePath() + */ +QPolygonF QwtSplineCurveFitter::fitCurve( const QPolygonF& points ) const +{ + const QPainterPath path = fitCurvePath( points ); + + const QList< QPolygonF > subPaths = path.toSubpathPolygons(); + if ( subPaths.size() == 1 ) + subPaths.first(); + + return QPolygonF(); +} + +/*! + Find a curve path which has the best fit to a series of data points + + \param points Series of data points + \return Fitted Curve + + \sa fitCurve() + */ +QPainterPath QwtSplineCurveFitter::fitCurvePath( const QPolygonF& points ) const +{ + QPainterPath path; + + if ( m_spline ) + path = m_spline->painterPath( points ); + + return path; +} diff --git a/libs/qwt/src/qwt_spline_curve_fitter.h b/libs/qwt/src/qwt_spline_curve_fitter.h new file mode 100644 index 00000000..457500a9 --- /dev/null +++ b/libs/qwt/src/qwt_spline_curve_fitter.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * 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_SPLINE_CURVE_FITTER_H +#define QWT_SPLINE_CURVE_FITTER_H + +#include "qwt_curve_fitter.h" + +class QwtSpline; + +/*! + \brief A curve fitter using a spline interpolation + + The default setting for the spline is a cardinal spline with + uniform parametrization. + + \sa QwtSpline, QwtSplineLocal + */ +class QWT_EXPORT QwtSplineCurveFitter : public QwtCurveFitter +{ + public: + QwtSplineCurveFitter(); + virtual ~QwtSplineCurveFitter(); + + void setSpline( QwtSpline* ); + + const QwtSpline* spline() const; + QwtSpline* spline(); + + virtual QPolygonF fitCurve( const QPolygonF& ) const QWT_OVERRIDE; + virtual QPainterPath fitCurvePath( const QPolygonF& ) const QWT_OVERRIDE; + + private: + QwtSpline* m_spline; +}; + +#endif diff --git a/libs/qwt/src/qwt_spline_local.cpp b/libs/qwt/src/qwt_spline_local.cpp new file mode 100644 index 00000000..61e1bb99 --- /dev/null +++ b/libs/qwt/src/qwt_spline_local.cpp @@ -0,0 +1,571 @@ +/****************************************************************************** + * 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_spline_local.h" +#include "qwt_spline_parametrization.h" +#include "qwt_spline_polynomial.h" + +#include + +static inline bool qwtIsStrictlyMonotonic( double dy1, double dy2 ) +{ + if ( dy1 == 0.0 || dy2 == 0.0 ) + return false; + + return ( dy1 > 0.0 ) == ( dy2 > 0.0 ); +} + +static inline double qwtSlopeLine( const QPointF& p1, const QPointF& p2 ) +{ + // ??? + const double dx = p2.x() - p1.x(); + return dx ? ( p2.y() - p1.y() ) / dx : 0.0; +} + +static inline double qwtSlopeCardinal( + double dx1, double dy1, double s1, double dx2, double dy2, double s2 ) +{ + Q_UNUSED(s1) + Q_UNUSED(s2) + + return ( dy1 + dy2 ) / ( dx1 + dx2 ); +} + +static inline double qwtSlopeParabolicBlending( + double dx1, double dy1, double s1, double dx2, double dy2, double s2 ) +{ + Q_UNUSED( dy1 ) + Q_UNUSED( dy2 ) + + return ( dx2 * s1 + dx1 * s2 ) / ( dx1 + dx2 ); +} + +static inline double qwtSlopePChip( + double dx1, double dy1, double s1, double dx2, double dy2, double s2 ) +{ + if ( qwtIsStrictlyMonotonic( dy1, dy2 ) ) + { +#if 0 + // weighting the slopes by the dx1/dx2 + const double w1 = ( 3 * dx1 + 3 * dx2 ) / ( 2 * dx1 + 4 * dx2 ); + const double w2 = ( 3 * dx1 + 3 * dx2 ) / ( 4 * dx1 + 2 * dx2 ); + + s1 *= w1; + s2 *= w2; + + // harmonic mean ( see https://en.wikipedia.org/wiki/Pythagorean_means ) + return 2.0 / ( 1.0 / s1 + 1.0 / s2 ); +#endif + // the same as above - but faster + + const double s12 = ( dy1 + dy2 ) / ( dx1 + dx2 ); + return 3.0 * ( s1 * s2 ) / ( s1 + s2 + s12 ); + } + + return 0.0; +} + +namespace QwtSplineLocalP +{ + class PathStore + { + public: + inline void init( const QVector< QPointF >& ) + { + } + + inline void start( const QPointF& p0, double ) + { + path.moveTo( p0 ); + } + + inline void addCubic( const QPointF& p1, double m1, + const QPointF& p2, double m2 ) + { + const double dx3 = ( p2.x() - p1.x() ) / 3.0; + + path.cubicTo( p1.x() + dx3, p1.y() + m1 * dx3, + p2.x() - dx3, p2.y() - m2 * dx3, + p2.x(), p2.y() ); + } + + QPainterPath path; + }; + + class ControlPointsStore + { + public: + inline void init( const QVector< QPointF >& points ) + { + if ( points.size() > 0 ) + controlPoints.resize( points.size() - 1 ); + m_cp = controlPoints.data(); + } + + inline void start( const QPointF&, double ) + { + } + + inline void addCubic( const QPointF& p1, double m1, + const QPointF& p2, double m2 ) + { + const double dx3 = ( p2.x() - p1.x() ) / 3.0; + + QLineF& l = *m_cp++; + l.setLine( p1.x() + dx3, p1.y() + m1 * dx3, + p2.x() - dx3, p2.y() - m2 * dx3 ); + } + + QVector< QLineF > controlPoints; + + private: + QLineF* m_cp; + }; + + class SlopeStore + { + public: + void init( const QVector< QPointF >& points ) + { + slopes.resize( points.size() ); + m_m = slopes.data(); + } + + inline void start( const QPointF&, double m0 ) + { + *m_m++ = m0; + } + + inline void addCubic( const QPointF&, double, + const QPointF&, double m2 ) + { + *m_m++ = m2; + } + + QVector< double > slopes; + + private: + double* m_m; + }; + + struct slopeCardinal + { + static inline double value( double dx1, double dy1, double s1, + double dx2, double dy2, double s2 ) + { + return qwtSlopeCardinal( dx1, dy1, s1, dx2, dy2, s2 ); + } + }; + + struct slopeParabolicBlending + { + static inline double value( double dx1, double dy1, double s1, + double dx2, double dy2, double s2 ) + { + return qwtSlopeParabolicBlending( dx1, dy1, s1, dx2, dy2, s2 ); + } + }; + + struct slopePChip + { + static inline double value( double dx1, double dy1, double s1, + double dx2, double dy2, double s2 ) + { + return qwtSlopePChip( dx1, dy1, s1, dx2, dy2, s2 ); + } + }; +} + +template< class Slope > +static inline double qwtSlopeP3( + const QPointF& p1, const QPointF& p2, const QPointF& p3 ) +{ + const double dx1 = p2.x() - p1.x(); + const double dy1 = p2.y() - p1.y(); + const double dx2 = p3.x() - p2.x(); + const double dy2 = p3.y() - p2.y(); + + return Slope::value( dx1, dy1, dy1 / dx1, dx2, dy2, dy2 / dx2 ); +} + +static inline double qwtSlopeAkima( double s1, double s2, double s3, double s4 ) +{ + if ( ( s1 == s2 ) && ( s3 == s4 ) ) + { + return 0.5 * ( s2 + s3 ); + } + + const double ds12 = qAbs( s2 - s1 ); + const double ds34 = qAbs( s4 - s3 ); + + return ( s2 * ds34 + s3 * ds12 ) / ( ds12 + ds34 ); +} + +static inline double qwtSlopeAkima( const QPointF& p1, const QPointF& p2, + const QPointF& p3, const QPointF& p4, const QPointF& p5 ) +{ + const double s1 = qwtSlopeLine( p1, p2 ); + const double s2 = qwtSlopeLine( p2, p3 ); + const double s3 = qwtSlopeLine( p3, p4 ); + const double s4 = qwtSlopeLine( p4, p5 ); + + return qwtSlopeAkima( s1, s2, s3, s4 ); +} + +template< class Slope > +static void qwtSplineBoundariesL1( + const QwtSplineLocal* spline, const QVector< QPointF >& points, + double& slopeBegin, double& slopeEnd ) +{ + const int n = points.size(); + const QPointF* p = points.constData(); + + if ( ( spline->boundaryType() == QwtSpline::PeriodicPolygon ) + || ( spline->boundaryType() == QwtSpline::ClosedPolygon ) ) + { + const QPointF pn = p[0] - ( p[n - 1] - p[n - 2] ); + slopeBegin = slopeEnd = qwtSlopeP3< Slope >( pn, p[0], p[1] ); + } + else + { + const double m2 = qwtSlopeP3< Slope >( p[0], p[1], p[2] ); + slopeBegin = spline->slopeAtBeginning( points, m2 ); + + const double mn2 = qwtSlopeP3< Slope >( p[n - 3], p[n - 2], p[n - 1] ); + slopeEnd = spline->slopeAtEnd( points, mn2 ); + } +} + +template< class SplineStore, class Slope > +static inline SplineStore qwtSplineL1( + const QwtSplineLocal* spline, const QVector< QPointF >& points ) +{ + const int size = points.size(); + const QPointF* p = points.constData(); + + double slopeBegin, slopeEnd; + qwtSplineBoundariesL1< Slope >( spline, points, slopeBegin, slopeEnd ); + + double m1 = slopeBegin; + + SplineStore store; + store.init( points ); + store.start( p[0], m1 ); + + double dx1 = p[1].x() - p[0].x(); + double dy1 = p[1].y() - p[0].y(); + double s1 = dy1 / dx1; + + for ( int i = 1; i < size - 1; i++ ) + { + const double dx2 = p[i + 1].x() - p[i].x(); + const double dy2 = p[i + 1].y() - p[i].y(); + + // cardinal spline doesn't need the line slopes, but + // the compiler will eliminate pointless calculations + const double s2 = dy2 / dx2; + + const double m2 = Slope::value( dx1, dy1, s1, dx2, dy2, s2 ); + + store.addCubic( p[i - 1], m1, p[i], m2 ); + + dx1 = dx2; + dy1 = dy2; + s1 = s2; + m1 = m2; + } + + store.addCubic( p[size - 2], m1, p[size - 1], slopeEnd ); + + return store; +} + +static inline void qwtSplineAkimaBoundaries( + const QwtSplineLocal* spline, const QVector< QPointF >& points, + double& slopeBegin, double& slopeEnd ) +{ + const int n = points.size(); + const QPointF* p = points.constData(); + + if ( ( spline->boundaryType() == QwtSpline::PeriodicPolygon ) + || ( spline->boundaryType() == QwtSpline::ClosedPolygon ) ) + { + const QPointF p2 = p[0] - ( p[n - 1] - p[n - 2] ); + const QPointF p1 = p2 - ( p[n - 2] - p[n - 3] ); + + slopeBegin = slopeEnd = qwtSlopeAkima( p1, p2, p[0], p[1], p[2] ); + + return; + } + + if ( spline->boundaryCondition( QwtSpline::AtBeginning ) == QwtSpline::Clamped1 + && spline->boundaryCondition( QwtSpline::AtEnd ) == QwtSpline::Clamped1 ) + { + slopeBegin = spline->boundaryValue( QwtSpline::AtBeginning); + slopeEnd = spline->boundaryValue( QwtSpline::AtEnd ); + + return; + } + + if ( n == 3 ) + { + const double s1 = qwtSlopeLine( p[0], p[1] ); + const double s2 = qwtSlopeLine( p[1], p[2] ); + const double m = qwtSlopeAkima( 0.5 * s1, s1, s2, 0.5 * s2 ); + + slopeBegin = spline->slopeAtBeginning( points, m ); + slopeEnd = spline->slopeAtEnd( points, m ); + } + else + { + double s[3]; + + s[0] = qwtSlopeLine( p[0], p[1] ); + s[1] = qwtSlopeLine( p[1], p[2] ); + s[2] = qwtSlopeLine( p[2], p[3] ); + + const double m2 = qwtSlopeAkima( 0.5 * s[0], s[0], s[1], s[2] ); + + slopeBegin = spline->slopeAtBeginning( points, m2 ); + + s[0] = qwtSlopeLine( p[n - 4], p[n - 3] ); + s[1] = qwtSlopeLine( p[n - 3], p[n - 2] ); + s[2] = qwtSlopeLine( p[n - 2], p[n - 1] ); + + const double mn2 = qwtSlopeAkima( s[0], s[1], s[2], 0.5 * s[2] ); + + slopeEnd = spline->slopeAtEnd( points, mn2 ); + } +} + +template< class SplineStore > +static inline SplineStore qwtSplineAkima( + const QwtSplineLocal* spline, const QVector< QPointF >& points ) +{ + const int size = points.size(); + const QPointF* p = points.constData(); + + double slopeBegin, slopeEnd; + qwtSplineAkimaBoundaries( spline, points, slopeBegin, slopeEnd ); + + double m1 = slopeBegin; + + SplineStore store; + store.init( points ); + store.start( p[0], m1 ); + + double s2 = qwtSlopeLine( p[0], p[1] ); + double s3 = qwtSlopeLine( p[1], p[2] ); + double s1 = 0.5 * s2; + + for ( int i = 0; i < size - 3; i++ ) + { + const double s4 = qwtSlopeLine( p[i + 2], p[i + 3] ); + + const double m2 = qwtSlopeAkima( s1, s2, s3, s4 ); + store.addCubic( p[i], m1, p[i + 1], m2 ); + + s1 = s2; + s2 = s3; + s3 = s4; + + m1 = m2; + } + + const double m2 = qwtSlopeAkima( s1, s2, s3, 0.5 * s3 ); + + store.addCubic( p[size - 3], m1, p[size - 2], m2 ); + store.addCubic( p[size - 2], m2, p[size - 1], slopeEnd ); + + return store; +} + +template< class SplineStore > +static inline SplineStore qwtSplineLocal( + const QwtSplineLocal* spline, const QVector< QPointF >& points ) +{ + SplineStore store; + + const int size = points.size(); + if ( size <= 1 ) + return store; + + if ( size == 2 ) + { + const double s0 = qwtSlopeLine( points[0], points[1] ); + const double m1 = spline->slopeAtBeginning( points, s0 ); + const double m2 = spline->slopeAtEnd( points, s0 ); + + store.init( points ); + store.start( points[0], m1 ); + store.addCubic( points[0], m1, points[1], m2 ); + + return store; + } + + switch( spline->type() ) + { + case QwtSplineLocal::Cardinal: + { + using namespace QwtSplineLocalP; + store = qwtSplineL1< SplineStore, slopeCardinal >( spline, points ); + break; + } + case QwtSplineLocal::ParabolicBlending: + { + using namespace QwtSplineLocalP; + store = qwtSplineL1< SplineStore, slopeParabolicBlending >( spline, points ); + break; + } + case QwtSplineLocal::PChip: + { + using namespace QwtSplineLocalP; + store = qwtSplineL1< SplineStore, slopePChip >( spline, points ); + break; + } + case QwtSplineLocal::Akima: + { + store = qwtSplineAkima< SplineStore >( spline, points ); + break; + } + default: + break; + } + + return store; +} + +/*! + \brief Constructor + + \param type Spline type, specifying the type of interpolation + \sa type() + */ +QwtSplineLocal::QwtSplineLocal( Type type ) + : m_type( type ) +{ + setBoundaryCondition( QwtSpline::AtBeginning, QwtSpline::LinearRunout ); + setBoundaryValue( QwtSpline::AtBeginning, 0.0 ); + + setBoundaryCondition( QwtSpline::AtEnd, QwtSpline::LinearRunout ); + setBoundaryValue( QwtSpline::AtEnd, 0.0 ); +} + +//! Destructor +QwtSplineLocal::~QwtSplineLocal() +{ +} + +/*! + \return Spline type, specifying the type of interpolation + */ +QwtSplineLocal::Type QwtSplineLocal::type() const +{ + return m_type; +} + +/*! + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns them as QPainterPath. + + \param points Control points + \return Painter path, that can be rendered by QPainter + */ +QPainterPath QwtSplineLocal::painterPath( const QPolygonF& points ) const +{ + if ( parametrization()->type() == QwtSplineParametrization::ParameterX ) + { + using namespace QwtSplineLocalP; + return qwtSplineLocal< PathStore >( this, points).path; + } + + return QwtSplineC1::painterPath( points ); +} + +/*! + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns the 2 control points of each curve as QLineF. + + \param points Control points + \return Control points of the interpolating Bezier curves + */ +QVector< QLineF > QwtSplineLocal::bezierControlLines( const QPolygonF& points ) const +{ + if ( parametrization()->type() == QwtSplineParametrization::ParameterX ) + { + using namespace QwtSplineLocalP; + return qwtSplineLocal< ControlPointsStore >( this, points ).controlPoints; + } + + return QwtSplineC1::bezierControlLines( points ); +} + +/*! + \brief Find the first derivative at the control points + + \param points Control nodes of the spline + \return Vector with the values of the 2nd derivate at the control points + + \note The x coordinates need to be increasing or decreasing + */ +QVector< double > QwtSplineLocal::slopes( const QPolygonF& points ) const +{ + using namespace QwtSplineLocalP; + return qwtSplineLocal< SlopeStore >( this, points ).slopes; +} + +/*! + \brief Calculate the interpolating polynomials for a non parametric spline + + \param points Control points + \return Interpolating polynomials + + \note The x coordinates need to be increasing or decreasing + \note The implementation simply calls QwtSplineC1::polynomials(), but is + intended to be replaced by a one pass calculation some day. + */ +QVector< QwtSplinePolynomial > QwtSplineLocal::polynomials( const QPolygonF& points ) const +{ + // Polynomial store -> TODO + return QwtSplineC1::polynomials( points ); +} + +/*! + The locality of an spline interpolation identifies how many adjacent + polynomials are affected, when changing the position of one point. + + The Cardinal, ParabolicBlending and PChip algorithms have a locality of 1, + while the Akima interpolation has a locality of 2. + + \return 1 or 2. + */ +uint QwtSplineLocal::locality() const +{ + switch ( m_type ) + { + case Akima: + { + // polynomials: 2 left, 2 right + return 2; + } + case Cardinal: + case ParabolicBlending: + case PChip: + { + // polynomials: 1 left, 1 right + return 1; + } + } + + return QwtSplineC1::locality(); +} diff --git a/libs/qwt/src/qwt_spline_local.h b/libs/qwt/src/qwt_spline_local.h new file mode 100644 index 00000000..d1dd8e0a --- /dev/null +++ b/libs/qwt/src/qwt_spline_local.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * 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_SPLINE_LOCAL_H +#define QWT_SPLINE_LOCAL_H + +#include "qwt_global.h" +#include "qwt_spline.h" + +/*! + \brief A spline with C1 continuity + + QwtSplineLocal offers several standard algorithms for interpolating + a curve with polynomials having C1 continuity at the control points. + All algorithms are local in a sense, that changing one control point + only few polynomials. + */ +class QWT_EXPORT QwtSplineLocal : public QwtSplineC1 +{ + public: + /*! + \brief Spline interpolation type + + All type of spline interpolations are lightweight algorithms + calculating the slopes at a point by looking 1 or 2 points back + and ahead. + */ + enum Type + { + /*! + A cardinal spline + + The cardinal spline interpolation is a very cheap calculation with + a locality of 1. + */ + Cardinal, + + /*! + Parabolic blending is a cheap calculation with a locality of 1. Sometimes + it is also called Cubic Bessel interpolation. + */ + ParabolicBlending, + + /*! + The algorithm of H.Akima is a calculation with a locality of 2. + */ + Akima, + + /*! + Piecewise Cubic Hermite Interpolating Polynomial (PCHIP) is an algorithm + that is popular because of being offered by MATLAB. + + It preserves the shape of the data and respects monotonicity. It has a + locality of 1. + */ + PChip + }; + + QwtSplineLocal( Type type ); + virtual ~QwtSplineLocal(); + + Type type() const; + + virtual uint locality() const QWT_OVERRIDE; + + virtual QPainterPath painterPath( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< QLineF > bezierControlLines( const QPolygonF& ) const QWT_OVERRIDE; + + // calculating the parametric equations + virtual QVector< QwtSplinePolynomial > polynomials( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< double > slopes( const QPolygonF& ) const QWT_OVERRIDE; + + private: + const Type m_type; +}; + +#endif diff --git a/libs/qwt/src/qwt_spline_parametrization.cpp b/libs/qwt/src/qwt_spline_parametrization.cpp new file mode 100644 index 00000000..6b4dba1e --- /dev/null +++ b/libs/qwt/src/qwt_spline_parametrization.cpp @@ -0,0 +1,75 @@ +/****************************************************************************** + * 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_spline_parametrization.h" + +/*! + Constructor + \param type Parametrization type + \sa type() + */ +QwtSplineParametrization::QwtSplineParametrization( int type ) + : m_type( type ) +{ +} + +//! Destructor +QwtSplineParametrization::~QwtSplineParametrization() +{ +} + +/*! + \brief Calculate the parameter value increment for 2 points + + \param point1 First point + \param point2 Second point + + \return Value increment + */ +double QwtSplineParametrization::valueIncrement( + const QPointF& point1, const QPointF& point2 ) const +{ + switch( m_type ) + { + case QwtSplineParametrization::ParameterX: + { + return valueIncrementX( point1, point2 ); + } + case QwtSplineParametrization::ParameterY: + { + return valueIncrementY( point1, point2 ); + } + case QwtSplineParametrization::ParameterCentripetal: + { + return valueIncrementCentripetal( point1, point2 ); + } + case QwtSplineParametrization::ParameterChordal: + { + return valueIncrementChordal( point1, point2 ); + } + case QwtSplineParametrization::ParameterManhattan: + { + return valueIncrementManhattan( point1, point2 ); + } + case QwtSplineParametrization::ParameterUniform: + { + return valueIncrementUniform( point1, point2 ); + } + default: + { + return 1; + } + } +} + +//! \return Parametrization type +int QwtSplineParametrization::type() const +{ + return m_type; +} diff --git a/libs/qwt/src/qwt_spline_parametrization.h b/libs/qwt/src/qwt_spline_parametrization.h new file mode 100644 index 00000000..37a0859f --- /dev/null +++ b/libs/qwt/src/qwt_spline_parametrization.h @@ -0,0 +1,216 @@ +/****************************************************************************** + * 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_SPLINE_PARAMETRIZATION_H +#define QWT_SPLINE_PARAMETRIZATION_H + +#include "qwt_global.h" +#include "qwt_math.h" + +#include + +/*! + \brief Curve parametrization used for a spline interpolation + + Parametrization is the process of finding a parameter value for + each curve point - usually related to some physical quantity + ( distance, time ... ). + + Often accumulating the curve length is the intended way of parametrization, + but as the interpolated curve is not known in advance an approximation + needs to be used. + + The values are calculated by cumulating increments, that are provided + by QwtSplineParametrization. As the curve parameters need to be + montonically increasing, each increment need to be positive. + + - t[0] = 0; + - t[i] = t[i-1] + valueIncrement( point[i-1], p[i] ); + + QwtSplineParametrization provides the most common used type of + parametrizations and offers an interface to inject custom implementations. + + \note The most relevant types of parametrization are trying to provide an + approximation of the curve length. + + \sa QwtSpline::setParametrization() + */ +class QWT_EXPORT QwtSplineParametrization +{ + public: + //! Parametrization type + enum Type + { + /*! + No parametrization: t[i] = x[i] + \sa valueIncrementX() + */ + ParameterX, + + /*! + No parametrization: t[i] = y[i] + \sa valueIncrementY() + */ + ParameterY, + + /*! + Uniform parametrization: t[i] = i; + + A very fast parametrization, with good results, when the geometry + of the control points is somehow "equidistant". F.e. when + recording the position of a body, that is moving with constant + speed every n seconds. + + \sa valueIncrementUniform() + */ + ParameterUniform, + + /*! + Parametrization using the chordal length between two control points + + The chordal length is the most commonly used approximation for + the curve length. + + \sa valueIncrementChordal() + */ + ParameterChordal, + + /*! + Centripetal parametrization + + Based on the square root of the chordal length. + + Its name stems from the physical observations regarding + the centripetal force, of a body moving along the curve. + + \sa valueIncrementCentripetal() + */ + ParameterCentripetal, + + + /*! + Parametrization using the manhattan length between two control points + + Approximating the curve length by the manhattan length is faster + than the chordal length, but usually gives worse results. + + \sa valueIncrementManhattan() + */ + ParameterManhattan + }; + + explicit QwtSplineParametrization( int type ); + virtual ~QwtSplineParametrization(); + + int type() const; + + virtual double valueIncrement( const QPointF&, const QPointF& ) const; + + static double valueIncrementX( const QPointF&, const QPointF& ); + static double valueIncrementY( const QPointF&, const QPointF& ); + static double valueIncrementUniform( const QPointF&, const QPointF& ); + static double valueIncrementChordal( const QPointF&, const QPointF& ); + static double valueIncrementCentripetal( const QPointF&, const QPointF& ); + static double valueIncrementManhattan( const QPointF&, const QPointF& ); + + private: + const int m_type; +}; + +/*! + \brief Calculate the ParameterX value increment for 2 points + + \param point1 First point + \param point2 Second point + + \return point2.x() - point1.x(); + */ +inline double QwtSplineParametrization::valueIncrementX( + const QPointF& point1, const QPointF& point2 ) +{ + return point2.x() - point1.x(); +} + +/*! + \brief Calculate the ParameterY value increment for 2 points + + \param point1 First point + \param point2 Second point + + \return point2.y() - point1.y(); + */ +inline double QwtSplineParametrization::valueIncrementY( + const QPointF& point1, const QPointF& point2 ) +{ + return point2.y() - point1.y(); +} + +/*! + \brief Calculate the ParameterUniform value increment + + \param point1 First point + \param point2 Second point + + \return 1.0 + */ +inline double QwtSplineParametrization::valueIncrementUniform( + const QPointF& point1, const QPointF& point2 ) +{ + Q_UNUSED( point1 ) + Q_UNUSED( point2 ) + + return 1.0; +} + +/*! + \brief Calculate the ParameterChordal value increment for 2 points + + \param point1 First point + \param point2 Second point + + \return qSqrt( dx * dx + dy * dy ); + */ +inline double QwtSplineParametrization::valueIncrementChordal( + const QPointF& point1, const QPointF& point2 ) +{ + const double dx = point2.x() - point1.x(); + const double dy = point2.y() - point1.y(); + + return std::sqrt( dx * dx + dy * dy ); +} + +/*! + \brief Calculate the ParameterCentripetal value increment for 2 points + + \param point1 First point + \param point2 Second point + + \return The square root of a chordal increment + */ +inline double QwtSplineParametrization::valueIncrementCentripetal( + const QPointF& point1, const QPointF& point2 ) +{ + return std::sqrt( valueIncrementChordal( point1, point2 ) ); +} + +/*! + \brief Calculate the ParameterManhattan value increment for 2 points + + \param point1 First point + \param point2 Second point + + \return | point2.x() - point1.x() | + | point2.y() - point1.y() | + */ +inline double QwtSplineParametrization::valueIncrementManhattan( + const QPointF& point1, const QPointF& point2 ) +{ + return qAbs( point2.x() - point1.x() ) + qAbs( point2.y() - point1.y() ); +} + +#endif diff --git a/libs/qwt/src/qwt_spline_pleasing.cpp b/libs/qwt/src/qwt_spline_pleasing.cpp new file mode 100644 index 00000000..7f3fef5e --- /dev/null +++ b/libs/qwt/src/qwt_spline_pleasing.cpp @@ -0,0 +1,351 @@ +/****************************************************************************** + * 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_spline_pleasing.h" +#include "qwt_spline_parametrization.h" + +#include + +static inline double qwtChordalLength( const QPointF& point1, const QPointF& point2 ) +{ + const double dx = point2.x() - point1.x(); + const double dy = point2.y() - point1.y(); + + return std::sqrt( dx * dx + dy * dy ); +} + +template< class Param > +static QPointF qwtVector( Param param, + const QPointF& p1, const QPointF& p2 ) +{ + return ( p2 - p1 ) / param( p1, p2 ); +} + +template< class Param > +static QPointF qwtVectorCardinal( Param param, + const QPointF& p1, const QPointF& p2, const QPointF& p3 ) +{ + const double t1 = param( p1, p2 ); + const double t2 = param( p2, p3 ); + + return t2 * ( p3 - p1 ) / ( t1 + t2 ); +} + +namespace QwtSplinePleasingP +{ + struct Tension + { + double t1; + double t2; + }; + + struct param + { + param( const QwtSplineParametrization* p ): + parameter( p ) + { + } + + inline double operator()( const QPointF& p1, const QPointF& p2 ) const + { + return parameter->valueIncrement( p1, p2 ); + } + + const QwtSplineParametrization* parameter; + }; + + struct paramUniform + { + inline double operator()( const QPointF& p1, const QPointF& p2 ) const + { + return QwtSplineParametrization::valueIncrementUniform( p1, p2 ); + } + }; + + class PathStore + { + public: + inline void init( int ) + { + } + + inline void start( const QPointF& p0 ) + { + path.moveTo( p0 ); + } + + inline void addCubic( const QPointF& cp1, + const QPointF& cp2, const QPointF& p2 ) + { + path.cubicTo( cp1, cp2, p2 ); + } + + QPainterPath path; + }; + + class ControlPointsStore + { + public: + inline ControlPointsStore(): + m_cp( NULL ) + { + } + + inline void init( int size ) + { + controlPoints.resize( size ); + m_cp = controlPoints.data(); + } + + inline void start( const QPointF& ) + { + } + + inline void addCubic( const QPointF& cp1, + const QPointF& cp2, const QPointF& ) + { + QLineF& l = *m_cp++; + l.setPoints( cp1, cp2 ); + } + + QVector< QLineF > controlPoints; + + private: + QLineF* m_cp; + }; +} + +static inline QwtSplinePleasingP::Tension qwtTensionPleasing( + double d13, double d23, double d24, + const QPointF& p1, const QPointF& p2, + const QPointF& p3, const QPointF& p4 ) +{ + QwtSplinePleasingP::Tension tension; + + const bool b1 = ( d13 / 3.0 ) < d23; + const bool b2 = ( d24 / 3.0 ) < d23; + + if ( b1 ) + { + if ( b2 ) + { + tension.t1 = ( p1 != p2 ) ? ( 1.0 / 3.0 ) : ( 2.0 / 3.0 ); + tension.t2 = ( p3 != p4 ) ? ( 1.0 / 3.0 ) : ( 2.0 / 3.0 ); + } + else + { + tension.t1 = tension.t2 = d23 / d24; + } + } + else + { + if ( b2 ) + { + tension.t1 = tension.t2 = d23 / d13; + } + else + { + tension.t1 = d23 / d13; + tension.t2 = d23 / d24; + } + } + + return tension; +} + +template< class SplineStore, class Param > +static SplineStore qwtSplinePathPleasing( const QPolygonF& points, + bool isClosed, Param param ) +{ + using namespace QwtSplinePleasingP; + + const int size = points.size(); + + const QPointF* p = points.constData(); + + SplineStore store; + store.init( isClosed ? size : size - 1 ); + store.start( p[0] ); + + double d13; + QPointF vec1; + + if ( isClosed ) + { + d13 = qwtChordalLength(p[0], p[2]); + + const Tension t0 = qwtTensionPleasing( + qwtChordalLength( p[size - 1], p[1]), qwtChordalLength(p[0], p[1]), + d13, p[size - 1], p[0], p[1], p[2] ); + + const QPointF vec0 = qwtVectorCardinal< Param >( param, p[size - 1], p[0], p[1] ); + vec1 = qwtVectorCardinal< Param >( param, p[0], p[1], p[2] ); + + store.addCubic( p[0] + vec0 * t0.t1, p[1] - vec1 * t0.t2, p[1] ); + } + else + { + d13 = qwtChordalLength(p[0], p[2]); + + const Tension t0 = qwtTensionPleasing( + qwtChordalLength( p[0], p[1]), qwtChordalLength(p[0], p[1]), + d13, p[0], p[0], p[1], p[2] ); + + const QPointF vec0 = 0.5 * qwtVector< Param >( param, p[0], p[1] ); + vec1 = qwtVectorCardinal< Param >( param, p[0], p[1], p[2] ); + + store.addCubic( p[0] + vec0 * t0.t1, p[1] - vec1 * t0.t2, p[1] ); + } + + for ( int i = 1; i < size - 2; i++ ) + { + const double d23 = qwtChordalLength( p[i], p[i + 1] ); + const double d24 = qwtChordalLength( p[i], p[i + 2] ); + + const QPointF vec2 = qwtVectorCardinal< Param >( param, p[i], p[i + 1], p[i + 2] ); + + const Tension t = qwtTensionPleasing( + d13, d23, d24, p[i - 1], p[i], p[i + 1], p[i + 2] ); + + store.addCubic( p[i] + vec1 * t.t1, p[i + 1] - vec2 * t.t2, p[i + 1] ); + + d13 = d24; + vec1 = vec2; + } + + if ( isClosed ) + { + const double d24 = qwtChordalLength( p[size - 2], p[0] ); + + const Tension tn = qwtTensionPleasing( + d13, qwtChordalLength( p[size - 2], p[size - 1] ), d24, + p[size - 3], p[size - 2], p[size - 1], p[0] ); + + const QPointF vec2 = qwtVectorCardinal< Param >( param, p[size - 2], p[size - 1], p[0] ); + store.addCubic( p[size - 2] + vec1 * tn.t1, p[size - 1] - vec2 * tn.t2, p[size - 1] ); + + const double d34 = qwtChordalLength( p[size - 1], p[0] ); + const double d35 = qwtChordalLength( p[size - 1], p[1] ); + + const Tension tc = qwtTensionPleasing( d24, d34, d35, p[size - 2], p[size - 1], p[0], p[1] ); + + const QPointF vec3 = qwtVectorCardinal< Param >( param, p[size - 1], p[0], p[1] ); + + store.addCubic( p[size - 1] + vec2 * tc.t1, p[0] - vec3 * tc.t2, p[0] ); + } + else + { + const double d24 = qwtChordalLength( p[size - 2], p[size - 1] ); + + const Tension tn = qwtTensionPleasing( + d13, qwtChordalLength( p[size - 2], p[size - 1] ), d24, + p[size - 3], p[size - 2], p[size - 1], p[size - 1] ); + + const QPointF vec2 = 0.5 * qwtVector< Param >( param, p[size - 2], p[size - 1] ); + store.addCubic( p[size - 2] + vec1 * tn.t1, p[size - 1] - vec2 * tn.t2, p[size - 1] ); + } + + return store; +} + +/*! + \brief Constructor + + The default setting is a non closing spline with uniform parametrization. + ( QwtSplineParametrization::ParameterUniform ). + + \sa QwtSpline::setParametrization(), QwtSpline::setBoundaryType() + */ +QwtSplinePleasing::QwtSplinePleasing() +{ + setParametrization( QwtSplineParametrization::ParameterUniform ); +} + +//! Destructor +QwtSplinePleasing::~QwtSplinePleasing() +{ +} + +//! \return 2 +uint QwtSplinePleasing::locality() const +{ + return 2; +} + +/*! + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns them as QPainterPath. + + \param points Control points + \return QPainterPath Painter path, that can be rendered by QPainter + */ +QPainterPath QwtSplinePleasing::painterPath( const QPolygonF& points ) const +{ + const int size = points.size(); + if ( size <= 2 ) + return QwtSplineG1::painterPath( points ); + + const bool isClosing = ( boundaryType() == QwtSpline::ClosedPolygon ); + + using namespace QwtSplinePleasingP; + + PathStore store; + if ( parametrization()->type() == QwtSplineParametrization::ParameterUniform ) + { + store = qwtSplinePathPleasing< PathStore >( points, + isClosing, paramUniform() ); + } + else + { + store = qwtSplinePathPleasing< PathStore >( points, + isClosing, param( parametrization() ) ); + } + + if ( isClosing ) + store.path.closeSubpath(); + + return store.path; +} + +/*! + \brief Interpolate a curve with Bezier curves + + Interpolates a polygon piecewise with cubic Bezier curves + and returns the 2 control points of each curve as QLineF. + + \param points Control points + \return Control points of the interpolating Bezier curves + */ +QVector< QLineF > QwtSplinePleasing::bezierControlLines( + const QPolygonF& points ) const +{ + const int size = points.size(); + if ( size <= 2 ) + return QVector< QLineF >(); + + const bool isClosing = ( boundaryType() == QwtSpline::ClosedPolygon ); + + using namespace QwtSplinePleasingP; + + ControlPointsStore store; + if ( parametrization()->type() == QwtSplineParametrization::ParameterUniform ) + { + store = qwtSplinePathPleasing< ControlPointsStore >( points, + isClosing, paramUniform() ); + } + else + { + store = qwtSplinePathPleasing< ControlPointsStore >( points, + isClosing, param( parametrization() ) ); + } + + return store.controlPoints; +} diff --git a/libs/qwt/src/qwt_spline_pleasing.h b/libs/qwt/src/qwt_spline_pleasing.h new file mode 100644 index 00000000..73ec3a3a --- /dev/null +++ b/libs/qwt/src/qwt_spline_pleasing.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * 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_SPLINE_PLEASING_H +#define QWT_SPLINE_PLEASING_H + +#include "qwt_spline.h" + +/*! + \brief A spline with G1 continuity + + QwtSplinePleasing is some sort of cardinal spline, with + non C1 continuous extra rules for narrow angles. It has a locality of 2. + + \note The algorithm is the one offered by a popular office package. + */ +class QWT_EXPORT QwtSplinePleasing : public QwtSplineG1 +{ + public: + QwtSplinePleasing(); + virtual ~QwtSplinePleasing(); + + virtual uint locality() const QWT_OVERRIDE; + + virtual QPainterPath painterPath( const QPolygonF& ) const QWT_OVERRIDE; + virtual QVector< QLineF > bezierControlLines( const QPolygonF& ) const QWT_OVERRIDE; +}; + +#endif diff --git a/libs/qwt/src/qwt_spline_polynomial.cpp b/libs/qwt/src/qwt_spline_polynomial.cpp new file mode 100644 index 00000000..40695955 --- /dev/null +++ b/libs/qwt/src/qwt_spline_polynomial.cpp @@ -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 "qwt_spline_polynomial.h" + +namespace +{ + static const struct RegisterQwtSplinePolynomial + { + inline RegisterQwtSplinePolynomial() + { qRegisterMetaType< QwtSplinePolynomial >(); } + + } qwtRegisterQwtSplinePolynomial; +} + +#ifndef QT_NO_DEBUG_STREAM + +#include + +QDebug operator<<( QDebug debug, const QwtSplinePolynomial& polynomial ) +{ + debug.nospace() << "Polynom(" << polynomial.c3 << ", " + << polynomial.c2 << ", " << polynomial.c1 << ")"; + return debug.space(); +} + +#endif + diff --git a/libs/qwt/src/qwt_spline_polynomial.h b/libs/qwt/src/qwt_spline_polynomial.h new file mode 100644 index 00000000..c0fd5b56 --- /dev/null +++ b/libs/qwt/src/qwt_spline_polynomial.h @@ -0,0 +1,219 @@ +/****************************************************************************** + * 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_SPLINE_POLYNOMIAL_H +#define QWT_SPLINE_POLYNOMIAL_H + +#include "qwt_global.h" + +#include +#include + +/*! + \brief A cubic polynomial without constant term + + QwtSplinePolynomial is a 3rd degree polynomial + of the form: y = c3 * x³ + c2 * x² + c1 * x; + + QwtSplinePolynomial is usually used in combination with polygon + interpolation, where it is not necessary to store a constant term ( c0 ), + as the translation is known from the corresponding polygon points. + + \sa QwtSplineC1 + */ +class QWT_EXPORT QwtSplinePolynomial +{ + public: + QwtSplinePolynomial( double c3 = 0.0, double c2 = 0.0, double c1 = 0.0 ); + + bool operator==( const QwtSplinePolynomial& ) const; + bool operator!=( const QwtSplinePolynomial& ) const; + + double valueAt( double x ) const; + double slopeAt( double x ) const; + double curvatureAt( double x ) const; + + static QwtSplinePolynomial fromSlopes( + const QPointF& p1, double m1, + const QPointF& p2, double m2 ); + + static QwtSplinePolynomial fromSlopes( + double x, double y, double m1, double m2 ); + + static QwtSplinePolynomial fromCurvatures( + const QPointF& p1, double cv1, + const QPointF& p2, double cv2 ); + + static QwtSplinePolynomial fromCurvatures( + double dx, double dy, double cv1, double cv2 ); + + public: + //! coefficient of the cubic summand + double c3; + + //! coefficient of the quadratic summand + double c2; + + //! coefficient of the linear summand + double c1; +}; + +Q_DECLARE_TYPEINFO( QwtSplinePolynomial, Q_MOVABLE_TYPE ); +Q_DECLARE_METATYPE( QwtSplinePolynomial ) + +/*! + \brief Constructor + + \param a3 Coefficient of the cubic summand + \param a2 Coefficient of the quadratic summand + \param a1 Coefficient of the linear summand + */ +inline QwtSplinePolynomial::QwtSplinePolynomial( double a3, double a2, double a1 ) + : c3( a3 ) + , c2( a2 ) + , c1( a1 ) +{ +} + +/*! + \param other Other polynomial + \return true, when both polynomials have the same coefficients + */ +inline bool QwtSplinePolynomial::operator==( const QwtSplinePolynomial& other ) const +{ + return ( c3 == other.c3 ) && ( c2 == other.c2 ) && ( c1 == other.c1 ); +} + +/*! + \param other Other polynomial + \return true, when the polynomials have different coefficients + */ +inline bool QwtSplinePolynomial::operator!=( const QwtSplinePolynomial& other ) const +{ + return ( !( *this == other ) ); +} + +/*! + Calculate the value of a polynomial for a given x + + \param x Parameter + \return Value at x + */ +inline double QwtSplinePolynomial::valueAt( double x ) const +{ + return ( ( ( c3 * x ) + c2 ) * x + c1 ) * x; +} + +/*! + Calculate the value of the first derivate of a polynomial for a given x + + \param x Parameter + \return Slope at x + */ +inline double QwtSplinePolynomial::slopeAt( double x ) const +{ + return ( 3.0 * c3 * x + 2.0 * c2 ) * x + c1; +} + +/*! + Calculate the value of the second derivate of a polynomial for a given x + + \param x Parameter + \return Curvature at x + */ +inline double QwtSplinePolynomial::curvatureAt( double x ) const +{ + return 6.0 * c3 * x + 2.0 * c2; +} + +/*! + Find the coefficients for the polynomial including 2 points with + specific values for the 1st derivates at these points. + + \param p1 First point + \param m1 Value of the first derivate at p1 + \param p2 Second point + \param m2 Value of the first derivate at p2 + + \return Coefficients of the polynomials + \note The missing constant term of the polynomial is p1.y() + */ +inline QwtSplinePolynomial QwtSplinePolynomial::fromSlopes( + const QPointF& p1, double m1, const QPointF& p2, double m2 ) +{ + return fromSlopes( p2.x() - p1.x(), p2.y() - p1.y(), m1, m2 ); +} + +/*! + Find the coefficients for the polynomial from the offset between 2 points + and specific values for the 1st derivates at these points. + + \param dx X-offset + \param dy Y-offset + \param m1 Value of the first derivate at p1 + \param m2 Value of the first derivate at p2 + + \return Coefficients of the polynomials + */ +inline QwtSplinePolynomial QwtSplinePolynomial::fromSlopes( + double dx, double dy, double m1, double m2 ) +{ + const double c2 = ( 3.0 * dy / dx - 2 * m1 - m2 ) / dx; + const double c3 = ( ( m2 - m1 ) / dx - 2.0 * c2 ) / ( 3.0 * dx ); + + return QwtSplinePolynomial( c3, c2, m1 ); +} + +/*! + Find the coefficients for the polynomial including 2 points with + specific values for the 2nd derivates at these points. + + \param p1 First point + \param cv1 Value of the second derivate at p1 + \param p2 Second point + \param cv2 Value of the second derivate at p2 + + \return Coefficients of the polynomials + \note The missing constant term of the polynomial is p1.y() + */ +inline QwtSplinePolynomial QwtSplinePolynomial::fromCurvatures( + const QPointF& p1, double cv1, const QPointF& p2, double cv2 ) +{ + return fromCurvatures( p2.x() - p1.x(), p2.y() - p1.y(), cv1, cv2 ); +} + +/*! + Find the coefficients for the polynomial from the offset between 2 points + and specific values for the 2nd derivates at these points. + + \param dx X-offset + \param dy Y-offset + \param cv1 Value of the second derivate at p1 + \param cv2 Value of the second derivate at p2 + + \return Coefficients of the polynomials + */ +inline QwtSplinePolynomial QwtSplinePolynomial::fromCurvatures( + double dx, double dy, double cv1, double cv2 ) +{ + const double c3 = ( cv2 - cv1 ) / ( 6.0 * dx ); + const double c2 = 0.5 * cv1; + const double c1 = dy / dx - ( c3 * dx + c2 ) * dx; + + return QwtSplinePolynomial( c3, c2, c1 ); +} + +#ifndef QT_NO_DEBUG_STREAM + +class QDebug; +QWT_EXPORT QDebug operator<<( QDebug, const QwtSplinePolynomial& ); + +#endif + +#endif diff --git a/libs/qwt/src/qwt_symbol.cpp b/libs/qwt/src/qwt_symbol.cpp new file mode 100644 index 00000000..f1c7ac73 --- /dev/null +++ b/libs/qwt/src/qwt_symbol.cpp @@ -0,0 +1,1798 @@ +/****************************************************************************** + * 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_symbol.h" +#include "qwt_painter.h" +#include "qwt_graphic.h" +#include "qwt_math.h" + +#include +#include +#include +#include +#ifndef QWT_NO_SVG +#include +#endif + +namespace QwtTriangle +{ + enum Type + { + Left, + Right, + Up, + Down + }; +} + +static QwtGraphic qwtPathGraphic( const QPainterPath& path, + const QPen& pen, const QBrush& brush ) +{ + QwtGraphic graphic; + graphic.setRenderHint( QwtGraphic::RenderPensUnscaled ); + + QPainter painter( &graphic ); + painter.setPen( pen ); + painter.setBrush( brush ); + painter.drawPath( path ); + painter.end(); + + return graphic; +} + +static inline QRectF qwtScaledBoundingRect( + const QwtGraphic& graphic, const QSizeF size ) +{ + QSizeF scaledSize = size; + if ( scaledSize.isEmpty() ) + scaledSize = graphic.defaultSize(); + + const QSizeF sz = graphic.controlPointRect().size(); + + double sx = 1.0; + if ( sz.width() > 0.0 ) + sx = scaledSize.width() / sz.width(); + + double sy = 1.0; + if ( sz.height() > 0.0 ) + sy = scaledSize.height() / sz.height(); + + return graphic.scaledBoundingRect( sx, sy ); +} + +static inline void qwtDrawPixmapSymbols( QPainter* painter, + const QPointF* points, int numPoints, const QwtSymbol& symbol ) +{ + QSize size = symbol.size(); + if ( size.isEmpty() ) + size = symbol.pixmap().size(); + + const QTransform transform = painter->transform(); + if ( transform.isScaling() ) + { + const QRect r( 0, 0, size.width(), size.height() ); + size = transform.mapRect( r ).size(); + } + + QPixmap pm = symbol.pixmap(); + if ( pm.size() != size ) + pm = pm.scaled( size ); + + QPointF pinPoint( 0.5 * size.width(), 0.5 * size.height() ); + if ( symbol.isPinPointEnabled() ) + pinPoint = symbol.pinPoint(); + + painter->resetTransform(); + + for ( int i = 0; i < numPoints; i++ ) + { + const QPointF pos = transform.map( points[i] ) - pinPoint; + + QwtPainter::drawPixmap( painter, + QRect( pos.toPoint(), pm.size() ), pm ); + } +} + +#ifndef QWT_NO_SVG + +static inline void qwtDrawSvgSymbols( QPainter* painter, + const QPointF* points, int numPoints, + QSvgRenderer* renderer, const QwtSymbol& symbol ) +{ + if ( renderer == NULL || !renderer->isValid() ) + return; + + const QRectF viewBox = renderer->viewBoxF(); + if ( viewBox.isEmpty() ) + return; + + QSizeF sz = symbol.size(); + if ( !sz.isValid() ) + sz = viewBox.size(); + + const double sx = sz.width() / viewBox.width(); + const double sy = sz.height() / viewBox.height(); + + QPointF pinPoint = viewBox.center(); + if ( symbol.isPinPointEnabled() ) + pinPoint = symbol.pinPoint(); + + const double dx = sx * ( pinPoint.x() - viewBox.left() ); + const double dy = sy * ( pinPoint.y() - viewBox.top() ); + + for ( int i = 0; i < numPoints; i++ ) + { + const double x = points[i].x() - dx; + const double y = points[i].y() - dy; + + renderer->render( painter, + QRectF( x, y, sz.width(), sz.height() ) ); + } +} + +#endif + +static inline void qwtDrawGraphicSymbols( QPainter* painter, + const QPointF* points, int numPoints, const QwtGraphic& graphic, + const QwtSymbol& symbol ) +{ + const QRectF pointRect = graphic.controlPointRect(); + if ( pointRect.isEmpty() ) + return; + + double sx = 1.0; + double sy = 1.0; + + const QSize sz = symbol.size(); + if ( sz.isValid() ) + { + sx = sz.width() / pointRect.width(); + sy = sz.height() / pointRect.height(); + } + + QPointF pinPoint = pointRect.center(); + if ( symbol.isPinPointEnabled() ) + pinPoint = symbol.pinPoint(); + + const QTransform transform = painter->transform(); + + for ( int i = 0; i < numPoints; i++ ) + { + QTransform tr = transform; + tr.translate( points[i].x(), points[i].y() ); + tr.scale( sx, sy ); + tr.translate( -pinPoint.x(), -pinPoint.y() ); + + painter->setTransform( tr ); + + graphic.render( painter ); + } + + painter->setTransform( transform ); +} + +static inline void qwtDrawEllipseSymbols( QPainter* painter, + const QPointF* points, int numPoints, const QwtSymbol& symbol ) +{ + painter->setBrush( symbol.brush() ); + painter->setPen( symbol.pen() ); + + const QSize size = symbol.size(); + + if ( QwtPainter::roundingAlignment( painter ) ) + { + const int sw = size.width(); + const int sh = size.height(); + const int sw2 = size.width() / 2; + const int sh2 = size.height() / 2; + + for ( int i = 0; i < numPoints; i++ ) + { + const int x = qRound( points[i].x() ); + const int y = qRound( points[i].y() ); + + const QRectF r( x - sw2, y - sh2, sw, sh ); + QwtPainter::drawEllipse( painter, r ); + } + } + else + { + const double sw = size.width(); + const double sh = size.height(); + const double sw2 = 0.5 * size.width(); + const double sh2 = 0.5 * size.height(); + + for ( int i = 0; i < numPoints; i++ ) + { + const double x = points[i].x(); + const double y = points[i].y(); + + const QRectF r( x - sw2, y - sh2, sw, sh ); + QwtPainter::drawEllipse( painter, r ); + } + } +} + +static inline void qwtDrawRectSymbols( QPainter* painter, + const QPointF* points, int numPoints, const QwtSymbol& symbol ) +{ + const QSize size = symbol.size(); + + QPen pen = symbol.pen(); + pen.setJoinStyle( Qt::MiterJoin ); + painter->setPen( pen ); + painter->setBrush( symbol.brush() ); + painter->setRenderHint( QPainter::Antialiasing, false ); + + if ( QwtPainter::roundingAlignment( painter ) ) + { + const int sw = size.width(); + const int sh = size.height(); + const int sw2 = size.width() / 2; + const int sh2 = size.height() / 2; + + for ( int i = 0; i < numPoints; i++ ) + { + const int x = qRound( points[i].x() ); + const int y = qRound( points[i].y() ); + + const QRect r( x - sw2, y - sh2, sw, sh ); + QwtPainter::drawRect( painter, r ); + } + } + else + { + const double sw = size.width(); + const double sh = size.height(); + const double sw2 = 0.5 * size.width(); + const double sh2 = 0.5 * size.height(); + + for ( int i = 0; i < numPoints; i++ ) + { + const double x = points[i].x(); + const double y = points[i].y(); + + const QRectF r( x - sw2, y - sh2, sw, sh ); + QwtPainter::drawRect( painter, r ); + } + } +} + +static inline void qwtDrawDiamondSymbols( QPainter* painter, + const QPointF* points, int numPoints, const QwtSymbol& symbol ) +{ + const QSize size = symbol.size(); + + QPen pen = symbol.pen(); + pen.setJoinStyle( Qt::MiterJoin ); + painter->setPen( pen ); + painter->setBrush( symbol.brush() ); + + if ( QwtPainter::roundingAlignment( painter ) ) + { + for ( int i = 0; i < numPoints; i++ ) + { + const int x = qRound( points[i].x() ); + const int y = qRound( points[i].y() ); + + const int x1 = x - size.width() / 2; + const int y1 = y - size.height() / 2; + const int x2 = x1 + size.width(); + const int y2 = y1 + size.height(); + + QPolygonF polygon; + polygon += QPointF( x, y1 ); + polygon += QPointF( x1, y ); + polygon += QPointF( x, y2 ); + polygon += QPointF( x2, y ); + + QwtPainter::drawPolygon( painter, polygon ); + } + } + else + { + for ( int i = 0; i < numPoints; i++ ) + { + const QPointF& pos = points[i]; + + const double x1 = pos.x() - 0.5 * size.width(); + const double y1 = pos.y() - 0.5 * size.height(); + const double x2 = x1 + size.width(); + const double y2 = y1 + size.height(); + + QPolygonF polygon; + polygon += QPointF( pos.x(), y1 ); + polygon += QPointF( x2, pos.y() ); + polygon += QPointF( pos.x(), y2 ); + polygon += QPointF( x1, pos.y() ); + + QwtPainter::drawPolygon( painter, polygon ); + } + } +} + +static inline void qwtDrawTriangleSymbols( + QPainter* painter, QwtTriangle::Type type, + const QPointF* points, int numPoints, + const QwtSymbol& symbol ) +{ + const QSize size = symbol.size(); + + QPen pen = symbol.pen(); + pen.setJoinStyle( Qt::MiterJoin ); + painter->setPen( pen ); + + painter->setBrush( symbol.brush() ); + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + double sw2 = 0.5 * size.width(); + double sh2 = 0.5 * size.height(); + + if ( doAlign ) + { + sw2 = std::floor( sw2 ); + sh2 = std::floor( sh2 ); + } + + QPolygonF triangle( 3 ); + QPointF* trianglePoints = triangle.data(); + + for ( int i = 0; i < numPoints; i++ ) + { + const QPointF& pos = points[i]; + + double x = pos.x(); + double y = pos.y(); + + if ( doAlign ) + { + x = qRound( x ); + y = qRound( y ); + } + + const double x1 = x - sw2; + const double x2 = x1 + size.width(); + const double y1 = y - sh2; + const double y2 = y1 + size.height(); + + switch ( type ) + { + case QwtTriangle::Left: + { + trianglePoints[0].rx() = x2; + trianglePoints[0].ry() = y1; + + trianglePoints[1].rx() = x1; + trianglePoints[1].ry() = y; + + trianglePoints[2].rx() = x2; + trianglePoints[2].ry() = y2; + + break; + } + case QwtTriangle::Right: + { + trianglePoints[0].rx() = x1; + trianglePoints[0].ry() = y1; + + trianglePoints[1].rx() = x2; + trianglePoints[1].ry() = y; + + trianglePoints[2].rx() = x1; + trianglePoints[2].ry() = y2; + + break; + } + case QwtTriangle::Up: + { + trianglePoints[0].rx() = x1; + trianglePoints[0].ry() = y2; + + trianglePoints[1].rx() = x; + trianglePoints[1].ry() = y1; + + trianglePoints[2].rx() = x2; + trianglePoints[2].ry() = y2; + + break; + } + case QwtTriangle::Down: + { + trianglePoints[0].rx() = x1; + trianglePoints[0].ry() = y1; + + trianglePoints[1].rx() = x; + trianglePoints[1].ry() = y2; + + trianglePoints[2].rx() = x2; + trianglePoints[2].ry() = y1; + + break; + } + } + QwtPainter::drawPolygon( painter, triangle ); + } +} + +static inline void qwtDrawLineSymbols( + QPainter* painter, int orientations, + const QPointF* points, int numPoints, const QwtSymbol& symbol ) +{ + const QSize size = symbol.size(); + + int off = 0; + + QPen pen = symbol.pen(); + if ( pen.width() > 1 ) + { + pen.setCapStyle( Qt::FlatCap ); + off = 1; + } + + painter->setPen( pen ); + painter->setRenderHint( QPainter::Antialiasing, false ); + + if ( QwtPainter::roundingAlignment( painter ) ) + { + const int sw = qwtFloor( size.width() ); + const int sh = qwtFloor( size.height() ); + const int sw2 = size.width() / 2; + const int sh2 = size.height() / 2; + + for ( int i = 0; i < numPoints; i++ ) + { + if ( orientations & Qt::Horizontal ) + { + const int x = qRound( points[i].x() ) - sw2; + const int y = qRound( points[i].y() ); + + QwtPainter::drawLine( painter, x, y, x + sw + off, y ); + } + if ( orientations & Qt::Vertical ) + { + const int x = qRound( points[i].x() ); + const int y = qRound( points[i].y() ) - sh2; + + QwtPainter::drawLine( painter, x, y, x, y + sh + off ); + } + } + } + else + { + const double sw = size.width(); + const double sh = size.height(); + const double sw2 = 0.5 * size.width(); + const double sh2 = 0.5 * size.height(); + + for ( int i = 0; i < numPoints; i++ ) + { + if ( orientations & Qt::Horizontal ) + { + const double x = points[i].x() - sw2; + const double y = points[i].y(); + + QwtPainter::drawLine( painter, x, y, x + sw, y ); + } + if ( orientations & Qt::Vertical ) + { + const double y = points[i].y() - sh2; + const double x = points[i].x(); + + QwtPainter::drawLine( painter, x, y, x, y + sh ); + } + } + } +} + +static inline void qwtDrawXCrossSymbols( QPainter* painter, + const QPointF* points, int numPoints, const QwtSymbol& symbol ) +{ + const QSize size = symbol.size(); + int off = 0; + + QPen pen = symbol.pen(); + if ( pen.width() > 1 ) + { + pen.setCapStyle( Qt::FlatCap ); + off = 1; + } + painter->setPen( pen ); + + + if ( QwtPainter::roundingAlignment( painter ) ) + { + const int sw = size.width(); + const int sh = size.height(); + const int sw2 = size.width() / 2; + const int sh2 = size.height() / 2; + + for ( int i = 0; i < numPoints; i++ ) + { + const QPointF& pos = points[i]; + + const int x = qRound( pos.x() ); + const int y = qRound( pos.y() ); + + const int x1 = x - sw2; + const int x2 = x1 + sw + off; + const int y1 = y - sh2; + const int y2 = y1 + sh + off; + + QwtPainter::drawLine( painter, x1, y1, x2, y2 ); + QwtPainter::drawLine( painter, x2, y1, x1, y2 ); + } + } + else + { + const double sw = size.width(); + const double sh = size.height(); + const double sw2 = 0.5 * size.width(); + const double sh2 = 0.5 * size.height(); + + for ( int i = 0; i < numPoints; i++ ) + { + const QPointF& pos = points[i]; + + const double x1 = pos.x() - sw2; + const double x2 = x1 + sw; + const double y1 = pos.y() - sh2; + const double y2 = y1 + sh; + + QwtPainter::drawLine( painter, x1, y1, x2, y2 ); + QwtPainter::drawLine( painter, x1, y2, x2, y1 ); + } + } +} + +static inline void qwtDrawStar1Symbols( QPainter* painter, + const QPointF* points, int numPoints, const QwtSymbol& symbol ) +{ + const QSize size = symbol.size(); + painter->setPen( symbol.pen() ); + + if ( QwtPainter::roundingAlignment( painter ) ) + { + QRect r( 0, 0, size.width(), size.height() ); + + for ( int i = 0; i < numPoints; i++ ) + { + r.moveCenter( points[i].toPoint() ); + + const double sqrt1_2 = 0.70710678118654752440; /* 1/sqrt(2) */ + + const double d1 = r.width() / 2.0 * ( 1.0 - sqrt1_2 ); + + QwtPainter::drawLine( painter, + qRound( r.left() + d1 ), qRound( r.top() + d1 ), + qRound( r.right() - d1 ), qRound( r.bottom() - d1 ) ); + QwtPainter::drawLine( painter, + qRound( r.left() + d1 ), qRound( r.bottom() - d1 ), + qRound( r.right() - d1), qRound( r.top() + d1 ) ); + + const QPoint c = r.center(); + + QwtPainter::drawLine( painter, + c.x(), r.top(), c.x(), r.bottom() ); + QwtPainter::drawLine( painter, + r.left(), c.y(), r.right(), c.y() ); + } + } + else + { + QRectF r( 0, 0, size.width(), size.height() ); + + for ( int i = 0; i < numPoints; i++ ) + { + r.moveCenter( points[i] ); + + const double sqrt1_2 = 0.70710678118654752440; /* 1/sqrt(2) */ + + const QPointF c = r.center(); + const double d1 = r.width() / 2.0 * ( 1.0 - sqrt1_2 ); + + QwtPainter::drawLine( painter, + r.left() + d1, r.top() + d1, + r.right() - d1, r.bottom() - d1 ); + QwtPainter::drawLine( painter, + r.left() + d1, r.bottom() - d1, + r.right() - d1, r.top() + d1 ); + QwtPainter::drawLine( painter, + c.x(), r.top(), + c.x(), r.bottom() ); + QwtPainter::drawLine( painter, + r.left(), c.y(), + r.right(), c.y() ); + } + } +} + +static inline void qwtDrawStar2Symbols( QPainter* painter, + const QPointF* points, int numPoints, const QwtSymbol& symbol ) +{ + QPen pen = symbol.pen(); + if ( pen.width() > 1 ) + pen.setCapStyle( Qt::FlatCap ); + pen.setJoinStyle( Qt::MiterJoin ); + painter->setPen( pen ); + + painter->setBrush( symbol.brush() ); + + const double cos30 = 0.866025; // cos(30°) + + const double dy = 0.25 * symbol.size().height(); + const double dx = 0.5 * symbol.size().width() * cos30 / 3.0; + + QPolygonF star( 12 ); + QPointF* starPoints = star.data(); + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + for ( int i = 0; i < numPoints; i++ ) + { + double x = points[i].x(); + double y = points[i].y(); + if ( doAlign ) + { + x = qRound( x ); + y = qRound( y ); + } + + double x1 = x - 3 * dx; + double y1 = y - 2 * dy; + if ( doAlign ) + { + x1 = qRound( x - 3 * dx ); + y1 = qRound( 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; + + starPoints[0].rx() = x4; + starPoints[0].ry() = y1; + + starPoints[1].rx() = x5; + starPoints[1].ry() = y2; + + starPoints[2].rx() = x7; + starPoints[2].ry() = y2; + + starPoints[3].rx() = x6; + starPoints[3].ry() = y3; + + starPoints[4].rx() = x7; + starPoints[4].ry() = y4; + + starPoints[5].rx() = x5; + starPoints[5].ry() = y4; + + starPoints[6].rx() = x4; + starPoints[6].ry() = y5; + + starPoints[7].rx() = x3; + starPoints[7].ry() = y4; + + starPoints[8].rx() = x1; + starPoints[8].ry() = y4; + + starPoints[9].rx() = x2; + starPoints[9].ry() = y3; + + starPoints[10].rx() = x1; + starPoints[10].ry() = y2; + + starPoints[11].rx() = x3; + starPoints[11].ry() = y2; + + QwtPainter::drawPolygon( painter, star ); + } +} + +static inline void qwtDrawHexagonSymbols( QPainter* painter, + const QPointF* points, int numPoints, const QwtSymbol& symbol ) +{ + painter->setBrush( symbol.brush() ); + painter->setPen( symbol.pen() ); + + const double cos30 = 0.866025; // cos(30°) + const double dx = 0.5 * ( symbol.size().width() - cos30 ); + + const double dy = 0.25 * symbol.size().height(); + + QPolygonF hexaPolygon( 6 ); + QPointF* hexaPoints = hexaPolygon.data(); + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + for ( int i = 0; i < numPoints; i++ ) + { + double x = points[i].x(); + double y = points[i].y(); + if ( doAlign ) + { + x = qRound( x ); + y = qRound( y ); + } + + double x1 = x - dx; + double y1 = y - 2 * dy; + if ( doAlign ) + { + x1 = std::ceil( x1 ); + y1 = std::ceil( y1 ); + } + + 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; + + hexaPoints[0].rx() = x2; + hexaPoints[0].ry() = y1; + + hexaPoints[1].rx() = x3; + hexaPoints[1].ry() = y2; + + hexaPoints[2].rx() = x3; + hexaPoints[2].ry() = y3; + + hexaPoints[3].rx() = x2; + hexaPoints[3].ry() = y4; + + hexaPoints[4].rx() = x1; + hexaPoints[4].ry() = y3; + + hexaPoints[5].rx() = x1; + hexaPoints[5].ry() = y2; + + QwtPainter::drawPolygon( painter, hexaPolygon ); + } +} + +class QwtSymbol::PrivateData +{ + public: + PrivateData( QwtSymbol::Style st, const QBrush& br, + const QPen& pn, const QSize& sz ) + : style( st ) + , size( sz ) + , brush( br ) + , pen( pn ) + , isPinPointEnabled( false ) + { + cache.policy = QwtSymbol::AutoCache; +#ifndef QWT_NO_SVG + svg.renderer = NULL; +#endif + } + + ~PrivateData() + { +#ifndef QWT_NO_SVG + delete svg.renderer; +#endif + } + + Style style; + QSize size; + QBrush brush; + QPen pen; + + bool isPinPointEnabled; + QPointF pinPoint; + + struct Path + { + QPainterPath path; + QwtGraphic graphic; + + } path; + + struct Pixmap + { + QPixmap pixmap; + + } pixmap; + + struct Graphic + { + QwtGraphic graphic; + + } graphic; + +#ifndef QWT_NO_SVG + struct SVG + { + QSvgRenderer* renderer; + } svg; +#endif + + struct PaintCache + { + QwtSymbol::CachePolicy policy; + QPixmap pixmap; + + } cache; +}; + +/*! + Default Constructor + \param style Symbol Style + + The symbol is constructed with gray interior, + black outline with zero width, no size and style 'NoSymbol'. + */ +QwtSymbol::QwtSymbol( Style style ) +{ + m_data = new PrivateData( style, QBrush( Qt::gray ), + QPen( Qt::black, 0 ), QSize() ); +} + +/*! + \brief Constructor + \param style Symbol Style + \param brush brush to fill the interior + \param pen outline pen + \param size size + + \sa setStyle(), setBrush(), setPen(), setSize() + */ +QwtSymbol::QwtSymbol( QwtSymbol::Style style, const QBrush& brush, + const QPen& pen, const QSize& size ) +{ + m_data = new PrivateData( style, brush, pen, size ); +} + +/*! + \brief Constructor + + The symbol gets initialized by a painter path. The style is + set to QwtSymbol::Path, the size is set to empty ( the path + is displayed unscaled ). + + \param path painter path + \param brush brush to fill the interior + \param pen outline pen + + \sa setPath(), setBrush(), setPen(), setSize() + */ + +QwtSymbol::QwtSymbol( const QPainterPath& path, + const QBrush& brush, const QPen& pen ) +{ + m_data = new PrivateData( QwtSymbol::Path, brush, pen, QSize() ); + setPath( path ); +} + +//! Destructor +QwtSymbol::~QwtSymbol() +{ + delete m_data; +} + +/*! + Change the cache policy + + The default policy is AutoCache + + \param policy Cache policy + \sa CachePolicy, cachePolicy() + */ +void QwtSymbol::setCachePolicy( + QwtSymbol::CachePolicy policy ) +{ + if ( m_data->cache.policy != policy ) + { + m_data->cache.policy = policy; + invalidateCache(); + } +} + +/*! + \return Cache policy + \sa CachePolicy, setCachePolicy() + */ +QwtSymbol::CachePolicy QwtSymbol::cachePolicy() const +{ + return m_data->cache.policy; +} + +/*! + \brief Set a painter path as symbol + + The symbol is represented by a painter path, where the + origin ( 0, 0 ) of the path coordinate system is mapped to + the position of the symbol. + + When the symbol has valid size the painter path gets scaled + to fit into the size. Otherwise the symbol size depends on + the bounding rectangle of the path. + + \par Example + The following code defines a symbol drawing an arrow: + + \code + #include + + QwtSymbol *symbol = new QwtSymbol(); + + QPen pen( Qt::black, 2 ); + pen.setJoinStyle( Qt::MiterJoin ); + + symbol->setPen( pen ); + symbol->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 ); + + symbol->setPath( path ); + symbol->setPinPoint( QPointF( 0.0, 0.0 ) ); + + setSize( 10, 14 ); + \endcode + + \param path Painter path + + \note The style is implicitly set to QwtSymbol::Path. + \sa path(), setSize() + */ +void QwtSymbol::setPath( const QPainterPath& path ) +{ + m_data->style = QwtSymbol::Path; + m_data->path.path = path; + m_data->path.graphic.reset(); +} + +/*! + \return Painter path for displaying the symbol + \sa setPath() + */ +const QPainterPath& QwtSymbol::path() const +{ + return m_data->path.path; +} + +/*! + Set a pixmap as symbol + + \param pixmap Pixmap + + \sa pixmap(), setGraphic() + + \note the style() is set to QwtSymbol::Pixmap + \note brush() and pen() have no effect + */ +void QwtSymbol::setPixmap( const QPixmap& pixmap ) +{ + m_data->style = QwtSymbol::Pixmap; + m_data->pixmap.pixmap = pixmap; +} + +/*! + \return Assigned pixmap + \sa setPixmap() + */ +const QPixmap& QwtSymbol::pixmap() const +{ + return m_data->pixmap.pixmap; +} + +/*! + Set a graphic as symbol + + \param graphic Graphic + + \sa graphic(), setPixmap() + + \note the style() is set to QwtSymbol::Graphic + \note brush() and pen() have no effect + */ +void QwtSymbol::setGraphic( const QwtGraphic& graphic ) +{ + m_data->style = QwtSymbol::Graphic; + m_data->graphic.graphic = graphic; +} + +/*! + \return Assigned graphic + \sa setGraphic() + */ +const QwtGraphic& QwtSymbol::graphic() const +{ + return m_data->graphic.graphic; +} + +#ifndef QWT_NO_SVG + +/*! + Set a SVG icon as symbol + + \param svgDocument SVG icon + + \sa setGraphic(), setPixmap() + + \note the style() is set to QwtSymbol::SvgDocument + \note brush() and pen() have no effect + */ +void QwtSymbol::setSvgDocument( const QByteArray& svgDocument ) +{ + m_data->style = QwtSymbol::SvgDocument; + if ( m_data->svg.renderer == NULL ) + m_data->svg.renderer = new QSvgRenderer(); + + m_data->svg.renderer->load( svgDocument ); +} + +#endif + +/*! + \brief Specify the symbol's size + + If the 'h' parameter is left out or less than 0, + and the 'w' parameter is greater than or equal to 0, + the symbol size will be set to (w,w). + + \param width Width + \param height Height (defaults to -1) + + \sa size() + */ +void QwtSymbol::setSize( int width, int height ) +{ + if ( ( width >= 0 ) && ( height < 0 ) ) + height = width; + + setSize( QSize( width, height ) ); +} + +/*! + Set the symbol's size + \param size Size + + \sa size() + */ +void QwtSymbol::setSize( const QSize& size ) +{ + if ( size.isValid() && size != m_data->size ) + { + m_data->size = size; + invalidateCache(); + } +} + +/*! + \return Size + \sa setSize() + */ +const QSize& QwtSymbol::size() const +{ + return m_data->size; +} + +/*! + \brief Assign a brush + + The brush is used to draw the interior of the symbol. + \param brush Brush + + \sa brush() + */ +void QwtSymbol::setBrush( const QBrush& brush ) +{ + if ( brush != m_data->brush ) + { + m_data->brush = brush; + invalidateCache(); + + if ( m_data->style == QwtSymbol::Path ) + m_data->path.graphic.reset(); + } +} + +/*! + \return Brush + \sa setBrush() + */ +const QBrush& QwtSymbol::brush() const +{ + return m_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 QwtSymbol::setPen( const QColor& color, + qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen + + The pen is used to draw the symbol's outline. + + \param pen Pen + \sa pen(), setBrush() + */ +void QwtSymbol::setPen( const QPen& pen ) +{ + if ( pen != m_data->pen ) + { + m_data->pen = pen; + invalidateCache(); + + if ( m_data->style == QwtSymbol::Path ) + m_data->path.graphic.reset(); + } +} + +/*! + \return Pen + \sa setPen(), brush() + */ +const QPen& QwtSymbol::pen() const +{ + return m_data->pen; +} + +/*! + \brief Set the color of the symbol + + Change the color of the brush for symbol types with a filled area. + For all other symbol types the color will be assigned to the pen. + + \param color Color + + \sa setBrush(), setPen(), brush(), pen() + */ +void QwtSymbol::setColor( const QColor& color ) +{ + switch ( m_data->style ) + { + case QwtSymbol::Ellipse: + case QwtSymbol::Rect: + case QwtSymbol::Diamond: + case QwtSymbol::Triangle: + case QwtSymbol::UTriangle: + case QwtSymbol::DTriangle: + case QwtSymbol::RTriangle: + case QwtSymbol::LTriangle: + case QwtSymbol::Star2: + case QwtSymbol::Hexagon: + { + if ( m_data->brush.color() != color ) + { + m_data->brush.setColor( color ); + invalidateCache(); + } + break; + } + case QwtSymbol::Cross: + case QwtSymbol::XCross: + case QwtSymbol::HLine: + case QwtSymbol::VLine: + case QwtSymbol::Star1: + { + if ( m_data->pen.color() != color ) + { + m_data->pen.setColor( color ); + invalidateCache(); + } + break; + } + default: + { + if ( m_data->brush.color() != color || + m_data->pen.color() != color ) + { + invalidateCache(); + } + + m_data->brush.setColor( color ); + m_data->pen.setColor( color ); + } + } +} + +/*! + \brief Set and enable a pin point + + The position of a complex symbol is not always aligned to its center + ( f.e an arrow, where the peak points to a position ). The pin point + defines the position inside of a Pixmap, Graphic, SvgDocument + or PainterPath symbol where the represented point has to + be aligned to. + + \param pos Position + \param enable En/Disable the pin point alignment + + \sa pinPoint(), setPinPointEnabled() + */ +void QwtSymbol::setPinPoint( const QPointF& pos, bool enable ) +{ + if ( m_data->pinPoint != pos ) + { + m_data->pinPoint = pos; + if ( m_data->isPinPointEnabled ) + { + invalidateCache(); + } + } + + setPinPointEnabled( enable ); +} + +/*! + \return Pin point + \sa setPinPoint(), setPinPointEnabled() + */ +QPointF QwtSymbol::pinPoint() const +{ + return m_data->pinPoint; +} + +/*! + En/Disable the pin point alignment + + \param on Enabled, when on is true + \sa setPinPoint(), isPinPointEnabled() + */ +void QwtSymbol::setPinPointEnabled( bool on ) +{ + if ( m_data->isPinPointEnabled != on ) + { + m_data->isPinPointEnabled = on; + invalidateCache(); + } +} + +/*! + \return True, when the pin point translation is enabled + \sa setPinPoint(), setPinPointEnabled() + */ +bool QwtSymbol::isPinPointEnabled() const +{ + return m_data->isPinPointEnabled; +} + +/*! + Render an array of symbols + + Painting several symbols is more effective than drawing symbols + one by one, as a couple of layout calculations and setting of pen/brush + can be done once for the complete array. + + \param painter Painter + \param points Array of points + \param numPoints Number of points + */ +void QwtSymbol::drawSymbols( QPainter* painter, + const QPointF* points, int numPoints ) const +{ + if ( numPoints <= 0 ) + return; + + bool useCache = false; + + // Don't use the pixmap, when the paint device + // could generate scalable vectors + + if ( QwtPainter::roundingAlignment( painter ) && + !painter->transform().isScaling() ) + { + if ( m_data->cache.policy == QwtSymbol::Cache ) + { + useCache = true; + } + else if ( m_data->cache.policy == QwtSymbol::AutoCache ) + { + switch( painter->paintEngine()->type() ) + { + case QPaintEngine::OpenGL: + case QPaintEngine::OpenGL2: + { + // using a FBO as cache ? + useCache = false; + break; + } + case QPaintEngine::OpenVG: + case QPaintEngine::SVG: + case QPaintEngine::Pdf: + case QPaintEngine::Picture: + { + // vector graphics + useCache = false; + break; + } + case QPaintEngine::X11: + { + switch( m_data->style ) + { + case QwtSymbol::XCross: + case QwtSymbol::HLine: + case QwtSymbol::VLine: + case QwtSymbol::Cross: + { + // for the very simple shapes using vector graphics is + // usually faster. + + useCache = false; + break; + } + + case QwtSymbol::Pixmap: + { + if ( m_data->size.isEmpty() || + m_data->size == m_data->pixmap.pixmap.size() ) + { + // no need to have a pixmap cache for a pixmap + // of the same size + + useCache = false; + } + break; + } + default: + break; + } + break; + } + default: + { + useCache = true; + } + } + } + } + + if ( useCache ) + { + const QRect br = boundingRect(); + + if ( m_data->cache.pixmap.isNull() ) + { + m_data->cache.pixmap = QwtPainter::backingStore( NULL, br.size() ); + m_data->cache.pixmap.fill( Qt::transparent ); + + QPainter p( &m_data->cache.pixmap ); + p.setRenderHints( painter->renderHints() ); + p.translate( -br.topLeft() ); + + const QPointF pos( 0.0, 0.0 ); + renderSymbols( &p, &pos, 1 ); + } + + const int dx = br.left(); + const int dy = br.top(); + + for ( int i = 0; i < numPoints; i++ ) + { + const int left = qRound( points[i].x() ) + dx; + const int top = qRound( points[i].y() ) + dy; + + painter->drawPixmap( left, top, m_data->cache.pixmap ); + } + } + else + { + painter->save(); + renderSymbols( painter, points, numPoints ); + painter->restore(); + } +} + +/*! + \brief Draw the symbol into a rectangle + + The symbol is painted centered and scaled into the target rectangle. + It is always painted uncached and the pin point is ignored. + + This method is primarily intended for drawing a symbol to + the legend. + + \param painter Painter + \param rect Target rectangle for the symbol + */ +void QwtSymbol::drawSymbol( QPainter* painter, const QRectF& rect ) const +{ + if ( m_data->style == QwtSymbol::NoSymbol ) + return; + + if ( m_data->style == QwtSymbol::Graphic ) + { + m_data->graphic.graphic.render( + painter, rect, Qt::KeepAspectRatio ); + } + else if ( m_data->style == QwtSymbol::Path ) + { + if ( m_data->path.graphic.isNull() ) + { + m_data->path.graphic = qwtPathGraphic( + m_data->path.path, m_data->pen, m_data->brush ); + } + + m_data->path.graphic.render( + painter, rect, Qt::KeepAspectRatio ); + return; + } + else if ( m_data->style == QwtSymbol::SvgDocument ) + { +#ifndef QWT_NO_SVG + if ( m_data->svg.renderer ) + { + QRectF scaledRect; + + QSizeF sz = m_data->svg.renderer->viewBoxF().size(); + if ( !sz.isEmpty() ) + { + sz.scale( rect.size(), Qt::KeepAspectRatio ); + scaledRect.setSize( sz ); + scaledRect.moveCenter( rect.center() ); + } + else + { + scaledRect = rect; + } + + m_data->svg.renderer->render( + painter, scaledRect ); + } +#endif + } + else + { + const QRect br = boundingRect(); + + // scale the symbol size to fit into rect. + + const double ratio = qMin( rect.width() / br.width(), + rect.height() / br.height() ); + + painter->save(); + + painter->translate( rect.center() ); + painter->scale( ratio, ratio ); + + const bool isPinPointEnabled = m_data->isPinPointEnabled; + m_data->isPinPointEnabled = false; + + const QPointF pos; + renderSymbols( painter, &pos, 1 ); + + m_data->isPinPointEnabled = isPinPointEnabled; + + painter->restore(); + } +} + +/*! + Render the symbol to series of points + + \param painter Qt painter + \param points Positions of the symbols + \param numPoints Number of points + */ +void QwtSymbol::renderSymbols( QPainter* painter, + const QPointF* points, int numPoints ) const +{ + switch ( m_data->style ) + { + case QwtSymbol::Ellipse: + { + qwtDrawEllipseSymbols( painter, points, numPoints, *this ); + break; + } + case QwtSymbol::Rect: + { + qwtDrawRectSymbols( painter, points, numPoints, *this ); + break; + } + case QwtSymbol::Diamond: + { + qwtDrawDiamondSymbols( painter, points, numPoints, *this ); + break; + } + case QwtSymbol::Cross: + { + qwtDrawLineSymbols( painter, Qt::Horizontal | Qt::Vertical, + points, numPoints, *this ); + break; + } + case QwtSymbol::XCross: + { + qwtDrawXCrossSymbols( painter, points, numPoints, *this ); + break; + } + case QwtSymbol::Triangle: + case QwtSymbol::UTriangle: + { + qwtDrawTriangleSymbols( painter, QwtTriangle::Up, + points, numPoints, *this ); + break; + } + case QwtSymbol::DTriangle: + { + qwtDrawTriangleSymbols( painter, QwtTriangle::Down, + points, numPoints, *this ); + break; + } + case QwtSymbol::RTriangle: + { + qwtDrawTriangleSymbols( painter, QwtTriangle::Right, + points, numPoints, *this ); + break; + } + case QwtSymbol::LTriangle: + { + qwtDrawTriangleSymbols( painter, QwtTriangle::Left, + points, numPoints, *this ); + break; + } + case QwtSymbol::HLine: + { + qwtDrawLineSymbols( painter, Qt::Horizontal, + points, numPoints, *this ); + break; + } + case QwtSymbol::VLine: + { + qwtDrawLineSymbols( painter, Qt::Vertical, + points, numPoints, *this ); + break; + } + case QwtSymbol::Star1: + { + qwtDrawStar1Symbols( painter, points, numPoints, *this ); + break; + } + case QwtSymbol::Star2: + { + qwtDrawStar2Symbols( painter, points, numPoints, *this ); + break; + } + case QwtSymbol::Hexagon: + { + qwtDrawHexagonSymbols( painter, points, numPoints, *this ); + break; + } + case QwtSymbol::Path: + { + if ( m_data->path.graphic.isNull() ) + { + m_data->path.graphic = qwtPathGraphic( m_data->path.path, + m_data->pen, m_data->brush ); + } + + qwtDrawGraphicSymbols( painter, points, numPoints, + m_data->path.graphic, *this ); + break; + } + case QwtSymbol::Pixmap: + { + qwtDrawPixmapSymbols( painter, points, numPoints, *this ); + break; + } + case QwtSymbol::Graphic: + { + qwtDrawGraphicSymbols( painter, points, numPoints, + m_data->graphic.graphic, *this ); + break; + } + case QwtSymbol::SvgDocument: + { +#ifndef QWT_NO_SVG + qwtDrawSvgSymbols( painter, points, numPoints, + m_data->svg.renderer, *this ); +#endif + break; + } + default:; + } +} + +/*! + Calculate the bounding rectangle for a symbol + at position (0,0). + + \return Bounding rectangle + */ +QRect QwtSymbol::boundingRect() const +{ + QRectF rect; + + bool pinPointTranslation = false; + + switch ( m_data->style ) + { + case QwtSymbol::Ellipse: + case QwtSymbol::Rect: + case QwtSymbol::Hexagon: + { + qreal pw = 0.0; + if ( m_data->pen.style() != Qt::NoPen ) + pw = QwtPainter::effectivePenWidth( m_data->pen ); + + rect.setSize( m_data->size + QSizeF( pw, pw ) ); + rect.moveCenter( QPointF( 0.0, 0.0 ) ); + + break; + } + case QwtSymbol::XCross: + case QwtSymbol::Diamond: + case QwtSymbol::Triangle: + case QwtSymbol::UTriangle: + case QwtSymbol::DTriangle: + case QwtSymbol::RTriangle: + case QwtSymbol::LTriangle: + case QwtSymbol::Star1: + case QwtSymbol::Star2: + { + qreal pw = 0.0; + if ( m_data->pen.style() != Qt::NoPen ) + pw = QwtPainter::effectivePenWidth( m_data->pen ); + + rect.setSize( m_data->size + QSizeF( 2 * pw, 2 * pw ) ); + rect.moveCenter( QPointF( 0.0, 0.0 ) ); + break; + } + case QwtSymbol::Path: + { + if ( m_data->path.graphic.isNull() ) + { + m_data->path.graphic = qwtPathGraphic( + m_data->path.path, m_data->pen, m_data->brush ); + } + + rect = qwtScaledBoundingRect( + m_data->path.graphic, m_data->size ); + pinPointTranslation = true; + + break; + } + case QwtSymbol::Pixmap: + { + if ( m_data->size.isEmpty() ) + rect.setSize( m_data->pixmap.pixmap.size() ); + else + rect.setSize( m_data->size ); + + pinPointTranslation = true; + + break; + } + case QwtSymbol::Graphic: + { + rect = qwtScaledBoundingRect( + m_data->graphic.graphic, m_data->size ); + pinPointTranslation = true; + + break; + } +#ifndef QWT_NO_SVG + case QwtSymbol::SvgDocument: + { + if ( m_data->svg.renderer ) + rect = m_data->svg.renderer->viewBoxF(); + + if ( m_data->size.isValid() && !rect.isEmpty() ) + { + QSizeF sz = rect.size(); + + const double sx = m_data->size.width() / sz.width(); + const double sy = m_data->size.height() / sz.height(); + + QTransform transform; + transform.scale( sx, sy ); + + rect = transform.mapRect( rect ); + } + pinPointTranslation = true; + break; + } +#endif + default: + { + rect.setSize( m_data->size ); + rect.moveCenter( QPointF( 0.0, 0.0 ) ); + } + } + + if ( pinPointTranslation ) + { + QPointF pinPoint( 0.0, 0.0 ); + if ( m_data->isPinPointEnabled ) + pinPoint = rect.center() - m_data->pinPoint; + + rect.moveCenter( pinPoint ); + } + + QRect r; + r.setLeft( qwtFloor( rect.left() ) ); + r.setTop( qwtFloor( rect.top() ) ); + r.setRight( qwtCeil( rect.right() ) ); + r.setBottom( qwtCeil( rect.bottom() ) ); + + if ( m_data->style != QwtSymbol::Pixmap ) + r.adjust( -1, -1, 1, 1 ); // for antialiasing + + return r; +} + +/*! + Invalidate the cached symbol pixmap + + The symbol invalidates its cache, whenever an attribute is changed + that has an effect ob how to display a symbol. In case of derived + classes with individual styles ( >= QwtSymbol::UserStyle ) it + might be necessary to call invalidateCache() for attributes + that are relevant for this style. + + \sa CachePolicy, setCachePolicy(), drawSymbols() + */ +void QwtSymbol::invalidateCache() +{ + if ( !m_data->cache.pixmap.isNull() ) + m_data->cache.pixmap = QPixmap(); +} + +/*! + Specify the symbol style + + \param style Style + \sa style() + */ +void QwtSymbol::setStyle( QwtSymbol::Style style ) +{ + if ( m_data->style != style ) + { + m_data->style = style; + invalidateCache(); + } +} + +/*! + \return Current symbol style + \sa setStyle() + */ +QwtSymbol::Style QwtSymbol::style() const +{ + return m_data->style; +} diff --git a/libs/qwt/src/qwt_symbol.h b/libs/qwt/src/qwt_symbol.h new file mode 100644 index 00000000..4999bf61 --- /dev/null +++ b/libs/qwt/src/qwt_symbol.h @@ -0,0 +1,257 @@ +/****************************************************************************** + * 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_SYMBOL_H +#define QWT_SYMBOL_H + +#include "qwt_global.h" + +#include +#include +#include + +class QPainter; +class QSize; +class QBrush; +class QPen; +class QColor; +class QPointF; +class QPainterPath; +class QPixmap; +class QByteArray; +class QwtGraphic; + +//! A class for drawing symbols +class QWT_EXPORT QwtSymbol +{ + public: + /*! + Symbol Style + \sa setStyle(), style() + */ + enum Style + { + //! No Style. The symbol cannot be drawn. + NoSymbol = -1, + + //! Ellipse or circle + Ellipse, + + //! Rectangle + Rect, + + //! Diamond + Diamond, + + //! Triangle pointing upwards + Triangle, + + //! Triangle pointing downwards + DTriangle, + + //! Triangle pointing upwards + UTriangle, + + //! Triangle pointing left + LTriangle, + + //! Triangle pointing right + RTriangle, + + //! Cross (+) + Cross, + + //! Diagonal cross (X) + XCross, + + //! Horizontal line + HLine, + + //! Vertical line + VLine, + + //! X combined with + + Star1, + + //! Six-pointed star + Star2, + + //! Hexagon + Hexagon, + + /*! + The symbol is represented by a painter path, where the + origin ( 0, 0 ) of the path coordinate system is mapped to + the position of the symbol. + + \sa setPath(), path() + */ + Path, + + /*! + The symbol is represented by a pixmap. The pixmap is centered + or aligned to its pin point. + + \sa setPinPoint() + */ + Pixmap, + + /*! + The symbol is represented by a graphic. The graphic is centered + or aligned to its pin point. + + \sa setPinPoint() + */ + Graphic, + + /*! + The symbol is represented by a SVG graphic. The graphic is centered + or aligned to its pin point. + + \sa setPinPoint() + */ + SvgDocument, + + /*! + Styles >= QwtSymbol::UserSymbol are reserved for derived + classes of QwtSymbol that overload drawSymbols() with + additional application specific symbol types. + */ + UserStyle = 1000 + }; + + /*! + Depending on the render engine and the complexity of the + symbol shape it might be faster to render the symbol + to a pixmap and to paint this pixmap. + + F.e. the raster paint engine is a pure software renderer + where in cache mode a draw operation usually ends in + raster operation with the the backing store, that are usually + faster, than the algorithms for rendering polygons. + But the opposite can be expected for graphic pipelines + that can make use of hardware acceleration. + + The default setting is AutoCache + + \sa setCachePolicy(), cachePolicy() + + \note The policy has no effect, when the symbol is painted + to a vector graphics format ( PDF, SVG ). + \warning Since Qt 4.8 raster is the default backend on X11 + */ + + enum CachePolicy + { + //! Don't use a pixmap cache + NoCache, + + //! Always use a pixmap cache + Cache, + + /*! + Use a cache when one of the following conditions is true: + + - The symbol is rendered with the software + renderer ( QPaintEngine::Raster ) + */ + AutoCache + }; + + public: + explicit QwtSymbol( Style = NoSymbol ); + QwtSymbol( Style, const QBrush&, const QPen&, const QSize& ); + QwtSymbol( const QPainterPath&, const QBrush&, const QPen& ); + + virtual ~QwtSymbol(); + + void setCachePolicy( CachePolicy ); + CachePolicy cachePolicy() const; + + void setSize( const QSize& ); + void setSize( int width, int height = -1 ); + const QSize& size() const; + + void setPinPoint( const QPointF& pos, bool enable = true ); + QPointF pinPoint() const; + + void setPinPointEnabled( bool ); + bool isPinPointEnabled() const; + + virtual void setColor( const QColor& ); + + void setBrush( const QBrush& ); + 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; + + void setPath( const QPainterPath& ); + const QPainterPath& path() const; + + void setPixmap( const QPixmap& ); + const QPixmap& pixmap() const; + + void setGraphic( const QwtGraphic& ); + const QwtGraphic& graphic() const; + +#ifndef QWT_NO_SVG + void setSvgDocument( const QByteArray& ); +#endif + + void drawSymbol( QPainter*, const QRectF& ) const; + void drawSymbol( QPainter*, const QPointF& ) const; + void drawSymbols( QPainter*, const QPolygonF& ) const; + void drawSymbols( QPainter*, + const QPointF*, int numPoints ) const; + + virtual QRect boundingRect() const; + void invalidateCache(); + + protected: + virtual void renderSymbols( QPainter*, + const QPointF*, int numPoints ) const; + + private: + Q_DISABLE_COPY(QwtSymbol) + + class PrivateData; + PrivateData* m_data; +}; + +/*! + \brief Draw the symbol at a specified position + + \param painter Painter + \param pos Position of the symbol in screen coordinates + */ +inline void QwtSymbol::drawSymbol( + QPainter* painter, const QPointF& pos ) const +{ + drawSymbols( painter, &pos, 1 ); +} + +/*! + \brief Draw symbols at the specified points + + \param painter Painter + \param points Positions of the symbols in screen coordinates + */ + +inline void QwtSymbol::drawSymbols( + QPainter* painter, const QPolygonF& points ) const +{ + drawSymbols( painter, points.data(), points.size() ); +} + +#endif diff --git a/libs/qwt/src/qwt_system_clock.cpp b/libs/qwt/src/qwt_system_clock.cpp new file mode 100644 index 00000000..bfed27c6 --- /dev/null +++ b/libs/qwt/src/qwt_system_clock.cpp @@ -0,0 +1,40 @@ +/****************************************************************************** + * 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_system_clock.h" +#include + +//! \return true, if the elapsed timer is valid +bool QwtSystemClock::isNull() const +{ + return m_timer.isValid(); +} + +//! Start the elapsed timer +void QwtSystemClock::start() +{ + m_timer.start(); +} + +/*! + Restart the elapsed timer + \return elapsed time in multiples of milliseconds + */ +double QwtSystemClock::restart() +{ + const qint64 nsecs = m_timer.restart(); + return nsecs / 1e6; +} + +//! \return elapsed time in multiples of milliseconds +double QwtSystemClock::elapsed() const +{ + const qint64 nsecs = m_timer.nsecsElapsed(); + return nsecs / 1e6; +} diff --git a/libs/qwt/src/qwt_system_clock.h b/libs/qwt/src/qwt_system_clock.h new file mode 100644 index 00000000..58abf097 --- /dev/null +++ b/libs/qwt/src/qwt_system_clock.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * 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_SYSTEM_CLOCK_H +#define QWT_SYSTEM_CLOCK_H + +#include "qwt_global.h" +#include + +/*! + \brief QwtSystemClock provides high resolution clock time functions. + + Precision and time intervals are multiples of milliseconds (ms). + + ( QwtSystemClock is deprecated as QElapsedTimer offers the same precision ) + */ + +class QWT_EXPORT QwtSystemClock +{ + public: + bool isNull() const; + + void start(); + double restart(); + double elapsed() const; + + private: + QElapsedTimer m_timer; +}; + +#endif diff --git a/libs/qwt/src/qwt_text.cpp b/libs/qwt/src/qwt_text.cpp new file mode 100644 index 00000000..54dcd261 --- /dev/null +++ b/libs/qwt/src/qwt_text.cpp @@ -0,0 +1,743 @@ +/****************************************************************************** + * 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_text.h" +#include "qwt_painter.h" +#include "qwt_text_engine.h" +#include "qwt_math.h" + +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= 0x050200 + +static QwtText qwtStringToText( const QString& text ) +{ + return QwtText( text ); +} + +#endif + +namespace +{ + static const struct RegisterQwtText + { + inline RegisterQwtText() + { + qRegisterMetaType< QwtText >(); + +#if QT_VERSION >= 0x050200 + QMetaType::registerConverter< QString, QwtText >( qwtStringToText ); +#endif + } + + } qwtRegisterQwtText; +} + +namespace +{ + class TextEngineDict + { + public: + static TextEngineDict& dict(); + + void setTextEngine( QwtText::TextFormat, QwtTextEngine* ); + + const QwtTextEngine* textEngine( QwtText::TextFormat ) const; + const QwtTextEngine* textEngine( const QString&, + QwtText::TextFormat ) const; + + private: + TextEngineDict(); + ~TextEngineDict(); + + typedef QMap< int, QwtTextEngine* > EngineMap; + + inline const QwtTextEngine* engine( EngineMap::const_iterator& it ) const + { + return it.value(); + } + + EngineMap m_map; + }; + + TextEngineDict& TextEngineDict::dict() + { + static TextEngineDict engineDict; + return engineDict; + } + + TextEngineDict::TextEngineDict() + { + m_map.insert( QwtText::PlainText, new QwtPlainTextEngine() ); + #ifndef QT_NO_RICHTEXT + m_map.insert( QwtText::RichText, new QwtRichTextEngine() ); + #endif + } + + TextEngineDict::~TextEngineDict() + { + for ( EngineMap::const_iterator it = m_map.constBegin(); + it != m_map.constEnd(); ++it ) + { + const QwtTextEngine* textEngine = engine( it ); + delete textEngine; + } + } + + const QwtTextEngine* TextEngineDict::textEngine( const QString& text, + QwtText::TextFormat format ) const + { + if ( format == QwtText::AutoText ) + { + for ( EngineMap::const_iterator it = m_map.begin(); + it != m_map.end(); ++it ) + { + if ( it.key() != QwtText::PlainText ) + { + const QwtTextEngine* e = engine( it ); + if ( e && e->mightRender( text ) ) + return e; + } + } + } + + EngineMap::const_iterator it = m_map.find( format ); + if ( it != m_map.end() ) + { + const QwtTextEngine* e = engine( it ); + if ( e ) + return e; + } + + it = m_map.find( QwtText::PlainText ); + return engine( it ); + } + + void TextEngineDict::setTextEngine( QwtText::TextFormat format, + QwtTextEngine* engine ) + { + if ( format == QwtText::AutoText ) + return; + + if ( format == QwtText::PlainText && engine == NULL ) + return; + + EngineMap::const_iterator it = m_map.constFind( format ); + if ( it != m_map.constEnd() ) + { + delete this->engine( it ); + m_map.remove( format ); + } + + if ( engine != NULL ) + m_map.insert( format, engine ); + } + + const QwtTextEngine* TextEngineDict::textEngine( + QwtText::TextFormat format ) const + { + const QwtTextEngine* e = NULL; + + EngineMap::const_iterator it = m_map.find( format ); + if ( it != m_map.end() ) + e = engine( it ); + + return e; + } +} + +class QwtText::PrivateData +{ + public: + PrivateData(): + renderFlags( Qt::AlignCenter ), + borderRadius( 0 ), + borderPen( Qt::NoPen ), + backgroundBrush( Qt::NoBrush ), + textEngine( NULL ) + { + } + + int renderFlags; + QString text; + QFont font; + QColor color; + double borderRadius; + QPen borderPen; + QBrush backgroundBrush; + + QwtText::PaintAttributes paintAttributes; + QwtText::LayoutAttributes layoutAttributes; + + const QwtTextEngine* textEngine; +}; + +class QwtText::LayoutCache +{ + public: + void invalidate() + { + textSize = QSizeF(); + } + + QFont font; + QSizeF textSize; +}; + +/*! + Constructor + */ +QwtText::QwtText() +{ + m_data = new PrivateData; + m_data->textEngine = textEngine( m_data->text, PlainText ); + + m_layoutCache = new LayoutCache; +} + +/*! + Constructor + + \param text Text content + \param textFormat Text format + */ +QwtText::QwtText( const QString& text, QwtText::TextFormat textFormat ) +{ + m_data = new PrivateData; + m_data->text = text; + m_data->textEngine = textEngine( text, textFormat ); + + m_layoutCache = new LayoutCache; +} + +//! Copy constructor +QwtText::QwtText( const QwtText& other ) +{ + m_data = new PrivateData; + *m_data = *other.m_data; + + m_layoutCache = new LayoutCache; + *m_layoutCache = *other.m_layoutCache; +} + +//! Destructor +QwtText::~QwtText() +{ + delete m_data; + delete m_layoutCache; +} + +//! Assignment operator +QwtText& QwtText::operator=( const QwtText& other ) +{ + *m_data = *other.m_data; + *m_layoutCache = *other.m_layoutCache; + return *this; +} + +//! Relational operator +bool QwtText::operator==( const QwtText& other ) const +{ + return m_data->renderFlags == other.m_data->renderFlags && + m_data->text == other.m_data->text && + m_data->font == other.m_data->font && + m_data->color == other.m_data->color && + m_data->borderRadius == other.m_data->borderRadius && + m_data->borderPen == other.m_data->borderPen && + m_data->backgroundBrush == other.m_data->backgroundBrush && + m_data->paintAttributes == other.m_data->paintAttributes && + m_data->textEngine == other.m_data->textEngine; +} + +//! Relational operator +bool QwtText::operator!=( const QwtText& other ) const // invalidate +{ + return !( other == *this ); +} + +/*! + Assign a new text content + + \param text Text content + \param textFormat Text format + + \sa text() + */ +void QwtText::setText( const QString& text, + QwtText::TextFormat textFormat ) +{ + m_data->text = text; + m_data->textEngine = textEngine( text, textFormat ); + m_layoutCache->invalidate(); +} + +/*! + \return Text as QString. + \sa setText() + */ +QString QwtText::text() const +{ + return m_data->text; +} + +/*! + \brief Change the render flags + + The default setting is Qt::AlignCenter + + \param renderFlags Bitwise OR of the flags used like in QPainter::drawText() + + \sa renderFlags(), QwtTextEngine::draw() + \note Some renderFlags might have no effect, depending on the text format. + */ +void QwtText::setRenderFlags( int renderFlags ) +{ + if ( renderFlags != m_data->renderFlags ) + { + m_data->renderFlags = renderFlags; + m_layoutCache->invalidate(); + } +} + +/*! + \return Render flags + \sa setRenderFlags() + */ +int QwtText::renderFlags() const +{ + return m_data->renderFlags; +} + +/*! + Set the font. + + \param font Font + \note Setting the font might have no effect, when + the text contains control sequences for setting fonts. + */ +void QwtText::setFont( const QFont& font ) +{ + m_data->font = font; + setPaintAttribute( PaintUsingTextFont ); +} + +//! Return the font. +QFont QwtText::font() const +{ + return m_data->font; +} + +/*! + Return the font of the text, if it has one. + Otherwise return defaultFont. + + \param defaultFont Default font + \return Font used for drawing the text + + \sa setFont(), font(), PaintAttributes + */ +QFont QwtText::usedFont( const QFont& defaultFont ) const +{ + if ( m_data->paintAttributes & PaintUsingTextFont ) + return m_data->font; + + return defaultFont; +} + +/*! + Set the pen color used for drawing the text. + + \param color Color + \note Setting the color might have no effect, when + the text contains control sequences for setting colors. + */ +void QwtText::setColor( const QColor& color ) +{ + m_data->color = color; + setPaintAttribute( PaintUsingTextColor ); +} + +//! Return the pen color, used for painting the text +QColor QwtText::color() const +{ + return m_data->color; +} + +/*! + Return the color of the text, if it has one. + Otherwise return defaultColor. + + \param defaultColor Default color + \return Color used for drawing the text + + \sa setColor(), color(), PaintAttributes + */ +QColor QwtText::usedColor( const QColor& defaultColor ) const +{ + if ( m_data->paintAttributes & PaintUsingTextColor ) + return m_data->color; + + return defaultColor; +} + +/*! + Set the radius for the corners of the border frame + + \param radius Radius of a rounded corner + \sa borderRadius(), setBorderPen(), setBackgroundBrush() + */ +void QwtText::setBorderRadius( double radius ) +{ + m_data->borderRadius = qwtMaxF( 0.0, radius ); +} + +/*! + \return Radius for the corners of the border frame + \sa setBorderRadius(), borderPen(), backgroundBrush() + */ +double QwtText::borderRadius() const +{ + return m_data->borderRadius; +} + +/*! + Set the background pen + + \param pen Background pen + \sa borderPen(), setBackgroundBrush() + */ +void QwtText::setBorderPen( const QPen& pen ) +{ + m_data->borderPen = pen; + setPaintAttribute( PaintBackground ); +} + +/*! + \return Background pen + \sa setBorderPen(), backgroundBrush() + */ +QPen QwtText::borderPen() const +{ + return m_data->borderPen; +} + +/*! + Set the background brush + + \param brush Background brush + \sa backgroundBrush(), setBorderPen() + */ +void QwtText::setBackgroundBrush( const QBrush& brush ) +{ + m_data->backgroundBrush = brush; + setPaintAttribute( PaintBackground ); +} + +/*! + \return Background brush + \sa setBackgroundBrush(), borderPen() + */ +QBrush QwtText::backgroundBrush() const +{ + return m_data->backgroundBrush; +} + +/*! + Change a paint attribute + + \param attribute Paint attribute + \param on On/Off + + \note Used by setFont(), setColor(), + setBorderPen() and setBackgroundBrush() + \sa testPaintAttribute() + */ +void QwtText::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( on ) + m_data->paintAttributes |= attribute; + else + m_data->paintAttributes &= ~attribute; +} + +/*! + Test a paint attribute + + \param attribute Paint attribute + \return true, if attribute is enabled + + \sa setPaintAttribute() + */ +bool QwtText::testPaintAttribute( PaintAttribute attribute ) const +{ + return m_data->paintAttributes & attribute; +} + +/*! + Change a layout attribute + + \param attribute Layout attribute + \param on On/Off + \sa testLayoutAttribute() + */ +void QwtText::setLayoutAttribute( LayoutAttribute attribute, bool on ) +{ + if ( on ) + m_data->layoutAttributes |= attribute; + else + m_data->layoutAttributes &= ~attribute; +} + +/*! + Test a layout attribute + + \param attribute Layout attribute + \return true, if attribute is enabled + + \sa setLayoutAttribute() + */ +bool QwtText::testLayoutAttribute( LayoutAttribute attribute ) const +{ + return m_data->layoutAttributes | attribute; +} + +/*! + Find the height for a given width + + \param width Width + \return Calculated height + */ + +double QwtText::heightForWidth( double width ) const +{ + return heightForWidth( width, QFont() ); +} + +/*! + Find the height for a given width + + \param defaultFont Font, used for the calculation if the text has no font + \param width Width + + \return Calculated height + */ +double QwtText::heightForWidth( double width, const QFont& defaultFont ) const +{ + // We want to calculate in screen metrics. So + // we need a font that uses screen metrics + + const QFont font = QwtPainter::scaledFont( usedFont( defaultFont ) ); + + double h = 0; + + if ( m_data->layoutAttributes & MinimumLayout ) + { + double left, right, top, bottom; + m_data->textEngine->textMargins( font, m_data->text, + left, right, top, bottom ); + + h = m_data->textEngine->heightForWidth( + font, m_data->renderFlags, m_data->text, + width + left + right ); + + h -= top + bottom; + } + else + { + h = m_data->textEngine->heightForWidth( + font, m_data->renderFlags, m_data->text, width ); + } + + return h; +} + +/*! + Returns the size, that is needed to render text + + \return Calculated size + */ +QSizeF QwtText::textSize() const +{ + return textSize( QFont() ); +} + +/*! + Returns the size, that is needed to render text + + \param defaultFont Font of the text + \return Calculated size + */ +QSizeF QwtText::textSize( const QFont& defaultFont ) const +{ + // We want to calculate in screen metrics. So + // we need a font that uses screen metrics + + const QFont font = QwtPainter::scaledFont( usedFont( defaultFont ) ); + + if ( !m_layoutCache->textSize.isValid() + || m_layoutCache->font != font ) + { + m_layoutCache->textSize = m_data->textEngine->textSize( + font, m_data->renderFlags, m_data->text ); + m_layoutCache->font = font; + } + + QSizeF sz = m_layoutCache->textSize; + + if ( m_data->layoutAttributes & MinimumLayout ) + { + double left, right, top, bottom; + m_data->textEngine->textMargins( font, m_data->text, + left, right, top, bottom ); + sz -= QSizeF( left + right, top + bottom ); + } + + return sz; +} + +/*! + Draw a text into a rectangle + + \param painter Painter + \param rect Rectangle + */ +void QwtText::draw( QPainter* painter, const QRectF& rect ) const +{ + if ( m_data->paintAttributes & PaintBackground ) + { + if ( m_data->borderPen != Qt::NoPen || + m_data->backgroundBrush != Qt::NoBrush ) + { + painter->save(); + + painter->setPen( m_data->borderPen ); + painter->setBrush( m_data->backgroundBrush ); + + if ( m_data->borderRadius == 0 ) + { + QwtPainter::drawRect( painter, rect ); + } + else + { + painter->setRenderHint( QPainter::Antialiasing, true ); + painter->drawRoundedRect( rect, + m_data->borderRadius, m_data->borderRadius ); + } + + painter->restore(); + } + } + + painter->save(); + + if ( m_data->paintAttributes & PaintUsingTextFont ) + { + painter->setFont( m_data->font ); + } + + if ( m_data->paintAttributes & PaintUsingTextColor ) + { + if ( m_data->color.isValid() ) + painter->setPen( m_data->color ); + } + + QRectF expandedRect = rect; + if ( m_data->layoutAttributes & MinimumLayout ) + { + // We want to calculate in screen metrics. So + // we need a font that uses screen metrics + + const QFont font = QwtPainter::scaledFont( painter->font() ); + + double left, right, top, bottom; + m_data->textEngine->textMargins( + font, m_data->text, left, right, top, bottom ); + + expandedRect.setTop( rect.top() - top ); + expandedRect.setBottom( rect.bottom() + bottom ); + expandedRect.setLeft( rect.left() - left ); + expandedRect.setRight( rect.right() + right ); + } + + m_data->textEngine->draw( painter, expandedRect, + m_data->renderFlags, m_data->text ); + + painter->restore(); +} + +/*! + Find the text engine for a text format + + In case of QwtText::AutoText the first text engine + (beside QwtPlainTextEngine) is returned, where QwtTextEngine::mightRender + returns true. If there is none QwtPlainTextEngine is returned. + + If no text engine is registered for the format QwtPlainTextEngine + is returned. + + \param text Text, needed in case of AutoText + \param format Text format + + \return Corresponding text engine + */ +const QwtTextEngine* QwtText::textEngine( const QString& text, + QwtText::TextFormat format ) +{ + return TextEngineDict::dict().textEngine( text, format ); +} + +/*! + Assign/Replace a text engine for a text format + + With setTextEngine it is possible to extend Qwt with + other types of text formats. + + For QwtText::PlainText it is not allowed to assign a engine == NULL. + + \param format Text format + \param engine Text engine + + \warning Using QwtText::AutoText does nothing. + */ +void QwtText::setTextEngine( QwtText::TextFormat format, + QwtTextEngine* engine ) +{ + TextEngineDict::dict().setTextEngine( format, engine ); +} + +/*! + \brief Find the text engine for a text format + + textEngine can be used to find out if a text format is supported. + + \param format Text format + \return The text engine, or NULL if no engine is available. + */ +const QwtTextEngine* QwtText::textEngine( QwtText::TextFormat format ) +{ + return TextEngineDict::dict().textEngine( format ); +} + +//! \return text().isNull() +bool QwtText::isNull() const +{ + return m_data->text.isNull(); +} + +//! \return text().isEmpty() +bool QwtText::isEmpty() const +{ + return m_data->text.isEmpty(); +} + diff --git a/libs/qwt/src/qwt_text.h b/libs/qwt/src/qwt_text.h new file mode 100644 index 00000000..f9ee74c9 --- /dev/null +++ b/libs/qwt/src/qwt_text.h @@ -0,0 +1,218 @@ +/****************************************************************************** + * 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_TEXT_H +#define QWT_TEXT_H + +#include "qwt_global.h" +#include + +class QFont; +class QString; +class QColor; +class QPen; +class QBrush; +class QSizeF; +class QRectF; +class QPainter; +class QwtTextEngine; + +/*! + \brief A class representing a text + + A QwtText is a text including a set of attributes how to render it. + + - Format\n + A text might include control sequences (f.e tags) describing + how to render it. Each format (f.e MathML, TeX, Qt Rich Text) + has its own set of control sequences, that can be handles by + a special QwtTextEngine for this format. + - Background\n + A text might have a background, defined by a QPen and QBrush + to improve its visibility. The corners of the background might + be rounded. + - Font\n + A text might have an individual font. + - Color\n + A text might have an individual color. + - Render Flags\n + Flags from Qt::AlignmentFlag and Qt::TextFlag used like in + QPainter::drawText(). + + \sa QwtTextEngine, QwtTextLabel + */ + +class QWT_EXPORT QwtText +{ + public: + + /*! + \brief Text format + + The text format defines the QwtTextEngine, that is used to render + the text. + + \sa QwtTextEngine, setTextEngine() + */ + + enum TextFormat + { + /*! + The text format is determined using QwtTextEngine::mightRender() for + all available text engines in increasing order > PlainText. + If none of the text engines can render the text is rendered + like QwtText::PlainText. + */ + AutoText = 0, + + //! Draw the text as it is, using a QwtPlainTextEngine. + PlainText, + + //! Use the Scribe framework (Qt Rich Text) to render the text. + RichText, + + /*! + Use a MathML (http://en.wikipedia.org/wiki/MathML) render engine + to display the text. In earlier versions of Qwt such an engine + was included - since Qwt 6.2 it can be found here: + https://github.com/uwerat/qwt-mml-dev + + To enable MathML support the following code needs to be added to the + application: + + \code + QwtText::setTextEngine( QwtText::MathMLText, new QwtMathMLTextEngine() ); + \endcode + */ + MathMLText, + + /*! + Use a TeX (http://en.wikipedia.org/wiki/TeX) render engine + to display the text ( not implemented yet ). + */ + TeXText, + + /*! + The number of text formats can be extended using setTextEngine. + Formats >= QwtText::OtherFormat are not used by Qwt. + */ + OtherFormat = 100 + }; + + /*! + \brief Paint Attributes + + Font and color and background are optional attributes of a QwtText. + The paint attributes hold the information, if they are set. + */ + enum PaintAttribute + { + //! The text has an individual font. + PaintUsingTextFont = 0x01, + + //! The text has an individual color. + PaintUsingTextColor = 0x02, + + //! The text has an individual background. + PaintBackground = 0x04 + }; + + Q_DECLARE_FLAGS( PaintAttributes, PaintAttribute ) + + /*! + \brief Layout Attributes + The layout attributes affects some aspects of the layout of the text. + */ + enum LayoutAttribute + { + /*! + Layout the text without its margins. This mode is useful if a + text needs to be aligned accurately, like the tick labels of a scale. + If QwtTextEngine::textMargins is not implemented for the format + of the text, MinimumLayout has no effect. + */ + MinimumLayout = 0x01 + }; + + Q_DECLARE_FLAGS( LayoutAttributes, LayoutAttribute ) + + QwtText(); + QwtText( const QString&, TextFormat textFormat = AutoText ); + QwtText( const QwtText& ); + + ~QwtText(); + + QwtText& operator=( const QwtText& ); + + bool operator==( const QwtText& ) const; + bool operator!=( const QwtText& ) const; + + void setText( const QString&, + QwtText::TextFormat textFormat = AutoText ); + QString text() const; + + bool isNull() const; + bool isEmpty() const; + + void setFont( const QFont& ); + QFont font() const; + + QFont usedFont( const QFont& ) const; + + void setRenderFlags( int ); + int renderFlags() const; + + void setColor( const QColor& ); + QColor color() const; + + QColor usedColor( const QColor& ) const; + + void setBorderRadius( double ); + double borderRadius() const; + + void setBorderPen( const QPen& ); + QPen borderPen() const; + + void setBackgroundBrush( const QBrush& ); + QBrush backgroundBrush() const; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setLayoutAttribute( LayoutAttribute, bool on = true ); + bool testLayoutAttribute( LayoutAttribute ) const; + + double heightForWidth( double width ) const; + double heightForWidth( double width, const QFont& ) const; + + QSizeF textSize() const; + QSizeF textSize( const QFont& ) const; + + void draw( QPainter* painter, const QRectF& rect ) const; + + static const QwtTextEngine* textEngine( + const QString& text, QwtText::TextFormat = AutoText ); + + static const QwtTextEngine* textEngine( QwtText::TextFormat ); + static void setTextEngine( QwtText::TextFormat, QwtTextEngine* ); + + private: + class PrivateData; + PrivateData* m_data; + + class LayoutCache; + LayoutCache* m_layoutCache; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtText::PaintAttributes ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtText::LayoutAttributes ) + +Q_DECLARE_METATYPE( QwtText ) + +#endif diff --git a/libs/qwt/src/qwt_text_engine.cpp b/libs/qwt/src/qwt_text_engine.cpp new file mode 100644 index 00000000..49941588 --- /dev/null +++ b/libs/qwt/src/qwt_text_engine.cpp @@ -0,0 +1,350 @@ +/****************************************************************************** + * 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_text_engine.h" +#include "qwt_painter.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static QString taggedRichText( const QString& text, int flags ) +{ + QString richText = text; + + // By default QSimpleRichText is Qt::AlignLeft + if ( flags & Qt::AlignJustify ) + { + richText.prepend( QLatin1String( "
" ) ); + richText.append( QLatin1String ( "
" ) ); + } + else if ( flags & Qt::AlignRight ) + { + richText.prepend( QLatin1String ( "
" ) ); + richText.append( QLatin1String ( "
" ) ); + } + else if ( flags & Qt::AlignHCenter ) + { + richText.prepend( QLatin1String ( "
" ) ); + richText.append( QLatin1String ( "
" ) ); + } + + return richText; +} + +namespace +{ + class QwtRichTextDocument : public QTextDocument + { + public: + QwtRichTextDocument( const QString& text, int flags, const QFont& font ) + { + setUndoRedoEnabled( false ); + setDefaultFont( font ); + setHtml( text ); + + // make sure we have a document layout + ( void )documentLayout(); + + QTextOption option = defaultTextOption(); + if ( flags & Qt::TextWordWrap ) + option.setWrapMode( QTextOption::WordWrap ); + else + option.setWrapMode( QTextOption::NoWrap ); + + option.setAlignment( static_cast< Qt::Alignment >( flags ) ); + setDefaultTextOption( option ); + + QTextFrame* root = rootFrame(); + QTextFrameFormat fm = root->frameFormat(); + fm.setBorder( 0 ); + fm.setMargin( 0 ); + fm.setPadding( 0 ); + fm.setBottomMargin( 0 ); + fm.setLeftMargin( 0 ); + root->setFrameFormat( fm ); + + adjustSize(); + } + }; +} + +class QwtPlainTextEngine::PrivateData +{ + public: + int effectiveAscent( const QFont& font ) const + { + const QString fontKey = font.key(); + + QMap< QString, int >::const_iterator it = + m_ascentCache.constFind( fontKey ); + + if ( it != m_ascentCache.constEnd() ) + return *it; + + const int ascent = findAscent( font ); + m_ascentCache.insert( fontKey, ascent ); + + return ascent; + } + + private: + static int findAscent( const QFont& font ) + { + static const QString dummy( "E" ); + static const QColor white( Qt::white ); + + const QFontMetrics fm( font ); + + QPixmap pm( QwtPainter::horizontalAdvance( fm, dummy ), fm.height() ); + pm.fill( white ); + + QPainter p( &pm ); + p.setFont( font ); + p.drawText( 0, 0, pm.width(), pm.height(), 0, dummy ); + p.end(); + + const QImage img = pm.toImage(); + + int row = 0; + for ( row = 0; row < img.height(); row++ ) + { + const QRgb* line = reinterpret_cast< const QRgb* >( + img.scanLine( row ) ); + + const int w = pm.width(); + for ( int col = 0; col < w; col++ ) + { + if ( line[col] != white.rgb() ) + return fm.ascent() - row + 1; + } + } + + return fm.ascent(); + } + + mutable QMap< QString, int > m_ascentCache; +}; + +//! Constructor +QwtTextEngine::QwtTextEngine() +{ +} + +//! Destructor +QwtTextEngine::~QwtTextEngine() +{ +} + +//! Constructor +QwtPlainTextEngine::QwtPlainTextEngine() +{ + m_data = new PrivateData; +} + +//! Destructor +QwtPlainTextEngine::~QwtPlainTextEngine() +{ + delete m_data; +} + +/*! + Find the height for a given width + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + \param width Width + + \return Calculated height + */ +double QwtPlainTextEngine::heightForWidth( const QFont& font, int flags, + const QString& text, double width ) const +{ + const QFontMetricsF fm( font ); + const QRectF rect = fm.boundingRect( + QRectF( 0, 0, width, QWIDGETSIZE_MAX ), flags, text ); + + return rect.height(); +} + +/*! + Returns the size, that is needed to render text + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + + \return Calculated size + */ +QSizeF QwtPlainTextEngine::textSize( const QFont& font, + int flags, const QString& text ) const +{ + const QFontMetricsF fm( font ); + const QRectF rect = fm.boundingRect( + QRectF( 0, 0, QWIDGETSIZE_MAX, QWIDGETSIZE_MAX ), flags, text ); + + return rect.size(); +} + +/*! + Return margins around the texts + + \param font Font of the text + \param left Return 0 + \param right Return 0 + \param top Return value for the top margin + \param bottom Return value for the bottom margin + */ +void QwtPlainTextEngine::textMargins( const QFont& font, const QString&, + double& left, double& right, double& top, double& bottom ) const +{ + left = right = top = 0; + + const QFontMetricsF fm( font ); + top = fm.ascent() - m_data->effectiveAscent( font ); + bottom = fm.descent(); +} + +/*! + \brief Draw the text in a clipping rectangle + + A wrapper for QPainter::drawText. + + \param painter Painter + \param rect Clipping rectangle + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + */ +void QwtPlainTextEngine::draw( QPainter* painter, const QRectF& rect, + int flags, const QString& text ) const +{ + QwtPainter::drawText( painter, rect, flags, text ); +} + +/*! + Test if a string can be rendered by this text engine. + \return Always true. All texts can be rendered by QwtPlainTextEngine + */ +bool QwtPlainTextEngine::mightRender( const QString& ) const +{ + return true; +} + +#ifndef QT_NO_RICHTEXT + +//! Constructor +QwtRichTextEngine::QwtRichTextEngine() +{ +} + +/*! + Find the height for a given width + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText() + \param text Text to be rendered + \param width Width + + \return Calculated height + */ +double QwtRichTextEngine::heightForWidth( const QFont& font, int flags, + const QString& text, double width ) const +{ + QwtRichTextDocument doc( text, flags, font ); + + doc.setPageSize( QSizeF( width, QWIDGETSIZE_MAX ) ); + return doc.documentLayout()->documentSize().height(); +} + +/*! + Returns the size, that is needed to render text + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText() + \param text Text to be rendered + + \return Calculated size + */ + +QSizeF QwtRichTextEngine::textSize( const QFont& font, + int flags, const QString& text ) const +{ + QwtRichTextDocument doc( text, flags, font ); + + QTextOption option = doc.defaultTextOption(); + if ( option.wrapMode() != QTextOption::NoWrap ) + { + option.setWrapMode( QTextOption::NoWrap ); + doc.setDefaultTextOption( option ); + doc.adjustSize(); + } + + return doc.size(); +} + +/*! + Draw the text in a clipping rectangle + + \param painter Painter + \param rect Clipping rectangle + \param flags Bitwise OR of the flags like in for QPainter::drawText() + \param text Text to be rendered + */ +void QwtRichTextEngine::draw( QPainter* painter, const QRectF& rect, + int flags, const QString& text ) const +{ + QwtRichTextDocument doc( text, flags, painter->font() ); + QwtPainter::drawSimpleRichText( painter, rect, flags, doc ); +} + +/*! + Wrap text into
tags according flags + + \param text Text + \param flags Bitwise OR of the flags like in for QPainter::drawText() + + \return Tagged text + */ +QString QwtRichTextEngine::taggedText( const QString& text, int flags ) const +{ + return taggedRichText( text, flags ); +} + +/*! + Test if a string can be rendered by this text engine + + \param text Text to be tested + \return Qt::mightBeRichText(text); + */ +bool QwtRichTextEngine::mightRender( const QString& text ) const +{ + return Qt::mightBeRichText( text ); +} + +/*! + Return margins around the texts + + \param left Return 0 + \param right Return 0 + \param top Return 0 + \param bottom Return 0 + */ +void QwtRichTextEngine::textMargins( const QFont&, const QString&, + double& left, double& right, double& top, double& bottom ) const +{ + left = right = top = bottom = 0; +} + +#endif // !QT_NO_RICHTEXT diff --git a/libs/qwt/src/qwt_text_engine.h b/libs/qwt/src/qwt_text_engine.h new file mode 100644 index 00000000..8de9ba70 --- /dev/null +++ b/libs/qwt/src/qwt_text_engine.h @@ -0,0 +1,174 @@ +/****************************************************************************** + * 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_TEXT_ENGINE_H +#define QWT_TEXT_ENGINE_H + +#include "qwt_global.h" +#include + +class QFont; +class QRectF; +class QString; +class QPainter; + +/*! + \brief Abstract base class for rendering text strings + + A text engine is responsible for rendering texts for a + specific text format. They are used by QwtText to render a text. + + \sa QwtText::setTextEngine() + */ + +class QWT_EXPORT QwtTextEngine +{ + public: + virtual ~QwtTextEngine(); + + /*! + Find the height for a given width + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + \param width Width + + \return Calculated height + */ + virtual double heightForWidth( const QFont& font, int flags, + const QString& text, double width ) const = 0; + + /*! + Returns the size, that is needed to render text + + \param font Font of the text + \param flags Bitwise OR of the flags like in for QPainter::drawText + \param text Text to be rendered + + \return Calculated size + */ + virtual QSizeF textSize( const QFont& font, int flags, + const QString& text ) const = 0; + + /*! + Test if a string can be rendered by this text engine + + \param text Text to be tested + \return true, if it can be rendered + */ + virtual bool mightRender( const QString& text ) const = 0; + + /*! + Return margins around the texts + + The textSize might include margins around the + text, like QFontMetrics::descent(). In situations + where texts need to be aligned in detail, knowing + these margins might improve the layout calculations. + + \param font Font of the text + \param text Text to be rendered + \param left Return value for the left margin + \param right Return value for the right margin + \param top Return value for the top margin + \param bottom Return value for the bottom margin + */ + virtual void textMargins( const QFont& font, const QString& text, + double& left, double& right, double& top, double& bottom ) const = 0; + + /*! + Draw the text in a clipping rectangle + + \param painter Painter + \param rect Clipping rectangle + \param flags Bitwise OR of the flags like in for QPainter::drawText() + \param text Text to be rendered + */ + virtual void draw( QPainter* painter, const QRectF& rect, + int flags, const QString& text ) const = 0; + + protected: + QwtTextEngine(); + + private: + Q_DISABLE_COPY(QwtTextEngine) +}; + + +/*! + \brief A text engine for plain texts + + QwtPlainTextEngine renders texts using the basic Qt classes + QPainter and QFontMetrics. + */ +class QWT_EXPORT QwtPlainTextEngine : public QwtTextEngine +{ + public: + QwtPlainTextEngine(); + virtual ~QwtPlainTextEngine(); + + virtual double heightForWidth( const QFont& font, int flags, + const QString& text, double width ) const QWT_OVERRIDE; + + virtual QSizeF textSize( const QFont& font, int flags, + const QString& text ) const QWT_OVERRIDE; + + virtual void draw( QPainter*, const QRectF& rect, + int flags, const QString& text ) const QWT_OVERRIDE; + + virtual bool mightRender( const QString& ) const QWT_OVERRIDE; + + virtual void textMargins( + const QFont&, const QString&, + double& left, double& right, + double& top, double& bottom ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + + +#ifndef QT_NO_RICHTEXT + +/*! + \brief A text engine for Qt rich texts + + QwtRichTextEngine renders Qt rich texts using the classes + of the Scribe framework of Qt. + */ +class QWT_EXPORT QwtRichTextEngine : public QwtTextEngine +{ + public: + QwtRichTextEngine(); + + virtual double heightForWidth( const QFont& font, int flags, + const QString& text, double width ) const QWT_OVERRIDE; + + virtual QSizeF textSize( const QFont& font, int flags, + const QString& text ) const QWT_OVERRIDE; + + virtual void draw( QPainter*, const QRectF& rect, + int flags, const QString& text ) const QWT_OVERRIDE; + + virtual bool mightRender( const QString& ) const QWT_OVERRIDE; + + virtual void textMargins( + const QFont&, const QString&, + double& left, double& right, + double& top, double& bottom ) const QWT_OVERRIDE; + + private: + QString taggedText( const QString&, int flags ) const; +}; + +#endif // !QT_NO_RICHTEXT + +#endif diff --git a/libs/qwt/src/qwt_text_label.cpp b/libs/qwt/src/qwt_text_label.cpp new file mode 100644 index 00000000..5949352b --- /dev/null +++ b/libs/qwt/src/qwt_text_label.cpp @@ -0,0 +1,346 @@ +/****************************************************************************** + * 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_text_label.h" +#include "qwt_text.h" +#include "qwt_painter.h" +#include "qwt_math.h" + +#include +#include +#include +#include +#include + +class QwtTextLabel::PrivateData +{ + public: + PrivateData() + : indent( 4 ) + , margin( 0 ) + { + } + + int indent; + int margin; + QwtText text; +}; + +/*! + Constructs an empty label. + \param parent Parent widget + */ +QwtTextLabel::QwtTextLabel( QWidget* parent ) + : QFrame( parent ) +{ + init(); +} + +/*! + Constructs a label that displays the text, text + \param parent Parent widget + \param text Text + */ +QwtTextLabel::QwtTextLabel( const QwtText& text, QWidget* parent ) + : QFrame( parent ) +{ + init(); + m_data->text = text; +} + +//! Destructor +QwtTextLabel::~QwtTextLabel() +{ + delete m_data; +} + +void QwtTextLabel::init() +{ + m_data = new PrivateData(); + setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ); +} + +/*! + Interface for the designer plugin - does the same as setText() + \sa plainText() + */ +void QwtTextLabel::setPlainText( const QString& text ) +{ + setText( QwtText( text ) ); +} + +/*! + Interface for the designer plugin + + \return Text as plain text + \sa setPlainText(), text() + */ +QString QwtTextLabel::plainText() const +{ + return m_data->text.text(); +} + +/*! + Change the label's text, keeping all other QwtText attributes + \param text New text + \param textFormat Format of text + + \sa QwtText + */ +void QwtTextLabel::setText( const QString& text, + QwtText::TextFormat textFormat ) +{ + m_data->text.setText( text, textFormat ); + + update(); + updateGeometry(); +} + +/*! + Change the label's text + \param text New text + */ +void QwtTextLabel::setText( const QwtText& text ) +{ + m_data->text = text; + + update(); + updateGeometry(); +} + +//! Return the text +const QwtText& QwtTextLabel::text() const +{ + return m_data->text; +} + +//! Clear the text and all QwtText attributes +void QwtTextLabel::clear() +{ + m_data->text = QwtText(); + + update(); + updateGeometry(); +} + +//! Return label's text indent in pixels +int QwtTextLabel::indent() const +{ + return m_data->indent; +} + +/*! + Set label's text indent in pixels + \param indent Indentation in pixels + */ +void QwtTextLabel::setIndent( int indent ) +{ + if ( indent < 0 ) + indent = 0; + + m_data->indent = indent; + + update(); + updateGeometry(); +} + +//! Return label's text margin in pixels +int QwtTextLabel::margin() const +{ + return m_data->margin; +} + +/*! + Set label's margin in pixels + \param margin Margin in pixels + */ +void QwtTextLabel::setMargin( int margin ) +{ + m_data->margin = margin; + + update(); + updateGeometry(); +} + +//! Return a size hint +QSize QwtTextLabel::sizeHint() const +{ + return minimumSizeHint(); +} + +//! Return a minimum size hint +QSize QwtTextLabel::minimumSizeHint() const +{ + QSizeF sz = m_data->text.textSize( font() ); + + const QMargins m = contentsMargins(); + + int mw = m.left() + m.right() + 2 * m_data->margin; + int mh = m.top() + m.bottom() + 2 * m_data->margin; + + int indent = m_data->indent; + if ( indent <= 0 ) + indent = defaultIndent(); + + if ( indent > 0 ) + { + const int align = m_data->text.renderFlags(); + if ( align & Qt::AlignLeft || align & Qt::AlignRight ) + mw += m_data->indent; + else if ( align & Qt::AlignTop || align & Qt::AlignBottom ) + mh += m_data->indent; + } + + sz += QSizeF( mw, mh ); + + return QSize( qwtCeil( sz.width() ), qwtCeil( sz.height() ) ); +} + +/*! + \param width Width + \return Preferred height for this widget, given the width. + */ +int QwtTextLabel::heightForWidth( int width ) const +{ + const int renderFlags = m_data->text.renderFlags(); + + int indent = m_data->indent; + if ( indent <= 0 ) + indent = defaultIndent(); + + const QMargins m = contentsMargins(); + + width -= m.left() + m.right() - 2 * m_data->margin; + if ( renderFlags & Qt::AlignLeft || renderFlags & Qt::AlignRight ) + width -= indent; + + int height = qwtCeil( m_data->text.heightForWidth( width, font() ) ); + if ( ( renderFlags & Qt::AlignTop ) || ( renderFlags & Qt::AlignBottom ) ) + height += indent; + + height += m.top() + m.bottom() + 2 * m_data->margin; + + return height; +} + +/*! + Qt paint event + \param event Paint event + */ +void QwtTextLabel::paintEvent( QPaintEvent* event ) +{ + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QStyleOption opt; + opt.initFrom(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); + + if ( !contentsRect().contains( event->rect() ) ) + { + painter.setClipRegion( event->region() & frameRect() ); + drawFrame( &painter ); + } + + painter.setClipRegion( event->region() & contentsRect() ); + + drawContents( &painter ); +} + +//! Redraw the text and focus indicator +void QwtTextLabel::drawContents( QPainter* painter ) +{ + const QRect r = textRect(); + if ( r.isEmpty() ) + return; + + painter->setFont( font() ); + painter->setPen( palette().color( QPalette::Active, QPalette::Text ) ); + + drawText( painter, QRectF( r ) ); + + if ( hasFocus() ) + { + const int m = 2; + + QRect focusRect = contentsRect().adjusted( m, m, -m + 1, -m + 1); + + QwtPainter::drawFocusRect( painter, this, focusRect ); + } +} + +//! Redraw the text +void QwtTextLabel::drawText( QPainter* painter, const QRectF& textRect ) +{ + m_data->text.draw( painter, textRect ); +} + +/*! + Calculate geometry for the text in widget coordinates + \return Geometry for the text + */ +QRect QwtTextLabel::textRect() const +{ + QRect r = contentsRect(); + + if ( !r.isEmpty() && m_data->margin > 0 ) + { + const int m = m_data->margin; + r.adjust( m, m, -m, -m ); + } + + if ( !r.isEmpty() ) + { + int indent = m_data->indent; + if ( indent <= 0 ) + indent = defaultIndent(); + + if ( indent > 0 ) + { + const int renderFlags = m_data->text.renderFlags(); + + if ( renderFlags & Qt::AlignLeft ) + { + r.setX( r.x() + indent ); + } + else if ( renderFlags & Qt::AlignRight ) + { + r.setWidth( r.width() - indent ); + } + else if ( renderFlags & Qt::AlignTop ) + { + r.setY( r.y() + indent ); + } + else if ( renderFlags & Qt::AlignBottom ) + { + r.setHeight( r.height() - indent ); + } + } + } + + return r; +} + +int QwtTextLabel::defaultIndent() const +{ + if ( frameWidth() <= 0 ) + return 0; + + QFont fnt; + if ( m_data->text.testPaintAttribute( QwtText::PaintUsingTextFont ) ) + fnt = m_data->text.font(); + else + fnt = font(); + + return QwtPainter::horizontalAdvance( QFontMetrics( fnt ), 'x' ) / 2; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_text_label.cpp" +#endif diff --git a/libs/qwt/src/qwt_text_label.h b/libs/qwt/src/qwt_text_label.h new file mode 100644 index 00000000..44f8c4bc --- /dev/null +++ b/libs/qwt/src/qwt_text_label.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_TEXT_LABEL_H +#define QWT_TEXT_LABEL_H + +#include "qwt_global.h" +#include "qwt_text.h" + +#include + +class QString; +class QPaintEvent; +class QPainter; + +/*! + \brief A Widget which displays a QwtText + */ + +class QWT_EXPORT QwtTextLabel : public QFrame +{ + Q_OBJECT + + Q_PROPERTY( int indent READ indent WRITE setIndent ) + Q_PROPERTY( int margin READ margin WRITE setMargin ) + Q_PROPERTY( QString plainText READ plainText WRITE setPlainText ) + + public: + explicit QwtTextLabel( QWidget* parent = NULL ); + explicit QwtTextLabel( const QwtText&, QWidget* parent = NULL ); + virtual ~QwtTextLabel(); + + void setPlainText( const QString& ); + QString plainText() const; + + public Q_SLOTS: + void setText( const QString&, + QwtText::TextFormat textFormat = QwtText::AutoText ); + virtual void setText( const QwtText& ); + + void clear(); + + public: + const QwtText& text() const; + + int indent() const; + void setIndent( int ); + + int margin() const; + void setMargin( int ); + + virtual QSize sizeHint() const QWT_OVERRIDE; + virtual QSize minimumSizeHint() const QWT_OVERRIDE; + virtual int heightForWidth( int ) const QWT_OVERRIDE; + + QRect textRect() const; + + virtual void drawText( QPainter*, const QRectF& ); + + protected: + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + virtual void drawContents( QPainter* ); + + private: + void init(); + int defaultIndent() const; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_thermo.cpp b/libs/qwt/src/qwt_thermo.cpp new file mode 100644 index 00000000..bad18862 --- /dev/null +++ b/libs/qwt/src/qwt_thermo.cpp @@ -0,0 +1,1012 @@ +/****************************************************************************** + * 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_thermo.h" +#include "qwt_scale_draw.h" +#include "qwt_scale_map.h" +#include "qwt_color_map.h" +#include "qwt_math.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +static inline void qwtDrawLine( QPainter* painter, int pos, + const QColor& color, const QRect& pipeRect, const QRect& liquidRect, + Qt::Orientation orientation ) +{ + painter->setPen( color ); + if ( orientation == Qt::Horizontal ) + { + if ( pos >= liquidRect.left() && pos < liquidRect.right() ) + painter->drawLine( pos, pipeRect.top(), pos, pipeRect.bottom() ); + } + else + { + if ( pos >= liquidRect.top() && pos < liquidRect.bottom() ) + painter->drawLine( pipeRect.left(), pos, pipeRect.right(), pos ); + } +} + +static QVector< double > qwtTickList( const QwtScaleDiv& scaleDiv ) +{ + QVector< double > values; + + double lowerLimit = scaleDiv.interval().minValue(); + double upperLimit = scaleDiv.interval().maxValue(); + + if ( upperLimit < lowerLimit ) + qSwap( lowerLimit, upperLimit ); + + values += lowerLimit; + + for ( int tickType = QwtScaleDiv::MinorTick; + tickType < QwtScaleDiv::NTickTypes; tickType++ ) + { + const QList< double > ticks = scaleDiv.ticks( tickType ); + + for ( int i = 0; i < ticks.count(); i++ ) + { + const double v = ticks[i]; + if ( v > lowerLimit && v < upperLimit ) + values += v; + } + } + + values += upperLimit; + + return values; +} + +class QwtThermo::PrivateData +{ + public: + PrivateData() + : orientation( Qt::Vertical ) + , scalePosition( QwtThermo::TrailingScale ) + , spacing( 3 ) + , borderWidth( 2 ) + , pipeWidth( 10 ) + , alarmLevel( 0.0 ) + , alarmEnabled( false ) + , autoFillPipe( true ) + , originMode( QwtThermo::OriginMinimum ) + , origin( 0.0 ) + , colorMap( NULL ) + , value( 0.0 ) + { + rangeFlags = QwtInterval::IncludeBorders; + } + + ~PrivateData() + { + delete colorMap; + } + + Qt::Orientation orientation; + QwtThermo::ScalePosition scalePosition; + + int spacing; + int borderWidth; + int pipeWidth; + + QwtInterval::BorderFlags rangeFlags; + double alarmLevel; + bool alarmEnabled; + bool autoFillPipe; + QwtThermo::OriginMode originMode; + double origin; + + QwtColorMap* colorMap; + + double value; +}; + +/*! + Constructor + \param parent Parent widget + */ +QwtThermo::QwtThermo( QWidget* parent ) + : QwtAbstractScale( parent ) +{ + m_data = new PrivateData; + + QSizePolicy policy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); + if ( m_data->orientation == Qt::Vertical ) + policy.transpose(); + + setSizePolicy( policy ); + + setAttribute( Qt::WA_WState_OwnSizePolicy, false ); + layoutThermo( true ); +} + +//! Destructor +QwtThermo::~QwtThermo() +{ + delete m_data; +} + +/*! + \brief Exclude/Include min/max values + + According to the flags minValue() and maxValue() + are included/excluded from the pipe. In case of an + excluded value the corresponding tick is painted + 1 pixel off of the pipeRect(). + + F.e. when a minimum + of 0.0 has to be displayed as an empty pipe the minValue() + needs to be excluded. + + \param flags Range flags + \sa rangeFlags() + */ +void QwtThermo::setRangeFlags( QwtInterval::BorderFlags flags ) +{ + if ( m_data->rangeFlags != flags ) + { + m_data->rangeFlags = flags; + update(); + } +} + +/*! + \return Range flags + \sa setRangeFlags() + */ +QwtInterval::BorderFlags QwtThermo::rangeFlags() const +{ + return m_data->rangeFlags; +} + +/*! + Set the current value. + + \param value New Value + \sa value() + */ +void QwtThermo::setValue( double value ) +{ + if ( m_data->value != value ) + { + m_data->value = value; + update(); + } +} + +//! Return the value. +double QwtThermo::value() const +{ + return m_data->value; +} + +/*! + \brief Set a scale draw + + For changing the labels of the scales, it + is necessary to derive from QwtScaleDraw and + overload QwtScaleDraw::label(). + + \param scaleDraw ScaleDraw object, that has to be created with + new and will be deleted in ~QwtThermo() or the next + call of setScaleDraw(). + */ +void QwtThermo::setScaleDraw( QwtScaleDraw* scaleDraw ) +{ + setAbstractScaleDraw( scaleDraw ); + layoutThermo( true ); +} + +/*! + \return the scale draw of the thermo + \sa setScaleDraw() + */ +const QwtScaleDraw* QwtThermo::scaleDraw() const +{ + return static_cast< const QwtScaleDraw* >( abstractScaleDraw() ); +} + +/*! + \return the scale draw of the thermo + \sa setScaleDraw() + */ +QwtScaleDraw* QwtThermo::scaleDraw() +{ + return static_cast< QwtScaleDraw* >( abstractScaleDraw() ); +} + +/*! + Paint event handler + \param event Paint event + */ +void QwtThermo::paintEvent( QPaintEvent* event ) +{ + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QStyleOption opt; + opt.initFrom(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); + + const QRect tRect = pipeRect(); + + if ( !tRect.contains( event->rect() ) ) + { + if ( m_data->scalePosition != QwtThermo::NoScale ) + scaleDraw()->draw( &painter, palette() ); + } + + const int bw = m_data->borderWidth; + + const QBrush brush = palette().brush( QPalette::Base ); + qDrawShadePanel( &painter, + tRect.adjusted( -bw, -bw, bw, bw ), + palette(), true, bw, + m_data->autoFillPipe ? &brush : NULL ); + + drawLiquid( &painter, tRect ); +} + +/*! + Resize event handler + \param event Resize event + */ +void QwtThermo::resizeEvent( QResizeEvent* event ) +{ + Q_UNUSED( event ); + layoutThermo( false ); +} + +/*! + Qt change event handler + \param event Event + */ +void QwtThermo::changeEvent( QEvent* event ) +{ + switch( event->type() ) + { + case QEvent::StyleChange: + case QEvent::FontChange: + { + layoutThermo( true ); + break; + } + default: + break; + } +} + +/*! + Recalculate the QwtThermo geometry and layout based on + pipeRect() and the fonts. + + \param update_geometry notify the layout system and call update + to redraw the scale + */ +void QwtThermo::layoutThermo( bool update_geometry ) +{ + const QRect tRect = pipeRect(); + const int bw = m_data->borderWidth + m_data->spacing; + const bool inverted = ( upperBound() < lowerBound() ); + + int from, to; + + if ( m_data->orientation == Qt::Horizontal ) + { + from = tRect.left(); + to = tRect.right(); + + if ( m_data->rangeFlags & QwtInterval::ExcludeMinimum ) + { + if ( inverted ) + to++; + else + from--; + } + if ( m_data->rangeFlags & QwtInterval::ExcludeMaximum ) + { + if ( inverted ) + from--; + else + to++; + } + + if ( m_data->scalePosition == QwtThermo::TrailingScale ) + { + scaleDraw()->setAlignment( QwtScaleDraw::TopScale ); + scaleDraw()->move( from, tRect.top() - bw ); + } + else + { + scaleDraw()->setAlignment( QwtScaleDraw::BottomScale ); + scaleDraw()->move( from, tRect.bottom() + bw ); + } + + scaleDraw()->setLength( qMax( to - from, 0 ) ); + } + else // Qt::Vertical + { + from = tRect.top(); + to = tRect.bottom(); + + if ( m_data->rangeFlags & QwtInterval::ExcludeMinimum ) + { + if ( inverted ) + from--; + else + to++; + } + if ( m_data->rangeFlags & QwtInterval::ExcludeMaximum ) + { + if ( inverted ) + to++; + else + from--; + } + + if ( m_data->scalePosition == QwtThermo::LeadingScale ) + { + scaleDraw()->setAlignment( QwtScaleDraw::RightScale ); + scaleDraw()->move( tRect.right() + bw, from ); + } + else + { + scaleDraw()->setAlignment( QwtScaleDraw::LeftScale ); + scaleDraw()->move( tRect.left() - bw, from ); + } + + scaleDraw()->setLength( qMax( to - from, 0 ) ); + } + + if ( update_geometry ) + { + updateGeometry(); + update(); + } +} + +/*! + \return Bounding rectangle of the pipe ( without borders ) + in widget coordinates + */ +QRect QwtThermo::pipeRect() const +{ + int mbd = 0; + if ( m_data->scalePosition != QwtThermo::NoScale ) + { + int d1, d2; + scaleDraw()->getBorderDistHint( font(), d1, d2 ); + mbd = qMax( d1, d2 ); + } + const int bw = m_data->borderWidth; + const int scaleOff = bw + mbd; + + const QRect cr = contentsRect(); + + QRect pipeRect = cr; + if ( m_data->orientation == Qt::Horizontal ) + { + pipeRect.adjust( scaleOff, 0, -scaleOff, 0 ); + + if ( m_data->scalePosition == QwtThermo::TrailingScale ) + pipeRect.setTop( cr.top() + cr.height() - bw - m_data->pipeWidth ); + else + pipeRect.setTop( bw ); + + pipeRect.setHeight( m_data->pipeWidth ); + } + else // Qt::Vertical + { + pipeRect.adjust( 0, scaleOff, 0, -scaleOff ); + + if ( m_data->scalePosition == QwtThermo::LeadingScale ) + pipeRect.setLeft( bw ); + else + pipeRect.setLeft( cr.left() + cr.width() - bw - m_data->pipeWidth ); + + pipeRect.setWidth( m_data->pipeWidth ); + } + + return pipeRect; +} + +/*! + \brief Set the orientation. + \param orientation Allowed values are Qt::Horizontal and Qt::Vertical. + + \sa orientation(), scalePosition() + */ +void QwtThermo::setOrientation( Qt::Orientation orientation ) +{ + if ( orientation == m_data->orientation ) + return; + + m_data->orientation = orientation; + + if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) ) + { + QSizePolicy sp = sizePolicy(); + sp.transpose(); + setSizePolicy( sp ); + + setAttribute( Qt::WA_WState_OwnSizePolicy, false ); + } + + layoutThermo( true ); +} + +/*! + \return Orientation + \sa setOrientation() + */ +Qt::Orientation QwtThermo::orientation() const +{ + return m_data->orientation; +} + +/*! + \brief Change how the origin is determined. + \sa originMode(), serOrigin(), origin() + */ +void QwtThermo::setOriginMode( OriginMode m ) +{ + if ( m == m_data->originMode ) + return; + + m_data->originMode = m; + update(); +} + +/*! + \return Mode, how the origin is determined. + \sa setOriginMode(), serOrigin(), origin() + */ +QwtThermo::OriginMode QwtThermo::originMode() const +{ + return m_data->originMode; +} + +/*! + \brief Specifies the custom origin. + + If originMode is set to OriginCustom this property controls where the + liquid starts. + + \param origin New origin level + \sa setOriginMode(), originMode(), origin() + */ +void QwtThermo::setOrigin( double origin ) +{ + if ( origin == m_data->origin ) + return; + + m_data->origin = origin; + update(); +} + +/*! + \return Origin of the thermo, when OriginCustom is enabled + \sa setOrigin(), setOriginMode(), originMode() + */ +double QwtThermo::origin() const +{ + return m_data->origin; +} + +/*! + \brief Change the position of the scale + \param scalePosition Position of the scale. + + \sa ScalePosition, scalePosition() + */ +void QwtThermo::setScalePosition( ScalePosition scalePosition ) +{ + if ( m_data->scalePosition == scalePosition ) + return; + + m_data->scalePosition = scalePosition; + + if ( testAttribute( Qt::WA_WState_Polished ) ) + layoutThermo( true ); +} + +/*! + \return Scale position. + \sa setScalePosition() + */ +QwtThermo::ScalePosition QwtThermo::scalePosition() const +{ + return m_data->scalePosition; +} + +//! Notify a scale change. +void QwtThermo::scaleChange() +{ + layoutThermo( true ); +} + +/*! + Redraw the liquid in thermometer pipe. + \param painter Painter + \param pipeRect Bounding rectangle of the pipe without borders + */ +void QwtThermo::drawLiquid( + QPainter* painter, const QRect& pipeRect ) const +{ + painter->save(); + painter->setClipRect( pipeRect, Qt::IntersectClip ); + painter->setPen( Qt::NoPen ); + + const QwtScaleMap scaleMap = scaleDraw()->scaleMap(); + + QRect liquidRect = fillRect( pipeRect ); + + if ( m_data->colorMap != NULL ) + { + const QwtInterval interval = scaleDiv().interval().normalized(); + + // Because the positions of the ticks are rounded + // we calculate the colors for the rounded tick values + + QVector< double > values = qwtTickList( scaleDraw()->scaleDiv() ); + + if ( scaleMap.isInverting() ) + std::sort( values.begin(), values.end(), std::greater< double >() ); + else + std::sort( values.begin(), values.end(), std::less< double >() ); + + int from; + if ( !values.isEmpty() ) + { + from = qRound( scaleMap.transform( values[0] ) ); + qwtDrawLine( painter, from, + m_data->colorMap->color( interval, values[0] ), + pipeRect, liquidRect, m_data->orientation ); + } + + for ( int i = 1; i < values.size(); i++ ) + { + const int to = qRound( scaleMap.transform( values[i] ) ); + + for ( int pos = from + 1; pos < to; pos++ ) + { + const double v = scaleMap.invTransform( pos ); + + qwtDrawLine( painter, pos, + m_data->colorMap->color( interval, v ), + pipeRect, liquidRect, m_data->orientation ); + } + + qwtDrawLine( painter, to, + m_data->colorMap->color( interval, values[i] ), + pipeRect, liquidRect, m_data->orientation ); + + from = to; + } + } + else + { + if ( !liquidRect.isEmpty() && m_data->alarmEnabled ) + { + const QRect r = alarmRect( liquidRect ); + if ( !r.isEmpty() ) + { + painter->fillRect( r, palette().brush( QPalette::Highlight ) ); + liquidRect = QRegion( liquidRect ).subtracted( r ).boundingRect(); + } + } + + painter->fillRect( liquidRect, palette().brush( QPalette::ButtonText ) ); + } + + painter->restore(); +} + +/*! + \brief Change the spacing between pipe and scale + + A spacing of 0 means, that the backbone of the scale is below + the pipe. + + The default setting is 3 pixels. + + \param spacing Number of pixels + \sa spacing(); + */ +void QwtThermo::setSpacing( int spacing ) +{ + if ( spacing <= 0 ) + spacing = 0; + + if ( spacing != m_data->spacing ) + { + m_data->spacing = spacing; + layoutThermo( true ); + } +} + +/*! + \return Number of pixels between pipe and scale + \sa setSpacing() + */ +int QwtThermo::spacing() const +{ + return m_data->spacing; +} + +/*! + Set the border width of the pipe. + \param width Border width + \sa borderWidth() + */ +void QwtThermo::setBorderWidth( int width ) +{ + if ( width <= 0 ) + width = 0; + + if ( width != m_data->borderWidth ) + { + m_data->borderWidth = width; + layoutThermo( true ); + } +} + +/*! + \return Border width of the thermometer pipe. + \sa setBorderWidth() + */ +int QwtThermo::borderWidth() const +{ + return m_data->borderWidth; +} + +/*! + \brief Assign a color map for the fill color + + \param colorMap Color map + \warning The alarm threshold has no effect, when + a color map has been assigned + */ +void QwtThermo::setColorMap( QwtColorMap* colorMap ) +{ + if ( colorMap != m_data->colorMap ) + { + delete m_data->colorMap; + m_data->colorMap = colorMap; + } +} + +/*! + \return Color map for the fill color + \warning The alarm threshold has no effect, when + a color map has been assigned + */ +QwtColorMap* QwtThermo::colorMap() +{ + return m_data->colorMap; +} + +/*! + \return Color map for the fill color + \warning The alarm threshold has no effect, when + a color map has been assigned + */ +const QwtColorMap* QwtThermo::colorMap() const +{ + return m_data->colorMap; +} + +/*! + \brief Change the brush of the liquid. + + Changes the QPalette::ButtonText brush of the palette. + + \param brush New brush. + \sa fillBrush(), QWidget::setPalette() + */ +void QwtThermo::setFillBrush( const QBrush& brush ) +{ + QPalette pal = palette(); + pal.setBrush( QPalette::ButtonText, brush ); + setPalette( pal ); +} + +/*! + \return Liquid ( QPalette::ButtonText ) brush. + \sa setFillBrush(), QWidget::palette() + */ +QBrush QwtThermo::fillBrush() const +{ + return palette().brush( QPalette::ButtonText ); +} + +/*! + \brief Specify the liquid brush above the alarm threshold + + Changes the QPalette::Highlight brush of the palette. + + \param brush New brush. + \sa alarmBrush(), QWidget::setPalette() + + \warning The alarm threshold has no effect, when + a color map has been assigned + */ +void QwtThermo::setAlarmBrush( const QBrush& brush ) +{ + QPalette pal = palette(); + pal.setBrush( QPalette::Highlight, brush ); + setPalette( pal ); +} + +/*! + \return Liquid brush ( QPalette::Highlight ) above the alarm threshold. + \sa setAlarmBrush(), QWidget::palette() + + \warning The alarm threshold has no effect, when + a color map has been assigned + */ +QBrush QwtThermo::alarmBrush() const +{ + return palette().brush( QPalette::Highlight ); +} + +/*! + Specify the alarm threshold. + + \param level Alarm threshold + \sa alarmLevel() + + \warning The alarm threshold has no effect, when + a color map has been assigned + */ +void QwtThermo::setAlarmLevel( double level ) +{ + m_data->alarmLevel = level; + m_data->alarmEnabled = 1; + update(); +} + +/*! + \return Alarm threshold. + \sa setAlarmLevel() + + \warning The alarm threshold has no effect, when + a color map has been assigned + */ +double QwtThermo::alarmLevel() const +{ + return m_data->alarmLevel; +} + +/*! + Change the width of the pipe. + + \param width Width of the pipe + \sa pipeWidth() + */ +void QwtThermo::setPipeWidth( int width ) +{ + if ( width > 0 ) + { + m_data->pipeWidth = width; + layoutThermo( true ); + } +} + +/*! + \return Width of the pipe. + \sa setPipeWidth() + */ +int QwtThermo::pipeWidth() const +{ + return m_data->pipeWidth; +} + +/*! + \brief Enable or disable the alarm threshold + \param on true (disabled) or false (enabled) + + \warning The alarm threshold has no effect, when + a color map has been assigned + */ +void QwtThermo::setAlarmEnabled( bool on ) +{ + m_data->alarmEnabled = on; + update(); +} + +/*! + \return True, when the alarm threshold is enabled. + + \warning The alarm threshold has no effect, when + a color map has been assigned + */ +bool QwtThermo::alarmEnabled() const +{ + return m_data->alarmEnabled; +} + +/*! + \return the minimum size hint + \sa minimumSizeHint() + */ +QSize QwtThermo::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \return Minimum size hint + \warning The return value depends on the font and the scale. + \sa sizeHint() + */ +QSize QwtThermo::minimumSizeHint() const +{ + int w = 0, h = 0; + + if ( m_data->scalePosition != NoScale ) + { + const int sdExtent = qwtCeil( scaleDraw()->extent( font() ) ); + const int sdLength = scaleDraw()->minLength( font() ); + + w = sdLength; + h = m_data->pipeWidth + sdExtent + m_data->spacing; + + } + else // no scale + { + w = 200; + h = m_data->pipeWidth; + } + + if ( m_data->orientation == Qt::Vertical ) + qSwap( w, h ); + + w += 2 * m_data->borderWidth; + h += 2 * m_data->borderWidth; + + // finally add the margins + const QMargins m = contentsMargins(); + w += m.left() + m.right(); + h += m.top() + m.bottom(); + + return QSize( w, h ); +} + +/*! + \brief Calculate the filled rectangle of the pipe + + \param pipeRect Rectangle of the pipe + \return Rectangle to be filled ( fill and alarm brush ) + + \sa pipeRect(), alarmRect() + */ +QRect QwtThermo::fillRect( const QRect& pipeRect ) const +{ + double origin; + if ( m_data->originMode == OriginMinimum ) + { + origin = qMin( lowerBound(), upperBound() ); + } + else if ( m_data->originMode == OriginMaximum ) + { + origin = qMax( lowerBound(), upperBound() ); + } + else // OriginCustom + { + origin = m_data->origin; + } + + const QwtScaleMap scaleMap = scaleDraw()->scaleMap(); + + int from = qRound( scaleMap.transform( m_data->value ) ); + int to = qRound( scaleMap.transform( origin ) ); + + if ( to < from ) + qSwap( from, to ); + + QRect fillRect = pipeRect; + if ( m_data->orientation == Qt::Horizontal ) + { + fillRect.setLeft( from ); + fillRect.setRight( to ); + } + else // Qt::Vertical + { + fillRect.setTop( from ); + fillRect.setBottom( to ); + } + + return fillRect.normalized(); +} + +/*! + \brief Calculate the alarm rectangle of the pipe + + \param fillRect Filled rectangle in the pipe + \return Rectangle to be filled with the alarm brush + + \sa pipeRect(), fillRect(), alarmLevel(), alarmBrush() + */ +QRect QwtThermo::alarmRect( const QRect& fillRect ) const +{ + QRect alarmRect( 0, 0, -1, -1); // something invalid + + if ( !m_data->alarmEnabled ) + return alarmRect; + + const bool inverted = ( upperBound() < lowerBound() ); + + bool increasing; + if ( m_data->originMode == OriginCustom ) + { + increasing = m_data->value > m_data->origin; + } + else + { + increasing = m_data->originMode == OriginMinimum; + } + + const QwtScaleMap map = scaleDraw()->scaleMap(); + const int alarmPos = qRound( map.transform( m_data->alarmLevel ) ); + const int valuePos = qRound( map.transform( m_data->value ) ); + + if ( m_data->orientation == Qt::Horizontal ) + { + int v1, v2; + if ( inverted ) + { + v1 = fillRect.left(); + + v2 = alarmPos - 1; + v2 = qMin( v2, increasing ? fillRect.right() : valuePos ); + } + else + { + v1 = alarmPos + 1; + v1 = qMax( v1, increasing ? fillRect.left() : valuePos ); + + v2 = fillRect.right(); + + } + alarmRect.setRect( v1, fillRect.top(), v2 - v1 + 1, fillRect.height() ); + } + else + { + int v1, v2; + if ( inverted ) + { + v1 = alarmPos + 1; + v1 = qMax( v1, increasing ? fillRect.top() : valuePos ); + + v2 = fillRect.bottom(); + } + else + { + v1 = fillRect.top(); + + v2 = alarmPos - 1; + v2 = qMin( v2, increasing ? fillRect.bottom() : valuePos ); + } + alarmRect.setRect( fillRect.left(), v1, fillRect.width(), v2 - v1 + 1 ); + } + + return alarmRect; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_thermo.cpp" +#endif diff --git a/libs/qwt/src/qwt_thermo.h b/libs/qwt/src/qwt_thermo.h new file mode 100644 index 00000000..89485571 --- /dev/null +++ b/libs/qwt/src/qwt_thermo.h @@ -0,0 +1,178 @@ +/****************************************************************************** + * 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_THERMO_H +#define QWT_THERMO_H + +#include "qwt_global.h" +#include "qwt_abstract_scale.h" +#include "qwt_interval.h" + +class QwtScaleDraw; +class QwtColorMap; + +/*! + \brief The Thermometer Widget + + QwtThermo is a widget which displays a value in an interval. It supports: + - a horizontal or vertical layout; + - a range; + - a scale; + - an alarm level. + + \image html sysinfo.png + + The fill colors might be calculated from an optional color map + If no color map has been assigned QwtThermo uses the + following colors/brushes from the widget palette: + + - QPalette::Base + Background of the pipe + - QPalette::ButtonText + Fill brush below the alarm level + - QPalette::Highlight + Fill brush for the values above the alarm level + - QPalette::WindowText + For the axis of the scale + - QPalette::Text + For the labels of the scale + */ +class QWT_EXPORT QwtThermo : public QwtAbstractScale +{ + Q_OBJECT + + Q_ENUMS( ScalePosition ) + Q_ENUMS( OriginMode ) + + Q_PROPERTY( Qt::Orientation orientation + READ orientation WRITE setOrientation ) + Q_PROPERTY( ScalePosition scalePosition + READ scalePosition WRITE setScalePosition ) + Q_PROPERTY( OriginMode originMode READ originMode WRITE setOriginMode ) + + Q_PROPERTY( bool alarmEnabled READ alarmEnabled WRITE setAlarmEnabled ) + Q_PROPERTY( double alarmLevel READ alarmLevel WRITE setAlarmLevel ) + Q_PROPERTY( double origin READ origin WRITE setOrigin ) + Q_PROPERTY( int spacing READ spacing WRITE setSpacing ) + Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth ) + Q_PROPERTY( int pipeWidth READ pipeWidth WRITE setPipeWidth ) + Q_PROPERTY( double value READ value WRITE setValue USER true ) + + public: + + /*! + Position of the scale + \sa setScalePosition(), setOrientation() + */ + enum ScalePosition + { + //! The slider has no scale + NoScale, + + //! The scale is right of a vertical or below of a horizontal slider + LeadingScale, + + //! The scale is left of a vertical or above of a horizontal slider + TrailingScale + }; + + /*! + Origin mode. This property specifies where the beginning of the liquid + is placed. + + \sa setOriginMode(), setOrigin() + */ + enum OriginMode + { + //! The origin is the minimum of the scale + OriginMinimum, + + //! The origin is the maximum of the scale + OriginMaximum, + + //! The origin is specified using the origin() property + OriginCustom + }; + + explicit QwtThermo( QWidget* parent = NULL ); + virtual ~QwtThermo(); + + void setOrientation( Qt::Orientation ); + Qt::Orientation orientation() const; + + void setScalePosition( ScalePosition ); + ScalePosition scalePosition() const; + + void setSpacing( int ); + int spacing() const; + + void setBorderWidth( int ); + int borderWidth() const; + + void setOriginMode( OriginMode ); + OriginMode originMode() const; + + void setOrigin( double ); + double origin() const; + + void setFillBrush( const QBrush& ); + QBrush fillBrush() const; + + void setAlarmBrush( const QBrush& ); + QBrush alarmBrush() const; + + void setAlarmLevel( double ); + double alarmLevel() const; + + void setAlarmEnabled( bool ); + bool alarmEnabled() const; + + void setColorMap( QwtColorMap* ); + QwtColorMap* colorMap(); + const QwtColorMap* colorMap() const; + + void setPipeWidth( int ); + int pipeWidth() const; + + void setRangeFlags( QwtInterval::BorderFlags ); + QwtInterval::BorderFlags rangeFlags() const; + + double value() const; + + virtual QSize sizeHint() const QWT_OVERRIDE; + virtual QSize minimumSizeHint() const QWT_OVERRIDE; + + void setScaleDraw( QwtScaleDraw* ); + const QwtScaleDraw* scaleDraw() const; + + public Q_SLOTS: + virtual void setValue( double ); + + protected: + virtual void drawLiquid( QPainter*, const QRect& ) const; + virtual void scaleChange() QWT_OVERRIDE; + + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + virtual void resizeEvent( QResizeEvent* ) QWT_OVERRIDE; + virtual void changeEvent( QEvent* ) QWT_OVERRIDE; + + QwtScaleDraw* scaleDraw(); + + QRect pipeRect() const; + QRect fillRect( const QRect& ) const; + QRect alarmRect( const QRect& ) const; + + private: + void layoutThermo( bool ); + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_transform.cpp b/libs/qwt/src/qwt_transform.cpp new file mode 100644 index 00000000..6d5b51d0 --- /dev/null +++ b/libs/qwt/src/qwt_transform.cpp @@ -0,0 +1,161 @@ +/****************************************************************************** + * 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_transform.h" +#include "qwt_math.h" + +//! Smallest allowed value for logarithmic scales: 1.0e-150 +const double QwtLogTransform::LogMin = 1.0e-150; + +//! Largest allowed value for logarithmic scales: 1.0e150 +const double QwtLogTransform::LogMax = 1.0e150; + +//! Constructor +QwtTransform::QwtTransform() +{ +} + +//! Destructor +QwtTransform::~QwtTransform() +{ +} + +/*! + \param value Value to be bounded + \return value unmodified + */ +double QwtTransform::bounded( double value ) const +{ + return value; +} + +//! Constructor +QwtNullTransform::QwtNullTransform(): + QwtTransform() +{ +} + +//! Destructor +QwtNullTransform::~QwtNullTransform() +{ +} + +/*! + \param value Value to be transformed + \return value unmodified + */ +double QwtNullTransform::transform( double value ) const +{ + return value; +} + +/*! + \param value Value to be transformed + \return value unmodified + */ +double QwtNullTransform::invTransform( double value ) const +{ + return value; +} + +//! \return Clone of the transformation +QwtTransform* QwtNullTransform::copy() const +{ + return new QwtNullTransform(); +} + +//! Constructor +QwtLogTransform::QwtLogTransform(): + QwtTransform() +{ +} + +//! Destructor +QwtLogTransform::~QwtLogTransform() +{ +} + +/*! + \param value Value to be transformed + \return log( value ) + */ +double QwtLogTransform::transform( double value ) const +{ + return std::log( value ); +} + +/*! + \param value Value to be transformed + \return exp( value ) + */ +double QwtLogTransform::invTransform( double value ) const +{ + return std::exp( value ); +} + +/*! + \param value Value to be bounded + \return qBound( LogMin, value, LogMax ) + */ +double QwtLogTransform::bounded( double value ) const +{ + return qBound( LogMin, value, LogMax ); +} + +//! \return Clone of the transformation +QwtTransform* QwtLogTransform::copy() const +{ + return new QwtLogTransform(); +} + +/*! + Constructor + \param exponent Exponent + */ +QwtPowerTransform::QwtPowerTransform( double exponent ): + QwtTransform(), + m_exponent( exponent ) +{ +} + +//! Destructor +QwtPowerTransform::~QwtPowerTransform() +{ +} + +/*! + \param value Value to be transformed + \return Exponentiation preserving the sign + */ +double QwtPowerTransform::transform( double value ) const +{ + if ( value < 0.0 ) + return -std::pow( -value, 1.0 / m_exponent ); + else + return std::pow( value, 1.0 / m_exponent ); + +} + +/*! + \param value Value to be transformed + \return Inverse exponentiation preserving the sign + */ +double QwtPowerTransform::invTransform( double value ) const +{ + if ( value < 0.0 ) + return -std::pow( -value, m_exponent ); + else + return std::pow( value, m_exponent ); +} + +//! \return Clone of the transformation +QwtTransform* QwtPowerTransform::copy() const +{ + return new QwtPowerTransform( m_exponent ); +} diff --git a/libs/qwt/src/qwt_transform.h b/libs/qwt/src/qwt_transform.h new file mode 100644 index 00000000..60fff020 --- /dev/null +++ b/libs/qwt/src/qwt_transform.h @@ -0,0 +1,140 @@ +/****************************************************************************** + * 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_TRANSFORM_H +#define QWT_TRANSFORM_H + +#include "qwt_global.h" + +/*! + \brief A transformation between coordinate systems + + QwtTransform manipulates values, when being mapped between + the scale and the paint device coordinate system. + + A transformation consists of 2 methods: + + - transform + - invTransform + + where one is is the inverse function of the other. + + When p1, p2 are the boundaries of the paint device coordinates + and s1, s2 the boundaries of the scale, QwtScaleMap uses the + following calculations: + + - p = p1 + ( p2 - p1 ) * ( T( s ) - T( s1 ) / ( T( s2 ) - T( s1 ) ); + - s = invT ( T( s1 ) + ( T( s2 ) - T( s1 ) ) * ( p - p1 ) / ( p2 - p1 ) ); + */ +class QWT_EXPORT QwtTransform +{ + public: + QwtTransform(); + virtual ~QwtTransform(); + + /*! + Modify value to be a valid value for the transformation. + The default implementation does nothing. + */ + virtual double bounded( double value ) const; + + /*! + Transformation function + + \param value Value + \return Modified value + + \sa invTransform() + */ + virtual double transform( double value ) const = 0; + + /*! + Inverse transformation function + + \param value Value + \return Modified value + + \sa transform() + */ + virtual double invTransform( double value ) const = 0; + + //! Virtualized copy operation + virtual QwtTransform* copy() const = 0; + + private: + Q_DISABLE_COPY(QwtTransform) +}; + +/*! + \brief Null transformation + + QwtNullTransform returns the values unmodified. + + */ +class QWT_EXPORT QwtNullTransform : public QwtTransform +{ + public: + QwtNullTransform(); + virtual ~QwtNullTransform(); + + virtual double transform( double value ) const QWT_OVERRIDE; + virtual double invTransform( double value ) const QWT_OVERRIDE; + + virtual QwtTransform* copy() const QWT_OVERRIDE; +}; +/*! + \brief Logarithmic transformation + + QwtLogTransform modifies the values using log() and exp(). + + \note In the calculations of QwtScaleMap the base of the log function + has no effect on the mapping. So QwtLogTransform can be used + for log2(), log10() or any other logarithmic scale. + */ +class QWT_EXPORT QwtLogTransform : public QwtTransform +{ + public: + QwtLogTransform(); + virtual ~QwtLogTransform(); + + virtual double transform( double value ) const QWT_OVERRIDE; + virtual double invTransform( double value ) const QWT_OVERRIDE; + + virtual double bounded( double value ) const QWT_OVERRIDE; + + virtual QwtTransform* copy() const QWT_OVERRIDE; + + static const double LogMin; + static const double LogMax; +}; + +/*! + \brief A transformation using pow() + + QwtPowerTransform preserves the sign of a value. + F.e. a transformation with a factor of 2 + transforms a value of -3 to -9 and v.v. Thus QwtPowerTransform + can be used for scales including negative values. + */ +class QWT_EXPORT QwtPowerTransform : public QwtTransform +{ + public: + explicit QwtPowerTransform( double exponent ); + virtual ~QwtPowerTransform(); + + virtual double transform( double value ) const QWT_OVERRIDE; + virtual double invTransform( double value ) const QWT_OVERRIDE; + + virtual QwtTransform* copy() const QWT_OVERRIDE; + + private: + const double m_exponent; +}; + +#endif diff --git a/libs/qwt/src/qwt_vectorfield_symbol.cpp b/libs/qwt/src/qwt_vectorfield_symbol.cpp new file mode 100644 index 00000000..ddbe3744 --- /dev/null +++ b/libs/qwt/src/qwt_vectorfield_symbol.cpp @@ -0,0 +1,160 @@ +/****************************************************************************** + * 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_vectorfield_symbol.h" + +#include +#include + +//! Constructor +QwtVectorFieldSymbol::QwtVectorFieldSymbol() +{ +} + +//! Destructor +QwtVectorFieldSymbol::~QwtVectorFieldSymbol() +{ +} + +class QwtVectorFieldArrow::PrivateData +{ + public: + PrivateData( qreal headW, qreal tailW ) + : headWidth( headW ) + , tailWidth( tailW ) + , length( headW + 4.0 ) + { + /* + Arrow is drawn horizontally, pointing into positive x direction + with tip at 0,0. + */ + + path.lineTo( -headWidth, headWidth ); + path.lineTo( -headWidth, tailWidth ); + path.lineTo( -length, tailWidth ); + path.lineTo( -length, -tailWidth ); + path.lineTo( -headWidth, -tailWidth ); + path.lineTo( -headWidth, -headWidth ); + + path.closeSubpath(); + } + + void setLength( qreal l ) + { + length = qMax( l, headWidth ); + + path.setElementPositionAt( 3, -length, tailWidth ); + path.setElementPositionAt( 4, -length, -tailWidth ); + } + + const qreal headWidth; + const qreal tailWidth; + qreal length; + + QPainterPath path; + +}; + +/*! + \brief Constructor + + The length is initialized by headWidth + 4 + + \param headWidth Width of the triangular head + \param tailWidth Width of the arrow tail + + \sa setLength() + */ +QwtVectorFieldArrow::QwtVectorFieldArrow( qreal headWidth, qreal tailWidth ) +{ + m_data = new PrivateData( headWidth, tailWidth ); +} + +//! Destructor +QwtVectorFieldArrow::~QwtVectorFieldArrow() +{ + delete m_data; +} + +void QwtVectorFieldArrow::setLength( qreal length ) +{ + m_data->setLength( length ); +} + +qreal QwtVectorFieldArrow::length() const +{ + return m_data->length; +} + +void QwtVectorFieldArrow::paint( QPainter* painter ) const +{ + painter->drawPath( m_data->path ); +} + +class QwtVectorFieldThinArrow::PrivateData +{ + public: + PrivateData( qreal headW ) + : headWidth( headW ) + , length( headW + 4.0 ) + { + path.lineTo( -headWidth, headWidth * 0.6 ); + path.moveTo( 0, 0 ); + path.lineTo( -headWidth, -headWidth * 0.6 ); + path.moveTo( 0, 0 ); + path.lineTo( -length, 0 ); + } + + const qreal headWidth; + qreal length; + + QPainterPath path; +}; + +/*! + \brief Constructor + + The length is initialized by headWidth + 4 + + \param headWidth Width of the triangular head + \sa setLength() + */ +QwtVectorFieldThinArrow::QwtVectorFieldThinArrow( qreal headWidth ) +{ + m_data = new PrivateData( headWidth ); +} + +//! \brief Destructor +QwtVectorFieldThinArrow::~QwtVectorFieldThinArrow() +{ + delete m_data; +} + +void QwtVectorFieldThinArrow::setLength( qreal length ) +{ + m_data->length = length; + + const qreal headWidth = qMin( m_data->headWidth, length / 3.0 ); + + QPainterPath& path = m_data->path; + + path.setElementPositionAt( 1, -headWidth, headWidth * 0.6 ); + path.setElementPositionAt( 3, -headWidth, -headWidth * 0.6 ); + path.setElementPositionAt( 5, -length, 0 ); +} + +qreal QwtVectorFieldThinArrow::length() const +{ + return m_data->length; +} + +void QwtVectorFieldThinArrow::paint(QPainter* p) const +{ + p->drawPath( m_data->path ); +} diff --git a/libs/qwt/src/qwt_vectorfield_symbol.h b/libs/qwt/src/qwt_vectorfield_symbol.h new file mode 100644 index 00000000..a28cf6d8 --- /dev/null +++ b/libs/qwt/src/qwt_vectorfield_symbol.h @@ -0,0 +1,97 @@ +/****************************************************************************** + * 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_VECTOR_FIELD_SYMBOL_H +#define QWT_VECTOR_FIELD_SYMBOL_H + +#include "qwt_global.h" + +class QPainter; +class QPainterPath; + +/*! + Defines abstract interface for arrow drawing routines. + + Arrow needs to be drawn horizontally with arrow tip at coordinate 0,0. + arrowLength() shall return the entire length of the arrow (needed + to translate the arrow for tail/centered alignment). + setArrowLength() defines arror length in pixels (screen coordinates). It + can be implemented to adjust other geometric properties such as + the head size and width of the arrow. It is _always_ called before + paint(). + + A new arrow implementation can be set with QwtPlotVectorField::setArrowSymbol(), whereby + ownership is transferred to the plot field. + */ +class QWT_EXPORT QwtVectorFieldSymbol +{ + public: + QwtVectorFieldSymbol(); + virtual ~QwtVectorFieldSymbol(); + + /*! + Set the length of the symbol/arrow + \sa length() + */ + virtual void setLength( qreal length ) = 0; + + /*! + \return length of the symbol/arrow + \sa setLength() + */ + virtual qreal length() const = 0; + + //! Draw the symbol/arrow + virtual void paint( QPainter* ) const = 0; + + private: + Q_DISABLE_COPY(QwtVectorFieldSymbol) +}; + +/*! + Arrow implementation that draws a filled arrow with outline, using + a triangular head of constant width. + */ +class QWT_EXPORT QwtVectorFieldArrow : public QwtVectorFieldSymbol +{ + public: + QwtVectorFieldArrow( qreal headWidth = 6.0, qreal tailWidth = 1.0 ); + virtual ~QwtVectorFieldArrow() QWT_OVERRIDE; + + virtual void setLength( qreal length ) QWT_OVERRIDE; + virtual qreal length() const QWT_OVERRIDE; + + virtual void paint( QPainter* ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +/*! + Arrow implementation that only used lines, with optionally a filled arrow or only + lines. + */ +class QWT_EXPORT QwtVectorFieldThinArrow : public QwtVectorFieldSymbol +{ + public: + QwtVectorFieldThinArrow( qreal headWidth = 6.0 ); + virtual ~QwtVectorFieldThinArrow() QWT_OVERRIDE; + + virtual void setLength( qreal length ) QWT_OVERRIDE; + virtual qreal length() const QWT_OVERRIDE; + + virtual void paint( QPainter* ) const QWT_OVERRIDE; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_weeding_curve_fitter.cpp b/libs/qwt/src/qwt_weeding_curve_fitter.cpp new file mode 100644 index 00000000..aacb99cc --- /dev/null +++ b/libs/qwt/src/qwt_weeding_curve_fitter.cpp @@ -0,0 +1,241 @@ +/****************************************************************************** + * 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_weeding_curve_fitter.h" +#include "qwt_math.h" + +#include +#include +#include +#include + +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 ) + : QwtCurveFitter( QwtCurveFitter::Polygon ) +{ + m_data = new PrivateData; + setTolerance( tolerance ); +} + +//! Destructor +QwtWeedingCurveFitter::~QwtWeedingCurveFitter() +{ + delete m_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 ) +{ + m_data->tolerance = qwtMaxF( tolerance, 0.0 ); +} + +/*! + \return Tolerance + \sa setTolerance() + */ +double QwtWeedingCurveFitter::tolerance() const +{ + return m_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 ); + + m_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 m_data->chunkSize; +} + +/*! + \param points Series of data points + \return Curve points + \sa fitCurvePath() + */ +QPolygonF QwtWeedingCurveFitter::fitCurve( const QPolygonF& points ) const +{ + if ( points.isEmpty() ) + return points; + + QPolygonF fittedPoints; + if ( m_data->chunkSize == 0 ) + { + fittedPoints = simplify( points ); + } + else + { + for ( int i = 0; i < points.size(); i += m_data->chunkSize ) + { + const QPolygonF p = points.mid( i, m_data->chunkSize ); + fittedPoints += simplify( p ); + } + } + + return fittedPoints; +} + +/*! + \param points Series of data points + \return Curve path + \sa fitCurve() + */ +QPainterPath QwtWeedingCurveFitter::fitCurvePath( const QPolygonF& points ) const +{ + QPainterPath path; + path.addPolygon( fitCurve( points ) ); + return path; +} + +QPolygonF QwtWeedingCurveFitter::simplify( const QPolygonF& points ) const +{ + const double toleranceSqr = m_data->tolerance * m_data->tolerance; + + QStack< Line > stack; + stack.reserve( 500 ); + + const QPointF* p = points.data(); + const int nPoints = points.size(); + + QVector< bool > 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 = std::sqrt( 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 = std::fabs( 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/libs/qwt/src/qwt_weeding_curve_fitter.h b/libs/qwt/src/qwt_weeding_curve_fitter.h new file mode 100644 index 00000000..9a273ba1 --- /dev/null +++ b/libs/qwt/src/qwt_weeding_curve_fitter.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * 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_WEEDING_CURVE_FITTER_H +#define QWT_WEEDING_CURVE_FITTER_H + +#include "qwt_curve_fitter.h" + +/*! + \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: + explicit 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 QWT_OVERRIDE; + virtual QPainterPath fitCurvePath( const QPolygonF& ) const QWT_OVERRIDE; + + private: + virtual QPolygonF simplify( const QPolygonF& ) const; + + class Line; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_wheel.cpp b/libs/qwt/src/qwt_wheel.cpp new file mode 100644 index 00000000..86d50cad --- /dev/null +++ b/libs/qwt/src/qwt_wheel.cpp @@ -0,0 +1,1308 @@ +/****************************************************************************** + * 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_wheel.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include "qwt.h" + +#include +#include +#include +#include +#include +#include +#include + +class QwtWheel::PrivateData +{ + public: + PrivateData() + : orientation( Qt::Horizontal ) + , viewAngle( 175.0 ) + , totalAngle( 360.0 ) + , tickCount( 10 ) + , wheelBorderWidth( 2 ) + , borderWidth( 2 ) + , wheelWidth( 20 ) + , mouseOffset( 0.0 ) + , updateInterval( 50 ) + , mass( 0.0 ) + , timerId( 0 ) + , speed( 0.0 ) + , mouseValue( 0.0 ) + , flyingValue( 0.0 ) + , minimum( 0.0 ) + , maximum( 100.0 ) + , singleStep( 1.0 ) + , pageStepCount( 1 ) + , value( 0.0 ) + , isScrolling( false ) + , tracking( true ) + , stepAlignment( true ) + , pendingValueChanged( false ) + , inverted( false ) + , wrapping( false ) + { + } + + Qt::Orientation orientation; + double viewAngle; + double totalAngle; + int tickCount; + int wheelBorderWidth; + int borderWidth; + int wheelWidth; + + double mouseOffset; + + int updateInterval; + double mass; + + // for the flying wheel effect + int timerId; + QElapsedTimer timer; + double speed; + double mouseValue; + double flyingValue; + + double minimum; + double maximum; + + double singleStep; + int pageStepCount; + + double value; + + bool isScrolling; + bool tracking; + bool stepAlignment; + bool pendingValueChanged; // when not tracking + bool inverted; + bool wrapping; +}; + +//! Constructor +QwtWheel::QwtWheel( QWidget* parent ): + QWidget( parent ) +{ + m_data = new PrivateData; + + setFocusPolicy( Qt::StrongFocus ); + setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ); + setAttribute( Qt::WA_WState_OwnSizePolicy, false ); +} + +//! Destructor +QwtWheel::~QwtWheel() +{ + delete m_data; +} + +/*! + \brief En/Disable tracking + + If tracking is enabled (the default), the wheel emits the valueChanged() + signal while the wheel is moving. If tracking is disabled, the wheel + emits the valueChanged() signal only when the wheel movement is terminated. + + The wheelMoved() signal is emitted regardless id tracking is enabled or not. + + \param enable On/Off + \sa isTracking() + */ +void QwtWheel::setTracking( bool enable ) +{ + m_data->tracking = enable; +} + +/*! + \return True, when tracking is enabled + \sa setTracking(), valueChanged(), wheelMoved() + */ +bool QwtWheel::isTracking() const +{ + return m_data->tracking; +} + +/*! + \brief Specify the update interval when the wheel is flying + + Default and minimum value is 50 ms. + + \param interval Interval in milliseconds + \sa updateInterval(), setMass(), setTracking() + */ +void QwtWheel::setUpdateInterval( int interval ) +{ + m_data->updateInterval = qMax( interval, 50 ); +} + +/*! + \return Update interval when the wheel is flying + \sa setUpdateInterval(), mass(), isTracking() + */ +int QwtWheel::updateInterval() const +{ + return m_data->updateInterval; +} + +/*! + \brief Mouse press event handler + + Start movement of the wheel. + + \param event Mouse event + */ +void QwtWheel::mousePressEvent( QMouseEvent* event ) +{ + stopFlying(); + + m_data->isScrolling = wheelRect().contains( event->pos() ); + + if ( m_data->isScrolling ) + { + m_data->timer.start(); + m_data->speed = 0.0; + m_data->mouseValue = valueAt( event->pos() ); + m_data->mouseOffset = m_data->mouseValue - m_data->value; + m_data->pendingValueChanged = false; + + Q_EMIT wheelPressed(); + } +} + +/*! + \brief Mouse Move Event handler + + Turn the wheel according to the mouse position + + \param event Mouse event + */ +void QwtWheel::mouseMoveEvent( QMouseEvent* event ) +{ + if ( !m_data->isScrolling ) + return; + + double mouseValue = valueAt( event->pos() ); + + if ( m_data->mass > 0.0 ) + { + double ms = m_data->timer.restart(); + + // the interval when mouse move events are posted are somehow + // random. To avoid unrealistic speed values we limit ms + + ms = qMax( ms, 5.0 ); + + m_data->speed = ( mouseValue - m_data->mouseValue ) / ms; + } + + m_data->mouseValue = mouseValue; + + double value = boundedValue( mouseValue - m_data->mouseOffset ); + if ( m_data->stepAlignment ) + value = alignedValue( value ); + + if ( value != m_data->value ) + { + m_data->value = value; + + update(); + + Q_EMIT wheelMoved( m_data->value ); + + if ( m_data->tracking ) + Q_EMIT valueChanged( m_data->value ); + else + m_data->pendingValueChanged = true; + } +} + +/*! + \brief Mouse Release Event handler + + When the wheel has no mass the movement of the wheel stops, otherwise + it starts flying. + + \param event Mouse event + */ + +void QwtWheel::mouseReleaseEvent( QMouseEvent* event ) +{ + Q_UNUSED( event ); + + if ( !m_data->isScrolling ) + return; + + m_data->isScrolling = false; + + bool startFlying = false; + + if ( m_data->mass > 0.0 ) + { + const qint64 ms = m_data->timer.elapsed(); + if ( ( std::fabs( m_data->speed ) > 0.0 ) && ( ms < 50 ) ) + startFlying = true; + } + + if ( startFlying ) + { + m_data->flyingValue = + boundedValue( m_data->mouseValue - m_data->mouseOffset ); + + m_data->timerId = startTimer( m_data->updateInterval ); + } + else + { + if ( m_data->pendingValueChanged ) + Q_EMIT valueChanged( m_data->value ); + } + + m_data->pendingValueChanged = false; + m_data->mouseOffset = 0.0; + + Q_EMIT wheelReleased(); +} + +/*! + \brief Qt timer event + + The flying wheel effect is implemented using a timer + + \param event Timer event + + \sa updateInterval() + */ +void QwtWheel::timerEvent( QTimerEvent* event ) +{ + if ( event->timerId() != m_data->timerId ) + { + QWidget::timerEvent( event ); + return; + } + + m_data->speed *= std::exp( -m_data->updateInterval * 0.001 / m_data->mass ); + + m_data->flyingValue += m_data->speed * m_data->updateInterval; + m_data->flyingValue = boundedValue( m_data->flyingValue ); + + double value = m_data->flyingValue; + if ( m_data->stepAlignment ) + value = alignedValue( value ); + + if ( std::fabs( m_data->speed ) < 0.001 * m_data->singleStep ) + { + // stop if m_data->speed < one step per second + stopFlying(); + } + + if ( value != m_data->value ) + { + m_data->value = value; + update(); + + if ( m_data->tracking || m_data->timerId == 0 ) + Q_EMIT valueChanged( m_data->value ); + } +} + + +/*! + \brief Handle wheel events + + In/Decrement the value + + \param event Wheel event + */ +void QwtWheel::wheelEvent( QWheelEvent* event ) +{ +#if QT_VERSION < 0x050e00 + const QPoint wheelPos = event->pos(); + const int wheelDelta = event->delta(); +#else + const QPoint wheelPos = event->position().toPoint(); + + const QPoint delta = event->angleDelta(); + const int wheelDelta = ( qAbs( delta.x() ) > qAbs( delta.y() ) ) + ? delta.x() : delta.y(); +#endif + + if ( !wheelRect().contains( wheelPos ) ) + { + event->ignore(); + return; + } + + if ( m_data->isScrolling ) + return; + + stopFlying(); + + double increment = 0.0; + + if ( ( event->modifiers() & Qt::ControlModifier ) || + ( event->modifiers() & Qt::ShiftModifier ) ) + { + // one page regardless of delta + increment = m_data->singleStep * m_data->pageStepCount; + if ( wheelDelta < 0 ) + increment = -increment; + } + else + { + const int numSteps = wheelDelta / 120; + increment = m_data->singleStep * numSteps; + } + + if ( m_data->orientation == Qt::Vertical && m_data->inverted ) + increment = -increment; + + double value = boundedValue( m_data->value + increment ); + + if ( m_data->stepAlignment ) + value = alignedValue( value ); + + if ( value != m_data->value ) + { + m_data->value = value; + update(); + + Q_EMIT valueChanged( m_data->value ); + Q_EMIT wheelMoved( m_data->value ); + } +} + +/*! + Handle key events + + - Qt::Key_Home\n + Step to minimum() + + - Qt::Key_End\n + Step to maximum() + + - Qt::Key_Up\n + In case of a horizontal or not inverted vertical wheel the value + will be incremented by the step size. For an inverted vertical wheel + the value will be decremented by the step size. + + - Qt::Key_Down\n + In case of a horizontal or not inverted vertical wheel the value + will be decremented by the step size. For an inverted vertical wheel + the value will be incremented by the step size. + + - Qt::Key_PageUp\n + The value will be incremented by pageStepSize() * singleStepSize(). + + - Qt::Key_PageDown\n + The value will be decremented by pageStepSize() * singleStepSize(). + + \param event Key event + */ +void QwtWheel::keyPressEvent( QKeyEvent* event ) +{ + if ( m_data->isScrolling ) + { + // don't interfere mouse scrolling + return; + } + + double value = m_data->value; + double increment = 0.0; + + switch ( event->key() ) + { + case Qt::Key_Down: + { + if ( m_data->orientation == Qt::Vertical && m_data->inverted ) + increment = m_data->singleStep; + else + increment = -m_data->singleStep; + + break; + } + case Qt::Key_Up: + { + if ( m_data->orientation == Qt::Vertical && m_data->inverted ) + increment = -m_data->singleStep; + else + increment = m_data->singleStep; + + break; + } + case Qt::Key_Left: + { + if ( m_data->orientation == Qt::Horizontal ) + { + if ( m_data->inverted ) + increment = m_data->singleStep; + else + increment = -m_data->singleStep; + } + break; + } + case Qt::Key_Right: + { + if ( m_data->orientation == Qt::Horizontal ) + { + if ( m_data->inverted ) + increment = -m_data->singleStep; + else + increment = m_data->singleStep; + } + break; + } + case Qt::Key_PageUp: + { + increment = m_data->pageStepCount * m_data->singleStep; + break; + } + case Qt::Key_PageDown: + { + increment = -m_data->pageStepCount * m_data->singleStep; + break; + } + case Qt::Key_Home: + { + value = m_data->minimum; + break; + } + case Qt::Key_End: + { + value = m_data->maximum; + break; + } + default:; + { + event->ignore(); + } + } + + if ( event->isAccepted() ) + stopFlying(); + + if ( increment != 0.0 ) + { + value = boundedValue( m_data->value + increment ); + + if ( m_data->stepAlignment ) + value = alignedValue( value ); + } + + if ( value != m_data->value ) + { + m_data->value = value; + update(); + + Q_EMIT valueChanged( m_data->value ); + Q_EMIT wheelMoved( m_data->value ); + } +} + +/*! + \brief Adjust the number of grooves in the wheel's surface. + + The number of grooves is limited to 6 <= count <= 50. + Values outside this range will be clipped. + The default value is 10. + + \param count Number of grooves per 360 degrees + \sa tickCount() + */ +void QwtWheel::setTickCount( int count ) +{ + count = qBound( 6, count, 50 ); + + if ( count != m_data->tickCount ) + { + m_data->tickCount = qBound( 6, count, 50 ); + update(); + } +} + +/*! + \return Number of grooves in the wheel's surface. + \sa setTickCnt() + */ +int QwtWheel::tickCount() const +{ + return m_data->tickCount; +} + +/*! + \brief Set the wheel border width of the wheel. + + The wheel border must not be smaller than 1 + and is limited in dependence on the wheel's size. + Values outside the allowed range will be clipped. + + The wheel border defaults to 2. + + \param borderWidth Border width + \sa internalBorder() + */ +void QwtWheel::setWheelBorderWidth( int borderWidth ) +{ + const int d = qMin( width(), height() ) / 3; + borderWidth = qMin( borderWidth, d ); + m_data->wheelBorderWidth = qMax( borderWidth, 1 ); + update(); +} + +/*! + \return Wheel border width + \sa setWheelBorderWidth() + */ +int QwtWheel::wheelBorderWidth() const +{ + return m_data->wheelBorderWidth; +} + +/*! + \brief Set the border width + + The border defaults to 2. + + \param width Border width + \sa borderWidth() + */ +void QwtWheel::setBorderWidth( int width ) +{ + m_data->borderWidth = qMax( width, 0 ); + update(); +} + +/*! + \return Border width + \sa setBorderWidth() + */ +int QwtWheel::borderWidth() const +{ + return m_data->borderWidth; +} + +/*! + \return Rectangle of the wheel without the outer border + */ +QRect QwtWheel::wheelRect() const +{ + const int bw = m_data->borderWidth; + return contentsRect().adjusted( bw, bw, -bw, -bw ); +} + +/*! + \brief Set the total angle which the wheel can be turned. + + One full turn of the wheel corresponds to an angle of + 360 degrees. A total angle of n*360 degrees means + that the wheel has to be turned n times around its axis + to get from the minimum value to the maximum value. + + The default setting of the total angle is 360 degrees. + + \param angle total angle in degrees + \sa totalAngle() + */ +void QwtWheel::setTotalAngle( double angle ) +{ + if ( angle < 0.0 ) + angle = 0.0; + + m_data->totalAngle = angle; + update(); +} + +/*! + \return Total angle which the wheel can be turned. + \sa setTotalAngle() + */ +double QwtWheel::totalAngle() const +{ + return m_data->totalAngle; +} + +/*! + \brief Set the wheel's orientation. + + The default orientation is Qt::Horizontal. + + \param orientation Qt::Horizontal or Qt::Vertical. + \sa orientation() + */ +void QwtWheel::setOrientation( Qt::Orientation orientation ) +{ + if ( m_data->orientation == orientation ) + return; + + if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) ) + { + QSizePolicy sp = sizePolicy(); + sp.transpose(); + setSizePolicy( sp ); + + setAttribute( Qt::WA_WState_OwnSizePolicy, false ); + } + + m_data->orientation = orientation; + update(); +} + +/*! + \return Orientation + \sa setOrientation() + */ +Qt::Orientation QwtWheel::orientation() const +{ + return m_data->orientation; +} + +/*! + \brief Specify the visible portion of the wheel. + + You may use this function for fine-tuning the appearance of + the wheel. The default value is 175 degrees. The value is + limited from 10 to 175 degrees. + + \param angle Visible angle in degrees + \sa viewAngle(), setTotalAngle() + */ +void QwtWheel::setViewAngle( double angle ) +{ + m_data->viewAngle = qBound( 10.0, angle, 175.0 ); + update(); +} + +/*! + \return Visible portion of the wheel + \sa setViewAngle(), totalAngle() + */ +double QwtWheel::viewAngle() const +{ + return m_data->viewAngle; +} + +/*! + Determine the value corresponding to a specified point + + \param pos Position + \return Value corresponding to pos + */ +double QwtWheel::valueAt( const QPoint& pos ) const +{ + const QRectF rect = wheelRect(); + + double w, dx; + if ( m_data->orientation == Qt::Vertical ) + { + w = rect.height(); + dx = rect.top() - pos.y(); + } + else + { + w = rect.width(); + dx = pos.x() - rect.left(); + } + + if ( w == 0.0 ) + return 0.0; + + if ( m_data->inverted ) + { + dx = w - dx; + } + + // w pixels is an arc of viewAngle degrees, + // so we convert change in pixels to change in angle + const double ang = dx * m_data->viewAngle / w; + + // value range maps to totalAngle degrees, + // so convert the change in angle to a change in value + const double val = ang * ( maximum() - minimum() ) / m_data->totalAngle; + + return val; +} + +/*! + \brief Qt Paint Event + \param event Paint event + */ +void QwtWheel::paintEvent( QPaintEvent* event ) +{ + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QStyleOption opt; + opt.initFrom(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); + + qDrawShadePanel( &painter, + contentsRect(), palette(), true, m_data->borderWidth ); + + drawWheelBackground( &painter, wheelRect() ); + drawTicks( &painter, wheelRect() ); + + if ( hasFocus() ) + QwtPainter::drawFocusRect( &painter, this ); +} + +/*! + Draw the Wheel's background gradient + + \param painter Painter + \param rect Geometry for the wheel + */ +void QwtWheel::drawWheelBackground( + QPainter* painter, const QRectF& rect ) +{ + painter->save(); + + QPalette pal = palette(); + + // draw shaded background + QLinearGradient gradient( rect.topLeft(), + ( m_data->orientation == Qt::Horizontal ) ? rect.topRight() : rect.bottomLeft() ); + gradient.setColorAt( 0.0, pal.color( QPalette::Button ) ); + gradient.setColorAt( 0.2, pal.color( QPalette::Midlight ) ); + gradient.setColorAt( 0.7, pal.color( QPalette::Mid ) ); + gradient.setColorAt( 1.0, pal.color( QPalette::Dark ) ); + + painter->fillRect( rect, gradient ); + + // draw internal border + + const QPen lightPen( palette().color( QPalette::Light ), + m_data->wheelBorderWidth, Qt::SolidLine, Qt::FlatCap ); + const QPen darkPen( pal.color( QPalette::Dark ), + m_data->wheelBorderWidth, Qt::SolidLine, Qt::FlatCap ); + + const double bw2 = 0.5 * m_data->wheelBorderWidth; + + if ( m_data->orientation == Qt::Horizontal ) + { + painter->setPen( lightPen ); + painter->drawLine( QPointF( rect.left(), rect.top() + bw2 ), + QPointF( rect.right(), rect.top() + bw2 ) ); + + painter->setPen( darkPen ); + painter->drawLine( QPointF( rect.left(), rect.bottom() - bw2 ), + QPointF( rect.right(), rect.bottom() - bw2 ) ); + } + else // Qt::Vertical + { + painter->setPen( lightPen ); + painter->drawLine( QPointF( rect.left() + bw2, rect.top() ), + QPointF( rect.left() + bw2, rect.bottom() ) ); + + painter->setPen( darkPen ); + painter->drawLine( QPointF( rect.right() - bw2, rect.top() ), + QPointF( rect.right() - bw2, rect.bottom() ) ); + } + + painter->restore(); +} + +/*! + Draw the Wheel's ticks + + \param painter Painter + \param rect Geometry for the wheel + */ +void QwtWheel::drawTicks( QPainter* painter, const QRectF& rect ) +{ + const double range = m_data->maximum - m_data->minimum; + + if ( range == 0.0 || m_data->totalAngle == 0.0 ) + { + return; + } + + const QPen lightPen( palette().color( QPalette::Light ), + 0, Qt::SolidLine, Qt::FlatCap ); + const QPen darkPen( palette().color( QPalette::Dark ), + 0, Qt::SolidLine, Qt::FlatCap ); + + const double cnvFactor = qAbs( m_data->totalAngle / range ); + const double halfIntv = 0.5 * m_data->viewAngle / cnvFactor; + const double loValue = value() - halfIntv; + const double hiValue = value() + halfIntv; + const double tickWidth = 360.0 / double( m_data->tickCount ) / cnvFactor; + const double sinArc = qFastSin( m_data->viewAngle * M_PI / 360.0 ); + + if ( m_data->orientation == Qt::Horizontal ) + { + const double radius = rect.width() * 0.5; + + double l1 = rect.top() + m_data->wheelBorderWidth; + double l2 = rect.bottom() - m_data->wheelBorderWidth - 1; + + // draw one point over the border if border > 1 + if ( m_data->wheelBorderWidth > 1 ) + { + l1--; + l2++; + } + + const double maxpos = rect.right() - 2; + const double minpos = rect.left() + 2; + + // draw tick marks + for ( double tickValue = std::ceil( loValue / tickWidth ) * tickWidth; + tickValue < hiValue; tickValue += tickWidth ) + { + const double angle = qwtRadians( tickValue - value() ); + const double s = qFastSin( angle * cnvFactor ); + + const double off = radius * ( sinArc + s ) / sinArc; + + double tickPos; + if ( m_data->inverted ) + tickPos = rect.left() + off; + else + tickPos = rect.right() - off; + + if ( ( tickPos <= maxpos ) && ( tickPos > minpos ) ) + { + painter->setPen( darkPen ); + painter->drawLine( QPointF( tickPos - 1, l1 ), + QPointF( tickPos - 1, l2 ) ); + painter->setPen( lightPen ); + painter->drawLine( QPointF( tickPos, l1 ), + QPointF( tickPos, l2 ) ); + } + } + } + else // Qt::Vertical + { + const double radius = rect.height() * 0.5; + + double l1 = rect.left() + m_data->wheelBorderWidth; + double l2 = rect.right() - m_data->wheelBorderWidth - 1; + + if ( m_data->wheelBorderWidth > 1 ) + { + l1--; + l2++; + } + + const double maxpos = rect.bottom() - 2; + const double minpos = rect.top() + 2; + + for ( double tickValue = std::ceil( loValue / tickWidth ) * tickWidth; + tickValue < hiValue; tickValue += tickWidth ) + { + const double angle = qwtRadians( tickValue - value() ); + const double s = qFastSin( angle * cnvFactor ); + + const double off = radius * ( sinArc + s ) / sinArc; + + double tickPos; + + if ( m_data->inverted ) + tickPos = rect.bottom() - off; + else + tickPos = rect.top() + off; + + if ( ( tickPos <= maxpos ) && ( tickPos > minpos ) ) + { + painter->setPen( darkPen ); + painter->drawLine( QPointF( l1, tickPos - 1 ), + QPointF( l2, tickPos - 1 ) ); + painter->setPen( lightPen ); + painter->drawLine( QPointF( l1, tickPos ), + QPointF( l2, tickPos ) ); + } + } + } +} + +/*! + \brief Set the width of the wheel + + Corresponds to the wheel height for horizontal orientation, + and the wheel width for vertical orientation. + + \param width the wheel's width + \sa wheelWidth() + */ +void QwtWheel::setWheelWidth( int width ) +{ + m_data->wheelWidth = width; + update(); +} + +/*! + \return Width of the wheel + \sa setWheelWidth() + */ +int QwtWheel::wheelWidth() const +{ + return m_data->wheelWidth; +} + +/*! + \return a size hint + */ +QSize QwtWheel::sizeHint() const +{ + const QSize hint = minimumSizeHint(); + return qwtExpandedToGlobalStrut( hint ); +} + +/*! + \return Minimum size hint + \warning The return value is based on the wheel width. + */ +QSize QwtWheel::minimumSizeHint() const +{ + QSize sz( 3 * m_data->wheelWidth + 2 * m_data->borderWidth, + m_data->wheelWidth + 2 * m_data->borderWidth ); + if ( m_data->orientation != Qt::Horizontal ) + sz.transpose(); + + return sz; +} + +/*! + \brief Set the step size of the counter + + A value <= 0.0 disables stepping + + \param stepSize Single step size + \sa singleStep(), setPageStepCount() + */ +void QwtWheel::setSingleStep( double stepSize ) +{ + m_data->singleStep = qwtMaxF( stepSize, 0.0 ); +} + +/*! + \return Single step size + \sa setSingleStep() + */ +double QwtWheel::singleStep() const +{ + return m_data->singleStep; +} + +/*! + \brief En/Disable step alignment + + When step alignment is enabled value changes initiated by + user input ( mouse, keyboard, wheel ) are aligned to + the multiples of the single step. + + \param on On/Off + \sa stepAlignment(), setSingleStep() + */ +void QwtWheel::setStepAlignment( bool on ) +{ + if ( on != m_data->stepAlignment ) + { + m_data->stepAlignment = on; + } +} + +/*! + \return True, when the step alignment is enabled + \sa setStepAlignment(), singleStep() + */ +bool QwtWheel::stepAlignment() const +{ + return m_data->stepAlignment; +} + +/*! + \brief Set the page step count + + pageStepCount is a multiplicator for the single step size + that typically corresponds to the user pressing PageUp or PageDown. + + A value of 0 disables page stepping. + + The default value is 1. + + \param count Multiplicator for the single step size + \sa pageStepCount(), setSingleStep() + */ +void QwtWheel::setPageStepCount( int count ) +{ + m_data->pageStepCount = qMax( 0, count ); +} + +/*! + \return Page step count + \sa setPageStepCount(), singleStep() + */ +int QwtWheel::pageStepCount() const +{ + return m_data->pageStepCount; +} + +/*! + \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 QwtWheel::setRange( double min, double max ) +{ + max = qwtMaxF( min, max ); + + if ( m_data->minimum == min && m_data->maximum == max ) + return; + + m_data->minimum = min; + m_data->maximum = max; + + if ( m_data->value < min || m_data->value > max ) + { + m_data->value = qBound( min, m_data->value, max ); + + update(); + Q_EMIT valueChanged( m_data->value ); + } +} +/*! + 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 QwtWheel::setMinimum( double value ) +{ + setRange( value, maximum() ); +} + +/*! + \return The minimum of the range + \sa setRange(), setMinimum(), maximum() + */ +double QwtWheel::minimum() const +{ + return m_data->minimum; +} + +/*! + Set the maximum value of the range + + \param value Maximum value + \sa setRange(), setMinimum(), maximum() + */ +void QwtWheel::setMaximum( double value ) +{ + setRange( minimum(), value ); +} + +/*! + \return The maximum of the range + \sa setRange(), setMaximum(), minimum() + */ +double QwtWheel::maximum() const +{ + return m_data->maximum; +} + +/*! + \brief Set a new value without adjusting to the step raster + + \param value New value + + \sa value(), valueChanged() + \warning The value is clipped when it lies outside the range. + */ +void QwtWheel::setValue( double value ) +{ + stopFlying(); + m_data->isScrolling = false; + + value = qBound( m_data->minimum, value, m_data->maximum ); + + if ( m_data->value != value ) + { + m_data->value = value; + + update(); + Q_EMIT valueChanged( m_data->value ); + } +} + +/*! + \return Current value of the wheel + \sa setValue(), valueChanged() + */ +double QwtWheel::value() const +{ + return m_data->value; +} + +/*! + \brief En/Disable inverted appearance + + An inverted wheel increases its values in the opposite direction. + The direction of an inverted horizontal wheel will be from right to left + an inverted vertical wheel will increase from bottom to top. + + \param on En/Disable inverted appearance + \sa isInverted() + + */ +void QwtWheel::setInverted( bool on ) +{ + if ( m_data->inverted != on ) + { + m_data->inverted = on; + update(); + } +} + +/*! + \return True, when the wheel is inverted + \sa setInverted() + */ +bool QwtWheel::isInverted() const +{ + return m_data->inverted; +} + +/*! + \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 QwtWheel::setWrapping( bool on ) +{ + m_data->wrapping = on; +} + +/*! + \return True, when wrapping is set + \sa setWrapping() + */ +bool QwtWheel::wrapping() const +{ + return m_data->wrapping; +} + +/*! + \brief Set the slider's mass for flywheel effect. + + If the slider's mass is greater then 0, it will continue + to move after the mouse button has been released. Its speed + decreases with time at a rate depending on the slider's mass. + A large mass means that it will continue to move for a + long time. + + Derived widgets may overload this function to make it public. + + \param mass New mass in kg + + \bug If the mass is smaller than 1g, it is set to zero. + The maximal mass is limited to 100kg. + \sa mass() + */ +void QwtWheel::setMass( double mass ) +{ + if ( mass < 0.001 ) + { + m_data->mass = 0.0; + } + else + { + m_data->mass = qwtMinF( 100.0, mass ); + } + + if ( m_data->mass <= 0.0 ) + stopFlying(); +} + +/*! + \return mass + \sa setMass() + */ +double QwtWheel::mass() const +{ + return m_data->mass; +} + +//! Stop the flying movement of the wheel +void QwtWheel::stopFlying() +{ + if ( m_data->timerId != 0 ) + { + killTimer( m_data->timerId ); + m_data->timerId = 0; + m_data->speed = 0.0; + } +} + +double QwtWheel::boundedValue( double value ) const +{ + const double range = m_data->maximum - m_data->minimum; + + if ( m_data->wrapping && range >= 0.0 ) + { + if ( value < m_data->minimum ) + { + value += std::ceil( ( m_data->minimum - value ) / range ) * range; + } + else if ( value > m_data->maximum ) + { + value -= std::ceil( ( value - m_data->maximum ) / range ) * range; + } + } + else + { + value = qBound( m_data->minimum, value, m_data->maximum ); + } + + return value; +} + +double QwtWheel::alignedValue( double value ) const +{ + const double stepSize = m_data->singleStep; + + if ( stepSize > 0.0 ) + { + value = m_data->minimum + + qRound( ( value - m_data->minimum ) / 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, m_data->maximum ) ) + { + // correct rounding error at the border + value = m_data->maximum; + } + } + } + + return value; +} + +#if QWT_MOC_INCLUDE +#include "moc_qwt_wheel.cpp" +#endif diff --git a/libs/qwt/src/qwt_wheel.h b/libs/qwt/src/qwt_wheel.h new file mode 100644 index 00000000..5dcffe30 --- /dev/null +++ b/libs/qwt/src/qwt_wheel.h @@ -0,0 +1,179 @@ +/****************************************************************************** + * 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_WHEEL_H +#define QWT_WHEEL_H + +#include "qwt_global.h" +#include + +/*! + \brief The Wheel Widget + + The wheel widget can be used to change values over a very large range + in very small steps. Using the setMass() member, it can be configured + as a flying wheel. + + The default range of the wheel is [0.0, 100.0] + + \sa The radio example. + */ +class QWT_EXPORT QwtWheel : public QWidget +{ + Q_OBJECT + + Q_PROPERTY( Qt::Orientation orientation + READ orientation WRITE setOrientation ) + + Q_PROPERTY( double value READ value WRITE setValue NOTIFY valueChanged USER true ) + + 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 pageStepCount READ pageStepCount WRITE setPageStepCount ) + Q_PROPERTY( bool stepAlignment READ stepAlignment WRITE setStepAlignment ) + + Q_PROPERTY( bool tracking READ isTracking WRITE setTracking ) + Q_PROPERTY( bool wrapping READ wrapping WRITE setWrapping ) + Q_PROPERTY( bool inverted READ isInverted WRITE setInverted ) + + Q_PROPERTY( double mass READ mass WRITE setMass ) + Q_PROPERTY( int updateInterval READ updateInterval WRITE setUpdateInterval ) + + Q_PROPERTY( double totalAngle READ totalAngle WRITE setTotalAngle ) + Q_PROPERTY( double viewAngle READ viewAngle WRITE setViewAngle ) + Q_PROPERTY( int tickCount READ tickCount WRITE setTickCount ) + Q_PROPERTY( int wheelWidth READ wheelWidth WRITE setWheelWidth ) + Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth ) + Q_PROPERTY( int wheelBorderWidth READ wheelBorderWidth WRITE setWheelBorderWidth ) + + public: + explicit QwtWheel( QWidget* parent = NULL ); + virtual ~QwtWheel(); + + double value() const; + + void setOrientation( Qt::Orientation ); + Qt::Orientation orientation() const; + + double totalAngle() const; + double viewAngle() const; + + void setTickCount( int ); + int tickCount() const; + + void setWheelWidth( int ); + int wheelWidth() const; + + void setWheelBorderWidth( int ); + int wheelBorderWidth() const; + + void setBorderWidth( int ); + int borderWidth() const; + + void setInverted( bool ); + bool isInverted() const; + + void setWrapping( bool ); + bool wrapping() const; + + void setSingleStep( double ); + double singleStep() const; + + void setPageStepCount( int ); + int pageStepCount() const; + + void setStepAlignment( bool on ); + bool stepAlignment() const; + + void setRange( double min, double max ); + + void setMinimum( double ); + double minimum() const; + + void setMaximum( double ); + double maximum() const; + + void setUpdateInterval( int ); + int updateInterval() const; + + void setTracking( bool ); + bool isTracking() const; + + double mass() const; + + public Q_SLOTS: + void setValue( double ); + void setTotalAngle ( double ); + void setViewAngle( double ); + void setMass( double ); + + Q_SIGNALS: + + /*! + \brief Notify a change of value. + + When tracking is enabled this signal will be emitted every + time the value changes. + + \param value new value + \sa setTracking() + */ + void valueChanged( double value ); + + /*! + This signal is emitted when the user presses the + the wheel with the mouse + */ + void wheelPressed(); + + /*! + This signal is emitted when the user releases the mouse + */ + void wheelReleased(); + + /*! + This signal is emitted when the user moves the + wheel with the mouse. + + \param value new value + */ + void wheelMoved( double value ); + + protected: + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + virtual void mousePressEvent( QMouseEvent* ) QWT_OVERRIDE; + virtual void mouseReleaseEvent( QMouseEvent* ) QWT_OVERRIDE; + virtual void mouseMoveEvent( QMouseEvent* ) QWT_OVERRIDE; + virtual void keyPressEvent( QKeyEvent* ) QWT_OVERRIDE; + virtual void wheelEvent( QWheelEvent* ) QWT_OVERRIDE; + virtual void timerEvent( QTimerEvent* ) QWT_OVERRIDE; + + void stopFlying(); + + QRect wheelRect() const; + + virtual QSize sizeHint() const QWT_OVERRIDE; + virtual QSize minimumSizeHint() const QWT_OVERRIDE; + + virtual void drawTicks( QPainter*, const QRectF& ); + virtual void drawWheelBackground( QPainter*, const QRectF& ); + + virtual double valueAt( const QPoint& ) const; + + private: + double alignedValue( double ) const; + double boundedValue( double ) const; + + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/qwt_widget_overlay.cpp b/libs/qwt/src/qwt_widget_overlay.cpp new file mode 100644 index 00000000..546c0a39 --- /dev/null +++ b/libs/qwt/src/qwt_widget_overlay.cpp @@ -0,0 +1,398 @@ +/****************************************************************************** + * 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_widget_overlay.h" +#include "qwt_painter.h" + +#include +#include +#include +#include +#include + +#include + +static QImage::Format qwtMaskImageFormat() +{ + if ( QwtPainter::isX11GraphicsSystem() ) + return QImage::Format_ARGB32; + + return QImage::Format_ARGB32_Premultiplied; +} + +static QRegion qwtAlphaMask( const QImage& image, const QRegion& region ) +{ + const int w = image.width(); + const int h = image.height(); + + QRegion mask; + QRect rect; + +#if QT_VERSION >= 0x050800 + for ( QRegion::const_iterator it = region.cbegin(); + it != region.cend(); ++it ) + { + const QRect& r = *it; +#else + const QVector< QRect > rects = region.rects(); + for ( int i = 0; i < rects.size(); i++ ) + { + const QRect& r = rects[i]; +#endif + int x1, x2, y1, y2; + r.getCoords( &x1, &y1, &x2, &y2 ); + + x1 = qMax( x1, 0 ); + x2 = qMin( x2, w - 1 ); + y1 = qMax( y1, 0 ); + y2 = qMin( y2, h - 1 ); + + for ( int y = y1; y <= y2; ++y ) + { + bool inRect = false; + int rx0 = -1; + + const uint* line = + reinterpret_cast< const uint* > ( image.scanLine( y ) ) + x1; + for ( int x = x1; x <= x2; x++ ) + { + const bool on = ( ( *line++ >> 24 ) != 0 ); + if ( on != inRect ) + { + if ( inRect ) + { + rect.setCoords( rx0, y, x - 1, y ); + mask += rect; + } + else + { + rx0 = x; + } + + inRect = on; + } + } + + if ( inRect ) + { + rect.setCoords( rx0, y, x2, y ); + mask = mask.united( rect ); + } + } + } + + return mask; +} + +class QwtWidgetOverlay::PrivateData +{ + public: + PrivateData() + : maskMode( QwtWidgetOverlay::MaskHint ) + , renderMode( QwtWidgetOverlay::AutoRenderMode ) + , rgbaBuffer( NULL ) + { + } + + ~PrivateData() + { + resetRgbaBuffer(); + } + + void resetRgbaBuffer() + { + if ( rgbaBuffer ) + { + std::free( rgbaBuffer ); + rgbaBuffer = NULL; + } + } + + MaskMode maskMode; + RenderMode renderMode; + uchar* rgbaBuffer; +}; + +/*! + \brief Constructor + \param widget Parent widget, where the overlay is aligned to + */ +QwtWidgetOverlay::QwtWidgetOverlay( QWidget* widget ) + : QWidget( widget ) +{ + m_data = new PrivateData; + + setAttribute( Qt::WA_TransparentForMouseEvents ); + setAttribute( Qt::WA_NoSystemBackground ); + setFocusPolicy( Qt::NoFocus ); + + if ( widget ) + { + resize( widget->size() ); + widget->installEventFilter( this ); + } +} + +//! Destructor +QwtWidgetOverlay::~QwtWidgetOverlay() +{ + delete m_data; +} + +/*! + \brief Specify how to find the mask for the overlay + + \param mode New mode + \sa maskMode() + */ +void QwtWidgetOverlay::setMaskMode( MaskMode mode ) +{ + if ( mode != m_data->maskMode ) + { + m_data->maskMode = mode; + m_data->resetRgbaBuffer(); + } +} + +/*! + \return Mode how to find the mask for the overlay + \sa setMaskMode() + */ +QwtWidgetOverlay::MaskMode QwtWidgetOverlay::maskMode() const +{ + return m_data->maskMode; +} + +/*! + Set the render mode + \param mode Render mode + + \sa RenderMode, renderMode() + */ +void QwtWidgetOverlay::setRenderMode( RenderMode mode ) +{ + m_data->renderMode = mode; +} + +/*! + \return Render mode + \sa RenderMode, setRenderMode() + */ +QwtWidgetOverlay::RenderMode QwtWidgetOverlay::renderMode() const +{ + return m_data->renderMode; +} + +/*! + Recalculate the mask and repaint the overlay + */ +void QwtWidgetOverlay::updateOverlay() +{ + updateMask(); + update(); +} + +void QwtWidgetOverlay::updateMask() +{ + m_data->resetRgbaBuffer(); + + QRegion mask; + + if ( m_data->maskMode == QwtWidgetOverlay::MaskHint ) + { + mask = maskHint(); + } + else if ( m_data->maskMode == QwtWidgetOverlay::AlphaMask ) + { + // TODO: the image doesn't need to be larger than + // the bounding rectangle of the hint !! + + QRegion hint = maskHint(); + if ( hint.isEmpty() ) + hint += QRect( 0, 0, width(), height() ); + + // A fresh buffer from calloc() is usually faster + // than reinitializing an existing one with + // QImage::fill( 0 ) or memset() + + m_data->rgbaBuffer = ( uchar* )::calloc( width() * height(), 4 ); + + QImage image( m_data->rgbaBuffer, + width(), height(), qwtMaskImageFormat() ); + + QPainter painter( &image ); + draw( &painter ); + painter.end(); + + mask = qwtAlphaMask( image, hint ); + + if ( m_data->renderMode == QwtWidgetOverlay::DrawOverlay ) + { + // we don't need the buffer later + m_data->resetRgbaBuffer(); + } + } + + // A bug in Qt initiates a full repaint of the widget + // when we change the mask, while we are visible ! + + setVisible( false ); + + if ( mask.isEmpty() ) + clearMask(); + else + setMask( mask ); + + setVisible( true ); +} + +/*! + Paint event + \param event Paint event + + \sa drawOverlay() + */ +void QwtWidgetOverlay::paintEvent( QPaintEvent* event ) +{ + const QRegion& clipRegion = event->region(); + + QPainter painter( this ); + + bool useRgbaBuffer = false; + if ( m_data->renderMode == QwtWidgetOverlay::CopyAlphaMask ) + { + useRgbaBuffer = true; + } + else if ( m_data->renderMode == QwtWidgetOverlay::AutoRenderMode ) + { + if ( painter.paintEngine()->type() == QPaintEngine::Raster ) + useRgbaBuffer = true; + } + + if ( m_data->rgbaBuffer && useRgbaBuffer ) + { + const QImage image( m_data->rgbaBuffer, + width(), height(), qwtMaskImageFormat() ); + + const int rectCount = clipRegion.rectCount(); + + if ( rectCount > 2000 ) + { + // the region is to complex + painter.setClipRegion( clipRegion ); + + const QRect r = clipRegion.boundingRect(); + painter.drawImage( r.topLeft(), image, r ); + } + else + { +#if QT_VERSION >= 0x050800 + for ( QRegion::const_iterator it = clipRegion.cbegin(); + it != clipRegion.cend(); ++it ) + { + const QRect& r = *it; + painter.drawImage( r.topLeft(), image, r ); + } +#else + const QVector< QRect > rects = clipRegion.rects(); + for ( int i = 0; i < rects.size(); i++ ) + { + const QRect& r = rects[i]; + painter.drawImage( r.topLeft(), image, r ); + } +#endif + } + } + else + { + painter.setClipRegion( clipRegion ); + draw( &painter ); + } +} + +/*! + Resize event + \param event Resize event + */ +void QwtWidgetOverlay::resizeEvent( QResizeEvent* event ) +{ + Q_UNUSED( event ); + + m_data->resetRgbaBuffer(); +} + +void QwtWidgetOverlay::draw( QPainter* painter ) const +{ + if ( QWidget* widget = parentWidget() ) + { + painter->setClipRect( widget->contentsRect() ); + + // something special for the plot canvas + + const int idx = widget->metaObject()->indexOfMethod( "borderPath(QRect)" ); + if ( idx >= 0 ) + { + QPainterPath clipPath; + + ( void )QMetaObject::invokeMethod( + widget, "borderPath", Qt::DirectConnection, + Q_RETURN_ARG( QPainterPath, clipPath ), Q_ARG( QRect, rect() ) ); + + if (!clipPath.isEmpty() ) + painter->setClipPath( clipPath, Qt::IntersectClip ); + } + } + + drawOverlay( painter ); +} + +/*! + \brief Calculate an approximation for the mask + + - MaskHint + The hint is used as mask. + + - AlphaMask + The hint is used to speed up the algorithm + for calculating a mask from non transparent pixels + + - NoMask + The hint is unused. + + The default implementation returns an invalid region + indicating no hint. + + \return Hint for the mask + */ +QRegion QwtWidgetOverlay::maskHint() const +{ + return QRegion(); +} + +/*! + \brief Event filter + + Resize the overlay according to the size of the parent widget. + + \param object Object to be filtered + \param event Event + + \return See QObject::eventFilter() + */ + +bool QwtWidgetOverlay::eventFilter( QObject* object, QEvent* event ) +{ + if ( object == parent() && event->type() == QEvent::Resize ) + { + QResizeEvent* resizeEvent = static_cast< QResizeEvent* >( event ); + resize( resizeEvent->size() ); + } + + return QObject::eventFilter( object, event ); +} diff --git a/libs/qwt/src/qwt_widget_overlay.h b/libs/qwt/src/qwt_widget_overlay.h new file mode 100644 index 00000000..e03c946b --- /dev/null +++ b/libs/qwt/src/qwt_widget_overlay.h @@ -0,0 +1,149 @@ +/****************************************************************************** + * 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_WIDGET_OVERLAY_H +#define QWT_WIDGET_OVERLAY_H + +#include "qwt_global.h" +#include + +class QPainter; +class QRegion; + +/*! + \brief An overlay for a widget + + The main use case of an widget overlay is to avoid + heavy repaint operation of the widget below. + + F.e. in combination with the plot canvas an overlay + avoid replots as the content of the canvas can be restored from + its backing store. + + QwtWidgetOverlay is an abstract base class. Deriving classes are + supposed to reimplement the following methods: + + - drawOverlay() + - maskHint() + + Internally QwtPlotPicker uses overlays for displaying + the rubber band and the tracker text. + + \sa QwtPlotCanvas::BackingStore + */ +class QWT_EXPORT QwtWidgetOverlay : public QWidget +{ + public: + /*! + \brief Mask mode + + When using masks the widget below gets paint events for + the masked regions of the overlay only. Otherwise + Qt triggers full repaints. On less powerful hardware + ( f.e embedded systems ) - or when using the raster paint + engine on a remote desktop - bit blitting is a noticeable + operation, that needs to be avoided. + + If and how to mask depends on how expensive the calculation + of the mask is and how many pixels can be excluded by the mask. + + The default setting is MaskHint. + + \sa setMaskMode(), maskMode() + */ + enum MaskMode + { + //! Don't use a mask. + NoMask, + + /*! + \brief Use maskHint() as mask + + For many situations a fast approximation is good enough + and it is not necessary to build a more detailed mask + ( f.e the bounding rectangle of a text ). + */ + MaskHint, + + /*! + \brief Calculate a mask by checking the alpha values + + Sometimes it is not possible to give a fast approximation + and the mask needs to be calculated by drawing the overlay + and testing the result. + + When a valid maskHint() is available + only pixels inside this approximation are checked. + */ + AlphaMask + }; + + /*! + \brief Render mode + + For calculating the alpha mask the overlay has already + been painted to a temporary QImage. Instead of rendering + the overlay twice this buffer can be copied for drawing + the overlay. + + On graphic systems using the raster paint engine ( QWS, Windows ) + it means usually copying some memory only. On X11 it results in an + expensive operation building a pixmap and for simple overlays + it might not be recommended. + + \note The render mode has no effect, when maskMode() != AlphaMask. + */ + enum RenderMode + { + //! Copy the buffer, when using the raster paint engine. + AutoRenderMode, + + //! Always copy the buffer + CopyAlphaMask, + + //! Never copy the buffer + DrawOverlay + }; + + explicit QwtWidgetOverlay( QWidget* ); + virtual ~QwtWidgetOverlay(); + + void setMaskMode( MaskMode ); + MaskMode maskMode() const; + + void setRenderMode( RenderMode ); + RenderMode renderMode() const; + + virtual bool eventFilter( QObject*, QEvent*) QWT_OVERRIDE; + + public Q_SLOTS: + void updateOverlay(); + + protected: + virtual void paintEvent( QPaintEvent* ) QWT_OVERRIDE; + virtual void resizeEvent( QResizeEvent* ) QWT_OVERRIDE; + + virtual QRegion maskHint() const; + + /*! + Draw the widget overlay + \param painter Painter + */ + virtual void drawOverlay( QPainter* painter ) const = 0; + + private: + void updateMask(); + void draw( QPainter* ) const; + + private: + class PrivateData; + PrivateData* m_data; +}; + +#endif diff --git a/libs/qwt/src/src.pri b/libs/qwt/src/src.pri new file mode 100644 index 00000000..e22ded39 --- /dev/null +++ b/libs/qwt/src/src.pri @@ -0,0 +1,370 @@ +################################################################ +# 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 +################################################################ + +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/qwt.h \ + $$PWD/qwt_abstract_scale_draw.h \ + $$PWD/qwt_bezier.h \ + $$PWD/qwt_clipper.h \ + $$PWD/qwt_color_map.h \ + $$PWD/qwt_column_symbol.h \ + $$PWD/qwt_date.h \ + $$PWD/qwt_date_scale_draw.h \ + $$PWD/qwt_date_scale_engine.h \ + $$PWD/qwt_dyngrid_layout.h \ + $$PWD/qwt_global.h \ + $$PWD/qwt_graphic.h \ + $$PWD/qwt_interval.h \ + $$PWD/qwt_interval_symbol.h \ + $$PWD/qwt_math.h \ + $$PWD/qwt_magnifier.h \ + $$PWD/qwt_null_paintdevice.h \ + $$PWD/qwt_painter.h \ + $$PWD/qwt_painter_command.h \ + $$PWD/qwt_panner.h \ + $$PWD/qwt_picker.h \ + $$PWD/qwt_picker_machine.h \ + $$PWD/qwt_pixel_matrix.h \ + $$PWD/qwt_point_3d.h \ + $$PWD/qwt_point_polar.h \ + $$PWD/qwt_round_scale_draw.h \ + $$PWD/qwt_scale_div.h \ + $$PWD/qwt_scale_draw.h \ + $$PWD/qwt_scale_engine.h \ + $$PWD/qwt_scale_map.h \ + $$PWD/qwt_spline.h \ + $$PWD/qwt_spline_basis.h \ + $$PWD/qwt_spline_parametrization.h \ + $$PWD/qwt_spline_local.h \ + $$PWD/qwt_spline_cubic.h \ + $$PWD/qwt_spline_pleasing.h \ + $$PWD/qwt_spline_polynomial.h \ + $$PWD/qwt_symbol.h \ + $$PWD/qwt_system_clock.h \ + $$PWD/qwt_text_engine.h \ + $$PWD/qwt_text_label.h \ + $$PWD/qwt_text.h \ + $$PWD/qwt_transform.h \ + $$PWD/qwt_widget_overlay.h + +SOURCES += \ + $$PWD/qwt.cpp \ + $$PWD/qwt_abstract_scale_draw.cpp \ + $$PWD/qwt_bezier.cpp \ + $$PWD/qwt_clipper.cpp \ + $$PWD/qwt_color_map.cpp \ + $$PWD/qwt_column_symbol.cpp \ + $$PWD/qwt_date.cpp \ + $$PWD/qwt_date_scale_draw.cpp \ + $$PWD/qwt_date_scale_engine.cpp \ + $$PWD/qwt_dyngrid_layout.cpp \ + $$PWD/qwt_event_pattern.cpp \ + $$PWD/qwt_graphic.cpp \ + $$PWD/qwt_interval.cpp \ + $$PWD/qwt_interval_symbol.cpp \ + $$PWD/qwt_math.cpp \ + $$PWD/qwt_magnifier.cpp \ + $$PWD/qwt_null_paintdevice.cpp \ + $$PWD/qwt_painter.cpp \ + $$PWD/qwt_painter_command.cpp \ + $$PWD/qwt_panner.cpp \ + $$PWD/qwt_picker.cpp \ + $$PWD/qwt_picker_machine.cpp \ + $$PWD/qwt_pixel_matrix.cpp \ + $$PWD/qwt_point_3d.cpp \ + $$PWD/qwt_point_polar.cpp \ + $$PWD/qwt_round_scale_draw.cpp \ + $$PWD/qwt_scale_div.cpp \ + $$PWD/qwt_scale_draw.cpp \ + $$PWD/qwt_scale_map.cpp \ + $$PWD/qwt_scale_engine.cpp \ + $$PWD/qwt_spline.cpp \ + $$PWD/qwt_spline_basis.cpp \ + $$PWD/qwt_spline_parametrization.cpp \ + $$PWD/qwt_spline_local.cpp \ + $$PWD/qwt_spline_cubic.cpp \ + $$PWD/qwt_spline_pleasing.cpp \ + $$PWD/qwt_spline_polynomial.cpp \ + $$PWD/qwt_symbol.cpp \ + $$PWD/qwt_system_clock.cpp \ + $$PWD/qwt_text_engine.cpp \ + $$PWD/qwt_text_label.cpp \ + $$PWD/qwt_text.cpp \ + $$PWD/qwt_transform.cpp \ + $$PWD/qwt_widget_overlay.cpp + + +contains(QWT_CONFIG, QwtPlot) { + + HEADERS += \ + $$PWD/qwt_axis.h \ + $$PWD/qwt_axis_id.h \ + $$PWD/qwt_curve_fitter.h \ + $$PWD/qwt_spline_curve_fitter.h \ + $$PWD/qwt_weeding_curve_fitter.h \ + $$PWD/qwt_event_pattern.h \ + $$PWD/qwt_abstract_legend.h \ + $$PWD/qwt_legend.h \ + $$PWD/qwt_legend_data.h \ + $$PWD/qwt_legend_label.h \ + $$PWD/qwt_plot.h \ + $$PWD/qwt_plot_renderer.h \ + $$PWD/qwt_plot_curve.h \ + $$PWD/qwt_plot_dict.h \ + $$PWD/qwt_plot_directpainter.h \ + $$PWD/qwt_plot_graphicitem.h \ + $$PWD/qwt_plot_grid.h \ + $$PWD/qwt_plot_histogram.h \ + $$PWD/qwt_plot_item.h \ + $$PWD/qwt_plot_abstract_barchart.h \ + $$PWD/qwt_plot_barchart.h \ + $$PWD/qwt_plot_multi_barchart.h \ + $$PWD/qwt_plot_intervalcurve.h \ + $$PWD/qwt_plot_tradingcurve.h \ + $$PWD/qwt_plot_layout.h \ + $$PWD/qwt_plot_marker.h \ + $$PWD/qwt_plot_zoneitem.h \ + $$PWD/qwt_plot_textlabel.h \ + $$PWD/qwt_plot_rasteritem.h \ + $$PWD/qwt_plot_spectrogram.h \ + $$PWD/qwt_plot_spectrocurve.h \ + $$PWD/qwt_plot_scaleitem.h \ + $$PWD/qwt_plot_legenditem.h \ + $$PWD/qwt_plot_seriesitem.h \ + $$PWD/qwt_plot_shapeitem.h \ + $$PWD/qwt_plot_vectorfield.h \ + $$PWD/qwt_plot_abstract_canvas.h \ + $$PWD/qwt_plot_canvas.h \ + $$PWD/qwt_plot_panner.h \ + $$PWD/qwt_plot_picker.h \ + $$PWD/qwt_plot_zoomer.h \ + $$PWD/qwt_plot_magnifier.h \ + $$PWD/qwt_plot_rescaler.h \ + $$PWD/qwt_point_mapper.h \ + $$PWD/qwt_raster_data.h \ + $$PWD/qwt_matrix_raster_data.h \ + $$PWD/qwt_vectorfield_symbol.h \ + $$PWD/qwt_sampling_thread.h \ + $$PWD/qwt_samples.h \ + $$PWD/qwt_series_data.h \ + $$PWD/qwt_series_store.h \ + $$PWD/qwt_point_data.h \ + $$PWD/qwt_scale_widget.h + + SOURCES += \ + $$PWD/qwt_curve_fitter.cpp \ + $$PWD/qwt_spline_curve_fitter.cpp \ + $$PWD/qwt_weeding_curve_fitter.cpp \ + $$PWD/qwt_abstract_legend.cpp \ + $$PWD/qwt_legend.cpp \ + $$PWD/qwt_legend_data.cpp \ + $$PWD/qwt_legend_label.cpp \ + $$PWD/qwt_plot.cpp \ + $$PWD/qwt_plot_renderer.cpp \ + $$PWD/qwt_plot_axis.cpp \ + $$PWD/qwt_plot_curve.cpp \ + $$PWD/qwt_plot_dict.cpp \ + $$PWD/qwt_plot_directpainter.cpp \ + $$PWD/qwt_plot_graphicitem.cpp \ + $$PWD/qwt_plot_grid.cpp \ + $$PWD/qwt_plot_histogram.cpp \ + $$PWD/qwt_plot_item.cpp \ + $$PWD/qwt_plot_abstract_barchart.cpp \ + $$PWD/qwt_plot_barchart.cpp \ + $$PWD/qwt_plot_multi_barchart.cpp \ + $$PWD/qwt_plot_intervalcurve.cpp \ + $$PWD/qwt_plot_zoneitem.cpp \ + $$PWD/qwt_plot_tradingcurve.cpp \ + $$PWD/qwt_plot_spectrogram.cpp \ + $$PWD/qwt_plot_spectrocurve.cpp \ + $$PWD/qwt_plot_scaleitem.cpp \ + $$PWD/qwt_plot_legenditem.cpp \ + $$PWD/qwt_plot_seriesitem.cpp \ + $$PWD/qwt_plot_shapeitem.cpp \ + $$PWD/qwt_plot_vectorfield.cpp \ + $$PWD/qwt_plot_marker.cpp \ + $$PWD/qwt_plot_textlabel.cpp \ + $$PWD/qwt_plot_layout.cpp \ + $$PWD/qwt_plot_abstract_canvas.cpp \ + $$PWD/qwt_plot_canvas.cpp \ + $$PWD/qwt_plot_panner.cpp \ + $$PWD/qwt_plot_rasteritem.cpp \ + $$PWD/qwt_plot_picker.cpp \ + $$PWD/qwt_plot_zoomer.cpp \ + $$PWD/qwt_plot_magnifier.cpp \ + $$PWD/qwt_plot_rescaler.cpp \ + $$PWD/qwt_point_mapper.cpp \ + $$PWD/qwt_raster_data.cpp \ + $$PWD/qwt_matrix_raster_data.cpp \ + $$PWD/qwt_vectorfield_symbol.cpp \ + $$PWD/qwt_sampling_thread.cpp \ + $$PWD/qwt_series_data.cpp \ + $$PWD/qwt_point_data.cpp \ + $$PWD/qwt_scale_widget.cpp + + contains(QWT_CONFIG, QwtOpenGL) { + + lessThan(QT_MAJOR_VERSION, 6) { + + HEADERS += \ + $$PWD/qwt_plot_glcanvas.h + + SOURCES += \ + $$PWD/qwt_plot_glcanvas.cpp + } + + greaterThan(QT_MAJOR_VERSION, 4) { + + lessThan( QT_MAJOR_VERSION, 6) { + + greaterThan(QT_MINOR_VERSION, 3) { + + HEADERS += $$PWD/qwt_plot_opengl_canvas.h + SOURCES += $$PWD/qwt_plot_opengl_canvas.cpp + } + } + else { + QT += openglwidgets + + HEADERS += $$PWD/qwt_plot_opengl_canvas.h + SOURCES += $$PWD/qwt_plot_opengl_canvas.cpp + } + + } + + } + + contains(QWT_CONFIG, QwtSvg) { + + HEADERS += \ + $$PWD/qwt_plot_svgitem.h + + SOURCES += \ + $$PWD/qwt_plot_svgitem.cpp + } + + contains(QWT_CONFIG, QwtPolar) { + + HEADERS += \ + $$PWD/qwt_polar.h \ + $$PWD/qwt_polar_canvas.h \ + $$PWD/qwt_polar_curve.h \ + $$PWD/qwt_polar_fitter.h \ + $$PWD/qwt_polar_grid.h \ + $$PWD/qwt_polar_itemdict.h \ + $$PWD/qwt_polar_item.h \ + $$PWD/qwt_polar_layout.h \ + $$PWD/qwt_polar_magnifier.h \ + $$PWD/qwt_polar_marker.h \ + $$PWD/qwt_polar_panner.h \ + $$PWD/qwt_polar_picker.h \ + $$PWD/qwt_polar_plot.h \ + $$PWD/qwt_polar_renderer.h \ + $$PWD/qwt_polar_spectrogram.h + + SOURCES += \ + $$PWD/qwt_polar_canvas.cpp \ + $$PWD/qwt_polar_curve.cpp \ + $$PWD/qwt_polar_fitter.cpp \ + $$PWD/qwt_polar_grid.cpp \ + $$PWD/qwt_polar_item.cpp \ + $$PWD/qwt_polar_itemdict.cpp \ + $$PWD/qwt_polar_layout.cpp \ + $$PWD/qwt_polar_magnifier.cpp \ + $$PWD/qwt_polar_marker.cpp \ + $$PWD/qwt_polar_panner.cpp \ + $$PWD/qwt_polar_picker.cpp \ + $$PWD/qwt_polar_plot.cpp \ + $$PWD/qwt_polar_renderer.cpp \ + $$PWD/qwt_polar_spectrogram.cpp + } +} + +greaterThan(QT_MAJOR_VERSION, 4) { + + QT += printsupport + QT += concurrent +} + +contains(QWT_CONFIG, QwtSvg) { + + greaterThan(QT_MAJOR_VERSION, 4) { + + qtHaveModule(svg) { + QT += svg + } + else { + warning("QwtSvg is enabled in qwtconfig.pri, but Qt has not been built with svg support") + } + } + else { + QT += svg + } +} +else { + + DEFINES += QWT_NO_SVG +} + +contains(QWT_CONFIG, QwtOpenGL) { + + greaterThan(QT_MAJOR_VERSION, 4) { + + qtHaveModule(opengl) { + QT += opengl + } + else { + warning("QwtOpenGL is enabled in qwtconfig.pri, but Qt has not been built with opengl support") + } + } + else { + QT += opengl + } + + QT += opengl +} +else { + + DEFINES += QWT_NO_OPENGL +} + +contains(QWT_CONFIG, QwtWidgets) { + + HEADERS += \ + $$PWD/qwt_abstract_slider.h \ + $$PWD/qwt_abstract_scale.h \ + $$PWD/qwt_arrow_button.h \ + $$PWD/qwt_analog_clock.h \ + $$PWD/qwt_compass.h \ + $$PWD/qwt_compass_rose.h \ + $$PWD/qwt_counter.h \ + $$PWD/qwt_dial.h \ + $$PWD/qwt_dial_needle.h \ + $$PWD/qwt_knob.h \ + $$PWD/qwt_slider.h \ + $$PWD/qwt_thermo.h \ + $$PWD/qwt_wheel.h + + SOURCES += \ + $$PWD/qwt_abstract_slider.cpp \ + $$PWD/qwt_abstract_scale.cpp \ + $$PWD/qwt_arrow_button.cpp \ + $$PWD/qwt_analog_clock.cpp \ + $$PWD/qwt_compass.cpp \ + $$PWD/qwt_compass_rose.cpp \ + $$PWD/qwt_counter.cpp \ + $$PWD/qwt_dial.cpp \ + $$PWD/qwt_dial_needle.cpp \ + $$PWD/qwt_knob.cpp \ + $$PWD/qwt_slider.cpp \ + $$PWD/qwt_thermo.cpp \ + $$PWD/qwt_wheel.cpp +} diff --git a/src/JSON/Generator.h b/src/JSON/Generator.h index 3477ed45..52bb985c 100644 --- a/src/JSON/Generator.h +++ b/src/JSON/Generator.h @@ -23,14 +23,11 @@ #ifndef JSON_GENERATOR_H #define JSON_GENERATOR_H -#include #include -#include #include #include #include #include -#include #include #include #include