diff --git b/openhantek/AUTHORS a/openhantek/AUTHORS new file mode 100644 index 0000000..306fd93 --- /dev/null +++ a/openhantek/AUTHORS @@ -0,0 +1,2 @@ +Oleg Khudyakov +Oliver Haag \ No newline at end of file diff --git b/openhantek/COPYING a/openhantek/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ a/openhantek/COPYING @@ -0,0 +1,674 @@ + GNU 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. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git b/openhantek/ChangeLog a/openhantek/ChangeLog new file mode 100644 index 0000000..1f1333f --- /dev/null +++ a/openhantek/ChangeLog @@ -0,0 +1,26 @@ +2010-05-29 Oliver Haag +* Version 0.1.0 - First public release: +* OpenGL accelerated scope +* Custom widgets for level sliders (Offset, trigger, ...) +* Markers and a zoom option that enables a second magnified scope +* Basic export/print functionality +* Fully customizable colors for screen/print +* Spectrum graphs with selectable window function +* Tables around scopes viewing settings and measurement results +* Configurable digital phosphor mode +* Experimental fast rate and large buffer mode +* German translation + +2010-07-10 Oliver Haag +* Replaced HantekDSOIO and HantekDSOAThread by DsoControl class +* New namespace Hantek (in directory src/hantek): +* Added Control class (Inherits DsoControl) +* Moved all Hantek DSO specific conversions into Control class +* Added Device class for handling usb transfers +* Created types.cpp/types.h with various Hantek-specific types +* USB transfers are handled by Control class, no direct access from elsewhere +* libusb 1.0 is used by default (Uses 0.1 when LIBUSB_VERSION is set to 0) +* DsoControl class could control non-Hantek DSOs too +* Added reference level and minimum magnitude for spectrum analysis to settings +* Made lower timebases available +* Spectrum and math graphs can be shown without enabling the voltage graph too \ No newline at end of file diff --git b/openhantek/Doxyfile a/openhantek/Doxyfile new file mode 100644 index 0000000..4a76466 --- /dev/null +++ a/openhantek/Doxyfile @@ -0,0 +1,1514 @@ +# Doxyfile 1.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = OpenHantek + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ./doc/ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = src/ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = src/ mainpage.dox + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP) +# there is already a search function so this one should typically +# be disabled. + +SEARCHENGINE = YES + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = $(DEFINES) + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git b/openhantek/INSTALL a/openhantek/INSTALL new file mode 100644 index 0000000..0a361ae --- /dev/null +++ a/openhantek/INSTALL @@ -0,0 +1,11 @@ +To build OpenHantek from source, you need Qt 4 and FFTW 3. Under Debian or Ubuntu you can just install the packages libqt4-dev and libfftw3-dev. I don't know the package names for other distributions but they may be similar. + +After you've installed the requirements run the following commands inside the directory of this package: +$ qmake +$ make +$ make install + +You can specify a prefix when running qmake: +$ qmake PREFIX=/usr + +To get your DSO working under linux you have to extract the firmware using dsoextractfw and add the rules to udev. See the file INSTALL in the dsoextractfw package for details. \ No newline at end of file diff --git b/openhantek/NEWS a/openhantek/NEWS new file mode 100644 index 0000000..e69de29 --- /dev/null +++ a/openhantek/NEWS diff --git b/openhantek/OpenHantek.pro a/openhantek/OpenHantek.pro new file mode 100644 index 0000000..9dc873a --- /dev/null +++ a/openhantek/OpenHantek.pro @@ -0,0 +1,144 @@ +TEMPLATE = app + +# Configuration +CONFIG += warn_on \ + qt +QT += opengl +LIBS += -lfftw3 + +# Source files +SOURCES += src/colorbox.cpp \ + src/configdialog.cpp \ + src/configpages.cpp \ + src/dataanalyzer.cpp \ + src/dockwindows.cpp \ + src/dsocontrol.cpp \ + src/dsowidget.cpp \ + src/exporter.cpp \ + src/glgenerator.cpp \ + src/glscope.cpp \ + src/helper.cpp \ + src/levelslider.cpp \ + src/main.cpp \ + src/openhantek.cpp \ + src/settings.cpp \ + src/hantek/control.cpp \ + src/hantek/device.cpp \ + src/hantek/types.cpp +HEADERS += src/colorbox.h \ + src/configdialog.h \ + src/configpages.h \ + src/constants.h \ + src/dataanalyzer.h \ + src/dockwindows.h \ + src/dsocontrol.h \ + src/dsowidget.h \ + src/exporter.h \ + src/glscope.h \ + src/glgenerator.h \ + src/helper.h \ + src/levelslider.h \ + src/openhantek.h \ + src/settings.h \ + src/hantek/control.h \ + src/hantek/device.h \ + src/hantek/types.h + +# Ressource files +RESOURCES += res/application.qrc \ + res/configdialog.qrc + +# Doxygen files +DOXYFILES += Doxyfile \ + mainpage.dox + +# Files copied into the distribution package +DISTFILES += ChangeLog \ + COPYING \ + INSTALL \ + res/images/*.png \ + res/images/*.svg \ + translations/*.qm \ + translations/*.ts \ + $${DOXYFILES} + +# Translations +TRANSLATIONS += translations/openhantek_de.ts + +# Program version +VERSION = 0.2.0-pre + +# Destination directory for built binaries +DESTDIR = bin + +# Prefix for installation +PREFIX = $$(PREFIX) + +# Build directories +OBJECTS_DIR = build/obj +UI_DIR = build/ui +MOC_DIR = build/moc + +# Include directory +QMAKE_CXXFLAGS += "-iquote src" + +# libusb version +LIBUSB_VERSION = $$(LIBUSB_VERSION) +contains(LIBUSB_VERSION, 0):LIBS += -lusb +else { + LIBUSB_VERSION = 1 + LIBS += -lusb-1.0 + DEFINES += LIBUSB_VERSION=1 +} +DEFINES += LIBUSB_VERSION=$${LIBUSB_VERSION} + +# Settings for different operating systems +unix:!macx { + isEmpty(PREFIX):PREFIX = /usr/local + TARGET = openhantek + + # Installation directories + target.path = $${PREFIX}/bin + translations.path = $${PREFIX}/share/apps/openhantek/translations + INCLUDEPATH += /usr/include/libusb + DEFINES += QMAKE_TRANSLATIONS_PATH=\\\"$${translations.path}\\\" \ + OS_UNIX +} +macx { + isEmpty(PREFIX):PREFIX = OpenHantek.app + TARGET = OpenHantek + + # Installation directories + target.path = $${PREFIX}/Contents/MacOS + translations.path = $${PREFIX}/Contents/Resources/translations + DEFINES += QMAKE_TRANSLATIONS_PATH=\\\"Contents/Resources/translations\\\" \ + OS_DARWIN +} +win32 { + isEmpty(PREFIX):PREFIX = OpenHantek + TARGET = OpenHantek + + # Installation directories + target.path = $${PREFIX} + translations.path = $${PREFIX}/translations + DEFINES += QMAKE_TRANSLATIONS_PATH=\\\"translations\\\" \ + OS_WINDOWS +} +translations.files += translations/*.qm +INSTALLS += target \ + translations +DEFINES += VERSION=\\\"$${VERSION}\\\" + +# Custom target "doc" for Doxygen +doxygen.target = doc +doxygen.commands = rm \ + -r \ + doc/; \ + env \ + DEFINES=\"$${DEFINES}\" \ + doxygen \ + Doxyfile +doxygen.depends = $${SOURCES} \ + $${HEADERS} \ + $${DOXYFILES} +QMAKE_EXTRA_UNIX_TARGETS += doxygen diff --git b/openhantek/README a/openhantek/README new file mode 100644 index 0000000..205dcac --- /dev/null +++ a/openhantek/README @@ -0,0 +1 @@ +OpenHantek is a free software for Hantek (Voltcraft/Darkwire/Protek/Acetech) USB DSOs based on HantekDSO. The UI is written in C++/Qt 4 and uses OpenGL to draw the graphs. It was tested with the DSO-2090, test results with other models are welcome. \ No newline at end of file diff --git b/openhantek/mainpage.dox a/openhantek/mainpage.dox new file mode 100644 index 0000000..9cee388 --- /dev/null +++ a/openhantek/mainpage.dox @@ -0,0 +1,18 @@ +/** + +\mainpage OpenHantek Developer Documentation + +\section sec_introduction Introduction +OpenHantek is a free software for Hantek (Voltcraft/Darkwire/Protek/Acetech) USB DSOs based on HantekDSO. The UI is written in C++/Qt 4 and uses OpenGL to draw the graphs. + +\section sec_compiling Compiling from source +\subsection ssec_dependencies Dependencies +You need the development packages for the following libraries to build OpenHantek from source: + + +**/ diff --git b/openhantek/res/application.qrc a/openhantek/res/application.qrc new file mode 100644 index 0000000..89b85ce --- /dev/null +++ a/openhantek/res/application.qrc @@ -0,0 +1,16 @@ + + + images/openhantek.png + + + images/actions/open.png + images/actions/save.png + images/actions/save-as.png + images/actions/print.png + images/actions/export-as.png + images/actions/start.png + images/actions/stop.png + images/actions/digitalphosphor.png + images/actions/zoom.png + + diff --git b/openhantek/res/configdialog.qrc a/openhantek/res/configdialog.qrc new file mode 100644 index 0000000..63f6393 --- /dev/null +++ a/openhantek/res/configdialog.qrc @@ -0,0 +1,8 @@ + + + + images/config/analysis.png + images/config/colors.png + images/config/scope.png + + diff --git b/openhantek/res/images/actions/cursors.png a/openhantek/res/images/actions/cursors.png new file mode 100644 index 0000000..1bcaac8 --- /dev/null +++ a/openhantek/res/images/actions/cursors.png diff --git b/openhantek/res/images/actions/digitalphosphor.png a/openhantek/res/images/actions/digitalphosphor.png new file mode 100644 index 0000000..bf04c5f --- /dev/null +++ a/openhantek/res/images/actions/digitalphosphor.png diff --git b/openhantek/res/images/actions/digitalphosphor.svg a/openhantek/res/images/actions/digitalphosphor.svg new file mode 100644 index 0000000..a06679e --- /dev/null +++ a/openhantek/res/images/actions/digitalphosphor.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git b/openhantek/res/images/actions/export-as.png a/openhantek/res/images/actions/export-as.png new file mode 100644 index 0000000..999a415 --- /dev/null +++ a/openhantek/res/images/actions/export-as.png diff --git b/openhantek/res/images/actions/open.png a/openhantek/res/images/actions/open.png new file mode 100644 index 0000000..7422ad3 --- /dev/null +++ a/openhantek/res/images/actions/open.png diff --git b/openhantek/res/images/actions/print.png a/openhantek/res/images/actions/print.png new file mode 100644 index 0000000..d655504 --- /dev/null +++ a/openhantek/res/images/actions/print.png diff --git b/openhantek/res/images/actions/save-as.png a/openhantek/res/images/actions/save-as.png new file mode 100644 index 0000000..9695a56 --- /dev/null +++ a/openhantek/res/images/actions/save-as.png diff --git b/openhantek/res/images/actions/save.png a/openhantek/res/images/actions/save.png new file mode 100644 index 0000000..7fa489c --- /dev/null +++ a/openhantek/res/images/actions/save.png diff --git b/openhantek/res/images/actions/start.png a/openhantek/res/images/actions/start.png new file mode 100644 index 0000000..7190685 --- /dev/null +++ a/openhantek/res/images/actions/start.png diff --git b/openhantek/res/images/actions/stop.png a/openhantek/res/images/actions/stop.png new file mode 100644 index 0000000..650874f --- /dev/null +++ a/openhantek/res/images/actions/stop.png diff --git b/openhantek/res/images/actions/zoom.png a/openhantek/res/images/actions/zoom.png new file mode 100644 index 0000000..c9860d4 --- /dev/null +++ a/openhantek/res/images/actions/zoom.png diff --git b/openhantek/res/images/config/analysis.png a/openhantek/res/images/config/analysis.png new file mode 100644 index 0000000..4c2a1f0 --- /dev/null +++ a/openhantek/res/images/config/analysis.png diff --git b/openhantek/res/images/config/analysis.svg a/openhantek/res/images/config/analysis.svg new file mode 100644 index 0000000..bc218f0 --- /dev/null +++ a/openhantek/res/images/config/analysis.svg @@ -0,0 +1,5877 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git b/openhantek/res/images/config/colors.png a/openhantek/res/images/config/colors.png new file mode 100644 index 0000000..b3c3548 --- /dev/null +++ a/openhantek/res/images/config/colors.png diff --git b/openhantek/res/images/config/options.png a/openhantek/res/images/config/options.png new file mode 100644 index 0000000..11a0c22 --- /dev/null +++ a/openhantek/res/images/config/options.png diff --git b/openhantek/res/images/config/options.svg a/openhantek/res/images/config/options.svg new file mode 100644 index 0000000..20c28a5 --- /dev/null +++ a/openhantek/res/images/config/options.svg @@ -0,0 +1,580 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git b/openhantek/res/images/config/scope.png a/openhantek/res/images/config/scope.png new file mode 100644 index 0000000..09da862 --- /dev/null +++ a/openhantek/res/images/config/scope.png diff --git b/openhantek/res/images/openhantek.png a/openhantek/res/images/openhantek.png new file mode 100644 index 0000000..f492e4d --- /dev/null +++ a/openhantek/res/images/openhantek.png diff --git b/openhantek/res/images/openhantek.svg a/openhantek/res/images/openhantek.svg new file mode 100644 index 0000000..5fbd1d0 --- /dev/null +++ a/openhantek/res/images/openhantek.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git b/openhantek/src/colorbox.cpp a/openhantek/src/colorbox.cpp new file mode 100644 index 0000000..2b942c9 --- /dev/null +++ a/openhantek/src/colorbox.cpp @@ -0,0 +1,73 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// colorbox.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include + + +#include "colorbox.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class ColorBox +/// \brief Initializes the widget. +/// \param color Initial color value. +/// \param parent The parent widget. +ColorBox::ColorBox(QColor color, QWidget *parent) : QPushButton(parent) { + this->setColor(color); + + connect(this, SIGNAL(clicked()), this, SLOT(waitForColor())); +} + +/// \brief Cleans up the widget. +ColorBox::~ColorBox() { +} + +/// \brief Get the current color. +/// \return The current color as QColor. +const QColor ColorBox::getColor() { + return this->color; +} + +/// \brief Sets the color. +/// \param color The new color. +void ColorBox::setColor(QColor color) { + this->color = color; + this->setText(QString("#%1").arg((unsigned int) this->color.rgba(), 8, 16, QChar('0'))); + this->setPalette(QPalette(this->color)); + + emit colorChanged(this->color); +} + +/// \brief Wait for the color dialog and apply chosen color. +void ColorBox::waitForColor() { + this->setFocus(); + this->setDown(true); + + QColor color = QColorDialog::getColor(this->color, this, 0, QColorDialog::ShowAlphaChannel); + + if(color.isValid()) + this->setColor(color); +} diff --git b/openhantek/src/colorbox.h a/openhantek/src/colorbox.h new file mode 100644 index 0000000..789f711 --- /dev/null +++ a/openhantek/src/colorbox.h @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file colorbox.h +/// \brief Declares the ColorBox class. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef COLORBOX_H +#define COLORBOX_H + + +#include +#include + + +//////////////////////////////////////////////////////////////////////////////// +/// \class ColorBox colorbox.h +/// \brief A widget for the selection of a color. +class ColorBox : public QPushButton { + Q_OBJECT + + public: + ColorBox(QColor color, QWidget *parent = 0); + ~ColorBox(); + + const QColor getColor(); + + public slots: + void setColor(QColor color); + void waitForColor(); + + private: + QColor color; + + signals: + void colorChanged(QColor color); +}; + + +#endif diff --git b/openhantek/src/configdialog.cpp a/openhantek/src/configdialog.cpp new file mode 100644 index 0000000..d904742 --- /dev/null +++ a/openhantek/src/configdialog.cpp @@ -0,0 +1,141 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// configdialog.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include +#include +#include +#include + + +#include "configdialog.h" + +#include "configpages.h" +#include "settings.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class DsoConfigDialog +/// \brief Creates the configuration dialog and sets initial values. +/// \param settings The target settings object. +/// \param parent The parent widget. +/// \param flags Flags for the window manager. +DsoConfigDialog::DsoConfigDialog(DsoSettings *settings, QWidget *parent, Qt::WindowFlags flags) : QDialog(parent, flags) { + this->settings = settings; + + this->setWindowTitle(tr("Settings")); + + this->contentsWidget = new QListWidget; + this->contentsWidget->setViewMode(QListView::IconMode); + this->contentsWidget->setIconSize(QSize(CONFIG_LIST_ICONSIZE, CONFIG_LIST_ICONSIZE)); + this->contentsWidget->setMovement(QListView::Static); + this->contentsWidget->setGridSize(QSize(CONFIG_LIST_WIDTH - 2 * this->contentsWidget->frameWidth(), CONFIG_LIST_ITEMHEIGHT)); + this->contentsWidget->setMaximumWidth(CONFIG_LIST_WIDTH); + this->contentsWidget->setMinimumHeight(CONFIG_LIST_ITEMHEIGHT * 3 + 2 * (this->contentsWidget->frameWidth())); + + this->analysisPage = new DsoConfigAnalysisPage(this->settings); + this->colorsPage = new DsoConfigColorsPage(this->settings); + this->scopePage = new DsoConfigScopePage(this->settings); + this->pagesWidget = new QStackedWidget; + this->pagesWidget->addWidget(this->analysisPage); + this->pagesWidget->addWidget(this->colorsPage); + this->pagesWidget->addWidget(this->scopePage); + + this->acceptButton = new QPushButton(tr("&Ok")); + this->acceptButton->setDefault(true); + this->applyButton = new QPushButton(tr("&Apply")); + this->rejectButton = new QPushButton(tr("&Cancel")); + + this->createIcons(); + this->contentsWidget->setCurrentRow(0); + + this->horizontalLayout = new QHBoxLayout; + this->horizontalLayout->addWidget(this->contentsWidget); + this->horizontalLayout->addWidget(this->pagesWidget, 1); + + this->buttonsLayout = new QHBoxLayout; + this->buttonsLayout->setSpacing(8); + this->buttonsLayout->addStretch(1); + this->buttonsLayout->addWidget(this->acceptButton); + this->buttonsLayout->addWidget(this->applyButton); + this->buttonsLayout->addWidget(this->rejectButton); + + this->mainLayout = new QVBoxLayout; + this->mainLayout->addLayout(this->horizontalLayout); + this->mainLayout->addStretch(1); + this->mainLayout->addSpacing(8); + this->mainLayout->addLayout(this->buttonsLayout); + this->setLayout(this->mainLayout); + + connect(this->acceptButton, SIGNAL(clicked()), this, SLOT(accept())); + connect(this->applyButton, SIGNAL(clicked()), this, SLOT(apply())); + connect(this->rejectButton, SIGNAL(clicked()), this, SLOT(reject())); +} + +/// \brief Cleans up the dialog. +DsoConfigDialog::~DsoConfigDialog() { +} + +/// \brief Create the icons for the pages. +void DsoConfigDialog::createIcons() { + QListWidgetItem *analysisButton = new QListWidgetItem(contentsWidget); + analysisButton->setIcon(QIcon(":config/analysis.png")); + analysisButton->setText(tr("Analysis")); + + QListWidgetItem *colorsButton = new QListWidgetItem(contentsWidget); + colorsButton->setIcon(QIcon(":config/colors.png")); + colorsButton->setText(tr("Colors")); + + QListWidgetItem *scopeButton = new QListWidgetItem(contentsWidget); + scopeButton->setIcon(QIcon(":config/scope.png")); + scopeButton->setText(tr("Scope")); + + connect(contentsWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(changePage(QListWidgetItem *, QListWidgetItem *))); +} + +/// \brief Saves the settings and closes the dialog. +void DsoConfigDialog::accept() { + this->apply(); + + QDialog::accept(); +} + +/// \brief Saves the settings. +void DsoConfigDialog::apply() { + this->analysisPage->saveSettings(); + this->colorsPage->saveSettings(); + this->scopePage->saveSettings(); +} + +/// \brief Change the config page. +/// \param current The page that has been selected. +/// \param previous The page that was selected before. +void DsoConfigDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) { + if (!current) + current = previous; + + pagesWidget->setCurrentIndex(contentsWidget->row(current)); +} diff --git b/openhantek/src/configdialog.h a/openhantek/src/configdialog.h new file mode 100644 index 0000000..bd05ef6 --- /dev/null +++ a/openhantek/src/configdialog.h @@ -0,0 +1,98 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file configdialog.h +/// \brief Declares the DsoConfigDialog class. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef CONFIGDIALOG_H +#define CONFIGDIALOG_H + + +#include + + +/*#if defined(OS_UNIX) +#define CONFIG_PATH QDir::homePath() + "/.config/paranoiacs.net/openhantek" +#define CONFIG_FILE CONFIG_PATH "/openhantek.conf" +#elif defined(OS_DARWIN) +#define CONFIG_PATH QDir::homePath() + "/Library/Application Support/OpenHantek" +#define CONFIG_FILE CONFIG_PATH "/openhantek.plist" +#elif defined(OS_WINDOWS) +//#define CONFIG_PATH QDir::homePath() + "" // Too hard to get and this OS sucks anyway, ignore it +#define CONFIG_FILE "HKEY_CURRENT_USER\\Software\\paranoiacs.net\\OpenHantek" +#endif*/ + +#define CONFIG_LIST_WIDTH 128 +#define CONFIG_LIST_ITEMHEIGHT 80 +#define CONFIG_LIST_ICONSIZE 48 + + +class DsoConfigAnalysisPage; +class DsoConfigColorsPage; +class DsoConfigScopePage; +class DsoSettings; + +class QHBoxLayout; +class QListWidget; +class QListWidgetItem; +class QPushButton; +class QStackedWidget; +class QVBoxLayout; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class DsoConfigDialog configdialog.h +/// \brief The dialog for the configuration options. +class DsoConfigDialog : public QDialog { + Q_OBJECT + + public: + DsoConfigDialog(DsoSettings *settings, QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~DsoConfigDialog(); + + public slots: + void accept(); + void apply(); + + void changePage(QListWidgetItem *current, QListWidgetItem *previous); + + private: + void createIcons(); + + DsoSettings *settings; + + QVBoxLayout *mainLayout; + QHBoxLayout *horizontalLayout; + QHBoxLayout *buttonsLayout; + + QListWidget *contentsWidget; + QStackedWidget *pagesWidget; + + DsoConfigAnalysisPage *analysisPage; + DsoConfigColorsPage *colorsPage; + DsoConfigScopePage *scopePage; + + QPushButton *acceptButton, *applyButton, *rejectButton; +}; + + +#endif diff --git b/openhantek/src/configpages.cpp a/openhantek/src/configpages.cpp new file mode 100644 index 0000000..5a15eea --- /dev/null +++ a/openhantek/src/configpages.cpp @@ -0,0 +1,280 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// configpages.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "configpages.h" + +#include "colorbox.h" +#include "settings.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class DsoConfigAnalysisPage +/// \brief Creates the widgets and sets their initial value. +/// \param settings The target settings object. +/// \param parent The parent widget. +DsoConfigAnalysisPage::DsoConfigAnalysisPage(DsoSettings *settings, QWidget *parent) : QWidget(parent) { + this->settings = settings; + + // Initialize lists for comboboxes + QStringList windowFunctionStrings; + windowFunctionStrings + << tr("Rectangular") + << tr("Hamming") + << tr("Hann") + << tr("Cosine") + << tr("Lanczos") + << tr("Bartlett") + << tr("Triangular") + << tr("Gauss") + << tr("Bartlett-Hann") + << tr("Blackman") + //<< tr("Kaiser") + << tr("Nuttall") + << tr("Blackman-Harris") + << tr("Blackman-Nuttall") + << tr("Flat top"); + + // Initialize elements + this->windowFunctionLabel = new QLabel(tr("Window function")); + this->windowFunctionComboBox = new QComboBox(); + this->windowFunctionComboBox->addItems(windowFunctionStrings); + this->windowFunctionComboBox->setCurrentIndex(this->settings->scope.spectrumWindow); + + this->referenceLevelLabel = new QLabel(tr("Reference level")); + this->referenceLevelSpinBox = new QDoubleSpinBox(); + this->referenceLevelSpinBox->setDecimals(1); + this->referenceLevelSpinBox->setMinimum(-40.0); + this->referenceLevelSpinBox->setMaximum(100.0); + this->referenceLevelSpinBox->setValue(this->settings->scope.spectrumReference); + this->referenceLevelUnitLabel = new QLabel(tr("dBm")); + this->referenceLevelLayout = new QHBoxLayout(); + this->referenceLevelLayout->addWidget(this->referenceLevelSpinBox); + this->referenceLevelLayout->addWidget(this->referenceLevelUnitLabel); + + this->minimumMagnitudeLabel = new QLabel(tr("Minimum magnitude")); + this->minimumMagnitudeSpinBox = new QDoubleSpinBox(); + this->minimumMagnitudeSpinBox->setDecimals(1); + this->minimumMagnitudeSpinBox->setMinimum(-40.0); + this->minimumMagnitudeSpinBox->setMaximum(100.0); + this->minimumMagnitudeSpinBox->setValue(this->settings->scope.spectrumLimit); + this->minimumMagnitudeUnitLabel = new QLabel(tr("dBm")); + this->minimumMagnitudeLayout = new QHBoxLayout(); + this->minimumMagnitudeLayout->addWidget(this->minimumMagnitudeSpinBox); + this->minimumMagnitudeLayout->addWidget(this->minimumMagnitudeUnitLabel); + + this->spectrumLayout = new QGridLayout(); + this->spectrumLayout->addWidget(this->windowFunctionLabel, 0, 0); + this->spectrumLayout->addWidget(this->windowFunctionComboBox, 0, 1); + this->spectrumLayout->addWidget(this->referenceLevelLabel, 1, 0); + this->spectrumLayout->addLayout(this->referenceLevelLayout, 1, 1); + this->spectrumLayout->addWidget(this->minimumMagnitudeLabel, 2, 0); + this->spectrumLayout->addLayout(this->minimumMagnitudeLayout, 2, 1); + + this->spectrumGroup = new QGroupBox(tr("Spectrum")); + this->spectrumGroup->setLayout(this->spectrumLayout); + + this->mainLayout = new QVBoxLayout(); + this->mainLayout->addWidget(this->spectrumGroup); + this->mainLayout->addStretch(1); + + this->setLayout(this->mainLayout); +} + +/// \brief Cleans up the widget. +DsoConfigAnalysisPage::~DsoConfigAnalysisPage() { +} + +/// \brief Saves the new settings. +void DsoConfigAnalysisPage::saveSettings() { + this->settings->scope.spectrumWindow = (Dso::WindowFunction) this->windowFunctionComboBox->currentIndex(); + this->settings->scope.spectrumReference = this->referenceLevelSpinBox->value(); + this->settings->scope.spectrumLimit = this->minimumMagnitudeSpinBox->value(); +} + + +//////////////////////////////////////////////////////////////////////////////// +// class DsoConfigColorsPage +/// \brief Creates the widgets and sets their initial value. +/// \param settings The target settings object. +/// \param parent The parent widget. +DsoConfigColorsPage::DsoConfigColorsPage(DsoSettings *settings, QWidget *parent) : QWidget(parent) { + this->settings = settings; + + // Initialize elements + // Screen category + this->axesLabel = new QLabel(tr("Axes")); + this->axesColorBox = new ColorBox(this->settings->view.color.screen.axes); + this->backgroundLabel = new QLabel(tr("Background")); + this->backgroundColorBox = new ColorBox(this->settings->view.color.screen.background); + this->borderLabel = new QLabel(tr("Border")); + this->borderColorBox = new ColorBox(this->settings->view.color.screen.border); + this->gridLabel = new QLabel(tr("Grid")); + this->gridColorBox = new ColorBox(this->settings->view.color.screen.grid); + this->markersLabel = new QLabel(tr("Markers")); + this->markersColorBox = new ColorBox(this->settings->view.color.screen.markers); + this->textLabel = new QLabel(tr("Text")); + this->textColorBox = new ColorBox(this->settings->view.color.screen.text); + + this->screenLayout = new QGridLayout(); + this->screenLayout->setColumnStretch(0, 1); + this->screenLayout->setColumnMinimumWidth(1, 80); + this->screenLayout->addWidget(this->backgroundLabel, 0, 0); + this->screenLayout->addWidget(this->backgroundColorBox, 0, 1); + this->screenLayout->addWidget(this->gridLabel, 1, 0); + this->screenLayout->addWidget(this->gridColorBox, 1, 1); + this->screenLayout->addWidget(this->axesLabel, 2, 0); + this->screenLayout->addWidget(this->axesColorBox, 2, 1); + this->screenLayout->addWidget(this->borderLabel, 3, 0); + this->screenLayout->addWidget(this->borderColorBox, 3, 1); + this->screenLayout->addWidget(this->markersLabel, 4, 0); + this->screenLayout->addWidget(this->markersColorBox, 4, 1); + this->screenLayout->addWidget(this->textLabel, 5, 0); + this->screenLayout->addWidget(this->textColorBox, 5, 1); + + this->screenGroup = new QGroupBox(tr("Screen")); + this->screenGroup->setLayout(this->screenLayout); + + // Graph category + this->channelLabel = new QLabel(tr("Channel")); + this->channelLabel->setAlignment(Qt::AlignHCenter); + this->spectrumLabel = new QLabel(tr("Spectrum")); + this->spectrumLabel->setAlignment(Qt::AlignHCenter); + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + this->colorLabel.append(new QLabel(this->settings->scope.voltage[channel].name)); + this->channelColorBox.append(new ColorBox(this->settings->view.color.screen.voltage[channel])); + this->spectrumColorBox.append(new ColorBox(this->settings->view.color.screen.spectrum[channel])); + } + + this->graphLayout = new QGridLayout(); + this->graphLayout->setColumnStretch(0, 1); + this->graphLayout->setColumnMinimumWidth(1, 80); + this->graphLayout->setColumnMinimumWidth(2, 80); + this->graphLayout->addWidget(this->channelLabel, 0, 1); + this->graphLayout->addWidget(this->spectrumLabel, 0, 2); + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + this->graphLayout->addWidget(this->colorLabel[channel], channel + 1, 0); + this->graphLayout->addWidget(this->channelColorBox[channel], channel + 1, 1); + this->graphLayout->addWidget(this->spectrumColorBox[channel], channel + 1, 2); + } + + this->graphGroup = new QGroupBox(tr("Graph")); + this->graphGroup->setLayout(this->graphLayout); + + // Main layout + this->mainLayout = new QVBoxLayout(); + this->mainLayout->addWidget(this->screenGroup); + this->mainLayout->addWidget(this->graphGroup); + this->mainLayout->addStretch(1); + + this->setLayout(this->mainLayout); +} + +/// \brief Cleans up the widget. +DsoConfigColorsPage::~DsoConfigColorsPage() { +} + +/// \brief Saves the new settings. +void DsoConfigColorsPage::saveSettings() { + // Screen category + this->settings->view.color.screen.axes = this->axesColorBox->getColor(); + this->settings->view.color.screen.background = this->backgroundColorBox->getColor(); + this->settings->view.color.screen.border = this->borderColorBox->getColor(); + this->settings->view.color.screen.grid = this->gridColorBox->getColor(); + this->settings->view.color.screen.markers = this->markersColorBox->getColor(); + this->settings->view.color.screen.text = this->textColorBox->getColor(); + + // Graph category + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + this->settings->view.color.screen.voltage[channel] = this->channelColorBox[channel]->getColor(); + this->settings->view.color.screen.spectrum[channel] = this->spectrumColorBox[channel]->getColor(); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// class DsoConfigScopePage +/// \brief Creates the widgets and sets their initial value. +/// \param settings The target settings object. +/// \param parent The parent widget. +DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) : QWidget(parent) { + this->settings = settings; + + // Initialize lists for comboboxes + QStringList interpolationStrings; + interpolationStrings + << tr("Off") + << tr("Linear") + << tr("Sinc"); + + // Initialize elements + this->antialiasingCheckBox = new QCheckBox(tr("Antialiasing")); + this->antialiasingCheckBox->setChecked(this->settings->view.antialiasing); + this->interpolationLabel = new QLabel(tr("Interpolation")); + this->interpolationComboBox = new QComboBox(); + this->interpolationComboBox->addItems(interpolationStrings); + this->interpolationComboBox->setCurrentIndex(this->settings->view.interpolation); + this->digitalPhosphorDepthLabel = new QLabel(tr("Digital phosphor depth")); + this->digitalPhosphorDepthSpinBox = new QSpinBox(); + this->digitalPhosphorDepthSpinBox->setMinimum(2); + this->digitalPhosphorDepthSpinBox->setMaximum(99); + this->digitalPhosphorDepthSpinBox->setValue(this->settings->view.digitalPhosphorDepth); + + this->graphLayout = new QGridLayout(); + this->graphLayout->addWidget(this->antialiasingCheckBox, 0, 0, 1, 2); + this->graphLayout->addWidget(this->interpolationLabel, 1, 0); + this->graphLayout->addWidget(this->interpolationComboBox, 1, 1); + this->graphLayout->addWidget(this->digitalPhosphorDepthLabel, 2, 0); + this->graphLayout->addWidget(this->digitalPhosphorDepthSpinBox, 2, 1); + + this->graphGroup = new QGroupBox(tr("Graph")); + this->graphGroup->setLayout(this->graphLayout); + + this->mainLayout = new QVBoxLayout(); + this->mainLayout->addWidget(this->graphGroup); + this->mainLayout->addStretch(1); + + this->setLayout(this->mainLayout); +} + +/// \brief Cleans up the widget. +DsoConfigScopePage::~DsoConfigScopePage() { +} + +/// \brief Saves the new settings. +void DsoConfigScopePage::saveSettings() { + this->settings->view.antialiasing = this->antialiasingCheckBox->isChecked(); + this->settings->view.interpolation = (GlInterpolationMode) this->interpolationComboBox->currentIndex(); + this->settings->view.digitalPhosphorDepth = this->digitalPhosphorDepthSpinBox->value(); +} + diff --git b/openhantek/src/configpages.h a/openhantek/src/configpages.h new file mode 100644 index 0000000..4779a17 --- /dev/null +++ a/openhantek/src/configpages.h @@ -0,0 +1,147 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file configpages.h +/// \brief Declares the pages for the DsoConfigDialog class. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef CONFIGPAGES_H +#define CONFIGPAGES_H + + +#include + + +#include "dsowidget.h" +#include "constants.h" + + +class ColorBox; +class DsoSettings; +class QCheckBox; +class QComboBox; +class QSpinBox; +class QStringList; +class QLabel; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class DsoConfigAnalysisPage configpages.h +/// \brief Config page for the data analysis. +class DsoConfigAnalysisPage : public QWidget { + Q_OBJECT + + public: + DsoConfigAnalysisPage(DsoSettings *settings, QWidget *parent = 0); + ~DsoConfigAnalysisPage(); + + public slots: + void saveSettings(); + + private: + DsoSettings *settings; + + QVBoxLayout *mainLayout; + + QGroupBox *spectrumGroup; + QGridLayout *spectrumLayout; + QLabel *windowFunctionLabel; + QComboBox *windowFunctionComboBox; + + QLabel *referenceLevelLabel; + QDoubleSpinBox *referenceLevelSpinBox; + QLabel *referenceLevelUnitLabel; + QHBoxLayout *referenceLevelLayout; + + QLabel *minimumMagnitudeLabel; + QDoubleSpinBox *minimumMagnitudeSpinBox; + QLabel *minimumMagnitudeUnitLabel; + QHBoxLayout *minimumMagnitudeLayout; + + private slots: +}; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class DsoConfigColorsPage configpages.h +/// \brief Config page for the colors. +class DsoConfigColorsPage : public QWidget { + Q_OBJECT + + public: + DsoConfigColorsPage(DsoSettings *settings, QWidget *parent = 0); + ~DsoConfigColorsPage(); + + public slots: + void saveSettings(); + + private: + DsoSettings *settings; + + QVBoxLayout *mainLayout; + + QGroupBox *screenGroup; + QGridLayout *screenLayout; + QLabel *axesLabel, *backgroundLabel, *borderLabel, *gridLabel, *markersLabel, *textLabel; + ColorBox *axesColorBox, *backgroundColorBox, *borderColorBox, *gridColorBox, *markersColorBox, *textColorBox; + + QGroupBox *graphGroup; + QGridLayout *graphLayout; + QLabel *channelLabel, *spectrumLabel; + QList colorLabel; + QList channelColorBox; + QList spectrumColorBox; + + private slots: +}; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class DsoConfigScopePage configpages.h +/// \brief Config page for the scope screen. +class DsoConfigScopePage : public QWidget { + Q_OBJECT + + public: + DsoConfigScopePage(DsoSettings *settings, QWidget *parent = 0); + ~DsoConfigScopePage(); + + public slots: + void saveSettings(); + + private: + DsoSettings *settings; + + QVBoxLayout *mainLayout; + + QGroupBox *graphGroup; + QGridLayout *graphLayout; + QCheckBox *antialiasingCheckBox; + QLabel *digitalPhosphorDepthLabel; + QSpinBox *digitalPhosphorDepthSpinBox; + QLabel *interpolationLabel; + QComboBox *interpolationComboBox; + + private slots: +}; + + +#endif diff --git b/openhantek/src/constants.h a/openhantek/src/constants.h new file mode 100644 index 0000000..4d1cef8 --- /dev/null +++ a/openhantek/src/constants.h @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file constants.h +/// \brief Defines global constants and enums. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef CONSTANTS_H +#define CONSTANTS_H + + +#define MARKER_COUNT 2 ///< Number of markers +//#define PHYS_CHANNEL_COUNT 2 ///< Number of real channels + + +//////////////////////////////////////////////////////////////////////////////// +/// \namespace Dso constants.h +/// \brief All DSO specific enums for different modes and so on. +namespace Dso { + ////////////////////////////////////////////////////////////////////////////// + /// \enum ChannelMode constants.h + /// \brief The channel display modes. + enum ChannelMode { + CHANNELMODE_VOLTAGE, ///< Standard voltage view + CHANNELMODE_SPECTRUM, ///< Spectrum view + CHANNELMODE_COUNT ///< The total number of modes + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum ChannelType constants.h + /// \brief All channels that can be displayed. + /// \todo Get rid of this enum, get the channel count from the oscilloscope + /*enum ChannelType { + CHANNELTYPE_CH, ///< First real channel + CHANNELTYPE_MATH = PHYS_CHANNEL_COUNT, ///< The math channel + CHANNEL_COUNT ///< The total number of channels + };*/ + + ////////////////////////////////////////////////////////////////////////////// + /// \enum GraphFormat constants.h + /// \brief The possible viewing formats for the graphs on the scope. + enum GraphFormat { + GRAPHFORMAT_TY, ///< The standard mode + GRAPHFORMAT_XY ///< CH1 on X-axis, CH2 on Y-axis + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum Coupling constants.h + /// \brief The coupling modes for the channels. + enum Coupling { + COUPLING_AC, ///< Offset filtered out by condensator + COUPLING_DC, ///< No filtering + COUPLING_GND ///< Channel is grounded + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum Slope constants.h + /// \brief The slope that causes a trigger. + enum Slope { + SLOPE_POSITIVE, ///< From lower to higher voltage + SLOPE_NEGATIVE ///< From higher to lower voltage + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum TriggerMode constants.h + /// \brief The different triggering modes. + enum TriggerMode { + TRIGGERMODE_AUTO, ///< Automatic without trigger event + TRIGGERMODE_NORMAL, ///< Normal mode + TRIGGERMODE_SINGLE ///< Stop after the first trigger event + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum MathMode constants.h + /// \brief The different math modes for the math-channel. + enum MathMode { + MATHMODE_1ADD2, ///< Add the values of the channels + MATHMODE_1SUB2, ///< Subtract CH2 from CH1 + MATHMODE_2SUB1 ///< Subtract CH1 from CH2 + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum WindowFunction constants.h + /// \brief The supported window functions. + /// These are needed for spectrum analysis and are applied to the sample values + /// before calculating the DFT. + enum WindowFunction { + WINDOW_RECTANGULAR, ///< Rectangular window (aka Dirichlet) + WINDOW_HAMMING, ///< Hamming window + WINDOW_HANN, ///< Hann window + WINDOW_COSINE, ///< Cosine window (aka Sine) + WINDOW_LANCZOS, ///< Lanczos window (aka Sinc) + WINDOW_BARTLETT, ///< Bartlett window (Endpoints == 0) + WINDOW_TRIANGULAR, ///< Triangular window (Endpoints != 0) + WINDOW_GAUSS, ///< Gauss window (simga = 0.4) + WINDOW_BARTLETTHANN, ///< Bartlett-Hann window + WINDOW_BLACKMAN, ///< Blackman window (alpha = 0.16) + //WINDOW_KAISER, ///< Kaiser window (alpha = 3.0) + WINDOW_NUTTALL, ///< Nuttall window, cont. first deriv. + WINDOW_BLACKMANHARRIS, ///< Blackman-Harris window + WINDOW_BLACKMANNUTTALL, ///< Blackman-Nuttall window + WINDOW_FLATTOP ///< Flat top window + }; +} + +//////////////////////////////////////////////////////////////////////////////// +/// \enum GlInterpolationMode constants.h +/// \brief The different interpolation modes for the graphs. +enum GlInterpolationMode { + INTERPOLATION_OFF = 0, ///< Just dots for each sample + INTERPOLATION_LINEAR, ///< Sample dots connected by lines + INTERPOLATION_SINC ///< Smooth graph through the dots +}; + + +#endif diff --git b/openhantek/src/dataanalyzer.cpp a/openhantek/src/dataanalyzer.cpp new file mode 100644 index 0000000..3966789 --- /dev/null +++ a/openhantek/src/dataanalyzer.cpp @@ -0,0 +1,376 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// dataanalyzer.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include + +#include +#include + +#include + + +#include "dataanalyzer.h" + +#include "glscope.h" +#include "helper.h" +#include "settings.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class HorizontalDock +/// \brief Initializes the buffers and other variables. +/// \param settings The target settings object. +/// \param parent The parent widget. +DataAnalyzer::DataAnalyzer(DsoSettings *settings, QObject *parent) : QThread(parent) { + this->settings = settings; + + this->lastBufferSize = 0; + this->lastWindow = (Dso::WindowFunction) -1; + this->window = 0; + + this->analyzedDataMutex = new QMutex(); +} + +/// \brief Deallocates the buffers. +DataAnalyzer::~DataAnalyzer() { + for(int channel = 0; channel < this->analyzedData.count(); channel++) { + if(this->analyzedData[channel]->samples.voltage.sample) + delete[] this->analyzedData[channel]->samples.voltage.sample; + if(this->analyzedData[channel]->samples.spectrum.sample) + delete[] this->analyzedData[channel]->samples.spectrum.sample; + } +} + +/// \brief Returns the analyzed data. +/// \param channel Channel, whose data should be returned. +/// \return Analyzed data as AnalyzedData struct. +const AnalyzedData *DataAnalyzer::data(int channel) const { + if(channel < 0 || channel >= this->analyzedData.count()) + return 0; + + return this->analyzedData[channel]; +} + +/// \brief Returns the mutex for the data. +/// \return Mutex for the analyzed data. +QMutex *DataAnalyzer::mutex() const { + return this->analyzedDataMutex; +} + +/// \brief Analyzes the data from the dso. +void DataAnalyzer::run() { + this->analyzedDataMutex->lock(); + + // Adapt the number of channels for analyzed data + for(int channel = this->analyzedData.count(); channel < this->settings->scope.voltage.count(); channel++) { + this->analyzedData.append(new AnalyzedData); + this->analyzedData[channel]->samples.voltage.count = 0; + this->analyzedData[channel]->samples.voltage.interval = 0; + this->analyzedData[channel]->samples.voltage.sample = 0; + this->analyzedData[channel]->samples.spectrum.count = 0; + this->analyzedData[channel]->samples.spectrum.interval = 0; + this->analyzedData[channel]->samples.spectrum.sample = 0; + this->analyzedData[channel]->amplitude = 0; + this->analyzedData[channel]->frequency = 0; + } + for(int channel = this->settings->scope.voltage.count(); channel < this->analyzedData.count(); channel++) { + if(this->analyzedData.last()->samples.voltage.sample) + delete[] this->analyzedData.last()->samples.voltage.sample; + if(this->analyzedData.last()->samples.spectrum.sample) + delete[] this->analyzedData.last()->samples.spectrum.sample; + this->analyzedData.removeLast(); + } + + for(unsigned int channel = 0; channel < (unsigned int) this->analyzedData.count(); channel++) { + // Check if we got data for this channel or if it's a math channel that can be calculated + if(((channel < this->settings->scope.physicalChannels) && channel < (unsigned int) this->waitingData.count() && this->waitingData[channel]) || ((channel >= this->settings->scope.physicalChannels) && (this->settings->scope.voltage[channel].used || this->settings->scope.spectrum[channel].used) && this->analyzedData.count() >= 2 && this->analyzedData[0]->samples.voltage.sample && this->analyzedData[1]->samples.voltage.sample)) { + // Set sampling interval + this->analyzedData[channel]->samples.voltage.interval = 1.0 / this->waitingDataSamplerate; + + unsigned int size; + if(channel < this->settings->scope.physicalChannels) + size = this->waitingDataSize[channel]; + else + size = this->waitingDataSize[0]; + // Reallocate memory for samples if the sample count has changed + if(this->analyzedData[channel]->samples.voltage.count != size) { + this->analyzedData[channel]->samples.voltage.count = size; + if(this->analyzedData[channel]->samples.voltage.sample) + delete[] this->analyzedData[channel]->samples.voltage.sample; + this->analyzedData[channel]->samples.voltage.sample = new double[size]; + } + + // Physical channels + if(channel < this->settings->scope.physicalChannels) { + // Copy the buffer of the oscilloscope into the sample buffer + if(channel < (unsigned int) this->waitingData.count()) + for(unsigned int position = 0; position < this->waitingDataSize[channel]; position++) + this->analyzedData[channel]->samples.voltage.sample[position] = this->waitingData[channel][position]; + } + // Math channel + else { + // Set sampling interval + this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.interval = this->analyzedData[0]->samples.voltage.interval; + + // Reallocate memory for samples if the sample count has changed + if(this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.count != this->analyzedData[0]->samples.voltage.count) { + this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.count = this->analyzedData[0]->samples.voltage.count; + if(this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.sample) + delete[] this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.sample; + this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.sample = new double[this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.count]; + } + + // Calculate values and write them into the sample buffer + for(unsigned int realPosition = 0; realPosition < this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.count; realPosition++) { + switch(this->settings->scope.voltage[this->settings->scope.physicalChannels].misc) { + case Dso::MATHMODE_1ADD2: + this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.sample[realPosition] = this->analyzedData[0]->samples.voltage.sample[realPosition] + this->analyzedData[1]->samples.voltage.sample[realPosition]; + break; + case Dso::MATHMODE_1SUB2: + this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.sample[realPosition] = this->analyzedData[0]->samples.voltage.sample[realPosition] - this->analyzedData[1]->samples.voltage.sample[realPosition]; + break; + case Dso::MATHMODE_2SUB1: + this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.sample[realPosition] = this->analyzedData[1]->samples.voltage.sample[realPosition] - this->analyzedData[0]->samples.voltage.sample[realPosition]; + break; + } + } + } + } + else { + // Clear unused channels + this->analyzedData[channel]->samples.voltage.count = 0; + this->analyzedData[this->settings->scope.physicalChannels]->samples.voltage.interval = 0; + if(this->analyzedData[channel]->samples.voltage.sample) { + delete[] this->analyzedData[channel]->samples.voltage.sample; + this->analyzedData[channel]->samples.voltage.sample = 0; + } + } + } + + this->waitingDataMutex->unlock(); + + // Lower priority for spectrum calculation + this->setPriority(QThread::LowPriority); + + // Calculate peak-to-peak voltage and frequency + for(int channel = 0; channel < this->analyzedData.count(); channel++) { + if(this->settings->scope.voltage[channel].used && this->analyzedData[channel]->samples.voltage.sample) { + bool aboveTrigger = this->analyzedData[channel]->samples.voltage.sample[0] > this->settings->scope.voltage[channel].trigger; + double minimalVoltage, maximalVoltage; + unsigned int firstTrigger = 0, lastTrigger = 0; + int triggerCount = -1; + this->analyzedData[channel]->amplitude = 0; + minimalVoltage = maximalVoltage = this->analyzedData[channel]->samples.voltage.sample[0]; + + for(unsigned int position = 0; position < this->analyzedData[channel]->samples.voltage.count; position++) { + // Check trigger condition + if(aboveTrigger != (this->analyzedData[channel]->samples.voltage.sample[position] > this->settings->scope.voltage[channel].trigger)) { + aboveTrigger = this->analyzedData[channel]->samples.voltage.sample[position] > this->settings->scope.voltage[channel].trigger; + // We measure the time between two trigger slopes + if(aboveTrigger == (this->settings->scope.trigger.slope == Dso::SLOPE_POSITIVE)) { + triggerCount++; + if(triggerCount == 0) + firstTrigger = position; + else + this->analyzedData[channel]->amplitude += maximalVoltage - minimalVoltage; + minimalVoltage = this->analyzedData[channel]->samples.voltage.sample[position]; + maximalVoltage = this->analyzedData[channel]->samples.voltage.sample[position]; + lastTrigger = position; + } + } + else { // Get minimal and maximal voltage + if(this->analyzedData[channel]->samples.voltage.sample[position] < minimalVoltage) + minimalVoltage = this->analyzedData[channel]->samples.voltage.sample[position]; + else if(this->analyzedData[channel]->samples.voltage.sample[position] > maximalVoltage) + maximalVoltage = this->analyzedData[channel]->samples.voltage.sample[position]; + } + } + + // Calculate values + if(triggerCount >= 0) { + this->analyzedData[channel]->amplitude /= triggerCount; + this->analyzedData[channel]->frequency = (double) triggerCount / (lastTrigger - firstTrigger) / this->analyzedData[channel]->samples.voltage.interval; + } + else { + this->analyzedData[channel]->amplitude = maximalVoltage - minimalVoltage; + this->analyzedData[channel]->frequency = 0; + } + } + } + + // Calculate spectrums + for(int channel = 0; channel < this->analyzedData.count(); channel++) { + if(this->settings->scope.spectrum[channel].used && this->analyzedData[channel]->samples.voltage.sample) { + // Calculate new window + if(this->lastWindow != this->settings->scope.spectrumWindow || this->lastBufferSize != this->analyzedData[channel]->samples.voltage.count) { + if(this->lastBufferSize != this->analyzedData[channel]->samples.voltage.count) { + this->lastBufferSize = this->analyzedData[channel]->samples.voltage.count; + + if(this->window) + fftw_free(this->window); + this->window = (double *) fftw_malloc(sizeof(double) * this->lastBufferSize); + } + + unsigned int windowEnd = this->lastBufferSize - 1; + this->lastWindow = this->settings->scope.spectrumWindow; + + switch(this->settings->scope.spectrumWindow) { + case Dso::WINDOW_HAMMING: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = 0.54 - 0.46 * cos(2.0 * M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_HANN: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = 0.5 * (1.0 - cos(2.0 * M_PI * windowPosition / windowEnd)); + break; + case Dso::WINDOW_COSINE: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = sin(M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_LANCZOS: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) { + double sincParameter = (2.0 * windowPosition / windowEnd - 1.0) * M_PI; + if(sincParameter == 0) + *(this->window + windowPosition) = 1; + else + *(this->window + windowPosition) = sin(sincParameter) / sincParameter; + } + break; + case Dso::WINDOW_BARTLETT: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = 2.0 / windowEnd * (windowEnd / 2 - abs(windowPosition - windowEnd / 2)); + break; + case Dso::WINDOW_TRIANGULAR: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = 2.0 / this->lastBufferSize * (this->lastBufferSize / 2 - abs(windowPosition - windowEnd / 2)); + break; + case Dso::WINDOW_GAUSS: + { + double sigma = 0.4; + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = exp(-0.5 * pow(((windowPosition - windowEnd / 2) / (sigma * windowEnd / 2)), 2)); + } + break; + case Dso::WINDOW_BARTLETTHANN: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = 0.62 - 0.48 * abs(windowPosition / windowEnd - 0.5) - 0.38 * cos(2.0 * M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_BLACKMAN: + { + double alpha = 0.16; + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = (1 - alpha) / 2 - 0.5 * cos(2.0 * M_PI * windowPosition / windowEnd) + alpha / 2 * cos(4.0 * M_PI * windowPosition / windowEnd); + } + break; + //case WINDOW_KAISER: + // TODO + //double alpha = 3.0; + //for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + //*(this->window + windowPosition) = ; + //break; + case Dso::WINDOW_NUTTALL: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = 0.355768 - 0.487396 * cos(2 * M_PI * windowPosition / windowEnd) + 0.144232 * cos(4 * M_PI * windowPosition / windowEnd) - 0.012604 * cos(6 * M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_BLACKMANHARRIS: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = 0.35875 - 0.48829 * cos(2 * M_PI * windowPosition / windowEnd) + 0.14128 * cos(4 * M_PI * windowPosition / windowEnd) - 0.01168 * cos(6 * M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_BLACKMANNUTTALL: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = 0.3635819 - 0.4891775 * cos(2 * M_PI * windowPosition / windowEnd) + 0.1365995 * cos(4 * M_PI * windowPosition / windowEnd) - 0.0106411 * cos(6 * M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_FLATTOP: + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = 1.0 - 1.93 * cos(2 * M_PI * windowPosition / windowEnd) + 1.29 * cos(4 * M_PI * windowPosition / windowEnd) - 0.388 * cos(6 * M_PI * windowPosition / windowEnd) + 0.032 * cos(8 * M_PI * windowPosition / windowEnd); + break; + default: // Dso::WINDOW_RECTANGULAR + for(unsigned int windowPosition = 0; windowPosition < this->lastBufferSize; windowPosition++) + *(this->window + windowPosition) = 1.0; + } + } + + // Set sampling interval + this->analyzedData[channel]->samples.spectrum.interval = 1.0 / this->analyzedData[channel]->samples.voltage.interval / this->analyzedData[channel]->samples.voltage.count; + + // Reallocate memory for samples if the sample count has changed + if(this->analyzedData[channel]->samples.spectrum.count != this->analyzedData[channel]->samples.voltage.count / 2) { + this->analyzedData[channel]->samples.spectrum.count = this->analyzedData[channel]->samples.voltage.count / 2; + if(this->analyzedData[channel]->samples.spectrum.sample) + delete[] this->analyzedData[channel]->samples.spectrum.sample; + this->analyzedData[channel]->samples.spectrum.sample = new double[this->analyzedData[channel]->samples.voltage.count]; + } + + // Create sample buffer and apply window + double *windowedValues = new double[this->analyzedData[channel]->samples.voltage.count]; + for(unsigned int position = 0; position < this->analyzedData[channel]->samples.voltage.count; position++) + windowedValues[position] = this->window[position] * this->analyzedData[channel]->samples.voltage.sample[position]; + + // Do discrete real to half-complex transformation + // TODO: Reuse plan and use FFTW_MEASURE to get fastest algorithm + fftw_plan fftPlan = fftw_plan_r2r_1d(this->analyzedData[channel]->samples.voltage.count, windowedValues, this->analyzedData[channel]->samples.spectrum.sample, FFTW_R2HC, FFTW_ESTIMATE); + fftw_execute(fftPlan); + fftw_destroy_plan(fftPlan); + + // Deallocate sample buffer + delete[] windowedValues; + + // Convert values into dB (Relative to the reference level) + double offset = 60 - this->settings->scope.spectrumReference - 20 * log10(sqrt(this->analyzedData[channel]->samples.spectrum.count)); + double offsetLimit = this->settings->scope.spectrumLimit - this->settings->scope.spectrumReference; + for(unsigned int position = 0; position < this->analyzedData[channel]->samples.spectrum.count; position++) { + this->analyzedData[channel]->samples.spectrum.sample[position] = 20 * log10(fabs(this->analyzedData[channel]->samples.spectrum.sample[position])) + offset; + + // Check if this value has to be limited + if(offsetLimit > this->analyzedData[channel]->samples.spectrum.sample[position]) + this->analyzedData[channel]->samples.spectrum.sample[position] = offsetLimit; + } + } + else if(this->analyzedData[channel]->samples.spectrum.sample) { + // Clear unused channels + this->analyzedData[channel]->samples.spectrum.count = 0; + this->analyzedData[channel]->samples.spectrum.interval = 0; + delete[] this->analyzedData[channel]->samples.spectrum.sample; + this->analyzedData[channel]->samples.spectrum.sample = 0; + } + } + + this->analyzedDataMutex->unlock(); +} + +void DataAnalyzer::analyze(const QList *data, const QList *size, double samplerate, QMutex *mutex) { + // Previous analysis still running, drop the new data + if(this->isRunning()) + return; + + // The thread will analyze it, just save the pointers + mutex->lock(); + this->waitingData.clear(); + this->waitingData.append(*data); + this->waitingDataSize.clear(); + this->waitingDataSize.append(*size); + this->waitingDataMutex = mutex; + this->waitingDataSamplerate = samplerate; + this->start(); +} diff --git b/openhantek/src/dataanalyzer.h a/openhantek/src/dataanalyzer.h new file mode 100644 index 0000000..cb69d06 --- /dev/null +++ a/openhantek/src/dataanalyzer.h @@ -0,0 +1,103 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file dataanalyzer.h +/// \brief Declares the DataAnalyzer class. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef DATAANALYZER_H +#define DATAANALYZER_H + + +#include + + +#include "constants.h" +#include "helper.h" + + +class DsoSettings; +class HantekDSOAThread; +class QMutex; + + +//////////////////////////////////////////////////////////////////////////////// +/// \struct SampleValues dataanalyzer.h +/// \brief Struct for a array of sample values. +struct SampleValues { + double *sample; + unsigned int count; + double interval; +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct SampleData dataanalyzer.h +/// \brief Struct for the sample value arrays. +struct SampleData { + SampleValues voltage, spectrum; +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct AnalyzedData dataanalyzer.h +/// \brief Struct for the analyzed data. +struct AnalyzedData { + SampleData samples; + double frequency, amplitude; +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \class DataAnalyzer dataanalyzer.h +/// \brief Analyzes the data from the dso. +/// Converts the levels into volts, calculates the spectrum and saves offsets +/// and the time-/frequencysteps between two values. +class DataAnalyzer : public QThread { + Q_OBJECT + + public: + DataAnalyzer(DsoSettings *settings, QObject *parent = 0); + ~DataAnalyzer(); + + const AnalyzedData *data(int channel) const; + QMutex *mutex() const; + + protected: + void run(); + + private: + DsoSettings *settings; + + QList analyzedData; + QMutex *analyzedDataMutex; + + unsigned int lastBufferSize; + Dso::WindowFunction lastWindow; + double *window; + + QList waitingData; + QList waitingDataSize; + double waitingDataSamplerate; + QMutex *waitingDataMutex; + + public slots: + void analyze(const QList *data, const QList *size, double samplerate, QMutex *mutex); +}; + +#endif diff --git b/openhantek/src/dockwindows.cpp a/openhantek/src/dockwindows.cpp new file mode 100644 index 0000000..d3c08f5 --- /dev/null +++ a/openhantek/src/dockwindows.cpp @@ -0,0 +1,623 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// dockwindows.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include +#include + + +#include "dockwindows.h" + +#include "settings.h" +#include "helper.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class HorizontalDock +/// \brief Initializes the horizontal axis docking window. +/// \param settings The target settings object. +/// \param parent The parent widget. +/// \param flags Flags for the window manager. +HorizontalDock::HorizontalDock(DsoSettings *settings, QWidget *parent, Qt::WindowFlags flags) : QDockWidget(tr("Horizontal"), parent, flags) { + this->settings = settings; + + // Initialize lists for comboboxes + this->timebaseSteps << 1e-8 << 2e-8 << 4e-8 << 1e-7 << 2e-7 << 4e-7 + << 1e-6 << 2e-6 << 4e-6 << 1e-5 << 2e-5 << 4e-5 << 1e-4 << 2e-4 << 4e-4 + << 1e-3 << 2e-3 << 4e-3 << 1e-2 << 2e-2 << 4e-2 << 1e-1 << 2e-1 << 4e-1 + << 1e0 << 2e0 << 4e0 << 1e1 << 2e1 << 4e1 << 6e1 << 12e1 << 24e1 + << 6e2 << 12e2 << 24e2 << 36e2; ///< Timebase steps in seconds/div + for(QList::iterator timebase = this->timebaseSteps.begin(); timebase != this->timebaseSteps.end(); ++timebase) + this->timebaseStrings << Helper::valueToString(*timebase, Helper::UNIT_SECONDS, 0); + this->frequencybaseSteps + << 1.0 << 2.0 << 5.0 << 1e1 << 2e1 << 5e1 << 1e2 << 2e2 << 5e2 + << 1e3 << 2e3 << 5e3 << 1e4 << 2e4 << 4e4 << 1e5 << 2e5 << 5e5 + << 1e6 << 2e6 << 5e6 << 1e7; ///< Frequencybase steps in Hz/div + for(QList::iterator frequencybase = this->frequencybaseSteps.begin(); frequencybase != this->frequencybaseSteps.end(); ++frequencybase) + this->frequencybaseStrings << Helper::valueToString(*frequencybase, Helper::UNIT_HERTZ, 0); + this->formatStrings << tr("T - Y") << tr("X - Y"); + + // Initialize elements + this->timebaseLabel = new QLabel(tr("Timebase")); + this->timebaseComboBox = new QComboBox(); + this->timebaseComboBox->addItems(this->timebaseStrings); + + this->frequencybaseLabel = new QLabel(tr("Frequencybase")); + this->frequencybaseComboBox = new QComboBox(); + this->frequencybaseComboBox->addItems(this->frequencybaseStrings); + + this->formatLabel = new QLabel(tr("Format")); + this->formatComboBox = new QComboBox(); + this->formatComboBox->addItems(this->formatStrings); + + this->dockLayout = new QGridLayout(); + this->dockLayout->setColumnMinimumWidth(0, 64); + this->dockLayout->setColumnStretch(1, 1); + this->dockLayout->addWidget(this->timebaseLabel, 0, 0); + this->dockLayout->addWidget(this->timebaseComboBox, 0, 1); + this->dockLayout->addWidget(this->frequencybaseLabel, 1, 0); + this->dockLayout->addWidget(this->frequencybaseComboBox, 1, 1); + this->dockLayout->addWidget(this->formatLabel, 2, 0); + this->dockLayout->addWidget(this->formatComboBox, 2, 1); + + this->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + + this->dockWidget = new QWidget(); + this->dockWidget->setLayout(this->dockLayout); + this->setWidget(this->dockWidget); + + // Connect signals and slots + connect(this->frequencybaseComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(frequencybaseSelected(int))); + connect(this->timebaseComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(timebaseSelected(int))); + connect(this->formatComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(formatSelected(int))); + + // Set values + this->setTimebase(this->settings->scope.horizontal.timebase); + this->setFrequencybase(this->settings->scope.horizontal.frequencybase); + this->setFormat(this->settings->scope.horizontal.format); +} + +/// \brief Cleans up everything. +HorizontalDock::~HorizontalDock() { +} + +/// \brief Don't close the dock, just hide it. +/// \param event The close event that should be handled. +void HorizontalDock::closeEvent(QCloseEvent *event) { + this->hide(); + + event->accept(); +} + +/// \brief Changes the frequencybase if the new value is supported. +/// \param frequencybase The frequencybase in hertz. +/// \return Index of frequencybase-value, -1 on error. +int HorizontalDock::setFrequencybase(double frequencybase) { + int index = this->frequencybaseSteps.indexOf(frequencybase); + + if(index != -1) + this->frequencybaseComboBox->setCurrentIndex(index); + + return index; +} + +/// \brief Changes the timebase if the new value is supported. +/// \param timebase The timebase in seconds. +/// \return Index of timebase-value, -1 on error. +int HorizontalDock::setTimebase(double timebase) { + int index = this->timebaseSteps.indexOf(timebase); + + if(index != -1) + this->timebaseComboBox->setCurrentIndex(index); + + return index; +} + +/// \brief Changes the format if the new value is supported. +/// \param format The format for the horizontal axis. +/// \return Index of format-value, -1 on error. +int HorizontalDock::setFormat(Dso::GraphFormat format) { + if(format >= Dso::GRAPHFORMAT_TY && format <= Dso::GRAPHFORMAT_XY) { + this->formatComboBox->setCurrentIndex(format); + return format; + } + + return -1; +} + +/// \brief Called when the frequencybase combo box changes it's value. +/// \param index The index of the combo box item. +void HorizontalDock::frequencybaseSelected(int index) { + this->settings->scope.horizontal.frequencybase = this->frequencybaseSteps.at(index); + emit frequencybaseChanged(this->settings->scope.horizontal.frequencybase); +} + +/// \brief Called when the timebase combo box changes it's value. +/// \param index The index of the combo box item. +void HorizontalDock::timebaseSelected(int index) { + this->settings->scope.horizontal.timebase = this->timebaseSteps.at(index); + emit timebaseChanged(this->settings->scope.horizontal.timebase); +} + +/// \brief Called when the format combo box changes it's value. +/// \param index The index of the combo box item. +void HorizontalDock::formatSelected(int index) { + this->settings->scope.horizontal.format = (Dso::GraphFormat) index; + emit formatChanged(this->settings->scope.horizontal.format); +} + + +//////////////////////////////////////////////////////////////////////////////// +// class TriggerDock +/// \brief Initializes the trigger settings docking window. +/// \param settings The target settings object. +/// \param parent The parent widget. +/// \param flags Flags for the window manager. +TriggerDock::TriggerDock(DsoSettings *settings, const QStringList *specialTriggers, QWidget *parent, Qt::WindowFlags flags) : QDockWidget(tr("Trigger"), parent, flags) { + this->settings = settings; + + // Initialize lists for comboboxes + this->modeStrings << tr("Auto") << tr("Normal") << tr("Single"); + for(unsigned int channel = 0; channel < this->settings->scope.physicalChannels; channel++) + this->sourceStandardStrings << tr("CH%1").arg(channel + 1); + this->sourceSpecialStrings << *specialTriggers; + this->slopeStrings << QString::fromUtf8("\u2197") << QString::fromUtf8("\u2198"); + + // Initialize elements + this->modeLabel = new QLabel(tr("Mode")); + this->modeComboBox = new QComboBox(); + this->modeComboBox->addItems(this->modeStrings); + + this->slopeLabel = new QLabel(tr("Slope")); + this->slopeComboBox = new QComboBox(); + this->slopeComboBox->addItems(this->slopeStrings); + + this->sourceLabel = new QLabel(tr("Source")); + this->sourceComboBox = new QComboBox(); + this->sourceComboBox->addItems(this->sourceStandardStrings); + this->sourceComboBox->addItems(this->sourceSpecialStrings); + + this->dockLayout = new QGridLayout(); + this->dockLayout->setColumnMinimumWidth(0, 64); + this->dockLayout->setColumnStretch(1, 1); + this->dockLayout->addWidget(this->modeLabel, 0, 0); + this->dockLayout->addWidget(this->modeComboBox, 0, 1); + this->dockLayout->addWidget(this->sourceLabel, 1, 0); + this->dockLayout->addWidget(this->sourceComboBox, 1, 1); + this->dockLayout->addWidget(this->slopeLabel, 2, 0); + this->dockLayout->addWidget(this->slopeComboBox, 2, 1); + + this->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + + this->dockWidget = new QWidget(); + this->dockWidget->setLayout(this->dockLayout); + this->setWidget(this->dockWidget); + + // Connect signals and slots + connect(this->modeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(modeSelected(int))); + connect(this->slopeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slopeSelected(int))); + connect(this->sourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(sourceSelected(int))); + + // Set values + this->setMode(this->settings->scope.trigger.mode); + this->setSlope(this->settings->scope.trigger.slope); + this->setSource(this->settings->scope.trigger.special, this->settings->scope.trigger.source); +} + +/// \brief Cleans up everything. +TriggerDock::~TriggerDock() { +} + +/// \brief Don't close the dock, just hide it +/// \param event The close event that should be handled. +void TriggerDock::closeEvent(QCloseEvent *event) { + this->hide(); + + event->accept(); +} + +/// \brief Changes the trigger mode if the new mode is supported. +/// \param mode The trigger mode. +/// \return Index of mode-value, -1 on error. +int TriggerDock::setMode(Dso::TriggerMode mode) { + if(mode >= Dso::TRIGGERMODE_AUTO && mode <=Dso:: TRIGGERMODE_SINGLE) { + this->modeComboBox->setCurrentIndex(mode); + return mode; + } + + return -1; +} + +/// \brief Changes the trigger slope if the new slope is supported. +/// \param slope The trigger slope. +/// \return Index of slope-value, -1 on error. +int TriggerDock::setSlope(Dso::Slope slope) { + if(slope >= Dso::SLOPE_POSITIVE && slope <= Dso::SLOPE_NEGATIVE) { + this->slopeComboBox->setCurrentIndex(slope); + return slope; + } + + return -1; +} + +/// \brief Changes the trigger source if the new source is supported. +/// \param special true for a special channel (EXT, ...) as trigger source. +/// \param id The number of the channel, that should be used as trigger. +/// \return Index of source item, -1 on error. +int TriggerDock::setSource(bool special, unsigned int id) { + if((!special && id >= (unsigned int) this->sourceStandardStrings.count()) || (special && id >= (unsigned int) this->sourceSpecialStrings.count())) + return -1; + + int index = id; + if(special) + index += this->sourceStandardStrings.count(); + this->sourceComboBox->setCurrentIndex(index); + + return index; +} + +/// \brief Called when the mode combo box changes it's value. +/// \param index The index of the combo box item. +void TriggerDock::modeSelected(int index) { + this->settings->scope.trigger.mode = (Dso::TriggerMode) index; + emit modeChanged(this->settings->scope.trigger.mode); +} + +/// \brief Called when the slope combo box changes it's value. +/// \param index The index of the combo box item. +void TriggerDock::slopeSelected(int index) { + this->settings->scope.trigger.slope = (Dso::Slope) index; + emit slopeChanged(this->settings->scope.trigger.slope); +} + +/// \brief Called when the source combo box changes it's value. +/// \param index The index of the combo box item. +void TriggerDock::sourceSelected(int index) { + unsigned int id = index; + bool special = false; + + if(id >= (unsigned int) this->sourceStandardStrings.count()) { + id -= this->sourceStandardStrings.count(); + special = true; + } + + this->settings->scope.trigger.source = id; + this->settings->scope.trigger.special = special; + emit sourceChanged(special, id); +} + + +//////////////////////////////////////////////////////////////////////////////// +// class SpectrumDock +/// \brief Initializes the spectrum view docking window. +/// \param settings The target settings object. +/// \param parent The parent widget. +/// \param flags Flags for the window manager. +SpectrumDock::SpectrumDock(DsoSettings *settings, QWidget *parent, Qt::WindowFlags flags) : QDockWidget(tr("Spectrum"), parent, flags) { + this->settings = settings; + + // Initialize lists for comboboxes + this->magnitudeSteps << 1e0 << 2e0 << 3e0 << 6e0 + << 1e1 << 2e1 << 3e1 << 6e1 << 1e2 << 2e2 << 3e2 << 6e2; ///< Magnitude steps in dB/div + for(QList::iterator magnitude = this->magnitudeSteps.begin(); magnitude != this->magnitudeSteps.end(); ++magnitude) + this->magnitudeStrings << Helper::valueToString(*magnitude, Helper::UNIT_DECIBEL, 0); + + // Initialize elements + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + this->magnitudeComboBox.append(new QComboBox()); + this->magnitudeComboBox[channel]->addItems(this->magnitudeStrings); + + this->usedCheckBox.append(new QCheckBox(this->settings->scope.voltage[channel].name)); + } + + this->dockLayout = new QGridLayout(); + this->dockLayout->setColumnMinimumWidth(0, 64); + this->dockLayout->setColumnStretch(1, 1); + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + this->dockLayout->addWidget(this->usedCheckBox[channel], channel, 0); + this->dockLayout->addWidget(this->magnitudeComboBox[channel], channel, 1); + } + + this->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + + this->dockWidget = new QWidget(); + this->dockWidget->setLayout(this->dockLayout); + this->setWidget(this->dockWidget); + + // Connect signals and slots + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + connect(this->magnitudeComboBox[channel], SIGNAL(currentIndexChanged(int)), this, SLOT(magnitudeSelected(int))); + connect(this->usedCheckBox[channel], SIGNAL(toggled(bool)), this, SLOT(usedSwitched(bool))); + } + + // Set values + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + this->setMagnitude(channel, this->settings->scope.spectrum[channel].magnitude); + this->setUsed(channel, this->settings->scope.spectrum[channel].used); + } +} + +/// \brief Cleans up everything. +SpectrumDock::~SpectrumDock() { +} + +/// \brief Don't close the dock, just hide it +/// \param event The close event that should be handled. +void SpectrumDock::closeEvent(QCloseEvent *event) { + this->hide(); + + event->accept(); +} + +/// \brief Sets the magnitude for a channel. +/// \param channel The channel, whose magnitude should be set. +/// \param magnitude The magnitude in dB. +/// \return Index of magnitude-value, -1 on error. +int SpectrumDock::setMagnitude(int channel, double magnitude) { + if(channel < 0 || channel >= this->settings->scope.voltage.count()) + return -1; + + int index = this->magnitudeSteps.indexOf(magnitude); + if(index != -1) + this->magnitudeComboBox[channel]->setCurrentIndex(index); + + return index; +} + +/// \brief Enables/disables a channel. +/// \param channel The channel, that should be enabled/disabled. +/// \param used True if the channel should be enabled, false otherwise. +/// \return Index of channel, -1 on error. +int SpectrumDock::setUsed(int channel, bool used) { + if(channel >= 0 && channel < this->settings->scope.voltage.count()) { + this->usedCheckBox[channel]->setChecked(used); + return channel; + } + + return -1; +} + +/// \brief Called when the source combo box changes it's value. +/// \param index The index of the combo box item. +void SpectrumDock::magnitudeSelected(int index) { + int channel; + + // Which combobox was it? + for(channel = 0; channel < this->settings->scope.voltage.count(); channel++) + if(this->sender() == this->magnitudeComboBox[channel]) + break; + + // Send signal if it was one of the comboboxes + if(channel < this->settings->scope.voltage.count()) { + this->settings->scope.spectrum[channel].magnitude = this->magnitudeSteps.at(index); + emit magnitudeChanged(channel, this->settings->scope.spectrum[channel].magnitude); + } +} + +/// \brief Called when the used checkbox is switched. +/// \param checked The check-state of the checkbox. +void SpectrumDock::usedSwitched(bool checked) { + int channel; + + // Which checkbox was it? + for(channel = 0; channel < this->settings->scope.voltage.count(); channel++) + if(this->sender() == this->usedCheckBox[channel]) + break; + + // Send signal if it was one of the checkboxes + if(channel < this->settings->scope.voltage.count()) { + this->settings->scope.spectrum[channel].used = checked; + emit usedChanged(channel, checked); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// class VoltageDock +/// \brief Initializes the vertical axis docking window. +/// \param settings The target settings object. +/// \param parent The parent widget. +/// \param flags Flags for the window manager. +VoltageDock::VoltageDock(DsoSettings *settings, QWidget *parent, Qt::WindowFlags flags) : QDockWidget(tr("Voltage"), parent, flags) { + this->settings = settings; + + // Initialize lists for comboboxes + this->couplingStrings << tr("AC") << tr("DC") << tr("GND"); + + this->modeStrings << tr("CH1 + CH2") << tr("CH1 - CH2") << tr("CH2 - CH1"); + + this->gainSteps << 1e-2 << 2e-2 << 5e-2 << 1e-1 << 2e-1 << 5e-1 + << 1e0 << 2e0 << 5e0; ///< Voltage steps in V/div + for(QList::iterator gain = this->gainSteps.begin(); gain != this->gainSteps.end(); gain++) + this->gainStrings << Helper::valueToString(*gain, Helper::UNIT_VOLTS, 0); + + // Initialize elements + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + this->miscComboBox.append(new QComboBox()); + if(channel < (int) this->settings->scope.physicalChannels) + this->miscComboBox[channel]->addItems(this->couplingStrings); + else + this->miscComboBox[channel]->addItems(this->modeStrings); + + this->gainComboBox.append(new QComboBox()); + this->gainComboBox[channel]->addItems(this->gainStrings); + + this->usedCheckBox.append(new QCheckBox(this->settings->scope.voltage[channel].name)); + } + + this->dockLayout = new QGridLayout(); + this->dockLayout->setColumnMinimumWidth(0, 64); + this->dockLayout->setColumnStretch(1, 1); + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + this->dockLayout->addWidget(this->usedCheckBox[channel], channel * 2, 0); + this->dockLayout->addWidget(this->gainComboBox[channel], channel * 2, 1); + this->dockLayout->addWidget(this->miscComboBox[channel], channel * 2 + 1, 1); + } + + this->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + + this->dockWidget = new QWidget(); + this->dockWidget->setLayout(this->dockLayout); + this->setWidget(this->dockWidget); + + // Connect signals and slots + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + connect(this->gainComboBox[channel], SIGNAL(currentIndexChanged(int)), this, SLOT(gainSelected(int))); + connect(this->miscComboBox[channel], SIGNAL(currentIndexChanged(int)), this, SLOT(miscSelected(int))); + connect(this->usedCheckBox[channel], SIGNAL(toggled(bool)), this, SLOT(usedSwitched(bool))); + } + + // Set values + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + if(channel < (int) this->settings->scope.physicalChannels) + this->setCoupling(channel, (Dso::Coupling) this->settings->scope.voltage[channel].misc); + else + this->setMode((Dso::MathMode) this->settings->scope.voltage[channel].misc); + this->setGain(channel, this->settings->scope.voltage[channel].gain); + this->setUsed(channel, this->settings->scope.voltage[channel].used); + } +} + +/// \brief Cleans up everything. +VoltageDock::~VoltageDock() { +} + +/// \brief Don't close the dock, just hide it +/// \param event The close event that should be handled. +void VoltageDock::closeEvent(QCloseEvent *event) { + this->hide(); + + event->accept(); +} + +/// \brief Sets the coupling for a channel. +/// \param channel The channel, whose coupling should be set. +/// \param coupling The coupling-mode. +/// \return Index of coupling-mode, -1 on error. +int VoltageDock::setCoupling(int channel, Dso::Coupling coupling) { + if(coupling < Dso::COUPLING_AC || coupling > Dso::COUPLING_GND) + return -1; + if(channel < 0 || channel >= (int) this->settings->scope.physicalChannels) + return -1; + + this->miscComboBox[channel]->setCurrentIndex(coupling); + return coupling; +} + +/// \brief Sets the gain for a channel. +/// \param channel The channel, whose gain should be set. +/// \param gain The gain in volts. +/// \return Index of gain-value, -1 on error. +int VoltageDock::setGain(int channel, double gain) { + if(channel < 0 || channel >= this->settings->scope.voltage.count()) + return -1; + + int index = this->gainSteps.indexOf(gain); + if(index != -1) + this->gainComboBox[channel]->setCurrentIndex(index); + + return index; +} + +/// \brief Sets the mode for the math channel. +/// \param mode The math-mode. +/// \return Index of math-mode, -1 on error. +int VoltageDock::setMode(Dso::MathMode mode) { + if(mode >= Dso::MATHMODE_1ADD2 && mode <= Dso::MATHMODE_2SUB1) { + this->miscComboBox[this->settings->scope.physicalChannels]->setCurrentIndex(mode); + return mode; + } + + return -1; +} + +/// \brief Enables/disables a channel. +/// \param channel The channel, that should be enabled/disabled. +/// \param used True if the channel should be enabled, false otherwise. +/// \return Index of channel, -1 on error. +int VoltageDock::setUsed(int channel, bool used) { + if(channel >= 0 && channel < this->settings->scope.voltage.count()) { + this->usedCheckBox[channel]->setChecked(used); + return channel; + } + + return -1; +} + +/// \brief Called when the gain combo box changes it's value. +/// \param index The index of the combo box item. +void VoltageDock::gainSelected(int index) { + int channel; + + // Which combobox was it? + for(channel = 0; channel < this->settings->scope.voltage.count(); channel++) + if(this->sender() == this->gainComboBox[channel]) + break; + + // Send signal if it was one of the comboboxes + if(channel < this->settings->scope.voltage.count()) { + this->settings->scope.voltage[channel].gain = this->gainSteps.at(index); + + emit gainChanged(channel, this->settings->scope.voltage[channel].gain); + } +} + +/// \brief Called when the misc combo box changes it's value. +/// \param index The index of the combo box item. +void VoltageDock::miscSelected(int index) { + int channel; + + // Which combobox was it? + for(channel = 0; channel < this->settings->scope.voltage.count(); channel++) + if(this->sender() == this->miscComboBox[channel]) + break; + + // Send signal if it was one of the comboboxes + if(channel < this->settings->scope.voltage.count()) { + this->settings->scope.voltage[channel].misc = index; + if(channel < (int) this->settings->scope.physicalChannels) + emit couplingChanged(channel, (Dso::Coupling) this->settings->scope.voltage[channel].misc); + else + emit modeChanged((Dso::MathMode) this->settings->scope.voltage[channel].misc); + } +} + +/// \brief Called when the used checkbox is switched. +/// \param checked The check-state of the checkbox. +void VoltageDock::usedSwitched(bool checked) { + int channel; + + // Which checkbox was it? + for(channel = 0; channel < this->settings->scope.voltage.count(); channel++) + if(this->sender() == this->usedCheckBox[channel]) + break; + + // Send signal if it was one of the checkboxes + if(channel < this->settings->scope.voltage.count()) { + this->settings->scope.voltage[channel].used = checked; + emit usedChanged(channel, checked); + } +} diff --git b/openhantek/src/dockwindows.h a/openhantek/src/dockwindows.h new file mode 100644 index 0000000..8714d36 --- /dev/null +++ a/openhantek/src/dockwindows.h @@ -0,0 +1,205 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file dockwindows.h +/// \brief Declares the docking window classes. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef DOCKWINDOWS_H +#define DOCKWINDOWS_H + + +#include +#include + + +#include "constants.h" +#include "settings.h" + + +class QLabel; +class QCheckBox; +class QComboBox; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class HorizontalDock dockwindows.h +/// \brief Dock window for the horizontal axis. +/// It contains the settings for the timebase and the display format. +class HorizontalDock : public QDockWidget { + Q_OBJECT + + public: + HorizontalDock(DsoSettings *settings, QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~HorizontalDock(); + + int setFrequencybase(double timebase); + int setTimebase(double timebase); + int setFormat(Dso::GraphFormat format); + + protected: + void closeEvent(QCloseEvent *event); + + QGridLayout *dockLayout; + QWidget *dockWidget; + QLabel *timebaseLabel, *frequencybaseLabel, *formatLabel; + QComboBox *timebaseComboBox, *frequencybaseComboBox, *formatComboBox; + + DsoSettings *settings; + + QList frequencybaseSteps, timebaseSteps; + QStringList frequencybaseStrings, timebaseStrings, formatStrings; + + protected slots: + void frequencybaseSelected(int index); + void timebaseSelected(int index); + void formatSelected(int index); + + signals: + void frequencybaseChanged(double frequencybase); + void timebaseChanged(double timebase); + void formatChanged(Dso::GraphFormat format); +}; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class TriggerDock dockwindows.h +/// \brief Dock window for the trigger settings. +/// It contains the settings for the trigger mode, source and slope. +class TriggerDock : public QDockWidget { + Q_OBJECT + + public: + TriggerDock(DsoSettings *settings, const QStringList *specialTriggers, QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~TriggerDock(); + + int setMode(Dso::TriggerMode mode); + int setSource(bool special, unsigned int id); + int setSlope(Dso::Slope slope); + + protected: + void closeEvent(QCloseEvent *event); + + QGridLayout *dockLayout; + QWidget *dockWidget; + QLabel *modeLabel, *sweepLabel, *sourceLabel, *slopeLabel; + QComboBox *modeComboBox, *sweepComboBox, *sourceComboBox, *slopeComboBox; + + DsoSettings *settings; + + QStringList modeStrings, sourceStandardStrings, sourceSpecialStrings, slopeStrings; + + protected slots: + void modeSelected(int index); + void slopeSelected(int index); + void sourceSelected(int index); + + signals: + void modeChanged(Dso::TriggerMode); + void sourceChanged(bool special, unsigned int id); + void slopeChanged(Dso::Slope); +}; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class VoltageDock dockwindows.h +/// \brief Dock window for the voltage channel settings. +/// It contains the settings for gain and coupling for both channels and +/// allows to enable/disable the channels. +class VoltageDock : public QDockWidget { + Q_OBJECT + + public: + VoltageDock(DsoSettings *settings, QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~VoltageDock(); + + int setCoupling(int channel, Dso::Coupling coupling); + int setGain(int channel, double gain); + int setMode(Dso::MathMode mode); + int setUsed(int channel, bool used); + + protected: + void closeEvent(QCloseEvent *event); + + QGridLayout *dockLayout; + QWidget *dockWidget; + QList usedCheckBox; + QList gainComboBox, miscComboBox; + + DsoSettings *settings; + + QStringList couplingStrings; + QStringList modeStrings; + QList gainSteps; + QStringList gainStrings; + + protected slots: + void gainSelected(int index); + void miscSelected(int index); + void usedSwitched(bool checked); + + signals: + void couplingChanged(unsigned int channel, Dso::Coupling coupling); + void gainChanged(unsigned int channel, double gain); + void modeChanged(Dso::MathMode mode); + void usedChanged(unsigned int channel, bool used); +}; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class SpectrumDock dockwindows.h +/// \brief Dock window for the spectrum view. +/// It contains the magnitude for all channels and allows to enable/disable the +/// channels. +class SpectrumDock : public QDockWidget { + Q_OBJECT + + public: + SpectrumDock(DsoSettings *settings, QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~SpectrumDock(); + + int setMagnitude(int channel, double magnitude); + int setUsed(int channel, bool used); + + protected: + void closeEvent(QCloseEvent *event); + + QGridLayout *dockLayout; + QWidget *dockWidget; + QList usedCheckBox; + QList magnitudeComboBox; + + DsoSettings *settings; + + QList magnitudeSteps; + QStringList magnitudeStrings; + + public slots: + void magnitudeSelected(int index); + void usedSwitched(bool checked); + + signals: + void magnitudeChanged(unsigned int channel, double magnitude); + void usedChanged(unsigned int channel, bool used); +}; + + +#endif diff --git b/openhantek/src/dsocontrol.cpp a/openhantek/src/dsocontrol.cpp new file mode 100644 index 0000000..949fef7 --- /dev/null +++ a/openhantek/src/dsocontrol.cpp @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// dsocontrol.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include "dsocontrol.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class DsoControl +/// \brief Initialize variables. +DsoControl::DsoControl(QObject *parent) : QThread(parent) { + this->sampling = false; + this->terminate = false; +} + +/// \brief Start sampling process. +void DsoControl::startSampling() { + this->sampling = true; +} + +/// \brief Stop sampling process. +void DsoControl::stopSampling() { + this->sampling = false; +} + +/// \brief Get a list of the names of the special trigger sources. +const QStringList *DsoControl::getSpecialTriggerSources() { + return &(this->specialTriggerSources); +} + +/// \brief Try to connect to the oscilloscope. +void DsoControl::connectDevice() { + this->terminate = false; + this->start(); +} + +/// \brief Disconnect the oscilloscope. +void DsoControl::disconnectDevice() { + this->terminate = true; +} diff --git b/openhantek/src/dsocontrol.h a/openhantek/src/dsocontrol.h new file mode 100644 index 0000000..86c111a --- /dev/null +++ a/openhantek/src/dsocontrol.h @@ -0,0 +1,88 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file dsocontrol.h +/// \brief Declares the abstract Control class. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef DSOCONTROL_H +#define DSOCONTROL_H + + +#include +#include + + +#include "constants.h" +#include "helper.h" + + +class QMutex; + + +/// \class DsoControl +/// \brief A abstraction layer that enables protocol-independent dso usage. +class DsoControl : public QThread { + Q_OBJECT + + public: + DsoControl(QObject *parent = 0); + + virtual void startSampling(); + virtual void stopSampling(); + + virtual unsigned int getChannelCount() = 0; + + const QStringList *getSpecialTriggerSources(); + + protected: + bool sampling; ///< true, if the oscilloscope is taking samples + bool terminate; ///< true, if the thread should be terminated + + QStringList specialTriggerSources; ///< Names of the special trigger sources + + signals: + void deviceConnected(); + void deviceDisconnected(); + void statusMessage(const QString &message, int timeout); + void samplesAvailable(const QList *data, const QList *size, double samplerate, QMutex *mutex); + + public slots: + virtual void connectDevice(); + virtual void disconnectDevice(); + + virtual unsigned long int setSamplerate(unsigned long int samplerate) = 0; + virtual double setBufferSize(unsigned int size) = 0; + + virtual int setTriggerMode(Dso::TriggerMode mode) = 0; + virtual int setTriggerSource(bool special, unsigned int id) = 0; + virtual double setTriggerLevel(unsigned int channel, double level) = 0; + virtual int setTriggerSlope(Dso::Slope slope) = 0; + virtual double setTriggerPosition(double position) = 0; + + virtual int setChannelUsed(unsigned int channel, bool used) = 0; + virtual int setCoupling(unsigned int channel, Dso::Coupling coupling) = 0; + virtual double setGain(unsigned int channel, double gain) = 0; + virtual double setOffset(unsigned int channel, double offset) = 0; +}; + + +#endif diff --git b/openhantek/src/dsowidget.cpp a/openhantek/src/dsowidget.cpp new file mode 100644 index 0000000..5f66a6e --- /dev/null +++ a/openhantek/src/dsowidget.cpp @@ -0,0 +1,553 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// dsowidget.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include + +#include +#include + + +#include "dsowidget.h" + +#include "dataanalyzer.h" +#include "exporter.h" +#include "glscope.h" +#include "helper.h" +#include "levelslider.h" +#include "settings.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class DsoWidget +/// \brief Initializes the components of the oszilloscope-screen. +/// \param settings The settings object containing the oscilloscope settings. +/// \param parent The parent widget. +/// \param flags Flags for the window manager. +DsoWidget::DsoWidget(DsoSettings *settings, DataAnalyzer *dataAnalyzer, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags) { + this->settings = settings; + this->dataAnalyzer = dataAnalyzer; + + // Palette for this widget + QPalette palette; + palette.setColor(QPalette::Background, this->settings->view.color.screen.background); + palette.setColor(QPalette::WindowText, this->settings->view.color.screen.text); + + // The OpenGL accelerated scope widgets + this->generator = new GlGenerator(this->settings, this); + this->generator->setDataAnalyzer(this->dataAnalyzer); + this->mainScope = new GlScope(this->settings); + this->mainScope->setGenerator(this->generator); + this->zoomScope = new GlScope(this->settings); + this->zoomScope->setGenerator(this->generator); + this->zoomScope->setZoomMode(true); + + // The offset sliders for all possible channels + this->offsetSlider = new LevelSlider(Qt::RightArrow); + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + this->offsetSlider->addSlider(this->settings->scope.voltage[channel].name, channel); + this->offsetSlider->setColor(channel, this->settings->view.color.screen.voltage[channel]); + this->offsetSlider->setLimits(channel, -DIVS_VOLTAGE / 2, DIVS_VOLTAGE / 2); + this->offsetSlider->setStep(channel, 0.2); + this->offsetSlider->setValue(channel, this->settings->scope.voltage[channel].offset); + this->offsetSlider->setVisible(channel, this->settings->scope.voltage[channel].used); + } + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + this->offsetSlider->addSlider(this->settings->scope.spectrum[channel].name, this->settings->scope.voltage.count() + channel); + this->offsetSlider->setColor(this->settings->scope.voltage.count() + channel, this->settings->view.color.screen.spectrum[channel]); + this->offsetSlider->setLimits(this->settings->scope.voltage.count() + channel, -DIVS_VOLTAGE / 2, DIVS_VOLTAGE / 2); + this->offsetSlider->setStep(this->settings->scope.voltage.count() + channel, 0.2); + this->offsetSlider->setValue(this->settings->scope.voltage.count() + channel, this->settings->scope.spectrum[channel].offset); + this->offsetSlider->setVisible(this->settings->scope.voltage.count() + channel, this->settings->scope.spectrum[channel].used); + } + + // The triggerPosition slider + this->triggerPositionSlider = new LevelSlider(Qt::DownArrow); + this->triggerPositionSlider->addSlider(); + this->triggerPositionSlider->setLimits(0, 0.0, 1.0); + this->triggerPositionSlider->setStep(0, 0.2 / DIVS_TIME); + this->triggerPositionSlider->setValue(0, this->settings->scope.trigger.position); + this->triggerPositionSlider->setVisible(0, true); + + // The sliders for the trigger levels + this->triggerLevelSlider = new LevelSlider(Qt::LeftArrow); + for(int channel = 0; channel < (int) this->settings->scope.physicalChannels; channel++) { + this->triggerLevelSlider->addSlider(channel); + this->triggerLevelSlider->setColor(channel, (!this->settings->scope.trigger.special && channel == (int) this->settings->scope.trigger.source) ? this->settings->view.color.screen.voltage[channel] : this->settings->view.color.screen.voltage[channel].darker()); + this->adaptTriggerLevelSlider(channel); + this->triggerLevelSlider->setValue(channel, this->settings->scope.voltage[channel].trigger); + this->triggerLevelSlider->setVisible(channel, this->settings->scope.voltage[channel].used); + } + + // The marker slider + this->markerSlider = new LevelSlider(Qt::UpArrow); + for(int marker = 0; marker < MARKER_COUNT; marker++) { + this->markerSlider->addSlider(QString::number(marker + 1), marker); + this->markerSlider->setLimits(marker, -DIVS_TIME / 2, DIVS_TIME / 2); + this->markerSlider->setStep(marker, 0.2); + this->markerSlider->setValue(marker, this->settings->scope.horizontal.marker[marker]); + this->markerSlider->setVisible(marker, true); + } + + // The table for the settings + this->settingsTriggerLabel = new QLabel(); + this->settingsTriggerLabel->setMinimumWidth(160); + this->settingsBufferLabel = new QLabel(); + this->settingsBufferLabel->setAlignment(Qt::AlignRight); + this->settingsBufferLabel->setPalette(palette); + this->settingsRateLabel = new QLabel(); + this->settingsRateLabel->setAlignment(Qt::AlignRight); + this->settingsRateLabel->setPalette(palette); + this->settingsTimebaseLabel = new QLabel(); + this->settingsTimebaseLabel->setAlignment(Qt::AlignRight); + this->settingsTimebaseLabel->setPalette(palette); + this->settingsFrequencybaseLabel = new QLabel(); + this->settingsFrequencybaseLabel->setAlignment(Qt::AlignRight); + this->settingsFrequencybaseLabel->setPalette(palette); + this->settingsLayout = new QHBoxLayout(); + this->settingsLayout->addWidget(this->settingsTriggerLabel); + this->settingsLayout->addWidget(this->settingsBufferLabel, 1); + this->settingsLayout->addWidget(this->settingsRateLabel, 1); + this->settingsLayout->addWidget(this->settingsTimebaseLabel, 1); + this->settingsLayout->addWidget(this->settingsFrequencybaseLabel, 1); + + // The table for the marker details + this->markerInfoLabel = new QLabel(); + this->markerInfoLabel->setMinimumWidth(160); + this->markerInfoLabel->setPalette(palette); + this->markerTimeLabel = new QLabel(); + this->markerTimeLabel->setAlignment(Qt::AlignRight); + this->markerTimeLabel->setPalette(palette); + this->markerFrequencyLabel = new QLabel(); + this->markerFrequencyLabel->setAlignment(Qt::AlignRight); + this->markerFrequencyLabel->setPalette(palette); + this->markerTimebaseLabel = new QLabel(); + this->markerTimebaseLabel->setAlignment(Qt::AlignRight); + this->markerTimebaseLabel->setPalette(palette); + this->markerFrequencybaseLabel = new QLabel(); + this->markerFrequencybaseLabel->setAlignment(Qt::AlignRight); + this->markerFrequencybaseLabel->setPalette(palette); + this->markerLayout = new QHBoxLayout(); + this->markerLayout->addWidget(this->markerInfoLabel); + this->markerLayout->addWidget(this->markerTimeLabel, 1); + this->markerLayout->addWidget(this->markerFrequencyLabel, 1); + this->markerLayout->addWidget(this->markerTimebaseLabel, 1); + this->markerLayout->addWidget(this->markerFrequencybaseLabel, 1); + + // The table for the measurements + QPalette tablePalette = palette; + this->measurementLayout = new QGridLayout(); + this->measurementLayout->setColumnMinimumWidth(0, 64); + this->measurementLayout->setColumnMinimumWidth(1, 32); + this->measurementLayout->setColumnStretch(2, 2); + this->measurementLayout->setColumnStretch(3, 2); + this->measurementLayout->setColumnStretch(4, 3); + this->measurementLayout->setColumnStretch(5, 3); + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + tablePalette.setColor(QPalette::WindowText, this->settings->view.color.screen.voltage[channel]); + this->measurementNameLabel.append(new QLabel(this->settings->scope.voltage[channel].name)); + this->measurementNameLabel[channel]->setPalette(tablePalette); + this->measurementCouplingLabel.append(new QLabel()); + this->measurementCouplingLabel[channel]->setPalette(tablePalette); + this->measurementGainLabel.append(new QLabel()); + this->measurementGainLabel[channel]->setAlignment(Qt::AlignRight); + this->measurementGainLabel[channel]->setPalette(tablePalette); + tablePalette.setColor(QPalette::WindowText, this->settings->view.color.screen.spectrum[channel]); + this->measurementMagnitudeLabel.append(new QLabel()); + this->measurementMagnitudeLabel[channel]->setAlignment(Qt::AlignRight); + this->measurementMagnitudeLabel[channel]->setPalette(tablePalette); + this->measurementAmplitudeLabel.append(new QLabel()); + this->measurementAmplitudeLabel[channel]->setAlignment(Qt::AlignRight); + this->measurementAmplitudeLabel[channel]->setPalette(palette); + this->measurementFrequencyLabel.append(new QLabel()); + this->measurementFrequencyLabel[channel]->setAlignment(Qt::AlignRight); + this->measurementFrequencyLabel[channel]->setPalette(palette); + this->setMeasurementVisible(channel, this->settings->scope.voltage[channel].used); + this->measurementLayout->addWidget(this->measurementNameLabel[channel], channel, 0); + this->measurementLayout->addWidget(this->measurementCouplingLabel[channel], channel, 1); + this->measurementLayout->addWidget(this->measurementGainLabel[channel], channel, 2); + this->measurementLayout->addWidget(this->measurementMagnitudeLabel[channel], channel, 3); + this->measurementLayout->addWidget(this->measurementAmplitudeLabel[channel], channel, 4); + this->measurementLayout->addWidget(this->measurementFrequencyLabel[channel], channel, 5); + this->updateVoltageCoupling(channel); + this->updateVoltageDetails(channel); + this->updateSpectrumDetails(channel); + } + + // The layout for the widgets + this->mainLayout = new QGridLayout(); + this->mainLayout->setColumnStretch(2, 1); // Scopes increase their size + this->mainLayout->setRowStretch(3, 1); + // Bars around the scope, needed because the slider-drawing-area is outside + // the scope at min/max + this->mainLayout->setColumnMinimumWidth(1, this->triggerPositionSlider->preMargin()); + this->mainLayout->setColumnMinimumWidth(3, this->triggerPositionSlider->postMargin()); + this->mainLayout->setRowMinimumHeight(2, this->offsetSlider->preMargin()); + this->mainLayout->setRowMinimumHeight(4, this->offsetSlider->postMargin()); + this->mainLayout->setRowMinimumHeight(6, 4); + this->mainLayout->setRowMinimumHeight(8, 4); + this->mainLayout->setRowMinimumHeight(10, 8); + this->mainLayout->setSpacing(0); + this->mainLayout->addLayout(this->settingsLayout, 0, 0, 1, 5); + this->mainLayout->addWidget(this->mainScope, 3, 2); + this->mainLayout->addWidget(this->offsetSlider, 2, 0, 3, 2, Qt::AlignRight); + this->mainLayout->addWidget(this->triggerPositionSlider, 1, 1, 2, 3, Qt::AlignBottom); + this->mainLayout->addWidget(this->triggerLevelSlider, 2, 3, 3, 2, Qt::AlignLeft); + this->mainLayout->addWidget(this->markerSlider, 4, 1, 2, 3, Qt::AlignTop); + this->mainLayout->addLayout(this->markerLayout, 7, 0, 1, 5); + this->mainLayout->addWidget(this->zoomScope, 9, 2); + this->mainLayout->addLayout(this->measurementLayout, 11, 0, 1, 5); + + // Apply settings and update measured values + this->updateTriggerDetails(); + this->updateBufferSize(); + this->updateFrequencybase(); + this->updateTimebase(); + this->updateZoom(this->settings->view.zoom); + + // The widget itself + this->setPalette(palette); + this->setBackgroundRole(QPalette::Background); + this->setAutoFillBackground(true); + this->setLayout(this->mainLayout); + + // Connect change-signals of sliders + this->connect(this->offsetSlider, SIGNAL(valueChanged(int, double)), this, SLOT(updateOffset(int, double))); + this->connect(this->triggerPositionSlider, SIGNAL(valueChanged(int, double)), this, SLOT(updateTriggerPosition(int, double))); + this->connect(this->triggerLevelSlider, SIGNAL(valueChanged(int, double)), this, SLOT(updateTriggerLevel(int, double))); + this->connect(this->markerSlider, SIGNAL(valueChanged(int, double)), this, SLOT(updateMarker(int, double))); + this->connect(this->markerSlider, SIGNAL(valueChanged(int, double)), this->mainScope, SLOT(updateGL())); + this->connect(this->markerSlider, SIGNAL(valueChanged(int, double)), this->zoomScope, SLOT(updateGL())); + + // Connect other signals + this->connect(this->dataAnalyzer, SIGNAL(finished()), this, SLOT(dataAnalyzed())); +} + +/// \brief Stops the oscilloscope thread and the timer. +DsoWidget::~DsoWidget() { +} + +/// \brief Set the trigger level sliders minimum and maximum to the new values. +void DsoWidget::adaptTriggerLevelSlider(unsigned int channel) { + this->triggerLevelSlider->setLimits(channel, (-DIVS_VOLTAGE / 2 - this->settings->scope.voltage[channel].offset) * this->settings->scope.voltage[channel].gain, (DIVS_VOLTAGE / 2 - this->settings->scope.voltage[channel].offset) * this->settings->scope.voltage[channel].gain); + this->triggerLevelSlider->setStep(channel, this->settings->scope.voltage[channel].gain * 0.2); +} + +/// \brief Show/Hide a line of the measurement table. +void DsoWidget::setMeasurementVisible(unsigned int channel, bool visible) { + this->measurementNameLabel[channel]->setVisible(visible); + this->measurementCouplingLabel[channel]->setVisible(visible); + this->measurementGainLabel[channel]->setVisible(visible); + this->measurementMagnitudeLabel[channel]->setVisible(visible); + this->measurementAmplitudeLabel[channel]->setVisible(visible); + this->measurementFrequencyLabel[channel]->setVisible(visible); + if(!visible) { + this->measurementGainLabel[channel]->setText(QString()); + this->measurementMagnitudeLabel[channel]->setText(QString()); + this->measurementAmplitudeLabel[channel]->setText(QString()); + this->measurementFrequencyLabel[channel]->setText(QString()); + } +} + +/// \brief Update the label about the marker measurements +void DsoWidget::updateMarkerDetails() { + double divs = fabs(this->settings->scope.horizontal.marker[1] - this->settings->scope.horizontal.marker[0]); + double time = divs * this->settings->scope.horizontal.timebase; + + if(this->settings->view.zoom) { + this->markerInfoLabel->setText(tr("Zoom x%L1").arg(DIVS_TIME / divs, -1, 'g', 3)); + this->markerTimebaseLabel->setText(Helper::valueToString(time / DIVS_TIME, Helper::UNIT_SECONDS, 3) + tr("/div")); + this->markerFrequencybaseLabel->setText(Helper::valueToString(divs * this->settings->scope.horizontal.frequencybase / DIVS_TIME, Helper::UNIT_HERTZ, 3) + tr("/div")); + } + this->markerTimeLabel->setText(Helper::valueToString(time, Helper::UNIT_SECONDS, 4)); + this->markerFrequencyLabel->setText(Helper::valueToString(1.0 / time, Helper::UNIT_HERTZ, 4)); +} + +/// \brief Update the label about the trigger settings +void DsoWidget::updateSpectrumDetails(unsigned int channel) { + this->setMeasurementVisible(channel, this->settings->scope.voltage[channel].used || this->settings->scope.spectrum[channel].used); + + if(this->settings->scope.spectrum[channel].used) + this->measurementMagnitudeLabel[channel]->setText(Helper::valueToString(this->settings->scope.spectrum[channel].magnitude, Helper::UNIT_DECIBEL, 0) + tr("/div")); + else + this->measurementMagnitudeLabel[channel]->setText(QString()); +} + +/// \brief Update the label about the trigger settings +void DsoWidget::updateTriggerDetails() { + // Update the trigger details + QPalette tablePalette = this->palette(); + tablePalette.setColor(QPalette::WindowText, this->settings->view.color.screen.voltage[this->settings->scope.trigger.source]); + this->settingsTriggerLabel->setPalette(tablePalette); + QString slopeString; + if(this->settings->scope.trigger.slope == Dso::SLOPE_POSITIVE) + slopeString = QString::fromUtf8("\u2197"); + else + slopeString = QString::fromUtf8("\u2198"); + QString levelString; + levelString = Helper::valueToString(this->settings->scope.voltage[this->settings->scope.trigger.source].trigger, Helper::UNIT_VOLTS, 3); + QString pretriggerString = tr("%L1%").arg((int) (this->settings->scope.trigger.position * 100 + 0.5)); + this->settingsTriggerLabel->setText(tr("%1 %2 %3 %4").arg(this->settings->scope.voltage[this->settings->scope.trigger.source].name, slopeString, levelString, pretriggerString)); + + /// \todo This won't work for special trigger sources +} + +/// \brief Update the label about the trigger settings +void DsoWidget::updateVoltageDetails(unsigned int channel) { + if(channel >= (unsigned int) this->settings->scope.voltage.count()) + return; + + this->setMeasurementVisible(channel, this->settings->scope.voltage[channel].used || this->settings->scope.spectrum[channel].used); + + if(this->settings->scope.voltage[channel].used) + this->measurementGainLabel[channel]->setText(Helper::valueToString(this->settings->scope.voltage[channel].gain, Helper::UNIT_VOLTS, 0) + tr("/div")); + else + this->measurementGainLabel[channel]->setText(QString()); +} + +/// \brief Handles frequencybaseChanged signal from the horizontal dock. +void DsoWidget::updateFrequencybase() { + this->settingsFrequencybaseLabel->setText(Helper::valueToString(this->settings->scope.horizontal.frequencybase, Helper::UNIT_HERTZ, 0) + tr("/div")); +} + +/// \brief Updates the samplerate field after changing the samplerate. +void DsoWidget::updateSamplerate() { + this->settingsRateLabel->setText(Helper::valueToString(this->settings->scope.horizontal.samplerate, Helper::UNIT_SAMPLES) + tr("/s")); +} + +/// \brief Handles timebaseChanged signal from the horizontal dock. +void DsoWidget::updateTimebase() { + this->settingsTimebaseLabel->setText(Helper::valueToString(this->settings->scope.horizontal.timebase, Helper::UNIT_SECONDS, 0) + tr("/div")); + + this->updateMarkerDetails(); +} + +/// \brief Handles magnitudeChanged signal from the spectrum dock. +/// \param channel The channel whose magnitude was changed. +void DsoWidget::updateSpectrumMagnitude(unsigned int channel) { + this->updateSpectrumDetails(channel); +} + +/// \brief Handles usedChanged signal from the spectrum dock. +/// \param channel The channel whose used-state was changed. +/// \param used The new used-state for the channel. +void DsoWidget::updateSpectrumUsed(unsigned int channel, bool used) { + if(channel >= (unsigned int) this->settings->scope.voltage.count()) + return; + + this->offsetSlider->setVisible(this->settings->scope.voltage.count() + channel, used); + + this->updateSpectrumDetails(channel); +} + +/// \brief Handles modeChanged signal from the trigger dock. +void DsoWidget::updateTriggerMode() { + this->updateTriggerDetails(); +} + +/// \brief Handles slopeChanged signal from the trigger dock. +void DsoWidget::updateTriggerSlope() { + this->updateTriggerDetails(); +} + +/// \brief Handles sourceChanged signal from the trigger dock. +/// \param special true for a special channel (EXT, ...) as trigger source. +/// \param id The number of the channel, that should be used as trigger. +void DsoWidget::updateTriggerSource() { + // Change the colors of the trigger sliders + if(this->settings->scope.trigger.special || this->settings->scope.trigger.source >= this->settings->scope.physicalChannels) + this->triggerPositionSlider->setColor(0, this->settings->view.color.screen.border); + else + this->triggerPositionSlider->setColor(0, this->settings->view.color.screen.voltage[this->settings->scope.trigger.source]); + + for(int channel = 0; channel < (int) this->settings->scope.physicalChannels; channel++) + this->triggerLevelSlider->setColor(channel, (!this->settings->scope.trigger.special && channel == (int) this->settings->scope.trigger.source) ? this->settings->view.color.screen.voltage[channel] : this->settings->view.color.screen.voltage[channel].darker()); + + this->updateTriggerDetails(); +} + +/// \brief Handles couplingChanged signal from the vertical dock. +/// \param channel The channel whose coupling was changed. +void DsoWidget::updateVoltageCoupling(unsigned int channel) { + if(channel >= (unsigned int) this->settings->scope.voltage.count()) + return; + + if(this->settings->scope.voltage[channel].used || this->settings->scope.spectrum[channel].used) { + switch(this->settings->scope.voltage[channel].misc) { + case Dso::COUPLING_AC: + this->measurementCouplingLabel[channel]->setText(tr("AC")); + break; + case Dso::COUPLING_DC: + this->measurementCouplingLabel[channel]->setText(tr("DC")); + break; + case Dso::COUPLING_GND: + this->measurementCouplingLabel[channel]->setText(tr("GND")); + break; + } + } +} + +/// \brief Handles gainChanged signal from the vertical dock. +/// \param channel The channel whose gain was changed. +void DsoWidget::updateVoltageGain(unsigned int channel) { + if(channel >= (unsigned int) this->settings->scope.voltage.count()) + return; + + if(channel < this->settings->scope.physicalChannels) + this->adaptTriggerLevelSlider(channel); + + this->updateVoltageDetails(channel); +} + +/// \brief Handles usedChanged signal from the vertical dock. +/// \param channel The channel whose used-state was changed. +/// \param used The new used-state for the channel. +void DsoWidget::updateVoltageUsed(unsigned int channel, bool used) { + if(channel >= (unsigned int) this->settings->scope.voltage.count()) + return; + + this->offsetSlider->setVisible(channel, used); + this->triggerLevelSlider->setVisible(channel, used); + this->setMeasurementVisible(channel, this->settings->scope.voltage[channel].used); + + this->updateVoltageDetails(channel); +} + +/// \brief Change the buffer size. +/// \param size The size of the buffer. +void DsoWidget::updateBufferSize() { + this->settingsBufferLabel->setText(tr("%1 S").arg(this->settings->scope.horizontal.samples)); +} + +/// \brief Export the oscilloscope screen to a file. +/// \return true if the document was exported successfully. +bool DsoWidget::exportAs() { + QStringList filters; + filters + << tr("Portable Document Format (*.pdf)") + << tr("PostScript (*.ps)") + << tr("Image (*.png *.xpm *.jpg)") + << tr("Comma-Separated Values (*.csv)"); + + QFileDialog fileDialog((QWidget *) this->parent(), "Export file...", QString(), filters.join(";;")); + fileDialog.setFileMode(QFileDialog::AnyFile); + if(fileDialog.exec() != QDialog::Accepted) + return false; + + Exporter exporter(this->settings, this->dataAnalyzer, (QWidget *) this->parent()); + exporter.setFilename(fileDialog.selectedFiles().first()); + exporter.setFormat((ExportFormat) (EXPORT_FORMAT_PDF + filters.indexOf(fileDialog.selectedFilter()))); + + return exporter.doExport(); +} + +/// \brief Print the oscilloscope screen. +/// \return true if the document was sent to the printer successfully. +bool DsoWidget::print() { + Exporter exporter(this->settings, this->dataAnalyzer, (QWidget *) this->parent()); + exporter.setFormat(EXPORT_FORMAT_PRINTER); + + return exporter.doExport(); +} + +/// \brief Stop the oscilloscope. +void DsoWidget::updateZoom(bool enabled) { + this->mainLayout->setRowStretch(9, enabled ? 1 : 0); + this->zoomScope->setVisible(enabled); + + // Show time-/frequencybase and zoom factor if the magnified scope is shown + this->markerLayout->setStretch(3, enabled ? 1 : 0); + this->markerTimebaseLabel->setVisible(enabled); + this->markerLayout->setStretch(4, enabled ? 1 : 0); + this->markerFrequencybaseLabel->setVisible(enabled); + if(enabled) + this->updateMarkerDetails(); + else + this->markerInfoLabel->setText(tr("Marker 1/2")); + + this->repaint(); +} + +/// \brief Prints analyzed data. +void DsoWidget::dataAnalyzed() { + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + if(this->settings->scope.voltage[channel].used) { + // Amplitude string representation (4 significant digits) + this->measurementAmplitudeLabel[channel]->setText(Helper::valueToString(this->dataAnalyzer->data(channel)->amplitude, Helper::UNIT_VOLTS, 4)); + // Frequency string representation (5 significant digits) + this->measurementFrequencyLabel[channel]->setText(Helper::valueToString(this->dataAnalyzer->data(channel)->frequency, Helper::UNIT_HERTZ, 5)); + } + } +} + +/// \brief Handles valueChanged signal from the offset sliders. +/// \param channel The channel whose offset was changed. +/// \param value The new offset for the channel. +void DsoWidget::updateOffset(int channel, double value) { + if(channel < this->settings->scope.voltage.count()) { + this->settings->scope.voltage[channel].offset = value; + + if(channel < (int) this->settings->scope.physicalChannels) + this->adaptTriggerLevelSlider(channel); + } + else if(channel < this->settings->scope.voltage.count() * 2) + this->settings->scope.spectrum[channel - this->settings->scope.voltage.count()].offset = value; + + emit offsetChanged(channel, value); +} + +/// \brief Handles valueChanged signal from the triggerPosition slider. +/// \param index The index of the slider. +/// \param value The new triggerPosition in seconds relative to the first sample. +void DsoWidget::updateTriggerPosition(int index, double value) { + if(index != 0) + return; + + this->settings->scope.trigger.position = value; + + this->updateTriggerDetails(); + + emit triggerPositionChanged(value * this->settings->scope.horizontal.timebase * DIVS_TIME); +} + +/// \brief Handles valueChanged signal from the trigger level slider. +/// \param index The index of the slider. +/// \param value The new trigger level. +void DsoWidget::updateTriggerLevel(int channel, double value) { + this->settings->scope.voltage[channel].trigger = value; + + this->updateTriggerDetails(); + + emit triggerLevelChanged(channel, value); +} + +/// \brief Handles valueChanged signal from the marker slider. +/// \param marker The index of the slider. +/// \param value The new marker position. +void DsoWidget::updateMarker(int marker, double value) { + this->settings->scope.horizontal.marker[marker] = value; + + this->updateMarkerDetails(); + + emit markerChanged(marker, value); +} diff --git b/openhantek/src/dsowidget.h a/openhantek/src/dsowidget.h new file mode 100644 index 0000000..316dc17 --- /dev/null +++ a/openhantek/src/dsowidget.h @@ -0,0 +1,138 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file dsowidget.h +/// \brief Declares the DsoWidget class. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef DSOWIDGET_H +#define DSOWIDGET_H + + +#include + +#include "dockwindows.h" +#include "glscope.h" +#include "levelslider.h" + + +class DataAnalyzer; +class DsoSettings; +class QGridLayout; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class DsoWidget dsowidget.h +/// \brief The widget for the oszilloscope-screen +/// This widget contains the scopes and all level sliders. +class DsoWidget : public QWidget { + Q_OBJECT + + public: + DsoWidget(DsoSettings *settings, DataAnalyzer *dataAnalyzer, QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~DsoWidget(); + + protected: + void adaptTriggerLevelSlider(unsigned int channel); + void setMeasurementVisible(unsigned int channel, bool visible); + void updateMarkerDetails(); + void updateSpectrumDetails(unsigned int channel); + void updateTriggerDetails(); + void updateVoltageDetails(unsigned int channel); + + QGridLayout *mainLayout; + GlGenerator *generator; + GlScope *mainScope, *zoomScope; + LevelSlider *offsetSlider; + LevelSlider *triggerPositionSlider, *triggerLevelSlider; + LevelSlider *markerSlider; + + QHBoxLayout *settingsLayout; + QLabel *settingsTriggerLabel; + QLabel *settingsBufferLabel, *settingsRateLabel; + QLabel *settingsTimebaseLabel, *settingsFrequencybaseLabel; + + QHBoxLayout *markerLayout; + QLabel *markerInfoLabel; + QLabel *markerTimeLabel, *markerFrequencyLabel; + QLabel *markerTimebaseLabel, *markerFrequencybaseLabel; + + QGridLayout *measurementLayout; + QList measurementNameLabel; + QList measurementGainLabel, measurementMagnitudeLabel; + QList measurementCouplingLabel; + QList measurementAmplitudeLabel, measurementFrequencyLabel; + + DsoSettings *settings; + + DataAnalyzer *dataAnalyzer; + + public slots: + // Horizontal axis + //void horizontalFormatChanged(HorizontalFormat format); + void updateFrequencybase(); + void updateSamplerate(); + void updateTimebase(); + + // Trigger + void updateTriggerMode(); + void updateTriggerSlope(); + void updateTriggerSource(); + + // Spectrum + void updateSpectrumMagnitude(unsigned int channel); + void updateSpectrumUsed(unsigned int channel, bool used); + + // Vertical axis + void updateVoltageCoupling(unsigned int channel); + void updateVoltageGain(unsigned int channel); + void updateVoltageUsed(unsigned int channel, bool used); + + // Menus + void updateBufferSize(); + + // Export + bool exportAs(); + bool print(); + + // Scope control + void updateZoom(bool enabled); + + // Data analyzer + void dataAnalyzed(); + + protected slots: + // Sliders + void updateOffset(int channel, double value); + void updateTriggerPosition(int index, double value); + void updateTriggerLevel(int channel, double value); + void updateMarker(int marker, double value); + + signals: + // Sliders + void offsetChanged(unsigned int channel, double value); + void triggerPositionChanged(double value); + void triggerLevelChanged(unsigned int channel, double value); + void markerChanged(unsigned int marker, double value); +}; + + +#endif diff --git b/openhantek/src/exporter.cpp a/openhantek/src/exporter.cpp new file mode 100644 index 0000000..a9622fa --- /dev/null +++ a/openhantek/src/exporter.cpp @@ -0,0 +1,269 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// exporter.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include +#include + + +#include "exporter.h" + +#include "dataanalyzer.h" +#include "glscope.h" +#include "helper.h" +#include "settings.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class HorizontalDock +/// \brief Initializes the printer object. +Exporter::Exporter(DsoSettings *settings, DataAnalyzer *dataAnalyzer, QWidget *parent) : QObject(parent) { + this->settings = settings; + this->dataAnalyzer = dataAnalyzer; + + this->format = EXPORT_FORMAT_PRINTER; + this->size = QSize(640, 480); +} + +/// \brief Cleans up everything. +Exporter::~Exporter() { +} + +/// \brief Set the filename of the output file (Not used for printing). +void Exporter::setFilename(QString filename) { + if(!filename.isEmpty()) + this->filename = filename; +} + +/// \brief Set the output format. +void Exporter::setFormat(ExportFormat format) { + if(format >= EXPORT_FORMAT_PRINTER && format <= EXPORT_FORMAT_IMAGE) + this->format = format; +} + +/// \brief Set the size for the output image. +void Exporter::setSize(QSize size) { + if(size.isValid()) + this->size = size; +} + +/// \brief Print the document (May be a file too) +bool Exporter::doExport() { + // Choose the color values we need + DsoSettingsColorValues *colorValues; + if(this->format == EXPORT_FORMAT_IMAGE && this->settings->view.screenColorImages) + colorValues = &(this->settings->view.color.screen); + else + colorValues = &(this->settings->view.color.print); + + QPaintDevice *paintDevice; + + if(this->format < EXPORT_FORMAT_IMAGE) { + // We need a QPrinter for printing, pdf- and ps-export + paintDevice = new QPrinter(QPrinter::HighResolution); + ((QPrinter *) paintDevice)->setOrientation(QPrinter::Landscape); + ((QPrinter *) paintDevice)->setPageMargins(20, 20, 20, 20, QPrinter::Millimeter); + + if(this->format == EXPORT_FORMAT_PRINTER) { + // Show the printing dialog + QPrintDialog *dialog = new QPrintDialog((QPrinter *) paintDevice, (QWidget *) this->parent()); + dialog->setWindowTitle(tr("Print oscillograph")); + if(dialog->exec() != QDialog::Accepted) + return false; + } + else { + // Configure the QPrinter + ((QPrinter *) paintDevice)->setOutputFileName(this->filename); + ((QPrinter *) paintDevice)->setOutputFormat((this->format == EXPORT_FORMAT_PDF) ? QPrinter::PdfFormat : QPrinter::PostScriptFormat); + } + } + else { + // We need a QPixmap for image-export + paintDevice = new QPixmap(this->size); + ((QPixmap *) paintDevice)->fill(colorValues->background); + } + + // Create a painter for our device + QPainter painter(paintDevice); + + // Get line height + QFont font; + QFontMetrics fontMetrics(font, paintDevice); + double lineHeight = fontMetrics.height(); + double valueColumnWidth = (double) (paintDevice->width() - lineHeight * 4) / 2; + + painter.setBrush(Qt::SolidPattern); + + int channelCount = 0; + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + if(this->settings->scope.voltage[channel].used) { + channelCount++; + double top = (double) paintDevice->height() - channelCount * lineHeight; + + painter.setPen(colorValues->voltage[channel]); + + // Print label + painter.drawText(QRectF(0, top, lineHeight * 4, lineHeight), this->settings->scope.voltage[channel].name); + + // Amplitude string representation (4 significant digits) + painter.drawText(QRectF(lineHeight * 4, top, valueColumnWidth, lineHeight), Helper::valueToString(this->dataAnalyzer->data(channel)->amplitude, Helper::UNIT_VOLTS, 4), QTextOption(Qt::AlignRight)); + // Frequency string representation (5 significant digits) + painter.drawText(QRectF(lineHeight * 4 + valueColumnWidth, top, valueColumnWidth, lineHeight), Helper::valueToString(this->dataAnalyzer->data(channel)->frequency, Helper::UNIT_HERTZ, 5), QTextOption(Qt::AlignRight)); + } + } + + // Set DIVS_TIME x DIVS_VOLTAGE matrix for oscillograph + double screenHeight = (double) paintDevice->height() - (channelCount + 1) * lineHeight; + painter.setMatrix(QMatrix((paintDevice->width() - 1) / DIVS_TIME, 0, 0, -(screenHeight - 1) / DIVS_VOLTAGE, (double) (paintDevice->width() - 1) / 2, (screenHeight - 1) / 2), false); + + + // Draw the graphs + painter.setRenderHint(QPainter::Antialiasing); + painter.setBrush(Qt::NoBrush); + + this->dataAnalyzer->mutex()->lock(); + + switch(this->settings->scope.horizontal.format) { + case Dso::GRAPHFORMAT_TY: + // Add graphs for channels + for(int channel = 0 ; channel < this->settings->scope.voltage.count(); channel++) { + if(this->settings->scope.voltage[channel].used) { + painter.setPen(colorValues->voltage[channel]); + + // What's the horizontal distance between sampling points? + double horizontalFactor = this->dataAnalyzer->data(channel)->samples.voltage.interval / this->settings->scope.horizontal.timebase; + // How many samples are visible? + unsigned int lastPosition = DIVS_TIME / horizontalFactor; + if(lastPosition >= this->dataAnalyzer->data(channel)->samples.voltage.count) + lastPosition = this->dataAnalyzer->data(channel)->samples.voltage.count - 1; + + // Draw graph + QPointF *graph = new QPointF[lastPosition + 1]; + for(unsigned int position = 0; position <= lastPosition; position++) + graph[position] = QPointF(position * horizontalFactor - DIVS_TIME / 2, this->dataAnalyzer->data(channel)->samples.voltage.sample[position] / this->settings->scope.voltage[channel].gain + this->settings->scope.voltage[channel].offset); + painter.drawPolyline(graph, lastPosition + 1); + } + } + + // Add spectrum graphs + for (int channel = 0; channel < this->settings->scope.spectrum.count(); channel++) { + if(this->settings->scope.spectrum[channel].used) { + painter.setPen(colorValues->spectrum[channel]); + + // What's the horizontal distance between sampling points? + double horizontalFactor = this->dataAnalyzer->data(channel)->samples.spectrum.interval / this->settings->scope.horizontal.frequencybase; + // How many samples are visible? + unsigned int lastPosition = DIVS_TIME / horizontalFactor; + if(lastPosition >= this->dataAnalyzer->data(channel)->samples.spectrum.count) + lastPosition = this->dataAnalyzer->data(channel)->samples.spectrum.count - 1; + + // Draw graph + QPointF *graph = new QPointF[lastPosition + 1]; + for(unsigned int position = 0; position <= lastPosition; position++) + graph[position] = QPointF(position * horizontalFactor - DIVS_TIME / 2, this->dataAnalyzer->data(channel)->samples.spectrum.sample[position] / this->settings->scope.spectrum[channel].magnitude + this->settings->scope.spectrum[channel].offset); + painter.drawPolyline(graph, lastPosition + 1); + } + } + break; + + case Dso::GRAPHFORMAT_XY: + break; + } + + this->dataAnalyzer->mutex()->unlock(); + + // Draw grid + painter.setRenderHint(QPainter::Antialiasing, false); + // Grid lines + painter.setPen(colorValues->grid); + + if(this->format < EXPORT_FORMAT_IMAGE) { + // Draw vertical lines + for(int div = 1; div < DIVS_TIME / 2; div++) { + for(int dot = 1; dot < DIVS_VOLTAGE / 2 * 5; dot++) { + painter.drawLine(QPointF((double) -div - 0.02, (double) -dot / 5), QPointF((double) -div + 0.02, (double) -dot / 5)); + painter.drawLine(QPointF((double) -div - 0.02, (double) dot / 5), QPointF((double) -div + 0.02, (double) dot / 5)); + painter.drawLine(QPointF((double) div - 0.02, (double) -dot / 5), QPointF((double) div + 0.02, (double) -dot / 5)); + painter.drawLine(QPointF((double) div - 0.02, (double) dot / 5), QPointF((double) div + 0.02, (double) dot / 5)); + } + } + // Draw horizontal lines + for(int div = 1; div < DIVS_VOLTAGE / 2; div++) { + for(int dot = 1; dot < DIVS_TIME / 2 * 5; dot++) { + painter.drawLine(QPointF((double) -dot / 5, (double) -div - 0.02), QPointF((double) -dot / 5, (double) -div + 0.02)); + painter.drawLine(QPointF((double) dot / 5, (double) -div - 0.02), QPointF((double) dot / 5, (double) -div + 0.02)); + painter.drawLine(QPointF((double) -dot / 5, (double) div - 0.02), QPointF((double) -dot / 5, (double) div + 0.02)); + painter.drawLine(QPointF((double) dot / 5, (double) div - 0.02), QPointF((double) dot / 5, (double) div + 0.02)); + } + } + } + else { + // Draw vertical lines + for(int div = 1; div < DIVS_TIME / 2; div++) { + for(int dot = 1; dot < DIVS_VOLTAGE / 2 * 5; dot++) { + painter.drawPoint(QPointF(-div, (double) -dot / 5)); + painter.drawPoint(QPointF(-div, (double) dot / 5)); + painter.drawPoint(QPointF(div, (double) -dot / 5)); + painter.drawPoint(QPointF(div, (double) dot / 5)); + } + } + // Draw horizontal lines + for(int div = 1; div < DIVS_VOLTAGE / 2; div++) { + for(int dot = 1; dot < DIVS_TIME / 2 * 5; dot++) { + if(dot % 5 == 0) + continue; // Already done by vertical lines + painter.drawPoint(QPointF((double) -dot / 5, -div)); + painter.drawPoint(QPointF((double) dot / 5, -div)); + painter.drawPoint(QPointF((double) -dot / 5, div)); + painter.drawPoint(QPointF((double) dot / 5, div)); + } + } + } + + // Axes + painter.setPen(colorValues->axes); + painter.drawLine(QPointF(-DIVS_TIME / 2, 0), QPointF(DIVS_TIME / 2, 0)); + painter.drawLine(QPointF(0, -DIVS_VOLTAGE / 2), QPointF(0, DIVS_VOLTAGE / 2)); + for(double div = 0.2; div <= DIVS_TIME / 2; div += 0.2) { + painter.drawLine(QPointF(div, -0.05), QPointF(div, 0.05)); + painter.drawLine(QPointF(-div, -0.05), QPointF(-div, 0.05)); + } + for(double div = 0.2; div <= DIVS_VOLTAGE / 2; div += 0.2) { + painter.drawLine(QPointF(-0.05, div), QPointF(0.05, div)); + painter.drawLine(QPointF(-0.05, -div), QPointF(0.05, -div)); + } + + // Borders + painter.setPen(colorValues->border); + painter.drawRect(QRectF(-DIVS_TIME / 2, -DIVS_VOLTAGE / 2, DIVS_TIME, DIVS_VOLTAGE)); + + painter.end(); + + if(this->format == EXPORT_FORMAT_IMAGE) + ((QPixmap *) paintDevice)->save(this->filename); + + return true; +} diff --git b/openhantek/src/exporter.h a/openhantek/src/exporter.h new file mode 100644 index 0000000..3e23004 --- /dev/null +++ a/openhantek/src/exporter.h @@ -0,0 +1,73 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file exporter.h +/// \brief Declares the Exporter class. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef EXPORTER_H +#define EXPORTER_H + + +#include +#include + + +class DsoSettings; +class DataAnalyzer; + + +//////////////////////////////////////////////////////////////////////////////// +/// \enum ExportFormat exporter.h +/// \brief Possible file formats for the export. +enum ExportFormat { + EXPORT_FORMAT_PRINTER, + EXPORT_FORMAT_PDF, EXPORT_FORMAT_PS, + EXPORT_FORMAT_IMAGE +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \class Exporter exporter.h +/// \brief Exports the oscilloscope screen to a file or prints it. +class Exporter : public QObject { + Q_OBJECT + + public: + Exporter(DsoSettings *settings, DataAnalyzer *dataAnalyzer, QWidget *parent = 0); + ~Exporter(); + + void setFilename(QString filename); + void setFormat(ExportFormat format); + void setSize(QSize size); + + bool doExport(); + + private: + DataAnalyzer *dataAnalyzer; + DsoSettings *settings; + + QString filename; + ExportFormat format; + QSize size; +}; + + +#endif diff --git b/openhantek/src/glgenerator.cpp a/openhantek/src/glgenerator.cpp new file mode 100644 index 0000000..44172b7 --- /dev/null +++ a/openhantek/src/glgenerator.cpp @@ -0,0 +1,284 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// glgenerator.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include +#include + + +#include "glgenerator.h" + +#include "dataanalyzer.h" +#include "settings.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class GlArray +/// \brief Initializes the array. +GlArray::GlArray() { + this->data = 0; + this->size = 0; +} + +/// \brief Deletes the array. +GlArray::~GlArray() { + if(this->data) + delete this->data; +} + +/// \brief Get the size of the array. +/// \return Number of array elements. +unsigned long int GlArray::getSize() { + return this->size; +} + +/// \brief Set the size of the array. +/// Previous array contents are lost. +/// \param size New number of array elements. +void GlArray::setSize(unsigned long size) { + if(this->size == size) + return; + + if(this->data) { + delete[] this->data; + } + if(size) + this->data = new GLfloat[size]; + else + this->data = 0; + + this->size = size; +} + + +//////////////////////////////////////////////////////////////////////////////// +// class GlGenerator +/// \brief Initializes the scope widget. +/// \param settings The target settings object. +/// \param parent The parent widget. +GlGenerator::GlGenerator(DsoSettings *settings, QObject *parent) : QObject(parent) { + this->settings = settings; + + this->dataAnalyzer = 0; + + this->generateGrid(); +} + +/// \brief Deletes OpenGL objects. +GlGenerator::~GlGenerator() { + delete[] this->vaChannel; +} + +/// \brief Set the data analyzer whose data will be drawn. +/// \param dataAnalyzer Pointer to the DataAnalyzer class. +void GlGenerator::setDataAnalyzer(DataAnalyzer *dataAnalyzer) { + if(this->dataAnalyzer) + disconnect(this->dataAnalyzer, SIGNAL(finished()), this, SLOT(generateGraphs())); + this->dataAnalyzer = dataAnalyzer; + connect(this->dataAnalyzer, SIGNAL(finished()), this, SLOT(generateGraphs())); +} + +/// \brief Prepare arrays for drawing the data we get from the data analyzer. +void GlGenerator::generateGraphs() { + if(!this->dataAnalyzer) + return; + + // Adapt the number of graphs + for(int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; mode++) { + for(int channel = this->vaChannel[mode].count(); channel < this->settings->scope.voltage.count(); channel++) + this->vaChannel[mode].append(QList()); + for(int channel = this->settings->scope.voltage.count(); channel < this->vaChannel[mode].count(); channel++) + this->vaChannel[mode].removeLast(); + } + + // Set digital phosphor depth to one if we don't use it + if(this->settings->view.digitalPhosphor) + this->digitalPhosphorDepth = this->settings->view.digitalPhosphorDepth; + else + this->digitalPhosphorDepth = 1; + + // Handle all digital phosphor related list manipulations + for(int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; mode++) { + for(int channel = 0; channel < this->vaChannel[mode].count(); channel++) { + // Resize lists for vector array if the digital phosphor depth has changed + if(this->vaChannel[mode][channel].count() != this->digitalPhosphorDepth) + for(int index = this->vaChannel[mode][channel].count(); index < this->digitalPhosphorDepth; index++) + this->vaChannel[mode][channel].append(new GlArray()); + for(int index = this->digitalPhosphorDepth; index < this->vaChannel[mode][channel].count(); index++) { + delete this->vaChannel[mode][channel].last(); + this->vaChannel[mode][channel].removeLast(); + } + + // Move the last list element to the front + this->vaChannel[mode][channel].move(this->digitalPhosphorDepth -1, 0); + } + } + + this->dataAnalyzer->mutex()->lock(); + + switch(this->settings->scope.horizontal.format) { + case Dso::GRAPHFORMAT_TY: + // Add graphs for channels + for(int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; mode++) { + for(int channel = 0 ; channel < this->settings->scope.voltage.count(); channel++) { + // Check if this channel is used and available at the data analyzer + if(((mode == Dso::CHANNELMODE_VOLTAGE) ? this->settings->scope.voltage[channel].used : this->settings->scope.spectrum[channel].used) && this->dataAnalyzer->data(channel)->samples.voltage.sample) { + // Check if the sample count has changed + unsigned int neededSize = ((mode == Dso::CHANNELMODE_VOLTAGE) ? this->dataAnalyzer->data(channel)->samples.voltage.count : this->dataAnalyzer->data(channel)->samples.spectrum.count) * 2; + for(int index = 0; index < this->digitalPhosphorDepth; index++) { + if(this->vaChannel[mode][channel][index]->getSize() != neededSize) + this->vaChannel[mode][channel][index]->setSize(0); + } + + // Check if the array is allocated + if(!this->vaChannel[mode][channel].first()->data) + this->vaChannel[mode][channel].first()->setSize(neededSize); + + GLfloat *vaNewChannel = this->vaChannel[mode][channel].first()->data; + + // What's the horizontal distance between sampling points? + double horizontalFactor; + if(mode == Dso::CHANNELMODE_VOLTAGE) + horizontalFactor = this->dataAnalyzer->data(channel)->samples.voltage.interval / this->settings->scope.horizontal.timebase; + else + horizontalFactor = this->dataAnalyzer->data(channel)->samples.spectrum.interval / this->settings->scope.horizontal.frequencybase; + + // Fill vector array + unsigned int arrayPosition = 0; + if(mode == Dso::CHANNELMODE_VOLTAGE) { + for(unsigned int position = 0; position < this->dataAnalyzer->data(channel)->samples.voltage.count; position++) { + vaNewChannel[arrayPosition++] = position * horizontalFactor - DIVS_TIME / 2; + vaNewChannel[arrayPosition++] = this->dataAnalyzer->data(channel)->samples.voltage.sample[position] / this->settings->scope.voltage[channel].gain + this->settings->scope.voltage[channel].offset; + } + } + else { + for(unsigned int position = 0; position < this->dataAnalyzer->data(channel)->samples.spectrum.count; position++) { + vaNewChannel[arrayPosition++] = position * horizontalFactor - DIVS_TIME / 2; + vaNewChannel[arrayPosition++] = this->dataAnalyzer->data(channel)->samples.spectrum.sample[position] / this->settings->scope.spectrum[channel].magnitude + this->settings->scope.spectrum[channel].offset; + } + } + } + else { + // Delete all vector arrays + for(int index = 0; index < this->digitalPhosphorDepth; index++) + this->vaChannel[mode][channel][index]->setSize(0); + } + } + } + break; + + case Dso::GRAPHFORMAT_XY: + break; + } + + this->dataAnalyzer->mutex()->unlock(); + + emit graphsGenerated(); +} + +/// \brief Create the needed OpenGL vertex arrays for the grid. +void GlGenerator::generateGrid() { + // Grid + this->vaGrid[0].setSize(((DIVS_TIME * DIVS_SUB - 2) * (DIVS_VOLTAGE - 2) + (DIVS_VOLTAGE * DIVS_SUB - 2) * (DIVS_TIME - 2) - ((DIVS_TIME - 2) * (DIVS_VOLTAGE - 2))) * 2); + int pointIndex = 0; + // Draw vertical lines + for(int div = 1; div < DIVS_TIME / 2; div++) { + for(int dot = 1; dot < DIVS_VOLTAGE / 2 * DIVS_SUB; dot++) { + float dotPosition = (float) dot / DIVS_SUB; + this->vaGrid[0].data[pointIndex++] = -div; + this->vaGrid[0].data[pointIndex++] = -dotPosition; + this->vaGrid[0].data[pointIndex++] = -div; + this->vaGrid[0].data[pointIndex++] = dotPosition; + this->vaGrid[0].data[pointIndex++] = div; + this->vaGrid[0].data[pointIndex++] = -dotPosition; + this->vaGrid[0].data[pointIndex++] = div; + this->vaGrid[0].data[pointIndex++] = dotPosition; + } + } + // Draw horizontal lines + for(int div = 1; div < DIVS_VOLTAGE / 2; div++) { + for(int dot = 1; dot < DIVS_TIME / 2 * DIVS_SUB; dot++) { + if(dot % DIVS_SUB == 0) + continue; // Already done by vertical lines + float dotPosition = (float) dot / DIVS_SUB; + this->vaGrid[0].data[pointIndex++] = -dotPosition; + this->vaGrid[0].data[pointIndex++] = -div; + this->vaGrid[0].data[pointIndex++] = dotPosition; + this->vaGrid[0].data[pointIndex++] = -div; + this->vaGrid[0].data[pointIndex++] = -dotPosition; + this->vaGrid[0].data[pointIndex++] = div; + this->vaGrid[0].data[pointIndex++] = dotPosition; + this->vaGrid[0].data[pointIndex++] = div; + } + } + + // Axes + this->vaGrid[1].setSize((2 + (DIVS_TIME * DIVS_SUB - 2) + (DIVS_VOLTAGE * DIVS_SUB - 2)) * 4); + pointIndex = 0; + // Horizontal axis + this->vaGrid[1].data[pointIndex++] = -DIVS_TIME / 2; + this->vaGrid[1].data[pointIndex++] = 0; + this->vaGrid[1].data[pointIndex++] = DIVS_TIME / 2; + this->vaGrid[1].data[pointIndex++] = 0; + // Vertical axis + this->vaGrid[1].data[pointIndex++] = 0; + this->vaGrid[1].data[pointIndex++] = -DIVS_VOLTAGE / 2; + this->vaGrid[1].data[pointIndex++] = 0; + this->vaGrid[1].data[pointIndex++] = DIVS_VOLTAGE / 2; + // Subdiv lines on horizontal axis + for(int line = 1; line < DIVS_TIME / 2 * DIVS_SUB; line++) { + float linePosition = (float) line / DIVS_SUB; + this->vaGrid[1].data[pointIndex++] = linePosition; + this->vaGrid[1].data[pointIndex++] = -0.05; + this->vaGrid[1].data[pointIndex++] = linePosition; + this->vaGrid[1].data[pointIndex++] = 0.05; + this->vaGrid[1].data[pointIndex++] = -linePosition; + this->vaGrid[1].data[pointIndex++] = -0.05; + this->vaGrid[1].data[pointIndex++] = -linePosition; + this->vaGrid[1].data[pointIndex++] = 0.05; + } + // Subdiv lines on vertical axis + for(int line = 1; line < DIVS_VOLTAGE / 2 * DIVS_SUB; line++) { + float linePosition = (float) line / DIVS_SUB; + this->vaGrid[1].data[pointIndex++] = -0.05; + this->vaGrid[1].data[pointIndex++] = linePosition; + this->vaGrid[1].data[pointIndex++] = 0.05; + this->vaGrid[1].data[pointIndex++] = linePosition; + this->vaGrid[1].data[pointIndex++] = -0.05; + this->vaGrid[1].data[pointIndex++] = -linePosition; + this->vaGrid[1].data[pointIndex++] = 0.05; + this->vaGrid[1].data[pointIndex++] = -linePosition; + } + + // Border + this->vaGrid[2].setSize(4 * 2); + // The 1e-4 offsets avoid that the border is outside our drawing area + this->vaGrid[2].data[0] = -DIVS_TIME / 2 + 1e-4; + this->vaGrid[2].data[1] = -DIVS_VOLTAGE / 2 + 1e-4; + this->vaGrid[2].data[2] = DIVS_TIME / 2 - 1e-4; + this->vaGrid[2].data[3] = -DIVS_VOLTAGE / 2 + 1e-4; + this->vaGrid[2].data[4] = DIVS_TIME / 2 - 1e-4; + this->vaGrid[2].data[5] = DIVS_VOLTAGE / 2 - 1e-4; + this->vaGrid[2].data[6] = -DIVS_TIME / 2 + 1e-4; + this->vaGrid[2].data[7] = DIVS_VOLTAGE / 2 - 1e-4; +} diff --git b/openhantek/src/glgenerator.h a/openhantek/src/glgenerator.h new file mode 100644 index 0000000..d13be3f --- /dev/null +++ a/openhantek/src/glgenerator.h @@ -0,0 +1,101 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file glgenerator.h +/// \brief Declares the GlScope class. +// +// Copyright (C) 2008, 2009 Oleg Khudyakov +// prcoder@potrebitel.ru +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef GLGENERATOR_H +#define GLGENERATOR_H + + +#include +#include +#include + + +#include "constants.h" + + +#define DIVS_TIME 10.0 ///< Number of horizontal screen divs +#define DIVS_VOLTAGE 8.0 ///< Number of vertical screen divs +#define DIVS_SUB 5 ///< Number of sub-divisions per div + + +class DataAnalyzer; +class DsoSettings; +class GlScope; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class GlArray glgenerator.h +/// \brief An array of GLfloat values and it's size. +class GlArray { + public: + GlArray(); + ~GlArray(); + + unsigned long int getSize(); + void setSize(unsigned long int size); + + GLfloat *data; + + protected: + unsigned long int size; +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \class GlGenerator glgenerator.h +/// \brief Generates the vertex arrays for the GlScope classes. +class GlGenerator : public QObject { + Q_OBJECT + + friend class GlScope; + + public: + GlGenerator(DsoSettings *settings, QObject *parent = 0); + ~GlGenerator(); + + void setDataAnalyzer(DataAnalyzer *dataAnalyzer); + + protected: + void generateGrid(); + + private: + DataAnalyzer *dataAnalyzer; + DsoSettings *settings; + + QList > vaChannel[Dso::CHANNELMODE_COUNT]; + GlArray vaGrid[3]; + + int digitalPhosphorDepth; + + public slots: + void generateGraphs(); + + signals: + void graphsGenerated(); +}; + + +#endif diff --git b/openhantek/src/glscope.cpp a/openhantek/src/glscope.cpp new file mode 100644 index 0000000..bef52fe --- /dev/null +++ a/openhantek/src/glscope.cpp @@ -0,0 +1,203 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// glscope.cpp +// +// Copyright (C) 2008, 2009 Oleg Khudyakov +// prcoder@potrebitel.ru +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include + +#include + + +#include "glscope.h" + +#include "dataanalyzer.h" +#include "glgenerator.h" +#include "settings.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class GlScope +/// \brief Initializes the scope widget. +/// \param parent The parent widget. +GlScope::GlScope(DsoSettings *settings, QWidget *parent) : QGLWidget(parent) { + this->settings = settings; + + this->generator = 0; + this->zoomed = false; +} + +/// \brief Deletes OpenGL objects. +GlScope::~GlScope() { +} + +/// \brief Initializes OpenGL output. +void GlScope::initializeGL() { + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glPointSize(1); + + qglClearColor(this->settings->view.color.screen.background); + + glShadeModel(GL_SMOOTH/*GL_FLAT*/); + glLineStipple(1, 0x3333); + + glEnableClientState(GL_VERTEX_ARRAY); +} + +/// \brief Draw the graphs and the grid. +void GlScope::paintGL() { + if(!this->generator || !this->isVisible()) + return; + + // Clear OpenGL buffer and configure settings + glClear(GL_COLOR_BUFFER_BIT); + glLineWidth(1); + if(this->settings->view.antialiasing) { + glEnable(GL_POINT_SMOOTH); + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + } + + // Apply zoom settings via matrix transformation + if(this->zoomed) { + glPushMatrix(); + glScalef(DIVS_TIME / fabs(this->settings->scope.horizontal.marker[1] - this->settings->scope.horizontal.marker[0]), 1.0, 1.0); + glTranslatef(-(this->settings->scope.horizontal.marker[0] + this->settings->scope.horizontal.marker[1]) / 2, 0.0, 0.0); + } + + // Values we need for the fading of the digital phosphor + double *fadingFactor = new double[this->generator->digitalPhosphorDepth]; + fadingFactor[0] = 100; + double fadingRatio = pow(10.0, 2.0 / this->generator->digitalPhosphorDepth); + for(int index = 1; index < this->generator->digitalPhosphorDepth; index++) + fadingFactor[index] = fadingFactor[index - 1] * fadingRatio; + + switch(this->settings->scope.horizontal.format) { + case Dso::GRAPHFORMAT_TY: { + // Real and virtual channels + for(int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; mode++) { + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + if((mode == Dso::CHANNELMODE_VOLTAGE) ? this->settings->scope.voltage[channel].used : this->settings->scope.spectrum[channel].used) { + // Draw graph for all available depths + for(int index = this->generator->digitalPhosphorDepth - 1; index >= 0; index--) { + if(this->generator->vaChannel[mode][channel][index]->data) { + if(mode == Dso::CHANNELMODE_VOLTAGE) + this->qglColor(this->settings->view.color.screen.voltage[channel].darker(fadingFactor[index])); + else + this->qglColor(this->settings->view.color.screen.spectrum[channel].darker(fadingFactor[index])); + glVertexPointer(2, GL_FLOAT, 0, this->generator->vaChannel[mode][channel][index]->data); + glDrawArrays((this->settings->view.interpolation == INTERPOLATION_OFF) ? GL_POINTS : GL_LINE_STRIP, 0, this->generator->vaChannel[mode][channel][index]->getSize() / 2); + } + } + } + } + } + + delete[] fadingFactor; + + break; + } + + case Dso::GRAPHFORMAT_XY: + break; + } + + glDisable(GL_POINT_SMOOTH); + glDisable(GL_LINE_SMOOTH); + + if(this->zoomed) + glPopMatrix(); + else { + // Draw vertical lines at marker positions + glEnable(GL_LINE_STIPPLE); + this->qglColor(this->settings->view.color.screen.markers); + + for(int marker = 0; marker < MARKER_COUNT; marker++) { + if(!this->vaMarker[marker].data) { + this->vaMarker[marker].setSize(2 * 2); + this->vaMarker[marker].data[1] = - DIVS_VOLTAGE; + this->vaMarker[marker].data[3] = DIVS_VOLTAGE; + } + + this->vaMarker[marker].data[0] = this->settings->scope.horizontal.marker[marker]; + this->vaMarker[marker].data[2] = this->settings->scope.horizontal.marker[marker]; + + glVertexPointer(2, GL_FLOAT, 0, this->vaMarker[marker].data); + glDrawArrays(GL_LINES, 0, this->vaMarker[marker].getSize() / 2); + } + + glDisable(GL_LINE_STIPPLE); + } + + // Draw grid + this->drawGrid(); +} + +/// \brief Resize the widget. +/// \param width The new width of the widget. +/// \param height The new height of the widget. +void GlScope::resizeGL(int width, int height) { + glViewport(0, 0, (GLint) width, (GLint) height); + //glViewport(1, 0, (GLint) width - 1, (GLint) height - 1); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(-DIVS_TIME / 2, DIVS_TIME / 2, -DIVS_VOLTAGE / 2, DIVS_VOLTAGE / 2, -1.0, 1.0); + glMatrixMode(GL_MODELVIEW); +} + +/// \brief Set the generator that provides the vertex arrays. +/// \param generator Pointer to the GlGenerator class. +void GlScope::setGenerator(GlGenerator *generator) { + if(this->generator) + disconnect(this->generator, SIGNAL(graphsGenerated()), this, SLOT(updateGL())); + this->generator = generator; + connect(this->generator, SIGNAL(graphsGenerated()), this, SLOT(updateGL())); +} + +/// \brief Set the zoom mode for this GlScope. +/// \param zoomed true magnifies the area between the markers. +void GlScope::setZoomMode(bool zoomed) { + this->zoomed = zoomed; +} + +/// \brief Draw the grid. +void GlScope::drawGrid() { + glDisable(GL_POINT_SMOOTH); + glDisable(GL_LINE_SMOOTH); + + // Grid + this->qglColor(this->settings->view.color.screen.grid); + glVertexPointer(2, GL_FLOAT, 0, this->generator->vaGrid[0].data); + glDrawArrays(GL_POINTS, 0, this->generator->vaGrid[0].getSize() / 2); + // Axes + this->qglColor(this->settings->view.color.screen.axes); + glVertexPointer(2, GL_FLOAT, 0, this->generator->vaGrid[1].data); + glDrawArrays(GL_LINES, 0, this->generator->vaGrid[1].getSize() / 2); + // Border + this->qglColor(this->settings->view.color.screen.border); + glVertexPointer(2, GL_FLOAT, 0, this->generator->vaGrid[2].data); + glDrawArrays(GL_LINE_LOOP, 0, this->generator->vaGrid[2].getSize() / 2); +} diff --git b/openhantek/src/glscope.h a/openhantek/src/glscope.h new file mode 100644 index 0000000..237dc64 --- /dev/null +++ a/openhantek/src/glscope.h @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file glscope.h +/// \brief Declares the GlScope class. +// +// Copyright (C) 2008, 2009 Oleg Khudyakov +// prcoder@potrebitel.ru +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef GLSCOPE_H +#define GLSCOPE_H + + +#include + + +#include "glgenerator.h" +#include "constants.h" + + +class DataAnalyzer; +class DsoSettings; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class GlScope glscope.h +/// \brief OpenGL accelerated widget that displays the oscilloscope screen. +class GlScope : public QGLWidget { + Q_OBJECT + + public: + GlScope(DsoSettings *settings, QWidget* parent = 0); + ~GlScope(); + + void setGenerator(GlGenerator *generator); + void setZoomMode(bool zoomed); + + protected: + void initializeGL(); + void paintGL(); + void resizeGL(int width, int height); + + void drawGrid(); + + private: + GlGenerator *generator; + DsoSettings *settings; + + GlArray vaMarker[2]; + bool zoomed; +}; + + +#endif diff --git b/openhantek/src/hantek/control.cpp a/openhantek/src/hantek/control.cpp new file mode 100644 index 0000000..04526e4 --- /dev/null +++ a/openhantek/src/hantek/control.cpp @@ -0,0 +1,653 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// hantek/control.cpp +// +// Copyright (C) 2008, 2009 Oleg Khudyakov +// prcoder@potrebitel.ru +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include +#include + + +#include "hantek/control.h" + +#include "helper.h" +#include "hantek/device.h" +#include "hantek/types.h" + + +namespace Hantek { + /// \brief Initializes the command buffers and lists. + /// \param parent The parent widget. + Control::Control(QObject *parent) : DsoControl(parent) { + // Values for the Gain and Timebase enums + this->gainSteps << 0.08 << 0.16 << 0.40 << 0.80 << 1.60 << 4.00 + << 8.0 << 16.0 << 40.0; + this->samplerateSteps << 1e8 << 5e7 << 25e6 << 1e7 + << 5e6 << 25e5 << 1e6 << 5e5 << 25e4 << 1e5 << 5e4 << 25e3 << 1e4 + << 5e3 << 25e2 << 1e3; + + // Special trigger sources + this->specialTriggerSources << tr("EXT") << tr("EXT/10"); + + // Transmission-ready bulk commands + this->command[COMMAND_SETFILTER] = new CommandSetFilter(); + this->command[COMMAND_SETTRIGGERANDSAMPLERATE] = new CommandSetTriggerAndSamplerate(); + this->command[COMMAND_FORCETRIGGER] = new CommandForceTrigger(); + this->command[COMMAND_STARTSAMPLING] = new CommandCaptureStart(); + this->command[COMMAND_ENABLETRIGGER] = new CommandTriggerEnabled(); + this->command[COMMAND_GETDATA] = new CommandGetData(); + this->command[COMMAND_GETCAPTURESTATE] = new CommandGetCaptureState(); + this->command[COMMAND_SETGAIN] = new CommandSetGain(); + this->command[COMMAND_SETLOGICALDATA] = new CommandSetLogicalData(); + this->command[COMMAND_GETLOGICALDATA] = new CommandGetLogicalData(); + + // Transmission-ready control commands + this->control[CONTROLINDEX_SETOFFSET] = new ControlSetOffset(); + this->controlCode[CONTROLINDEX_SETOFFSET] = CONTROL_SETOFFSET; + this->control[CONTROLINDEX_SETRELAYS] = new ControlSetRelays(); + this->controlCode[CONTROLINDEX_SETRELAYS] = CONTROL_SETRELAYS; + + // Channel level data + for(unsigned int channel = 0; channel < HANTEK_CHANNELS; channel++) { + for(unsigned int gainId = 0; gainId < GAIN_COUNT; gainId++) { + this->channelLevels[channel][gainId][OFFSET_START] = 0x0000; + this->channelLevels[channel][gainId][OFFSET_END] = 0xffff; + } + } + + // Cached variables + for(unsigned int channel = 0; channel < HANTEK_CHANNELS; channel++) { + this->setChannelUsed(channel, channel == 0); + this->setCoupling(channel, Dso::COUPLING_AC); + this->setGain(channel, 8.0); + this->setOffset(channel, 0.5); + this->setTriggerLevel(channel, 0.0); + } + this->setSamplerate(1e6); + this->setBufferSize(BUFFER_SMALL); + this->setTriggerMode(Dso::TRIGGERMODE_NORMAL); + this->setTriggerPosition(0.5); + this->setTriggerSlope(Dso::SLOPE_POSITIVE); + this->setTriggerSource(false, 0); + + // Sample buffers + for(unsigned int channel = 0; channel < HANTEK_CHANNELS; channel++) { + this->samples.append(0); + this->samplesSize.append(0); + } + + // USB device + this->device = new Device(this); + + connect(this->device, SIGNAL(disconnected()), this, SLOT(disconnectDevice())); + } + + /// \brief Disconnects the device. + Control::~Control() { + this->device->disconnect(); + } + + /// \brief Gets the physical channel count for this oscilloscope. + /// \returns The number of physical channels. + unsigned int Control::getChannelCount() { + return HANTEK_CHANNELS; + } + + /// \brief Handles all USB things until the device gets disconnected. + void Control::run() { + int errorCode; + + emit statusMessage(this->device->search(), 0); + if(!this->device->isConnected()) + return; + + // Set all configuration commands as pending + this->commandPending[COMMAND_SETFILTER] = true; + this->commandPending[COMMAND_SETTRIGGERANDSAMPLERATE] = true; + this->commandPending[COMMAND_FORCETRIGGER] = false; + this->commandPending[COMMAND_STARTSAMPLING] = false; + this->commandPending[COMMAND_ENABLETRIGGER] = false; + this->commandPending[COMMAND_GETDATA] = false; + this->commandPending[COMMAND_GETCAPTURESTATE] = false; + this->commandPending[COMMAND_SETGAIN] = true; + this->commandPending[COMMAND_SETLOGICALDATA] = true; + this->commandPending[COMMAND_GETLOGICALDATA] = false; + for(int control = 0; control < CONTROLINDEX_COUNT; control++) + this->controlPending[control] = true; + + // Get calibration data + errorCode = this->device->controlRead(CONTROL_VALUE, (unsigned char*) &(this->channelLevels), sizeof(this->channelLevels), (int) VALUE_CHANNELLEVEL); + if(errorCode < 0) { + this->device->disconnect(); + emit statusMessage(tr("Couldn't get channel level data from oscilloscope"), 0); + return; + } + + // Adapt offsets + for(unsigned int channel = 0; channel < HANTEK_CHANNELS; channel++) + this->setOffset(channel, this->offset[channel]); + + // The control loop is running until the device is disconnected + int captureState = CAPTURE_WAITING; + bool samplingStarted = false; + + while(captureState != LIBUSB_ERROR_NO_DEVICE && !this->terminate) { + // Send all pending bulk commands + for(int command = 0; command < COMMAND_COUNT; command++) { + if(!this->commandPending[command]) + continue; + + errorCode = this->device->bulkCommand(this->command[command]); + if(errorCode < 0) { + qDebug("Sending bulk command 0x%02x failed: %s", command, Helper::libUsbErrorString(errorCode).toLocal8Bit().data()); + + if(errorCode == LIBUSB_ERROR_NO_DEVICE) { + captureState = LIBUSB_ERROR_NO_DEVICE; + break; + } + } + else + this->commandPending[command] = false; + } + if(captureState == LIBUSB_ERROR_NO_DEVICE) + break; + + // Send all pending control commands + for(int control = 0; control < CONTROLINDEX_COUNT; control++) { + if(!this->controlPending[control]) + continue; + + errorCode = this->device->controlWrite(this->controlCode[control], this->control[control]->data(), this->control[control]->getSize()); + if(errorCode < 0) { + qDebug("Sending control command 0x%2x failed: %s", control, Helper::libUsbErrorString(errorCode).toLocal8Bit().data()); + + if(errorCode == LIBUSB_ERROR_NO_DEVICE) { + captureState = LIBUSB_ERROR_NO_DEVICE; + break; + } + } + else + this->controlPending[control] = false; + } + if(captureState == LIBUSB_ERROR_NO_DEVICE) + break; + + // Check the current oscilloscope state every 50 ms + /// \todo Maybe the time interval could be improved... + this->msleep(50); + + if(!this->sampling) + continue; + captureState = this->getCaptureState(); + switch(captureState) { + case CAPTURE_READY: + case CAPTURE_READY5200: + samplingStarted = false; + + errorCode = this->getSamples(); + if(errorCode < 0) + qDebug("Getting sample data failed: %s", Helper::libUsbErrorString(errorCode).toLocal8Bit().data()); + + // Start next capture if necessary by leaving out the break statement + if(!this->sampling) + break; + case CAPTURE_WAITING: + if(samplingStarted) + break; + + // Start capturing + errorCode = this->device->bulkCommand(this->command[COMMAND_STARTSAMPLING]); + if(errorCode < 0) { + if(errorCode == LIBUSB_ERROR_NO_DEVICE) + captureState = LIBUSB_ERROR_NO_DEVICE; + break; + } + // Enable trigger + errorCode = this->device->bulkCommand(this->command[COMMAND_ENABLETRIGGER]); + if(errorCode < 0) { + if(errorCode == LIBUSB_ERROR_NO_DEVICE) + captureState = LIBUSB_ERROR_NO_DEVICE; + break; + } + if(this->triggerMode == Dso::TRIGGERMODE_AUTO) { + // Force triggering + errorCode = this->device->bulkCommand(this->command[COMMAND_FORCETRIGGER]); + if(errorCode == LIBUSB_ERROR_NO_DEVICE) + captureState = LIBUSB_ERROR_NO_DEVICE; + } + samplingStarted = true; + break; + case CAPTURE_SAMPLING: + break; + default: + if(captureState < 0) + qDebug("Getting capture state failed: %s", Helper::libUsbErrorString(captureState).toLocal8Bit().data()); + break; + } + } + + this->device->disconnect(); + emit statusMessage(tr("The device has been disconnected"), 0); + } + + /// \brief Calculates the trigger point from the CommandGetCaptureState data. + /// \param value The data value that contains the trigger point. + /// \return The calculated trigger point for the given data. + unsigned int Control::calculateTriggerPoint(unsigned int value) { + unsigned int min = value; + unsigned int max = 1; + while(min > 0) { + min >>= 1; + max <<= 1; + } + max--; + + unsigned check = 0; + unsigned lastLowCheck = 0; + bool tooHigh = true; + + while(max > min) { + check = (max - min + 1) / 2 + lastLowCheck; + + bool higher = check > value; + if(!higher) + lastLowCheck = check; + + tooHigh = higher == tooHigh; + if(tooHigh) + max = (max + min - 1) / 2; + else + min = (max + min + 1) / 2; + } + + return min; + } + + /// \brief Gets the current state. + /// \return The current CaptureState of the oscilloscope. + int Control::getCaptureState() { + int errorCode; + + errorCode = this->device->bulkCommand(this->command[COMMAND_GETCAPTURESTATE], 1); + if(errorCode < 0) + return errorCode; + + ResponseGetCaptureState response; + errorCode = this->device->bulkRead(response.data(), response.getSize()); + if(errorCode < 0) + return errorCode; + + this->triggerPoint = this->calculateTriggerPoint(response.getTriggerPoint()); + + return (int) response.getCaptureState(); + } + + /// \brief Gets sample data from the oscilloscope and converts it. + /// \return 0 on success, libusb error code on error. + int Control::getSamples() { + int errorCode; + + errorCode = this->device->bulkCommand(this->command[COMMAND_GETDATA], 1); + if(errorCode < 0) + return errorCode; + + unsigned int dataCount = this->bufferSize * HANTEK_CHANNELS; + unsigned char data[dataCount]; + errorCode = this->device->bulkReadMulti(data, dataCount); + if(errorCode < 0) + return errorCode; + + this->samplesMutex.lock(); + + // Convert channel data + if(((CommandSetTriggerAndSamplerate *) this->command[COMMAND_SETTRIGGERANDSAMPLERATE])->getFastRate()) { + // Fast rate mode, one channel is using all buffers + int channel; + if(((CommandSetTriggerAndSamplerate *) this->command[COMMAND_SETTRIGGERANDSAMPLERATE])->getUsedChannel() == USED_CH1) + channel = 0; + else + channel = 1; + + + // Clear unused channels + for(int channelCounter = 0; channelCounter < HANTEK_CHANNELS; channelCounter++) + if(channelCounter != channel && this->samples[channelCounter]) { + + delete this->samples[channelCounter]; + this->samples[channelCounter] = 0; + } + + if(channel < HANTEK_CHANNELS) { + // Reallocate memory for samples if the sample count has changed + if(!this->samples[channel] || this->samplesSize[channel] != dataCount) { + if(this->samples[channel]) + delete this->samples[channel]; + this->samples[channel] = new double[dataCount]; + this->samplesSize[channel] = dataCount; + } + + // Convert data from the oscilloscope and write it into the sample buffer + unsigned int bufferPosition = this->triggerPoint; + for(unsigned int realPosition = 0; realPosition < dataCount; realPosition++, bufferPosition++) { + if(bufferPosition >= dataCount) + bufferPosition %= dataCount; + + this->samples[channel][realPosition] = ((double) data[bufferPosition] / 0xff - this->offsetReal[channel]) * this->gainSteps[this->gain[channel]]; + } + } + } + else { + // Normal mode, channel are using their separate buffers + unsigned char usedChannels = ((CommandSetTriggerAndSamplerate *) this->command[COMMAND_SETTRIGGERANDSAMPLERATE])->getUsedChannel(); + for(int channel = 0; channel < HANTEK_CHANNELS; channel++) { + if(usedChannels == USED_CH1CH2 || channel == usedChannels) { + // Reallocate memory for samples if the sample count has changed + if(!this->samples[channel] || this->samplesSize[channel] != this->bufferSize) { + if(this->samples[channel]) + delete this->samples[channel]; + this->samples[channel] = new double[this->bufferSize]; + this->samplesSize[channel] = this->bufferSize; + } + + // Convert data from the oscilloscope and write it into the sample buffer + unsigned int bufferPosition = this->triggerPoint * 2; + for(unsigned int realPosition = 0; realPosition < this->bufferSize; realPosition++, bufferPosition += 2) { + if(bufferPosition >= dataCount) + bufferPosition %= dataCount; + + this->samples[channel][realPosition] = ((double) data[bufferPosition + HANTEK_CHANNELS - 1 - channel] / 256.0 - this->offsetReal[channel]) * this->gainSteps[this->gain[channel]]; + } + } + else if(this->samples[channel]) { + // Clear unused channels + delete this->samples[channel]; + this->samples[channel] = 0; + this->samplesSize[channel] = 0; + } + } + } + + this->samplesMutex.unlock(); + emit samplesAvailable(&(this->samples), &(this->samplesSize), this->samplerateSteps[this->samplerate], &(this->samplesMutex)); + + return 0; + } + + /// \brief Sets the size of the oscilloscopes sample buffer. + /// \param size The buffer size that should be met (S). + /// \return The buffer size that has been set. + double Control::setBufferSize(unsigned int size) { + unsigned int sizeId = (size <= BUFFER_SMALL) ? 1 : 2; + + // SetTriggerAndSamplerate bulk command for samplerate + ((CommandSetTriggerAndSamplerate *) this->command[COMMAND_SETTRIGGERANDSAMPLERATE])->setSampleSize(sizeId); + this->commandPending[COMMAND_SETTRIGGERANDSAMPLERATE] = true; + + this->setSamplerate(this->samplerateSteps[this->samplerate]); + + this->bufferSize = (sizeId == 1) ? BUFFER_SMALL : BUFFER_LARGE; + return this->bufferSize; + } + + /// \brief Sets the samplerate of the oscilloscope. + /// \param samplerate The samplerate that should be met (S/s). + /// \return The samplerate that has been set. + unsigned long int Control::setSamplerate(unsigned long int samplerate) { + // Find lowest supported samplerate thats at least as high as the requested + int samplerateId; + for(samplerateId = SAMPLERATE_COUNT - 1; samplerateId > 0; samplerateId--) + if(this->samplerateSteps[samplerateId] >= samplerate) + break; + // Fastrate is only possible if we're not using both channels + if(samplerateId == SAMPLERATE_100MS && ((CommandSetTriggerAndSamplerate *) this->command[COMMAND_SETTRIGGERANDSAMPLERATE])->getUsedChannel() == USED_CH1CH2) + samplerateId = SAMPLERATE_50MS; + + // The values that are understood by the oscilloscope + static const unsigned char valueFastSmall[5] = {0, 1, 2, 3, 4}; + static const unsigned char valueFastLarge[5] = {0, 0, 0, 2, 3}; + static const unsigned short int valueSlowSmall[13] = {0xffff, 0xfffc, 0xfff7, 0xffe8, 0xffce, 0xff9c, 0xff07, 0xfe0d, 0xfc19, 0xf63d, 0xec79, 0xd8f1, 0xffed}; + static const unsigned short int valueSlowLarge[13] = {0xffff, 0x0000, 0xfffc, 0xfff7, 0xffe8, 0xffce, 0xff9d, 0xff07, 0xfe0d, 0xfc19, 0xf63d, 0xec79, 0xffed}; /// \todo Check those values + + // SetTriggerAndSamplerate bulk command for samplerate + CommandSetTriggerAndSamplerate *commandSetTriggerAndSamplerate = (CommandSetTriggerAndSamplerate *) this->command[COMMAND_SETTRIGGERANDSAMPLERATE]; + + // Set SamplerateFast bits for high sampling rates + if(samplerateId <= SAMPLERATE_5MS) + commandSetTriggerAndSamplerate->setSamplerateFast(this->bufferSize == BUFFER_SMALL ? valueFastSmall[samplerateId] : valueFastLarge[samplerateId]); + else + commandSetTriggerAndSamplerate->setSamplerateFast(4); + + // Set normal Samplerate value for lower sampling rates + if(samplerateId >= SAMPLERATE_2_5MS) + commandSetTriggerAndSamplerate->setSamplerate(this->bufferSize == BUFFER_SMALL ? valueSlowSmall[samplerateId - SAMPLERATE_2_5MS] : valueSlowLarge[samplerateId - SAMPLERATE_2_5MS]); + else + commandSetTriggerAndSamplerate->setSamplerate(0x0000); + + // Set fast rate when used + commandSetTriggerAndSamplerate->setFastRate(samplerateId == SAMPLERATE_100MS); + + this->commandPending[COMMAND_SETTRIGGERANDSAMPLERATE] = true; + + this->samplerate = (Samplerate) samplerateId; + return this->samplerateSteps[samplerateId]; + } + + /// \brief Enables/disables filtering of the given channel. + /// \param channel The channel that should be set. + /// \param filtered true if the channel should be filtered. + /// \return 0 on success, -1 on invalid channel. + int Control::setChannelUsed(unsigned int channel, bool used) { + if(channel >= HANTEK_CHANNELS) + return -1; + + // SetFilter bulk command for channel filter (used has to be inverted!) + CommandSetFilter *commandSetFilter = (CommandSetFilter *) this->command[COMMAND_SETFILTER]; + commandSetFilter->setChannel(channel, !used); + this->commandPending[COMMAND_SETFILTER] = true; + + // SetTriggerAndSamplerate bulk command for trigger source + unsigned char usedChannel = USED_CH1; + if(!commandSetFilter->getChannel(1)) { + if(commandSetFilter->getChannel(0)) + usedChannel = USED_CH2; + else + usedChannel = USED_CH1CH2; + } + ((CommandSetTriggerAndSamplerate *) this->command[COMMAND_SETTRIGGERANDSAMPLERATE])->setUsedChannel(usedChannel); + this->commandPending[COMMAND_SETTRIGGERANDSAMPLERATE] = true; + + return 0; + } + + /// \brief Set the coupling for the given channel. + /// \param channel The channel that should be set. + /// \param coupling The new coupling for the channel. + /// \return 0 on success, -1 on invalid channel. + int Control::setCoupling(unsigned int channel, Dso::Coupling coupling) { + if(channel >= HANTEK_CHANNELS) + return -1; + + // SetRelays control command for coupling relays + ((ControlSetRelays *) this->control[CONTROLINDEX_SETRELAYS])->setCoupling(channel, coupling != Dso::COUPLING_AC); + this->controlPending[CONTROLINDEX_SETRELAYS] = true; + + return 0; + } + + /// \brief Sets the gain for the given channel. + /// \param gain The gain that should be met (V/div). + /// \return The gain that has been set, -1.0 on invalid channel. + double Control::setGain(unsigned int channel, double gain) { + if(channel >= HANTEK_CHANNELS) + return -1.0; + + // Find lowest gain voltage thats at least as high as the requested + int gainId; + for(gainId = 0; gainId < GAIN_COUNT - 1; gainId++) + if(this->gainSteps[gainId] >= gain) + break; + + // SetGain bulk command for gain + ((CommandSetGain *) this->command[COMMAND_SETGAIN])->setGain(channel, gainId % 3); + this->commandPending[COMMAND_SETGAIN] = true; + + // SetRelays control command for gain relays + ControlSetRelays *controlSetRelays = (ControlSetRelays *) this->control[CONTROLINDEX_SETRELAYS]; + controlSetRelays->setBelow1V(channel, gainId < GAIN_1V); + controlSetRelays->setBelow100mV(channel, gainId < GAIN_100MV); + this->controlPending[CONTROLINDEX_SETRELAYS] = true; + + this->gain[channel] = (Gain) gainId; + + this->setOffset(channel, this->offset[channel]); + + return this->gainSteps[gainId]; + } + + /// \brief Set the offset for the given channel. + /// \param channel The channel that should be set. + /// \param offset The new offset value (0.0 - 1.0). + /// \return The offset that has been set, -1.0 on invalid channel. + double Control::setOffset(unsigned int channel, double offset) { + if(channel >= HANTEK_CHANNELS) + return -1.0; + + // Calculate the offset value (The range is given by the calibration data) + unsigned short int minimum = this->channelLevels[channel][this->gain[channel]][OFFSET_START] >> 8; + unsigned short int maximum = this->channelLevels[channel][this->gain[channel]][OFFSET_END] >> 8; + unsigned short int offsetValue = offset * (maximum - minimum) + minimum + 0.5; + double offsetReal = (double) (offsetValue - minimum) / (maximum - minimum); + + // SetOffset control command for channel offset + ((ControlSetOffset *) this->control[CONTROLINDEX_SETOFFSET])->setChannel(channel, offsetValue); + this->controlPending[CONTROLINDEX_SETOFFSET] = true; + + this->offset[channel] = offset; + this->offsetReal[channel] = offsetReal; + + this->setTriggerLevel(channel, this->triggerLevel[channel]); + + return offsetReal; + } + + /// \brief Set the trigger mode. + /// \return 0 on success, -1 on invalid mode. + int Control::setTriggerMode(Dso::TriggerMode mode) { + if(mode < Dso::TRIGGERMODE_AUTO || mode > Dso::TRIGGERMODE_SINGLE) + return -1; + + this->triggerMode = mode; + return 0; + } + + /// \brief Set the trigger source. + /// \param special true for a special channel (EXT, ...) as trigger source. + /// \param id The number of the channel, that should be used as trigger. + /// \return 0 on success, -1 on invalid channel. + int Control::setTriggerSource(bool special, unsigned int id) { + if((!special && id >= HANTEK_CHANNELS) || (special && id >= HANTEK_SPECIAL_CHANNELS)) + return -1; + + // Generate trigger source value that will be transmitted + int sourceValue; + if(special) + sourceValue = TRIGGER_EXT + id; + else + sourceValue = TRIGGER_CH1 - id; + + // SetTriggerAndSamplerate bulk command for trigger source + ((CommandSetTriggerAndSamplerate *) this->command[COMMAND_SETTRIGGERANDSAMPLERATE])->setTriggerSource(sourceValue); + this->commandPending[COMMAND_SETTRIGGERANDSAMPLERATE] = true; + + // SetRelays control command for external trigger relay + ((ControlSetRelays *) this->control[CONTROLINDEX_SETRELAYS])->setTrigger(special); + this->controlPending[CONTROLINDEX_SETRELAYS] = true; + + this->triggerSpecial = special; + this->triggerSource = id; + + // Apply trigger level of the new source + if(special) { + // SetOffset control command for changed trigger level + ((ControlSetOffset *) this->control[CONTROLINDEX_SETOFFSET])->setTrigger(0x7f); + this->controlPending[CONTROLINDEX_SETOFFSET] = true; + } + else + this->setTriggerLevel(id, this->triggerLevel[id]); + + return 0; + } + + /// \brief Set the trigger level. + /// \param channel The channel that should be set. + /// \param level The new trigger level (V). + /// \return The trigger level that has been set, -1.0 on invalid channel. + double Control::setTriggerLevel(unsigned int channel, double level) { + if(channel >= HANTEK_CHANNELS) + return -1.0; + + // Calculate the trigger level value (0x00 - 0xfe) + unsigned short int levelValue = (this->offsetReal[channel] + level / this->gainSteps[this->gain[channel]]) * 0xfe + 0.5; + + if(this->triggerSpecial && channel == this->triggerSource) { + // SetOffset control command for trigger level + ((ControlSetOffset *) this->control[CONTROLINDEX_SETOFFSET])->setTrigger(levelValue); + this->controlPending[CONTROLINDEX_SETOFFSET] = true; + } + + /// \todo Get alternating trigger in here + + this->triggerLevel[channel] = level; + return (double) (levelValue / 0xfe - this->offsetReal[channel]) * this->gainSteps[this->gain[channel]]; + } + + /// \brief Set the trigger slope. + /// \param slope The Slope that should cause a trigger. + /// \return 0 on success, -1 on invalid slope. + int Control::setTriggerSlope(Dso::Slope slope) { + if(slope != Dso::SLOPE_NEGATIVE && slope != Dso::SLOPE_POSITIVE) + return -1; + + // SetTriggerAndSamplerate bulk command for trigger position + ((CommandSetTriggerAndSamplerate *) this->command[COMMAND_SETTRIGGERANDSAMPLERATE])->setTriggerSlope(slope); + this->commandPending[COMMAND_SETTRIGGERANDSAMPLERATE] = true; + + return 0; + } + + /// \brief Set the trigger position. + /// \param level The new trigger position (0.0 - 1.0). + /// \return The trigger position that has been set. + double Control::setTriggerPosition(double position) { + // Calculate the position value (Varying start point, measured in samples) + //unsigned long int positionRange = (this->bufferSize == BUFFER_SMALL) ? 10000 : 32768; + unsigned long int positionStart = (this->bufferSize == BUFFER_SMALL) ? 0x77660 : 0x78000; + unsigned long int positionValue = position * this->samplerateSteps[this->samplerate] + positionStart; + + // SetTriggerAndSamplerate bulk command for trigger position + ((CommandSetTriggerAndSamplerate *) this->command[COMMAND_SETTRIGGERANDSAMPLERATE])->setTriggerPosition(positionValue); + this->commandPending[COMMAND_SETTRIGGERANDSAMPLERATE] = true; + + return (double) (positionValue - positionStart) / this->samplerateSteps[this->samplerate]; + } +} diff --git b/openhantek/src/hantek/control.h a/openhantek/src/hantek/control.h new file mode 100644 index 0000000..72ce155 --- /dev/null +++ a/openhantek/src/hantek/control.h @@ -0,0 +1,124 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file hantek/control.h +/// \brief Declares the Hantek::Control class. +// +// Copyright (C) 2008, 2009 Oleg Khudyakov +// prcoder@potrebitel.ru +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef HANTEK_CONTROL_H +#define HANTEK_CONTROL_H + + +#include + + +#include "dsocontrol.h" +#include "helper.h" +#include "hantek/types.h" + + +namespace Hantek { + class Device; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum ControlIndex hantek/control.h + /// \brief The array indices for the waiting control commands. + enum ControlIndex { + //CONTROLINDEX_VALUE, + //CONTROLINDEX_GETSPEED, + //CONTROLINDEX_BEGINCOMMAND, + CONTROLINDEX_SETOFFSET, + CONTROLINDEX_SETRELAYS, + CONTROLINDEX_COUNT + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class Control hantek/control.h + /// \brief The DsoControl abstraction layer for Hantek USB DSOs. + class Control : public DsoControl { + Q_OBJECT + + public: + Control(QObject *parent = 0); + ~Control(); + + unsigned int getChannelCount(); + + protected: + void run(); + + unsigned int calculateTriggerPoint(unsigned int value); + int getCaptureState(); + int getSamples(); + + Device *device; + + Helper::DataArray *command[COMMAND_COUNT]; ///< Pointers to commands, ready to be transmitted + bool commandPending[COMMAND_COUNT]; ///< true, when the command should be executed + Helper::DataArray *control[CONTROLINDEX_COUNT]; ///< Pointers to control commands + unsigned char controlCode[CONTROLINDEX_COUNT]; ///< Request codes for control commands + bool controlPending[CONTROLINDEX_COUNT]; ///< true, when the control command should be executed + + /// Calibration data for the channel offsets + unsigned short channelLevels[HANTEK_CHANNELS][GAIN_COUNT][OFFSET_COUNT]; + + // Various cached settings + Samplerate samplerate; + Gain gain[HANTEK_CHANNELS]; + double offset[HANTEK_CHANNELS]; + double offsetReal[HANTEK_CHANNELS]; + double triggerLevel[HANTEK_CHANNELS]; + unsigned int bufferSize; + unsigned int triggerPoint; + Dso::TriggerMode triggerMode; + bool triggerSpecial; + unsigned int triggerSource; + + QList samples; ///< Sample data arrays + QList samplesSize; ///< Number of samples data array + QMutex samplesMutex; ///< Mutex for the sample data + + // Lists for enums + QList gainSteps; ///< Voltage steps in V/screenheight + QList samplerateSteps; ///< Samplerate steps in S/s + QList samplerateValues; ///< Values sent to the oscilloscope + + public slots: + unsigned long int setSamplerate(unsigned long int samplerate); + double setBufferSize(unsigned int size); + + int setChannelUsed(unsigned int channel, bool used); + int setCoupling(unsigned int channel, Dso::Coupling coupling); + double setGain(unsigned int channel, double gain); + double setOffset(unsigned int channel, double offset); + + int setTriggerMode(Dso::TriggerMode mode); + int setTriggerSource(bool special, unsigned int id); + double setTriggerLevel(unsigned int channel, double level); + int setTriggerSlope(Dso::Slope slope); + double setTriggerPosition(double position); + }; +} + + +#endif diff --git b/openhantek/src/hantek/device.cpp a/openhantek/src/hantek/device.cpp new file mode 100644 index 0000000..ca2c99f --- /dev/null +++ a/openhantek/src/hantek/device.cpp @@ -0,0 +1,526 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// hantek/device.cpp +// +// Copyright (C) 2008, 2009 Oleg Khudyakov +// prcoder@potrebitel.ru +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include + + +#include "hantek/device.h" + +#include "helper.h" +#include "hantek/types.h" + + +namespace Hantek { + //////////////////////////////////////////////////////////////////////////////// + // class Hantek::Device + /// \brief Initializes the usb things and lists. + /// \param parent The parent widget. + Device::Device(QObject *parent) : QObject(parent) { + // Product ids and names for the Model enum + this->modelIds << 0x2090 << 0x2100 << 0x2150 << 0x2250 + << 0x5200 << 0x520A; + this->modelStrings << "DSO-2090" << "DSO-2100" << "DSO-2150" << "DSO-2250" + << "DSO-5200" << "DSO-5200A"; + + this->beginCommandControl = new ControlBeginCommand(); + + this->handle = 0; + this->interface = -1; + this->error = libusb_init(&(this->context)); + } + + /// \brief Disconnects the device. + Device::~Device() { + this->disconnect(); + } + + /// \brief Search for compatible devices. + /// \return A string with the result of the search. + QString Device::search() { + if(this->error) + return tr("Can't search for Hantek oscilloscopes: ").arg(Helper::libUsbErrorString(this->error)); + QString message; + +#if LIBUSB_VERSION == 0 + usb_init(); + usb_find_busses(); + usb_find_devices(); + + struct usb_device *usbDSO = NULL; + for (struct usb_bus *usb_bus = usb_busses; usb_bus; usb_bus = usb_bus->next) + { + for (struct usb_device *dev = usb_bus->devices; dev; dev = dev->next) + { + if (dev->descriptor.idVendor == deviceVendor) + { + for (int i = 0; deviceModelsList[i] != DSO_LAST; i++) + { + if (dev->descriptor.idProduct == deviceModelsList[i]) + { + usbDSO = dev; + deviceModel = dev->descriptor.idProduct; + qDebug("Hantek DSO model %4X found", deviceModel); + break; + } + } + } + } + } + + if (usbDSO == NULL) + { + dsoIOMutex.unlock(); + qDebug("Hantek DSO not found"); + return -1; + } + + if ((deviceModel == DSO_5200) || (deviceModel == DSO_5200A)) + { + extraBitsData = true; + qDebug("Using a 9-bita data model"); + } + + usbDSOHandle = ::usb_open(usbDSO); + if (usbDSOHandle == NULL) + { + dsoIOMutex.unlock(); + qDebug("Can't open USB device"); + return -2; + } + + struct usb_config_descriptor *usbConfig = usbDSO->config; + for (int i = 0; i < usbConfig->bNumInterfaces; i++) + { + struct usb_interface *usbInterface = &usbConfig->interface[i]; + if (usbInterface->num_altsetting < 1) + continue; + + struct usb_interface_descriptor *usbInterfaceDescr = &usbInterface->altsetting[0]; + if (usbInterfaceDescr->bInterfaceClass == USB_CLASS_VENDOR_SPEC + && usbInterfaceDescr->bInterfaceSubClass == 0 + && usbInterfaceDescr->bInterfaceProtocol == 0 + && usbInterfaceDescr->bNumEndpoints == 2) + { + if (::usb_claim_interface(usbDSOHandle, usbInterfaceDescr->bInterfaceNumber)) + { + if (::usb_close(usbDSOHandle)) + { + qDebug("Can't close USB handle"); + } + + dsoIOMutex.unlock(); + qDebug("Not able to claim USB interface"); + return -3; + } + + interfaceNumber = usbInterfaceDescr->bInterfaceNumber; + interfaceIsClaimed = true; + + for (int i = 0; i < usbInterfaceDescr->bNumEndpoints; i++) + { + usb_endpoint_descriptor *usbEndpointDescr = &usbInterfaceDescr->endpoint[i]; + switch (usbEndpointDescr->bEndpointAddress) + { + case 0x02: // EP OUT + epOutMaxPacketLen = usbEndpointDescr->wMaxPacketSize; + qDebug("EP OUT MaxPacketLen = %i", epOutMaxPacketLen); + break; + case 0x86: // EP IN + epInMaxPacketLen = usbEndpointDescr->wMaxPacketSize; + qDebug("EP IN MaxPacketLen = %i", epInMaxPacketLen); + break; + default: + qDebug("Unknown endpoint #%02X", usbEndpointDescr->bEndpointAddress); + } + } + + break; + } + } + + if (!interfaceIsClaimed) + { + qDebug("Can't find USB interface (Class:0xFF, SubClass:0, Protocol:0) with two endpoints"); + return -4; + } + + dsoIOMutex.unlock(); + + return 0; +#else + libusb_device **deviceList; + libusb_device *device; + int errorCode = LIBUSB_SUCCESS; + QString deviceAddress; + + if(this->handle) + libusb_close(this->handle); + + ssize_t deviceCount = libusb_get_device_list(this->context, &deviceList); + if (deviceCount < 0) + return tr("Failed to get device list: %3").arg(Helper::libUsbErrorString(errorCode)); + + // Iterate through all usb devices + for(ssize_t deviceIterator = 0; deviceIterator < deviceCount; deviceIterator++) { + device = deviceList[deviceIterator]; + // Get device descriptor + if(libusb_get_device_descriptor(device, &(this->descriptor)) < 0) + continue; + + // Check VID and PID + if(this->descriptor.idVendor == HANTEK_VENDOR_ID) { + this->model = (Model) this->modelIds.indexOf(this->descriptor.idProduct); + if(this->model >= 0) + break; // Found a compatible device, ignore others + } + } + + if(this->model >= 0) { + // Open device + deviceAddress = QString("%1:%2").arg(libusb_get_bus_number(device), 3, 10, QLatin1Char('0')).arg(libusb_get_device_address(device), 3, 10, QLatin1Char('0')); + errorCode = libusb_open(device, &(this->handle)); + if(errorCode == LIBUSB_SUCCESS) { + libusb_config_descriptor *configDescriptor; + const libusb_interface *interface; + const libusb_interface_descriptor *interfaceDescriptor; + + // Search for the needed interface + libusb_get_config_descriptor(device, 0, &configDescriptor); + for(int interfaceIndex = 0; interfaceIndex < (int) configDescriptor->bNumInterfaces; interfaceIndex++) { + interface = &configDescriptor->interface[interfaceIndex]; + if(interface->num_altsetting < 1) + continue; + + interfaceDescriptor = &interface->altsetting[0]; + if(interfaceDescriptor->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC && interfaceDescriptor->bInterfaceSubClass == 0 && interfaceDescriptor->bInterfaceProtocol == 0 && interfaceDescriptor->bNumEndpoints == 2) { + // That's the interface we need, claim it + errorCode = libusb_claim_interface(this->handle, interfaceDescriptor->bInterfaceNumber); + if(errorCode < 0) { + libusb_close(this->handle); + this->handle = 0; + message = tr("Failed to claim interface %1 of device %2: %3").arg(QString::number(interfaceDescriptor->bInterfaceNumber), deviceAddress, Helper::libUsbErrorString(errorCode)); + } + else { + this->interface = interfaceDescriptor->bInterfaceNumber; + + // Check the maximum endpoint packet size + const libusb_endpoint_descriptor *endpointDescriptor; + this->outPacketLength = 0; + this->inPacketLength = 0; + for (int endpoint = 0; endpoint < interfaceDescriptor->bNumEndpoints; endpoint++) { + endpointDescriptor = &(interfaceDescriptor->endpoint[endpoint]); + switch(endpointDescriptor->bEndpointAddress) { + case HANTEK_EP_OUT: + this->outPacketLength = endpointDescriptor->wMaxPacketSize; + break; + case HANTEK_EP_IN: + this->inPacketLength = endpointDescriptor->wMaxPacketSize; + break; + } + } + message = tr("Device found: Hantek %1 (%2)").arg(this->modelStrings[this->model], deviceAddress); + emit connected(); + } + } + } + + libusb_free_config_descriptor(configDescriptor); + } + else { + this->handle = 0; + message = tr("Couldn't open device %1: %2").arg(deviceAddress, Helper::libUsbErrorString(errorCode)); + } + } + else + message = tr("No Hantek oscilloscope found"); + + libusb_free_device_list(deviceList, true); + + return message; +#endif + } + + /// \brief Disconnect the device. + void Device::disconnect() { + if(!this->handle) + return; + + // Release claimed interface + libusb_release_interface(this->handle, this->interface); + this->interface = -1; + + // Close device handle + libusb_close(this->handle); + this->handle = 0; + + emit disconnected(); + } + + /// \brief Check if the oscilloscope is connected. + /// \return true, if a connection is up. + bool Device::isConnected() { + return this->handle != 0; + } + +#if LIBUSB_VERSION != 0 + /// \brief Bulk transfer to the oscilloscope. + /// \param endpoint Endpoint number, also sets the direction of the transfer. + /// \param data Buffer for the sent/recieved data. + /// \param length The length of the packet. + /// \param attempts The number of attempts, that are done on timeouts. + /// \return 0 on success, libusb error code on error. + int Device::bulkTransfer(unsigned char endpoint, unsigned char *data, unsigned int length, int attempts) { + if(!this->handle) + return LIBUSB_ERROR_NO_DEVICE; + + int errorCode = LIBUSB_ERROR_TIMEOUT; + int transferred; + for(int attempt = 0; (attempt < attempts || attempts == -1) && errorCode == LIBUSB_ERROR_TIMEOUT; attempt++) + errorCode = libusb_bulk_transfer(this->handle, endpoint, data, length, &transferred, HANTEK_TIMEOUT); + + if(errorCode == LIBUSB_ERROR_NO_DEVICE) + this->disconnect(); + if(errorCode < 0) + return errorCode; + else + return transferred; + } +#endif + + /// \brief Bulk write to the oscilloscope. + /// \param data Buffer for the sent/recieved data. + /// \param length The length of the packet. + /// \param attempts The number of attempts, that are done on timeouts. + /// \return 0 on success, libusb error code on error. + int Device::bulkWrite(unsigned char *data, unsigned int length, int attempts) { + if(!this->handle) + return LIBUSB_ERROR_NO_DEVICE; + + int errorCode = this->getConnectionSpeed(); + if(errorCode < 0) + return errorCode; + +#if LIBUSB_VERSION == 0 + int i, rv = -ETIMEDOUT; + for(i = 0; (rv == -ETIMEDOUT) && (i < attempts); i++) + { + rv = ::usb_bulk_write(usbDSOHandle, EP_BULK_OUT | USB_ENDPOINT_OUT, (char*)data, length, timeout); + } + + if (rv < 0) + { + qDebug("Usb write bulk returns error %i", rv); + qDebug("Error: %s", ::usb_strerror()); + return rv; + } + + return 0; +#else + return this->bulkTransfer(HANTEK_EP_OUT, data, length, attempts); +#endif + } + + /// \brief Bulk read from the oscilloscope. + /// \param data Buffer for the sent/recieved data. + /// \param length The length of the packet. + /// \param attempts The number of attempts, that are done on timeouts. + /// \return 0 on success, libusb error code on error. + int Device::bulkRead(unsigned char *data, unsigned int length, int attempts) { + if(!this->handle) + return LIBUSB_ERROR_NO_DEVICE; + + int errorCode = this->getConnectionSpeed(); + if(errorCode < 0) + return errorCode; + +#if LIBUSB_VERSION == 0 + int i, rv = -ETIMEDOUT; + for(i = 0; (rv == -ETIMEDOUT) && (i < attempts); i++) + { + rv = ::usb_bulk_read(usbDSOHandle, EP_BULK_IN | USB_ENDPOINT_IN, (char*)data, length, timeout); + } + + if (rv < 0) + { + qDebug("Usb read bulk returns error %i", rv); + qDebug("Error: %s", ::usb_strerror()); + return rv; + } + + return 0; +#else + return this->bulkTransfer(HANTEK_EP_IN, data, length, attempts); +#endif + } + + /// \brief Send a bulk command to the oscilloscope. + /// \param command The command, that should be sent. + /// \param attempts The number of attempts, that are done on timeouts. + /// \return 0 on success, libusb error code on error. + int Device::bulkCommand(Helper::DataArray *command, int attempts) { + if(!this->handle) + return LIBUSB_ERROR_NO_DEVICE; + + // Send BeginCommand control command + int errorCode = this->controlWrite(CONTROL_BEGINCOMMAND, this->beginCommandControl->data(), this->beginCommandControl->getSize()); + if(errorCode < 0) + return errorCode; + + // Send bulk command + return this->bulkWrite(command->data(), command->getSize(), attempts); + } + + /// \brief Multi packet bulk read from the oscilloscope. + /// \param data Buffer for the sent/recieved data. + /// \param length The length of data contained in the packets. + /// \param attempts The number of attempts, that are done on timeouts. + /// \return 0 on success, libusb error code on error. + int Device::bulkReadMulti(unsigned char *data, unsigned int length, int attempts) { + if(!this->handle) + return LIBUSB_ERROR_NO_DEVICE; + + int errorCode = 0; + + errorCode = this->getConnectionSpeed(); + if(errorCode < 0) + return errorCode; + + int packetCount = length / this->inPacketLength; + + errorCode = this->inPacketLength; + int packet; + for(packet = 0; packet < packetCount && errorCode == this->inPacketLength; packet++) + errorCode = this->bulkTransfer(HANTEK_EP_IN, data + packet * this->inPacketLength, this->inPacketLength, attempts); + + if(errorCode < 0) + return errorCode; + else + return (packet - 1) * this->inPacketLength + errorCode; + } + +#if LIBUSB_VERSION != 0 + /// \brief Control transfer to the oscilloscope. + /// \param type The request type, also sets the direction of the transfer. + /// \param request The request field of the packet. + /// \param data Buffer for the sent/recieved data. + /// \param length The length field of the packet. + /// \param value The value field of the packet. + /// \param index The index field of the packet. + /// \param attempts The number of attempts, that are done on timeouts. + /// \return 0 on success, libusb error code on error. + int Device::controlTransfer(unsigned char type, unsigned char request, unsigned char *data, unsigned int length, int value, int index, int attempts) { + if(!this->handle) + return LIBUSB_ERROR_NO_DEVICE; + + int errorCode = LIBUSB_ERROR_TIMEOUT; + for(int attempt = 0; (attempt < attempts || attempts == -1) && errorCode == LIBUSB_ERROR_TIMEOUT; attempt++) + errorCode = libusb_control_transfer(this->handle, type, request, value, index, data, length, HANTEK_TIMEOUT); + + if(errorCode == LIBUSB_ERROR_NO_DEVICE) + this->disconnect(); + return errorCode; + } +#endif + + /// \brief Control write to the oscilloscope. + /// \param request The request field of the packet. + /// \param data Buffer for the sent/recieved data. + /// \param length The length field of the packet. + /// \param value The value field of the packet. + /// \param index The index field of the packet. + /// \param attempts The number of attempts, that are done on timeouts. + /// \return 0 on success, libusb error code on error. + int Device::controlWrite(unsigned char request, unsigned char *data, unsigned int length, int value, int index, int attempts) { + if(!this->handle) + return LIBUSB_ERROR_NO_DEVICE; + +#if LIBUSB_VERSION == 0 + int i, rv = -ETIMEDOUT; + for(i = 0; (rv == -ETIMEDOUT) && (i < attempts); i++) + { + rv = ::usb_control_msg(usbDSOHandle, USB_ENDPOINT_OUT | USB_TYPE_VENDOR, + request, value, index, (char*)data, length, timeout); + } + + if (rv < 0) + { + qDebug("Usb write control message %02X returns error %i", request, rv); + qDebug("Error: %s", ::usb_strerror()); + return rv; + } + + return 0; +#else + return this->controlTransfer(LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, request, data, length, value, index,attempts); +#endif + } + + /// \brief Control read to the oscilloscope. + /// \param request The request field of the packet. + /// \param data Buffer for the sent/recieved data. + /// \param length The length field of the packet. + /// \param value The value field of the packet. + /// \param index The index field of the packet. + /// \param attempts The number of attempts, that are done on timeouts. + /// \return 0 on success, libusb error code on error. + int Device::controlRead(unsigned char request, unsigned char *data, unsigned int length, int value, int index, int attempts) { + if(!this->handle) + return LIBUSB_ERROR_NO_DEVICE; + +#if LIBUSB_VERSION == 0 + int i, rv = -ETIMEDOUT; + for(i = 0; (rv == -ETIMEDOUT) && (i < attempts); i++) + { + rv = ::usb_control_msg(usbDSOHandle, USB_ENDPOINT_IN | USB_TYPE_VENDOR, + request, value, index, (char*)data, length, timeout); + } + + if (rv < 0) + { + qDebug("Usb read control message %02X returns error %i", request, rv); + qDebug("Error: %s", ::usb_strerror()); + return rv; + } + + return 0; +#else + return this->controlTransfer(LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, request, data, length, value, index, attempts); +#endif + } + + /// \brief Gets the speed of the connection. + /// \return The #ConnectionSpeed of the USB connection. + int Device::getConnectionSpeed() { + int errorCode; + ControlGetSpeed response; + + errorCode = this->controlRead(CONTROL_GETSPEED, response.data(), response.getSize()); + if(errorCode < 0) + return errorCode; + + return response.getSpeed(); + } +} diff --git b/openhantek/src/hantek/device.h a/openhantek/src/hantek/device.h new file mode 100644 index 0000000..fd74a29 --- /dev/null +++ a/openhantek/src/hantek/device.h @@ -0,0 +1,111 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file hantek/device.h +/// \brief Declares the Hantek::Device class. +// +// Copyright (C) 2008, 2009 Oleg Khudyakov +// prcoder@potrebitel.ru +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef HANTEK_DEVICE_H +#define HANTEK_DEVICE_H + + +#include +#include + +#if LIBUSB_VERSION == 0 +#include +#define libusb_device_handle usb_device_handle +#define libusb_device_descriptor usb_device_descriptor +#else +#include +#endif + + +#include "helper.h" +#include "hantek/types.h" + + +namespace Hantek { + ////////////////////////////////////////////////////////////////////////////// + /// \class Device hantek/device.h + /// \brief This class handles the USB communication with the oscilloscope. + class Device : public QObject { + Q_OBJECT + + public: + Device(QObject *parent = 0); + ~Device(); + + QString search(); + void disconnect(); + bool isConnected(); + + // Various methods to handle USB transfers +#if LIBUSB_VERSION != 0 + int bulkTransfer(unsigned char endpoint, unsigned char *data, unsigned int length, int attempts = HANTEK_ATTEMPTS_DEFAULT); +#endif + int bulkWrite(unsigned char *data, unsigned int length, int attempts = HANTEK_ATTEMPTS_DEFAULT); + int bulkRead(unsigned char *data, unsigned int length, int attempts = HANTEK_ATTEMPTS_DEFAULT); + + int bulkCommand(Helper::DataArray *command, int attempts = HANTEK_ATTEMPTS_DEFAULT); + int bulkReadMulti(unsigned char *data, unsigned int length, int attempts = HANTEK_ATTEMPTS_DEFAULT); + +#if LIBUSB_VERSION != 0 + int controlTransfer(unsigned char type, unsigned char request, unsigned char *data, unsigned int length, int value, int index, int attempts = HANTEK_ATTEMPTS_DEFAULT); +#endif + int controlWrite(unsigned char request, unsigned char *data, unsigned int length, int value = 0, int index = 0, int attempts = HANTEK_ATTEMPTS_DEFAULT); + int controlRead(unsigned char request, unsigned char *data, unsigned int length, int value = 0, int index = 0, int attempts = HANTEK_ATTEMPTS_DEFAULT); + + int getConnectionSpeed(); + + protected: + // Lists for enums + QList modelIds; ///< Product ID for each #Model + QStringList modelStrings; ///< The name as QString for each #Model + + // Command buffers + ControlBeginCommand *beginCommandControl; + + // Libusb specific variables +#if LIBUSB_VERSION != 0 + libusb_context *context; ///< The usb context used for this device +#endif + Model model; ///< The model of the connected oscilloscope + libusb_device_handle *handle; ///< The USB handle for the oscilloscope + libusb_device_descriptor descriptor; ///< The device descriptor of the oscilloscope + int interface; ///< The number of the claimed interface + int error; ///< The libusb error, that happened on initialization + int outPacketLength; ///< Packet length for the OUT endpoint + int inPacketLength; ///< Packet length for the IN endpoint + + signals: + void connected(); + void disconnected(); + + public slots: + + }; +} + + +#endif diff --git b/openhantek/src/hantek/types.cpp a/openhantek/src/hantek/types.cpp new file mode 100644 index 0000000..4c87612 --- /dev/null +++ a/openhantek/src/hantek/types.cpp @@ -0,0 +1,560 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// hantek/types.cpp +// +// Copyright (C) 2008, 2009 Oleg Khudyakov +// prcoder@potrebitel.ru +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include + + +#include "hantek/types.h" + + +namespace Hantek { + //////////////////////////////////////////////////////////////////////////////// + // class CommandSetFilter + /// \brief Sets the data array to the default values. + CommandSetFilter::CommandSetFilter() : Helper::DataArray(8) { + this->init(); + } + + /// \brief Sets the FilterByte to the given value. + /// \param channel1 true if channel 1 is filtered. + /// \param channel2 true if channel 2 is filtered. + /// \param trigger true if trigger is filtered. + CommandSetFilter::CommandSetFilter(bool channel1, bool channel2, bool trigger) : Helper::DataArray(8) { + this->init(); + + this->setChannel(0, channel1); + this->setChannel(1, channel2); + this->setTrigger(trigger); + } + + /// \brief Gets the filtering state of one channel. + /// \param channel The channel whose filtering state should be returned. + /// \return The filtering state of the channel. + bool CommandSetFilter::getChannel(unsigned int channel) { + FilterBits *filterBits = (FilterBits *) &(this->array[2]); + if(channel == 0) + return filterBits->channel1 == 1; + else + return filterBits->channel2 == 1; + } + + /// \brief Enables/disables filtering of one channel. + /// \param channel The channel that should be set. + /// \param filtered true if the channel should be filtered. + void CommandSetFilter::setChannel(unsigned int channel, bool filtered) { + FilterBits *filterBits = (FilterBits *) &(this->array[2]); + if(channel == 0) + filterBits->channel1 = filtered ? 1 : 0; + else + filterBits->channel2 = filtered ? 1 : 0; + } + + /// \brief Gets the filtering state for the trigger. + /// \return The filtering state of the trigger. + bool CommandSetFilter::getTrigger() { + return ((FilterBits *) &(this->array[2]))->trigger == 1; + } + + /// \brief Enables/disables filtering for the trigger. + /// \param filtered true if the trigger should be filtered. + void CommandSetFilter::setTrigger(bool filtered) { + FilterBits *filterBits = (FilterBits *) &(this->array[2]); + + filterBits->trigger = filtered ? 1 : 0; + } + + /// \brief Initialize the array to the needed values. + void CommandSetFilter::init() { + this->array[0] = COMMAND_SETFILTER; + this->array[1] = 0x0f; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class CommandSetTriggerAndSamplerate + /// \brief Sets the data array to the default values. + CommandSetTriggerAndSamplerate::CommandSetTriggerAndSamplerate() : Helper::DataArray(12) { + this->init(); + } + + /// \brief Sets the data bytes to the specified values. + /// \param tsr1Byte The Tsr1 value. + /// \param tsr2Byte The Tsr2 value. + /// \param timebase The new timebase value. + /// \param triggerPosition The new horizontal trigger position. + CommandSetTriggerAndSamplerate::CommandSetTriggerAndSamplerate(unsigned short int samplerate, unsigned long int triggerPosition, unsigned char triggerSource, unsigned char sampleSize, unsigned char timebaseFast, unsigned char selectedChannel, bool fastRate, unsigned char triggerSlope) : Helper::DataArray(12) { + this->init(); + + this->setTriggerSource(triggerSource); + this->setSampleSize(sampleSize); + this->setSamplerateFast(timebaseFast); + this->setUsedChannel(selectedChannel); + this->setFastRate(fastRate); + this->setTriggerSlope(triggerSlope); + this->setSamplerate(samplerate); + this->setTriggerPosition(triggerPosition); + } + + /// \brief Get the triggerSource value in Tsr1Bits. + /// \return The triggerSource value. + unsigned char CommandSetTriggerAndSamplerate::getTriggerSource() { + return ((Tsr1Bits *) &(this->array[2]))->triggerSource; + } + + /// \brief Set the triggerSource in Tsr1Bits to the given value. + /// \param value The new triggerSource value. + void CommandSetTriggerAndSamplerate::setTriggerSource(unsigned char value) { + ((Tsr1Bits *) &(this->array[2]))->triggerSource = value; + } + + /// \brief Get the sampleSize value in Tsr1Bits. + /// \return The sampleSize value. + unsigned char CommandSetTriggerAndSamplerate::getSampleSize() { + return ((Tsr1Bits *) &(this->array[2]))->sampleSize; + } + + /// \brief Set the sampleSize in Tsr1Bits to the given value. + /// \param value The new sampleSize value. + void CommandSetTriggerAndSamplerate::setSampleSize(unsigned char value) { + ((Tsr1Bits *) &(this->array[2]))->sampleSize = value; + } + + /// \brief Get the samplerateFast value in Tsr1Bits. + /// \return The samplerateFast value. + unsigned char CommandSetTriggerAndSamplerate::getSamplerateFast() { + return ((Tsr1Bits *) &(this->array[2]))->samplerateFast; + } + + /// \brief Set the samplerateFast in Tsr1Bits to the given value. + /// \param value The new samplerateFast value. + void CommandSetTriggerAndSamplerate::setSamplerateFast(unsigned char value) { + ((Tsr1Bits *) &(this->array[2]))->samplerateFast = value; + } + + /// \brief Get the selectedChannel value in Tsr2Bits. + /// \return The selectedChannel value. + unsigned char CommandSetTriggerAndSamplerate::getUsedChannel() { + return ((Tsr2Bits *) &(this->array[3]))->selectedChannel; + } + + /// \brief Set the selectedChannel in Tsr2Bits to the given value. + /// \param value The new selectedChannel value. + void CommandSetTriggerAndSamplerate::setUsedChannel(unsigned char value) { + ((Tsr2Bits *) &(this->array[3]))->selectedChannel = value; + } + + /// \brief Get the fastRate state in Tsr2Bits. + /// \return The fastRate state. + bool CommandSetTriggerAndSamplerate::getFastRate() { + return ((Tsr2Bits *) &(this->array[3]))->fastRate == 1; + } + + /// \brief Set the fastRate in Tsr2Bits to the given state. + /// \param fastRate The new fastRate state. + void CommandSetTriggerAndSamplerate::setFastRate(bool fastRate) { + ((Tsr2Bits *) &(this->array[3]))->fastRate = fastRate ? 1 : 0; + } + + /// \brief Get the triggerSlope value in Tsr2Bits. + /// \return The triggerSlope value. + unsigned char CommandSetTriggerAndSamplerate::getTriggerSlope() { + return ((Tsr2Bits *) &(this->array[3]))->triggerSlope; + } + + /// \brief Set the triggerSlope in Tsr2Bits to the given value. + /// \param slope The new triggerSlope value. + void CommandSetTriggerAndSamplerate::setTriggerSlope(unsigned char slope) { + ((Tsr2Bits *) &(this->array[3]))->triggerSlope = slope; + } + + /// \brief Get the Samplerate value. + /// \return The samplerate value. + unsigned short int CommandSetTriggerAndSamplerate::getSamplerate() { + return (unsigned short int) this->array[4] | ((unsigned short int) this->array[5] << 8); + } + + /// \brief Set the Samplerate to the given value. + /// \param timebase The new samplerate value. + void CommandSetTriggerAndSamplerate::setSamplerate(unsigned short int samplerate) { + this->array[4] = (unsigned char) samplerate; + this->array[5] = (unsigned char) (samplerate >> 8); + } + + /// \brief Get the TriggerPosition value. + /// \return The horizontal trigger position. + unsigned long int CommandSetTriggerAndSamplerate::getTriggerPosition() { + return (unsigned long int) this->array[6] | ((unsigned long int) this->array[7] << 8) | ((unsigned long int) this->array[10] << 16); + } + + /// \brief Set the TriggerPosition to the given value. + /// \param position The new horizontal trigger position. + void CommandSetTriggerAndSamplerate::setTriggerPosition(unsigned long int position) { + this->array[6] = (unsigned char) position; + this->array[7] = (unsigned char) (position >> 8); + this->array[10] = (unsigned char) (position >> 16); + } + + /// \brief Initialize the array to the needed values. + void CommandSetTriggerAndSamplerate::init() { + this->array[0] = COMMAND_SETTRIGGERANDSAMPLERATE; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class CommandForceTrigger + /// \brief Sets the data array to needed values. + CommandForceTrigger::CommandForceTrigger() : Helper::DataArray(2) { + this->array[0] = COMMAND_FORCETRIGGER; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class CommandCaptureStart + /// \brief Sets the data array to needed values. + CommandCaptureStart::CommandCaptureStart() : Helper::DataArray(2) { + this->array[0] = COMMAND_STARTSAMPLING; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class CommandTriggerEnabled + /// \brief Sets the data array to needed values. + CommandTriggerEnabled::CommandTriggerEnabled() : Helper::DataArray(2) { + this->array[0] = COMMAND_ENABLETRIGGER; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class CommandGetData + /// \brief Sets the data array to needed values. + CommandGetData::CommandGetData() : Helper::DataArray(2) { + this->array[0] = COMMAND_GETDATA; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class CommandGetCaptureState + /// \brief Sets the data array to needed values. + CommandGetCaptureState::CommandGetCaptureState() : Helper::DataArray(2) { + this->array[0] = COMMAND_GETCAPTURESTATE; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class ResponseGetCaptureState + /// \brief Initializes the array. + ResponseGetCaptureState::ResponseGetCaptureState() : Helper::DataArray(512) { + } + + /// \brief Gets the capture state. + /// \return The CaptureState of the oscilloscope. + CaptureState ResponseGetCaptureState::getCaptureState() { + return (CaptureState) this->array[0]; + } + + /// \brief Gets the trigger point. + /// \return The trigger point for the captured samples. + unsigned int ResponseGetCaptureState::getTriggerPoint() { + return this->array[2] | (this->array[3] << 8); + } + + + //////////////////////////////////////////////////////////////////////////////// + // class CommandSetGain + /// \brief Sets the data array to needed values. + CommandSetGain::CommandSetGain() : Helper::DataArray(8) { + this->init(); + } + + /// \brief Sets the gain to the given values. + /// \param channel1 The gain value for channel 1. + /// \param channel2 The gain value for channel 2. + CommandSetGain::CommandSetGain(unsigned char channel1, unsigned char channel2) : Helper::DataArray(8) { + this->init(); + + this->setGain(0, channel1); + this->setGain(1, channel2); + } + + /// \brief Get the gain for the given channel. + /// \param channel The channel whose gain should be returned. + /// \returns The gain value. + unsigned char CommandSetGain::getGain(unsigned int channel) { + GainBits *gainBits = (GainBits *) &(this->array[2]); + if(channel == 0) + return gainBits->channel1; + else + return gainBits->channel2; + } + + /// \brief Set the gain for the given channel. + /// \param channel The channel that should be set. + /// \param value The new gain value for the channel. + void CommandSetGain::setGain(unsigned int channel, unsigned char value) { + GainBits *gainBits = (GainBits *) &(this->array[2]); + if(channel == 0) + gainBits->channel1 = value; + else + gainBits->channel2 = value; + } + + /// \brief Initialize the array to the needed values. + void CommandSetGain::init() { + this->array[0] = COMMAND_SETGAIN; + this->array[1] = 0x0f; + ((GainBits *) &(this->array[2]))->constant = 3; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class CommandSetLogicalData + /// \brief Sets the data array to needed values. + CommandSetLogicalData::CommandSetLogicalData() : Helper::DataArray(8) { + this->init(); + } + + /// \brief Sets the data to the given value. + /// \param data The data byte. + CommandSetLogicalData::CommandSetLogicalData(unsigned char data) : Helper::DataArray(8) { + this->init(); + + this->setData(data); + } + + /// \brief Gets the data. + /// \returns The data byte. + unsigned char CommandSetLogicalData::getData() { + return this->array[2]; + } + + /// \brief Sets the data to the given value. + /// \param data The new data byte. + void CommandSetLogicalData::setData(unsigned char data) { + this->array[2] = data; + } + + /// \brief Initialize the array to the needed values. + void CommandSetLogicalData::init() { + this->array[0] = COMMAND_SETLOGICALDATA; + this->array[1] = 0x0f; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class CommandGetLogicalData + /// \brief Sets the data array to needed values. + CommandGetLogicalData::CommandGetLogicalData() : Helper::DataArray(2) { + this->array[0] = COMMAND_GETLOGICALDATA; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class ControlGetSpeed + /// \brief Initializes the array. + ControlGetSpeed::ControlGetSpeed() : Helper::DataArray(10) { + } + + /// \brief Gets the speed of the connection. + /// \return The speed level of the USB connection. + ConnectionSpeed ControlGetSpeed::getSpeed() { + return (ConnectionSpeed) this->array[0]; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class ControlBeginCommand + /// \brief Sets the command index to the given value. + /// \param index The CommandIndex for the command. + ControlBeginCommand::ControlBeginCommand(CommandIndex index) : Helper::DataArray(10) { + this->init(); + + this->setIndex(index); + } + + /// \brief Gets the command index. + /// \return The CommandIndex for the command. + CommandIndex ControlBeginCommand::getIndex() { + return (CommandIndex) this->array[1]; + } + + /// \brief Sets the command index to the given value. + /// \param index The new CommandIndex for the command. + void ControlBeginCommand::setIndex(CommandIndex index) { + memset(&(this->array[1]), (unsigned char) index, 3); + } + + /// \brief Initialize the array to the needed values. + void ControlBeginCommand::init() { + this->array[0] = 0x0f; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class ControlSetOffset + /// \brief Sets the data array to the default values. + ControlSetOffset::ControlSetOffset() : Helper::DataArray(17) { + } + + /// \brief Sets the offsets to the given values. + /// \param channel1 The offset for channel 1. + /// \param channel2 The offset for channel 2. + /// \param ext The offset for ext. trigger. + ControlSetOffset::ControlSetOffset(unsigned short int channel1, unsigned short int channel2, unsigned short int trigger) : Helper::DataArray(17) { + this->setChannel(0, channel1); + this->setChannel(1, channel2); + this->setTrigger(trigger); + } + + /// \brief Get the offset for the given channel. + /// \param channel The channel whose offset should be returned. + /// \return The channel offset value. + unsigned short int ControlSetOffset::getChannel(unsigned int channel) { + if(channel == 0) + return ((this->array[0] & 0xdf) << 8) | this->array[1]; + else + return ((this->array[2] & 0xdf) << 8) | this->array[3]; + } + + /// \brief Set the offset for the given channel. + /// \param channel The channel that should be set. + /// \param offset The new channel offset value. + void ControlSetOffset::setChannel(unsigned int channel, unsigned short int offset) { + if(channel == 0) { + this->array[0] = (unsigned char) (offset >> 8) | 0x20; + this->array[1] = (unsigned char) offset; + } + else { + this->array[2] = (unsigned char) (offset >> 8) | 0x20; + this->array[3] = (unsigned char) offset; + } + } + + /// \brief Get the trigger level. + /// \return The trigger level value. + unsigned short int ControlSetOffset::getTrigger() { + return ((this->array[4] & 0xdf) << 8) | this->array[5]; + } + + /// \brief Set the trigger level. + /// \param level The new trigger level value. + void ControlSetOffset::setTrigger(unsigned short int level) { + this->array[4] = (unsigned char) (level >> 8) | 0x20; + this->array[5] = (unsigned char) level; + } + + + //////////////////////////////////////////////////////////////////////////////// + // class ControlSetRelays + /// \brief Sets all relay states. + /// \param ch1Below1V Sets the state of the Channel 1 below 1 V relay. + /// \param ch1Below100mV Sets the state of the Channel 1 below 100 mV relay. + /// \param ch1CouplingDC Sets the state of the Channel 1 coupling relay. + /// \param ch2Below1V Sets the state of the Channel 2 below 1 V relay. + /// \param ch2Below100mV Sets the state of the Channel 2 below 100 mV relay. + /// \param ch2CouplingDC Sets the state of the Channel 2 coupling relay. + /// \param triggerExt Sets the state of the external trigger relay. + ControlSetRelays::ControlSetRelays(bool ch1Below1V, bool ch1Below100mV, bool ch1CouplingDC, bool ch2Below1V, bool ch2Below100mV, bool ch2CouplingDC, bool triggerExt) : Helper::DataArray(17) { + this->setBelow1V(0, ch1Below1V); + this->setBelow100mV(0, ch1Below100mV); + this->setCoupling(0, ch1CouplingDC); + this->setBelow1V(1, ch2Below1V); + this->setBelow100mV(1, ch2Below100mV); + this->setCoupling(1, ch2CouplingDC); + this->setTrigger(triggerExt); + } + + /// \brief Get the below 1 V relay state for the given channel. + /// \param channel The channel whose relay state should be returned. + /// \return true, if the gain of the channel is below 1 V. + bool ControlSetRelays::getBelow1V(unsigned int channel) { + if(channel == 0) + return (this->array[1] & 0x04) == 0x00; + else + return (this->array[4] & 0x20) == 0x00; + } + + /// \brief Set the below 1 V relay for the given channel. + /// \param channel The channel that should be set. + /// \param below true, if the gain of the channel should be below 1 V. + void ControlSetRelays::setBelow1V(unsigned int channel, bool below) { + if(channel == 0) + this->array[1] = below ? 0xfb : 0x04; + else + this->array[4] = below ? 0xdf : 0x20; + } + + /// \brief Get the below 1 V relay state for the given channel. + /// \param channel The channel whose relay state should be returned. + /// \return true, if the gain of the channel is below 1 V. + bool ControlSetRelays::getBelow100mV(unsigned int channel) { + if(channel == 0) + return (this->array[2] & 0x08) == 0x00; + else + return (this->array[5] & 0x40) == 0x00; + } + + /// \brief Set the below 100 mV relay for the given channel. + /// \param channel The channel that should be set. + /// \param below true, if the gain of the channel should be below 100 mV. + void ControlSetRelays::setBelow100mV(unsigned int channel, bool below) { + if(channel == 0) + this->array[2] = below ? 0xf7 : 0x08; + else + this->array[5] = below ? 0xbf : 0x40; + } + + /// \brief Get the coupling relay state for the given channel. + /// \param channel The channel whose relay state should be returned. + /// \return true, if the coupling of the channel is DC. + bool ControlSetRelays::getCoupling(unsigned int channel) { + if(channel == 0) + return (this->array[3] & 0x02) == 0x00; + else + return (this->array[6] & 0x10) == 0x00; + } + + /// \brief Set the coupling relay for the given channel. + /// \param channel The channel that should be set. + /// \param below true, if the coupling of the channel should be DC. + void ControlSetRelays::setCoupling(unsigned int channel, bool dc) { + if(channel == 0) + this->array[3] = dc ? 0xfd : 0x02; + else + this->array[6] = dc ? 0xef : 0x10; + } + + /// \brief Get the external trigger relay state. + /// \return true, if the trigger is external (EXT-Connector). + bool ControlSetRelays::getTrigger() { + return (this->array[7] & 0x01) == 0x00; + } + + /// \brief Set the external trigger relay. + /// \param below true, if the trigger should be external (EXT-Connector). + void ControlSetRelays::setTrigger(bool ext) { + this->array[7] = ext ? 0xfe : 0x01; + } +} diff --git b/openhantek/src/hantek/types.h a/openhantek/src/hantek/types.h new file mode 100644 index 0000000..cecb572 --- /dev/null +++ a/openhantek/src/hantek/types.h @@ -0,0 +1,674 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file hantek/types.h +/// \brief Declares types needed for the Hantek::Device class. +// +// Copyright (C) 2008, 2009 Oleg Khudyakov +// prcoder@potrebitel.ru +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef HANTEK_TYPES_H +#define HANTEK_TYPES_H + + +#include "helper.h" + + +#define HANTEK_VENDOR_ID 0x04b5 ///< VID for Hantek DSOs with loaded fw +#define HANTEK_EP_OUT 0x02 ///< OUT Endpoint for bulk transfers +#define HANTEK_EP_IN 0x86 ///< IN Endpoint for bulk transfers +#define HANTEK_TIMEOUT 500 ///< Timeout for USB transfers in ms +#define HANTEK_ATTEMPTS_DEFAULT 3 ///< The number of transfer attempts + +#define HANTEK_CHANNELS 2 ///< Number of physical channels +#define HANTEK_SPECIAL_CHANNELS 2 ///< Number of special channels + + +//////////////////////////////////////////////////////////////////////////////// +/// \namespace Hantek hantek/types.h +/// \brief All Hantek DSO device specific things. +namespace Hantek { + ////////////////////////////////////////////////////////////////////////////// + /// \enum CommandCode hantek/types.h + /// \brief All supported bulk commands. + /// Indicies given in square brackets specify byte numbers in little endian format. + enum CommandCode { + /// This command sets channel and trigger filter: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
0x000x0fFilterBits0x000x000x000x000x00
+ COMMAND_SETFILTER, + + /// This command sets trigger and timebase: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
0x010x00Tsr1BitsTsr2BitsSamplerate[0]Samplerate[1]TriggerPosition[0]TriggerPosition[1]0x000x00TriggerPosition[2]0x00
+ COMMAND_SETTRIGGERANDSAMPLERATE, + + /// This command forces triggering: + /// + /// + /// + /// + /// + ///
0x020x00
+ COMMAND_FORCETRIGGER, + + /// This command starts to capture data: + /// + /// + /// + /// + /// + ///
0x030x00
+ COMMAND_STARTSAMPLING, + + /// This command sets the trigger: + /// + /// + /// + /// + /// + ///
0x040x00
+ COMMAND_ENABLETRIGGER, + + /// This command reads data from the hardware: + /// + /// + /// + /// + /// + ///
0x050x00
+ /// The oscilloscope returns the sample data, that will be split if it's larger than the IN endpoint packet length: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
Sample[0]...Sample[511]
Sample[512]...Sample[1023]
Sample[1024]......
+ COMMAND_GETDATA, + + /// This command checks the capture state: + /// + /// + /// + /// + /// + ///
0x060x00
+ /// The oscilloscope returns it's capture state and the trigger point (Not sure about this, looks like 248 16-bit words with nearly constant values): + /// + /// + /// + /// + /// + /// + /// + /// + ///
#CaptureState0x00TriggerPoint[0]TriggerPoint[1]...
+ COMMAND_GETCAPTURESTATE, + + /// This command sets the gain: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
0x070x0fGainBits0x000x000x000x000x00
+ /// It is usually used in combination with #CONTROL_SETRELAYS. + COMMAND_SETGAIN, + + /// This command sets the logical data (And what the hell is this?...): + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
0x080x0fData0x000x000x000x000x00
+ COMMAND_SETLOGICALDATA, + + /// This command reads the logical data (And what the hell is this?...): + /// + /// + /// + /// + /// + ///
0x090x00
+ /// The oscilloscope returns the logical data: + /// + /// + /// + /// + /// + /// + /// + ///
???...
+ COMMAND_GETLOGICALDATA, + + COMMAND_COUNT ///< Total number of commands + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum ControlCode hantek/types.h + /// \brief All supported control commands. + enum ControlCode { + /// This control read/write command gives access to a #ControlValue. + CONTROL_VALUE = 0xA2, + + /// This control read command gets the speed level of the USB connection: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
#ConnectionSpeed0x000x000x000x000x000x000x000x000x00
+ CONTROL_GETSPEED = 0xB2, + + /// This control write command is sent before any bulk command: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
0x0f#CommandIndex#CommandIndex#CommandIndex0x000x000x000x000x000x00
+ CONTROL_BEGINCOMMAND = 0xB3, + + /// This control write command sets the channel offsets: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
Ch1Offset[1] | 0x20Ch1Offset[0]Ch2Offset[1] | 0x20Ch2Offset[0]TriggerOffset[1] | 0x20TriggerOffset[0]0x000x000x000x000x000x000x000x000x000x000x00
+ CONTROL_SETOFFSET = 0xB4, + + /// This control write command sets the internal relays: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
0x000x04 ^ (Ch1Gain < 1 V)0x08 ^ (Ch1Gain < 100 mV)0x02 ^ (Ch1Coupling == DC)0x20 ^ (Ch2Gain < 1 V)0x40 ^ (Ch2Gain < 100 mV)0x10 ^ (Ch2Coupling == DC)0x01 ^ (Trigger == EXT)0x000x000x000x000x000x000x000x000x00
+ CONTROL_SETRELAYS = 0xB5 + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum ControlValue hantek/types.h + /// \brief All supported values for control commands. + enum ControlValue { + VALUE_CHANNELLEVEL = 0x08, + VALUE_DEVICEADDRESS = 0x0A, + VALUE_CALIBRATIONDATA = 0x60 + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum Model hantek/types.h + /// \brief All supported Hantek DSO models. + enum Model { + MODEL_UNKNOWN = -1, + MODEL_DSO2090, MODEL_DSO2100, MODEL_DSO2150, MODEL_DSO2250, + MODEL_DSO5200, MODEL_DSO5200A, + MODEL_COUNT + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum ConnectionSpeed hantek/types.h + /// \brief The speed level of the USB connection. + enum ConnectionSpeed { + CONNECTION_FULLSPEED = 0, + CONNECTION_HIGHSPEED = 1 + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum Samplerate hantek/types.h + /// \brief The different samplerates supported by Hantek DSOs. + enum Samplerate { + SAMPLERATE_100MS, + SAMPLERATE_50MS, SAMPLERATE_25MS, SAMPLERATE_10MS, + SAMPLERATE_5MS, SAMPLERATE_2_5MS, SAMPLERATE_1MS, + SAMPLERATE_500KS, SAMPLERATE_250KS, SAMPLERATE_100KS, + SAMPLERATE_50KS, SAMPLERATE_25KS, SAMPLERATE_10KS, + SAMPLERATE_5KS, SAMPLERATE_2_5KS, SAMPLERATE_1KS, + SAMPLERATE_COUNT + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum Gain hantek/types.h + /// \brief The different gain steps supported by Hantek DSOs. + enum Gain { + GAIN_10MV, GAIN_20MV, GAIN_50MV, + GAIN_100MV, GAIN_200MV, GAIN_500MV, + GAIN_1V, GAIN_2V, GAIN_5V, + GAIN_COUNT + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum UsedChannels hantek/types.h + /// \brief The enabled channels. + enum UsedChannels { + USED_CH1, USED_CH2, + USED_CH1CH2 + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum TriggerSource hantek/types.h + /// \brief The possible trigger sources. + enum TriggerSource { + TRIGGER_CH2, TRIGGER_CH1, + TRIGGER_ALT, + TRIGGER_EXT, TRIGGER_EXT10 + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum BufferSize hantek/types.h + /// \brief The size of the sample buffer. + enum BufferSize { + BUFFER_SMALL = 10240, + BUFFER_LARGE = 32768 + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum CaptureState hantek/types.h + /// \brief The different capture states which the oscilloscope returns. + enum CaptureState { + CAPTURE_WAITING = 0, + CAPTURE_SAMPLING = 1, + CAPTURE_READY = 2, + CAPTURE_READY5200 = 7 + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum CommandIndex hantek/types.h + /// \brief Can be set by CONTROL_BEGINCOMMAND, maybe it allows multiple commands at the same time? + enum CommandIndex { + COMMANDINDEX_0 = 0x03, + COMMANDINDEX_1 = 0x0a, + COMMANDINDEX_2 = 0x09, + COMMANDINDEX_3 = 0x01, + COMMANDINDEX_4 = 0x02, + COMMANDINDEX_5 = 0x08 + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \enum LevelOffset hantek/types.h + /// \brief The array indicies for the CalibrationData. + enum LevelOffset { + OFFSET_START, + OFFSET_END, + OFFSET_COUNT + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \struct FilterBits hantek/types.h + /// \brief The bits for COMMAND_SETFILTER. + struct FilterBits { + unsigned char channel1:1; + unsigned char channel2:1; + unsigned char trigger:1; + unsigned char reserved:5; + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \union FilterByte hantek/types.h + /// \brief Allows to read the FilterBits as unsigned char. + union FilterByte { + FilterBits bits; + unsigned char byte; + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \struct GainBits hantek/types.h + /// \brief The gain bits for COMMAND_SETVOLTAGEANDCOUPLING. + struct GainBits { + unsigned char channel1:2; + unsigned char channel2:2; + unsigned char constant:4; + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \union GainByte hantek/types.h + /// \brief Allows to read the GainBits as unsigned char. + union GainByte { + GainBits bits; + unsigned char byte; + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \struct Tsr1Bits hantek/types.h + /// \brief Trigger and samplerate bits (Byte 1). + struct Tsr1Bits { + unsigned char triggerSource:2; + unsigned char sampleSize:3; + unsigned char samplerateFast:3; + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \union Tsr1Byte hantek/types.h + /// \brief Allows to read the Tsr1Bits as unsigned char. + union Tsr1Byte { + Tsr1Bits bits; + unsigned char byte; + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \struct Tsr2Bits hantek/types.h + /// \brief Trigger and samplerate bits (Byte 2). + struct Tsr2Bits { + unsigned char selectedChannel:2; + unsigned char fastRate:1; + unsigned char triggerSlope:1; + unsigned char reserved:4; + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \union Tsr2Byte hantek/types.h + /// \brief Allows to read the Tsr2Bits as unsigned char. + union Tsr2Byte { + Tsr2Bits bits; + unsigned char byte; + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class CommandSetFilter hantek/types.h + /// \brief The COMMAND_SETFILTER builder. + class CommandSetFilter : public Helper::DataArray { + public: + CommandSetFilter(); + CommandSetFilter(bool channel1, bool channel2, bool trigger); + + bool getChannel(unsigned int channel); + void setChannel(unsigned int channel, bool filtered); + bool getTrigger(); + void setTrigger(bool filtered); + + private: + void init(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class CommandSetTriggerAndSamplerate hantek/types.h + /// \brief The COMMAND_SETTRIGGERANDSAMPLERATE builder. + class CommandSetTriggerAndSamplerate : public Helper::DataArray { + public: + CommandSetTriggerAndSamplerate(); + CommandSetTriggerAndSamplerate(unsigned short int samplerate, unsigned long int triggerPosition, unsigned char triggerSource = 0, unsigned char sampleSize = 0, unsigned char timebaseFast = 0, unsigned char selectedChannel = 0, bool fastRate = false, unsigned char triggerSlope = 0); + + unsigned char getTriggerSource(); + void setTriggerSource(unsigned char value); + unsigned char getSampleSize(); + void setSampleSize(unsigned char value); + unsigned char getSamplerateFast(); + void setSamplerateFast(unsigned char value); + unsigned char getUsedChannel(); + void setUsedChannel(unsigned char value); + bool getFastRate(); + void setFastRate(bool fastRate); + unsigned char getTriggerSlope(); + void setTriggerSlope(unsigned char slope); + unsigned short int getSamplerate(); + void setSamplerate(unsigned short int samplerate); + unsigned long int getTriggerPosition(); + void setTriggerPosition(unsigned long int position); + + private: + void init(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class CommandForceTrigger hantek/types.h + /// \brief The COMMAND_FORCETRIGGER builder. + class CommandForceTrigger : public Helper::DataArray { + public: + CommandForceTrigger(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class CommandCaptureStart hantek/types.h + /// \brief The COMMAND_CAPTURESTART builder. + class CommandCaptureStart : public Helper::DataArray { + public: + CommandCaptureStart(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class CommandTriggerEnabled hantek/types.h + /// \brief The COMMAND_TRIGGERENABLED builder. + class CommandTriggerEnabled : public Helper::DataArray { + public: + CommandTriggerEnabled(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class CommandGetData hantek/types.h + /// \brief The COMMAND_GETDATA builder. + class CommandGetData : public Helper::DataArray { + public: + CommandGetData(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class CommandGetCaptureState hantek/types.h + /// \brief The COMMAND_GETCAPTURESTATE builder. + class CommandGetCaptureState : public Helper::DataArray { + public: + CommandGetCaptureState(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class ResponseGetCaptureState hantek/types.h + /// \brief The parser for the COMMAND_GETCAPTURESTATE response. + class ResponseGetCaptureState : public Helper::DataArray { + public: + ResponseGetCaptureState(); + + CaptureState getCaptureState(); + unsigned int getTriggerPoint(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class CommandSetGain hantek/types.h + /// \brief The COMMAND_SETGAIN builder. + class CommandSetGain : public Helper::DataArray { + public: + CommandSetGain(); + CommandSetGain(unsigned char channel1, unsigned char channel2); + + unsigned char getGain(unsigned int channel); + void setGain(unsigned int channel, unsigned char value); + + private: + void init(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class CommandSetLogicalData hantek/types.h + /// \brief The COMMAND_SETLOGICALDATA builder. + class CommandSetLogicalData : public Helper::DataArray { + public: + CommandSetLogicalData(); + CommandSetLogicalData(unsigned char data); + + unsigned char getData(); + void setData(unsigned char data); + + private: + void init(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class CommandGetLogicalData hantek/types.h + /// \brief The COMMAND_GETLOGICALDATA builder. + class CommandGetLogicalData : public Helper::DataArray { + public: + CommandGetLogicalData(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class ControlGetSpeed hantek/types.h + /// \brief The CONTROL_GETSPEED parser. + class ControlGetSpeed : public Helper::DataArray { + public: + ControlGetSpeed(); + + ConnectionSpeed getSpeed(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class ControlBeginCommand hantek/types.h + /// \brief The CONTROL_BEGINCOMMAND builder. + class ControlBeginCommand : public Helper::DataArray { + public: + ControlBeginCommand(CommandIndex index = COMMANDINDEX_0); + + CommandIndex getIndex(); + void setIndex(CommandIndex index); + + private: + void init(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class ControlSetOffset hantek/types.h + /// \brief The CONTROL_SETOFFSET builder. + class ControlSetOffset : public Helper::DataArray { + public: + ControlSetOffset(); + ControlSetOffset(unsigned short int channel1, unsigned short int channel2, unsigned short int trigger); + + unsigned short int getChannel(unsigned int channel); + void setChannel(unsigned int channel, unsigned short int offset); + unsigned short int getTrigger(); + void setTrigger(unsigned short int level); + + private: + void init(); + }; + + ////////////////////////////////////////////////////////////////////////////// + /// \class ControlSetRelays hantek/types.h + /// \brief The CONTROL_SETRELAYS builder. + class ControlSetRelays : public Helper::DataArray { + public: + ControlSetRelays(bool ch1Below1V = false, bool ch1Below100mV = false, bool ch1CouplingDC = false, bool ch2Below1V = false, bool ch2Below100mV = false, bool ch2CouplingDC = false, bool triggerExt = false); + + bool getBelow1V(unsigned int channel); + void setBelow1V(unsigned int channel, bool below); + bool getBelow100mV(unsigned int channel); + void setBelow100mV(unsigned int channel, bool below); + bool getCoupling(unsigned int channel); + void setCoupling(unsigned int channel, bool dc); + bool getTrigger(); + void setTrigger(bool ext); + }; +} + + +#endif diff --git b/openhantek/src/helper.cpp a/openhantek/src/helper.cpp new file mode 100644 index 0000000..46bd238 --- /dev/null +++ a/openhantek/src/helper.cpp @@ -0,0 +1,145 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// helper.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include + +#include + +#if LIBUSB_VERSION == 0 +#include +#define libusb_device usb_device +#else +#include +#endif + + +#include "helper.h" + + +namespace Helper { + /// \brief Returns string representation for libusb errors. + /// \param error The error code. + /// \return String explaining the error. + QString libUsbErrorString(int error) { + switch(error) { + case LIBUSB_SUCCESS: + return "Success (no error)"; + case LIBUSB_ERROR_IO: + return "Input/output error"; + case LIBUSB_ERROR_INVALID_PARAM: + return "Invalid parameter"; + case LIBUSB_ERROR_ACCESS: + return "Access denied (insufficient permissions)"; + case LIBUSB_ERROR_NO_DEVICE: + return "No such device (it may have been disconnected)"; + case LIBUSB_ERROR_NOT_FOUND: + return "Entity not found"; + case LIBUSB_ERROR_BUSY: + return "Resource busy"; + case LIBUSB_ERROR_TIMEOUT: + return "Operation timed out"; + case LIBUSB_ERROR_OVERFLOW: + return "Overflow"; + case LIBUSB_ERROR_PIPE: + return "Pipe error"; + case LIBUSB_ERROR_INTERRUPTED: + return "System call interrupted (perhaps due to signal)"; + case LIBUSB_ERROR_NO_MEM: + return "Insufficient memory"; + case LIBUSB_ERROR_NOT_SUPPORTED: + return "Operation not supported or unimplemented on this platform"; + default: + return "Other error"; + } + } + + /// \brief Converts double to string containing value and (prefix+)unit. + /// \param value The value in prefixless units. + /// \param unit The unit for the value. + /// \param precision Significant digits, 0 for integer, -1 for auto. + /// \return String with the value and unit. + QString valueToString(double value, Unit unit, int precision) { + char format = (precision < 0) ? 'g' : 'f'; + + switch(unit) { + case UNIT_VOLTS: { + // Voltage string representation + int logarithm = floor(log10(fabs(value))); + if(value < 1e-3) + return QApplication::tr("%L1 \265V").arg(value * 1e6, 0, format, (precision <= 0) ? precision : qBound(0, precision - 7 - logarithm, precision)); + else if(value < 1.0) + return QApplication::tr("%L1 mV").arg(value * 1e3, 0, format, (precision <= 0) ? precision : (precision - 4 - logarithm)); + else + return QApplication::tr("%L1 V").arg(value, 0, format, (precision <= 0) ? precision : qMax(0, precision - 1 - logarithm)); + } + case UNIT_DECIBEL: + // Power level string representation + return QApplication::tr("%L1 dB").arg(value, 0, format, (precision <= 0) ? precision : qBound(0, precision - 1 - (int) floor(log10(fabs(value))), precision)); + + case UNIT_SECONDS: + // Time string representation + if(value < 1e-9) + return QApplication::tr("%L1 ps").arg(value * 1e12, 0, format, (precision <= 0) ? precision : qBound(0, precision - 13 - (int) floor(log10(fabs(value))), precision)); + else if(value < 1e-6) + return QApplication::tr("%L1 ns").arg(value * 1e9, 0, format, (precision <= 0) ? precision : (precision - 10 - (int) floor(log10(fabs(value))))); + else if(value < 1e-3) + return QApplication::tr("%L1 \265s").arg(value * 1e6, 0, format, (precision <= 0) ? precision : (precision - 7 - (int) floor(log10(fabs(value))))); + else if(value < 1.0) + return QApplication::tr("%L1 ms").arg(value * 1e3, 0, format, (precision <= 0) ? precision : (precision - 4 - (int) floor(log10(fabs(value))))); + else if(value < 60) + return QApplication::tr("%L1 s").arg(value, 0, format, (precision <= 0) ? precision : (precision - 1 - (int) floor(log10(fabs(value))))); + else if(value < 3600) + return QApplication::tr("%L1 min").arg(value / 60, 0, format, (precision <= 0) ? precision : (precision - 1 - (int) floor(log10(value / 60)))); + else + return QApplication::tr("%L1 h").arg(value / 3600, 0, format, (precision <= 0) ? precision : qMax(0, precision - 1 - (int) floor(log10(value / 3600)))); + + case UNIT_HERTZ: { + // Frequency string representation + int logarithm = floor(log10(fabs(value))); + if(value < 1e3) + return QApplication::tr("%L1 Hz").arg(value, 0, format, (precision <= 0) ? precision : qBound(0, precision - 1 - logarithm, precision)); + else if(value < 1e6) + return QApplication::tr("%L1 kHz").arg(value / 1e3, 0, format, (precision <= 0) ? precision : precision + 2 - logarithm); + else if(value < 1e9) + return QApplication::tr("%L1 MHz").arg(value / 1e6, 0, format, (precision <= 0) ? precision : precision + 5 - logarithm); + else + return QApplication::tr("%L1 GHz").arg(value / 1e9, 0, format, (precision <= 0) ? precision : qMax(0, precision + 8 - logarithm)); + } + case UNIT_SAMPLES: { + // Sample count string representation + int logarithm = floor(log10(fabs(value))); + if(value < 1e3) + return QApplication::tr("%L1 S").arg(value, 0, format, (precision <= 0) ? precision : qBound(0, precision - 1 - logarithm, precision)); + else if(value < 1e6) + return QApplication::tr("%L1 kS").arg(value / 1e3, 0, format, (precision <= 0) ? precision : precision + 2 - logarithm); + else if(value < 1e9) + return QApplication::tr("%L1 MS").arg(value / 1e6, 0, format, (precision <= 0) ? precision : precision + 5 - logarithm); + else + return QApplication::tr("%L1 GS").arg(value / 1e9, 0, format, (precision <= 0) ? precision : qMax(0, precision + 8 - logarithm)); + } + default: + return QString(); + } + } +} diff --git b/openhantek/src/helper.h a/openhantek/src/helper.h new file mode 100644 index 0000000..22f1566 --- /dev/null +++ a/openhantek/src/helper.h @@ -0,0 +1,98 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file helper.h +/// \brief Provides miscellaneous helper functions. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef HELPER_H +#define HELPER_H + + +#include + + +namespace Helper { + ////////////////////////////////////////////////////////////////////////////// + /// \enum Unit helper.h + /// \brief The various units supported by valueToString. + enum Unit { + UNIT_VOLTS, UNIT_DECIBEL, + UNIT_SECONDS, UNIT_HERTZ, + UNIT_SAMPLES + }; + + QString libUsbErrorString(int error); + QString valueToString(double value, Unit unit, int precision = -1); + + ////////////////////////////////////////////////////////////////////////////// + /// \class DataArray helper.h + /// \brief A class template for a simple array with a fixed size. + template class DataArray { + public: + DataArray(unsigned int size); + ~DataArray(); + + T *data(); + T operator[](unsigned int index); + + unsigned int getSize() const; + + protected: + T *array; + unsigned int size; + }; + + /// \brief Initializes the data array. + /// \param size Size of the data array. + template DataArray::DataArray(unsigned int size) { + this->array = new T[size]; + for(unsigned int index = 0; index < size; index++) + this->array[index] = 0; + this->size = size; + } + + /// \brief Deletes the allocated data array. + template DataArray::~DataArray() { + delete[] this->array; + } + + /// \brief Returns a pointer to the array data. + /// \return The internal data array. + template T *DataArray::data() { + return this->array; + } + + /// \brief Returns array element when using square brackets. + /// \return The array element. + template T DataArray::operator[](unsigned int index) { + return this->array[index]; + } + + /// \brief Gets the size of the array. + /// \return The size of the command in bytes. + template unsigned int DataArray::getSize() const { + return this->size; + } +}; + + +#endif diff --git b/openhantek/src/levelslider.cpp a/openhantek/src/levelslider.cpp new file mode 100644 index 0000000..99a2113 --- /dev/null +++ a/openhantek/src/levelslider.cpp @@ -0,0 +1,579 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// levelslider.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include + +#include +#include +#include + + +#include "levelslider.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class LevelSlider +/// \brief Initializes the slider container. +/// \param direction The side on which the sliders are shown. +/// \param parent The parent widget. +LevelSlider::LevelSlider(Qt::ArrowType direction, QWidget *parent) : QWidget(parent) { + QFont font = this->font(); + font.setPointSize(font.pointSize() * 0.8); + this->setFont(font); + + this->pressedSlider = -1; + this->sliderWidth = 8; + + this->setDirection(direction); +} + +/// \brief Cleans up the widget. +LevelSlider::~LevelSlider() { +} + +/// \brief Return the margin before the slider. +/// \return The margin the Slider has at the top/left. +int LevelSlider::preMargin() const { + return this->_preMargin; +} + +/// \brief Return the margin after the slider. +/// \return The margin the Slider has at the bottom/right. +int LevelSlider::postMargin() const { + return this->_postMargin; +} + +/// \brief Add a new slider to the slider container. +/// \param index The index where the slider should be inserted, 0 to append. +/// \return The index of the slider, -1 on error. +int LevelSlider::addSlider(int index) { + return this->addSlider(0, index); +} + +/// \brief Add a new slider to the slider container. +/// \param text The text that will be shown next to the slider. +/// \param index The index where the slider should be inserted, 0 to append. +/// \return The index of the slider, -1 on error. +int LevelSlider::addSlider(QString text, int index) { + if(index < -1) + return -1; + + LevelSliderParameters *parameters = new LevelSliderParameters; + parameters->color = Qt::white; + parameters->minimum = 0x00; + parameters->maximum = 0xff; + parameters->value = 0x00; + parameters->visible = false; + + if(index == -1) { + this->slider.append(parameters); + index = this->slider.count() - 1; + } + else + this->slider.insert(index, parameters); + + this->setText(index, text); + + return index; +} + +/// \brief Remove a slider from the slider container. +/// \param index The index of the slider that should be removed. +/// \return The index of the removed slider, -1 on error. +int LevelSlider::removeSlider(int index) { + if(index < -1) + return -1; + + if(index == -1) { + this->slider.removeLast(); + index = this->slider.count(); + } + else { + this->slider.removeAt(index); + } + + this->calculateWidth(); + + return index; +} + +/// \brief Size hint for the widget. +/// \return The recommended size for the widget. +QSize LevelSlider::sizeHint() const { + if(this->_direction == Qt::RightArrow || this->_direction == Qt::LeftArrow) + return QSize(this->sliderWidth, 16); + else + return QSize(16, this->sliderWidth); +} + +/// \brief Return the color of a slider. +/// \param index The index of the slider whose color should be returned. +/// \return The current color of the slider. +const QColor LevelSlider::color(int index) const { + if(index < 0 || index >= this->slider.count()) + return Qt::black; + + return this->slider[index]->color; +} + +/// \brief Set the color of the slider. +/// \param index The index of the slider whose color should be set. +/// \param color The new color for the slider. +/// \return The index of the slider, -1 on error. +int LevelSlider::setColor(int index, QColor color) { + if(index < 0 || index >= this->slider.count()) + return -1; + + this->slider[index]->color = color; + this->repaint(); + + return index; +} + +/// \brief Return the text shown beside a slider. +/// \param index The index of the slider whose text should be returned. +/// \return The current text of the slider. +const QString LevelSlider::text(int index) const { + if(index < 0 || index >= this->slider.count()) + return QString(); + + return this->slider[index]->text; +} + +/// \brief Set the text for a slider. +/// \param index The index of the slider whose text should be set. +/// \param text The text shown next to the slider. +/// \return The index of the slider, -1 on error. +int LevelSlider::setText(int index, QString text) { + if(index < 0 || index >= this->slider.count()) + return -1; + + this->slider[index]->text = text; + this->calculateWidth(); + + return index; +} + +/// \brief Return the visibility of a slider. +/// \param index The index of the slider whose visibility should be returned. +/// \return true if the slider is visible, false if it's hidden. +bool LevelSlider::visible(int index) const { + if(index < 0 || index >= this->slider.count()) + return false; + + return this->slider[index]->visible; +} + +/// \brief Set the visibility of a slider. +/// \param index The index of the slider whose visibility should be set. +/// \param visible true to show the slider, false to hide it. +/// \return The index of the slider, -1 on error. +int LevelSlider::setVisible(int index, bool visible) { + if(index < 0 || index >= this->slider.count()) + return -1; + + this->slider[index]->visible = visible; + this->repaint(); + + return index; +} + +/// \brief Return the minimal value of the sliders. +/// \return The value a slider has at the bottommost/leftmost position. +double LevelSlider::minimum(int index) const { + if(index < 0 || index >= this->slider.count()) + return -1; + + return this->slider[index]->minimum; +} + +/// \brief Return the maximal value of the sliders. +/// \return The value a slider has at the topmost/rightmost position. +double LevelSlider::maximum(int index) const { + if(index < 0 || index >= this->slider.count()) + return -1; + + return this->slider[index]->maximum; +} + +/// \brief Set the maximal value of the sliders. +/// \param maximum The value a slider has at the topmost/rightmost position. +/// \return -1 on error, fixValue result on success. +int LevelSlider::setLimits(int index, double minimum, double maximum) { + if(index < 0 || index >= this->slider.count()) + return -1; + + this->slider[index]->minimum = minimum; + this->slider[index]->maximum = maximum; + int result = this->fixValue(index); + + this->calculateRect(index); + this->repaint(); + + return result; +} + +/// \brief Return the step width of the sliders. +/// \return The distance between the selectable slider positions. +double LevelSlider::step(int index) const { + if(index < 0 || index >= this->slider.count()) + return -1; + + return this->slider[index]->step; +} + +/// \brief Set the step width of the sliders. +/// \param maximum The distance between the selectable slider positions. +/// \return The new step width. +double LevelSlider::setStep(int index, double step) { + if(index < 0 || index >= this->slider.count()) + return -1; + + if(step > 0) + this->slider[index]->step = step; + + return this->slider[index]->step; +} + +/// \brief Return the current position of a slider. +/// \param index The index of the slider whose value should be returned. +/// \return The value of the slider. +double LevelSlider::value(int index) const { + if(index < 0 || index >= this->slider.count()) + return -1; + + return this->slider[index]->value; +} + +/// \brief Set the current position of a slider. +/// \param index The index of the slider whose value should be set. +/// \param value The new value of the slider. +/// \return The new value of the slider. +double LevelSlider::setValue(int index, double value) { + if(index < 0 || index >= this->slider.count()) + return -1; + + // Apply new value + this->slider[index]->value = value; + this->fixValue(index); + + this->calculateRect(index); + this->repaint(); + + if(this->pressedSlider < 0) + emit valueChanged(index, value); + + return this->slider[index]->value; +} + +/// \brief Return the direction of the sliders. +/// \return The side on which the sliders are shown. +Qt::ArrowType LevelSlider::direction() const { + return this->_direction; +} + +/// \brief Set the direction of the sliders. +/// \param direction The side on which the sliders are shown. +/// \return The index of the direction, -1 on error. +int LevelSlider::setDirection(Qt::ArrowType direction) { + if(direction < Qt::UpArrow || direction > Qt::RightArrow) + return -1; + + this->_direction = direction; + + if(this->_direction == Qt::RightArrow || this->_direction == Qt::LeftArrow) { + this->_preMargin = this->fontMetrics().lineSpacing(); + this->_postMargin = 3; + } + else { + this->_preMargin = this->fontMetrics().averageCharWidth() * 3; + this->_postMargin = 3; + } + + return this->_direction; +} + +/// \brief Move the slider if it's pressed. +/// \param event The mouse event that should be handled. +void LevelSlider::mouseMoveEvent(QMouseEvent *event) { + if(this->pressedSlider < 0) { + event->ignore(); + return; + } + + // Get new value + double value; + if(this->_direction == Qt::RightArrow || this->_direction == Qt::LeftArrow) + value = this->slider[pressedSlider]->maximum - (this->slider[pressedSlider]->maximum - this->slider[pressedSlider]->minimum) * (event->y() - this->_preMargin) / (this->height() - this->_preMargin - this->_postMargin - 1); + else + value = this->slider[pressedSlider]->minimum + (this->slider[pressedSlider]->maximum - this->slider[pressedSlider]->minimum) * (event->x() - this->_preMargin) / (this->width() - this->_preMargin - this->_postMargin - 1); + + // Move the slider + if(event->modifiers() & Qt::AltModifier) + // Alt allows every position + this->setValue(this->pressedSlider, value); + else + // Set to nearest possible position + this->setValue(this->pressedSlider, floor(value / this->slider[pressedSlider]->step + 0.5) * this->slider[pressedSlider]->step); + + event->accept(); +} + +/// \brief Prepare slider for movement if the left mouse button is pressed. +/// \param event The mouse event that should be handled. +void LevelSlider::mousePressEvent(QMouseEvent *event) { + if(!(event->button() & Qt::LeftButton)) { + event->ignore(); + return; + } + + this->pressedSlider = -1; + for(int sliderId = 0; sliderId < this->slider.count(); sliderId++) { + if(this->slider[sliderId]->visible && this->slider[sliderId]->rect.contains(event->pos())) { + this->pressedSlider = sliderId; + break; + } + } + + // Accept event if a slider was pressed + event->setAccepted(this->pressedSlider >= 0); +} + +/// \brief Movement is done if the left mouse button is released. +/// \param event The mouse event that should be handled. +void LevelSlider::mouseReleaseEvent(QMouseEvent *event) { + if(!(event->button() & Qt::LeftButton) || this->pressedSlider == -1) { + event->ignore(); + return; + } + + emit valueChanged(this->pressedSlider, this->slider[this->pressedSlider]->value); + this->pressedSlider = -1; + + event->accept(); +} + +/// \brief Paint the widget. +/// \param event The paint event that should be handled. +void LevelSlider::paintEvent(QPaintEvent *event) { + QPainter painter(this); + + Qt::Alignment alignment; + switch(this->_direction) { + case Qt::LeftArrow: + alignment = Qt::AlignLeft | Qt::AlignBottom; + break; + case Qt::UpArrow: + alignment = Qt::AlignTop | Qt::AlignHCenter; + break; + case Qt::DownArrow: + alignment = Qt::AlignBottom | Qt::AlignHCenter; + break; + default: + alignment = Qt::AlignRight | Qt::AlignBottom; + } + + QList::iterator slider = this->slider.end(); + while(slider != this->slider.begin()) { + slider--; + + if(!(*slider)->visible) + continue; + + painter.setPen((*slider)->color); + + if((*slider)->text.isEmpty()) { + int needlePoints[6]; + + switch(this->_direction) { + case Qt::LeftArrow: + needlePoints[0] = (*slider)->rect.left() + 4; needlePoints[1] = (*slider)->rect.top(); + needlePoints[2] = (*slider)->rect.left() + 1; needlePoints[3] = (*slider)->rect.top() + 3; + needlePoints[4] = (*slider)->rect.left() + 4; needlePoints[5] = (*slider)->rect.top() + 6; + break; + case Qt::UpArrow: + needlePoints[0] = (*slider)->rect.left(); needlePoints[1] = (*slider)->rect.top() + 4; + needlePoints[2] = (*slider)->rect.left() + 3; needlePoints[3] = (*slider)->rect.top() + 1; + needlePoints[4] = (*slider)->rect.left() + 6; needlePoints[5] = (*slider)->rect.top() + 4; + break; + case Qt::DownArrow: + needlePoints[0] = (*slider)->rect.left(); needlePoints[1] = (*slider)->rect.top() + this->sliderWidth - 5; + needlePoints[2] = (*slider)->rect.left() + 3; needlePoints[3] = (*slider)->rect.top() + this->sliderWidth - 2; + needlePoints[4] = (*slider)->rect.left() + 6; needlePoints[5] = (*slider)->rect.top() + this->sliderWidth - 5; + break; + default: + needlePoints[0] = (*slider)->rect.left() + this->sliderWidth - 5; needlePoints[1] = (*slider)->rect.top(); + needlePoints[2] = (*slider)->rect.left() + this->sliderWidth - 2; needlePoints[3] = (*slider)->rect.top() + 3; + needlePoints[4] = (*slider)->rect.left() + this->sliderWidth - 5; needlePoints[5] =(*slider)->rect.top() + 6; + } + + painter.setBrush(QBrush((*slider)->color, Qt::SolidPattern)); + painter.drawPolygon(QPolygon(3, needlePoints)); + painter.setBrush(Qt::NoBrush); + } + else { + // Get rect for text and draw needle + QRect textRect = (*slider)->rect; + if(this->_direction == Qt::UpArrow || this->_direction == Qt::DownArrow) { + textRect.setRight(textRect.right() - 1); + if(this->_direction == Qt::UpArrow) { + textRect.setTop(textRect.top() + 1); + painter.drawLine((*slider)->rect.right(), 0, (*slider)->rect.right(), 7); + } + else { + textRect.setBottom(textRect.bottom() - 1); + painter.drawLine((*slider)->rect.right(), this->sliderWidth - 8, (*slider)->rect.right(), this->sliderWidth - 1); + } + } + else + { + textRect.setBottom(textRect.bottom() - 1); + if(this->_direction == Qt::LeftArrow) { + textRect.setLeft(textRect.left() + 1); + painter.drawLine(0, (*slider)->rect.bottom(), 7, (*slider)->rect.bottom()); + } + else { + textRect.setRight(textRect.right() - 1); + painter.drawLine(this->sliderWidth - 8, (*slider)->rect.bottom(), this->sliderWidth - 1, (*slider)->rect.bottom()); + } + } + // Draw text + painter.drawText(textRect, alignment, (*slider)->text); + } + } + + event->accept(); +} + +/// \brief Resize the widget and adapt the slider positions. +/// \param event The resize event that should be handled. +void LevelSlider::resizeEvent(QResizeEvent *event) { + Q_UNUSED(event); + + for(int sliderId = 0; sliderId < this->slider.count(); sliderId++) + this->calculateRect(sliderId); + + this->repaint(); +} + +/// \brief Calculate the drawing area for the slider for it's current value. +/// \param sliderId The id of the slider whose rect should be calculated. +/// \return The calculated rect. +QRect LevelSlider::calculateRect(int sliderId) { + // Is it a vertical slider? + if(this->_direction == Qt::RightArrow || this->_direction == Qt::LeftArrow) { + // Is it a triangular needle? + if(this->slider[sliderId]->text.isEmpty()) { + this->slider[sliderId]->rect = QRect( + 0, // Start at the left side + // The needle should be center-aligned + (long) (this->height() - this->_preMargin - this->_postMargin - 1) * (this->slider[sliderId]->maximum - this->slider[sliderId]->value) / (this->slider[sliderId]->maximum - this->slider[sliderId]->minimum) + this->_preMargin - 3, + this->sliderWidth, // Fill the whole width + 7 // The needle is 5 px wide + ); + } + // Or a thin needle with text? + else { + this->slider[sliderId]->rect = QRect( + 0, // Start at the left side + // The needle is at the bottom, the text above it + (long) (this->height() - this->_preMargin - this->_postMargin - 1) * (this->slider[sliderId]->maximum - this->slider[sliderId]->value) / (this->slider[sliderId]->maximum - this->slider[sliderId]->minimum), + this->sliderWidth, // Fill the whole width + this->preMargin() + 1 // Use the full margin + ); + } + } + // Or a horizontal slider? + else { + // Is it a triangular needle? + if(this->slider[sliderId]->text.isEmpty()) { + this->slider[sliderId]->rect = QRect( + // The needle should be center-aligned + (long) (this->width() - this->_preMargin - this->_postMargin - 1) * (this->slider[sliderId]->value - this->slider[sliderId]->minimum) / (this->slider[sliderId]->maximum - this->slider[sliderId]->minimum) + this->_preMargin - 3, + 0, // Start at the top + 7, // The needle is 5 px wide + this->sliderWidth // Fill the whole height + ); + } + // Or a thin needle with text? + else { + int sliderLength = this->fontMetrics().size(0, this->slider[sliderId]->text).width() + 2; + this->slider[sliderId]->rect = QRect( + // The needle is at the right side, the text before it + (long) (this->width() - this->_preMargin - this->_postMargin - 1) * (this->slider[sliderId]->value - this->slider[sliderId]->minimum) / (this->slider[sliderId]->maximum - this->slider[sliderId]->minimum) + this->_preMargin - sliderLength + 1, + 0, // Start at the top + sliderLength, // The width depends on the text + this->sliderWidth // Fill the whole height + ); + } + } + + return this->slider[sliderId]->rect; +} + +/// \brief Search for the widest slider element. +/// \return The calculated width of the slider. +int LevelSlider::calculateWidth() { + // At least 8 px for the needles + this->sliderWidth = 8; + + // Is it a vertical slider? + if(this->_direction == Qt::RightArrow || this->_direction == Qt::LeftArrow) { + for(QList::iterator slider = this->slider.begin(); slider != this->slider.end(); ++slider) { + int sliderWidth = this->fontMetrics().size(0, (*slider)->text).width(); + if(sliderWidth > this->sliderWidth) + this->sliderWidth = sliderWidth; + } + } + // Or a horizontal slider? + else { + for(QList::iterator slider = this->slider.begin(); slider != this->slider.end(); ++slider) { + int sliderWidth = this->fontMetrics().size(0, (*slider)->text).height(); + if(sliderWidth > this->sliderWidth) + this->sliderWidth = sliderWidth; + } + } + + return this->sliderWidth; +} + +/// \brief Fix the value if it's outside the limits. +/// \param index The index of the slider who should be fixed. +/// \return 0 when ok, -1 on error, 1 when increased and 2 when decreased. +int LevelSlider::fixValue(int index) { + if(index < 0 || index >= this->slider.count()) + return -1; + + double lowest = qMin(this->slider[index]->minimum, this->slider[index]->maximum); + double highest = qMax(this->slider[index]->minimum, this->slider[index]->maximum); + if(this->slider[index]->value < lowest) { + this->slider[index]->value = lowest; + return 1; + } + else if(this->slider[index]->value > highest) { + this->slider[index]->value = highest; + return 2; + } + return 0; +} diff --git b/openhantek/src/levelslider.h a/openhantek/src/levelslider.h new file mode 100644 index 0000000..4a122c9 --- /dev/null +++ a/openhantek/src/levelslider.h @@ -0,0 +1,116 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file levelslider.h +/// \brief Declares the LevelSlider class. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef LEVELSLIDER_H +#define LEVELSLIDER_H + + +#include + + +class QColor; + + +//////////////////////////////////////////////////////////////////////////////// +/// \struct LevelSliderParameters levelslider.h +/// \brief Contains the color, text and value of one slider. +struct LevelSliderParameters { + QColor color; + QString text; + bool visible; + + double minimum, maximum; + double step; + double value; + + // Needed for moving and drawing + QRect rect; +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \class LevelSlider levelslider.h +/// \brief Slider widget for multiple level sliders. +/// These are used for the trigger levels, offsets and so on. +class LevelSlider : public QWidget { + Q_OBJECT + + public: + LevelSlider(Qt::ArrowType direction = Qt::RightArrow, QWidget *parent = 0); + ~LevelSlider(); + + QSize sizeHint() const; + + int preMargin() const; + int postMargin() const; + + int addSlider(int index = -1); + int addSlider(QString text, int index = -1); + int removeSlider(int index = -1); + + // Parameters for a specific slider + const QColor color(int index) const; + int setColor(int index, QColor color); + const QString text(int index) const; + int setText(int index, QString text); + bool visible(int index) const; + int setVisible(int index, bool visible); + + double minimum(int index) const; + double maximum(int index) const; + int setLimits(int index, double minimum, double maximum); + double step(int index) const; + double setStep(int index, double step); + double value(int index) const; + double setValue(int index, double value); + + // Parameters for all sliders + Qt::ArrowType direction() const; + int setDirection(Qt::ArrowType direction); + + protected: + void mouseMoveEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + + void paintEvent(QPaintEvent *event); + void resizeEvent(QResizeEvent *event); + + QRect calculateRect(int sliderId); + int calculateWidth(); + int fixValue(int index); + + QList slider; + int pressedSlider; + int sliderWidth; + + Qt::ArrowType _direction; + int _preMargin, _postMargin; + + signals: + void valueChanged(int index, double value); +}; + + +#endif diff --git b/openhantek/src/main.cpp a/openhantek/src/main.cpp new file mode 100644 index 0000000..c0b865e --- /dev/null +++ a/openhantek/src/main.cpp @@ -0,0 +1,52 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// main.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include + + +#include "openhantek.h" + + +/// \brief Initialize resources and translations and show the main window. +int main(int argc, char *argv[]) { + Q_INIT_RESOURCE(application); + + QApplication openHantekApplication(argc, argv); + + QTranslator qtTranslator; + qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + openHantekApplication.installTranslator(&qtTranslator); + + QTranslator openHantekTranslator; + openHantekTranslator.load("openhantek_" + QLocale::system().name(), QMAKE_TRANSLATIONS_PATH); + openHantekApplication.installTranslator(&openHantekTranslator); + + OpenHantekMainWindow *openHantekMainWindow = new OpenHantekMainWindow(); + openHantekMainWindow->show(); + + return openHantekApplication.exec(); +} diff --git b/openhantek/src/openhantek.cpp a/openhantek/src/openhantek.cpp new file mode 100644 index 0000000..b9a2951 --- /dev/null +++ a/openhantek/src/openhantek.cpp @@ -0,0 +1,669 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// openhantek.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "openhantek.h" + +#include "configdialog.h" +#include "dataanalyzer.h" +#include "dockwindows.h" +#include "dsocontrol.h" +#include "dsowidget.h" +#include "settings.h" +#include "hantek/control.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class OpenHantekMainWindow +/// \brief Initializes the gui elements of the main window. +/// \param parent The parent widget. +/// \param flags Flags for the window manager. +OpenHantekMainWindow::OpenHantekMainWindow(QWidget *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { + // Set application information + QCoreApplication::setOrganizationName("paranoiacs.net"); + QCoreApplication::setOrganizationDomain("paranoiacs.net"); + QCoreApplication::setApplicationName("OpenHantek"); + + // Window title + this->setWindowIcon(QIcon(":openhantek.png")); + this->setWindowTitle(tr("OpenHantek")); + + // Default window dimensions + //this->move(152, 144); + this->resize(720, 480); + + // Create the controller for the oscilloscope, provides channel count for settings + this->dsoControl = new Hantek::Control(); + + // Application settings + this->settings = new DsoSettings(); + this->settings->setChannelCount(this->dsoControl->getChannelCount()); + this->readSettings(); + + // Create dock windows before the dso widget, they fix messed up settings + this->createDockWindows(); + + // The data analyzer + this->dataAnalyzer = new DataAnalyzer(this->settings); + + // Central oszilloscope widget + this->dsoWidget = new DsoWidget(this->settings, this->dataAnalyzer); + this->setCentralWidget(this->dsoWidget); + + // Subroutines for window elements + this->createActions(); + this->createMenus(); + this->createToolBars(); + this->createStatusBar(); + + // Connect general signals + connect(this, SIGNAL(settingsChanged()), this, SLOT(applySettings())); + //connect(this->dsoWidget, SIGNAL(stopped()), this, SLOT(stopped())); + connect(this->dsoControl, SIGNAL(statusMessage(QString, int)), this->statusBar(), SLOT(showMessage(QString, int))); + connect(this->dsoControl, SIGNAL(samplesAvailable(const QList *, const QList *, double, QMutex *)), this->dataAnalyzer, SLOT(analyze(const QList *, const QList *, double, QMutex *))); + + // Connect signals to DSO controller and widget + //connect(this->horizontalDock, SIGNAL(formatChanged(HorizontalFormat)), this->dsoWidget, SLOT(horizontalFormatChanged(HorizontalFormat))); + connect(this->horizontalDock, SIGNAL(timebaseChanged(double)), this, SLOT(updateTimebase())); + connect(this->horizontalDock, SIGNAL(timebaseChanged(double)), this->dsoWidget, SLOT(updateTimebase())); + connect(this->horizontalDock, SIGNAL(frequencybaseChanged(double)), this->dsoWidget, SLOT(updateFrequencybase())); + + connect(this->triggerDock, SIGNAL(modeChanged(Dso::TriggerMode)), this->dsoControl, SLOT(setTriggerMode(Dso::TriggerMode))); + connect(this->triggerDock, SIGNAL(modeChanged(Dso::TriggerMode)), this->dsoWidget, SLOT(updateTriggerMode())); + connect(this->triggerDock, SIGNAL(sourceChanged(bool, unsigned int)), this->dsoControl, SLOT(setTriggerSource(bool, unsigned int))); + connect(this->triggerDock, SIGNAL(sourceChanged(bool, unsigned int)), this->dsoWidget, SLOT(updateTriggerSource())); + connect(this->triggerDock, SIGNAL(slopeChanged(Dso::Slope)), this->dsoControl, SLOT(setTriggerSlope(Dso::Slope))); + connect(this->triggerDock, SIGNAL(slopeChanged(Dso::Slope)), this->dsoWidget, SLOT(updateTriggerSlope())); + connect(this->dsoWidget, SIGNAL(triggerPositionChanged(double)), this->dsoControl, SLOT(setTriggerPosition(double))); + connect(this->dsoWidget, SIGNAL(triggerLevelChanged(unsigned int, double)), this->dsoControl, SLOT(setTriggerLevel(unsigned int, double))); + + connect(this->voltageDock, SIGNAL(usedChanged(unsigned int, bool)), this, SLOT(updateUsed(unsigned int))); + connect(this->voltageDock, SIGNAL(usedChanged(unsigned int, bool)), this->dsoWidget, SLOT(updateVoltageUsed(unsigned int, bool))); + connect(this->voltageDock, SIGNAL(couplingChanged(unsigned int, Dso::Coupling)), this->dsoControl, SLOT(setCoupling(unsigned int, Dso::Coupling))); + connect(this->voltageDock, SIGNAL(couplingChanged(unsigned int, Dso::Coupling)), this->dsoWidget, SLOT(updateVoltageCoupling(unsigned int))); + connect(this->voltageDock, SIGNAL(gainChanged(unsigned int, double)), this, SLOT(updateVoltageGain(unsigned int))); + connect(this->voltageDock, SIGNAL(gainChanged(unsigned int, double)), this->dsoWidget, SLOT(updateVoltageGain(unsigned int))); + connect(this->dsoWidget, SIGNAL(offsetChanged(unsigned int, double)), this, SLOT(updateOffset(unsigned int))); + + connect(this->spectrumDock, SIGNAL(usedChanged(unsigned int, bool)), this, SLOT(updateUsed(unsigned int))); + connect(this->spectrumDock, SIGNAL(usedChanged(unsigned int, bool)), this->dsoWidget, SLOT(updateSpectrumUsed(unsigned int, bool))); + connect(this->spectrumDock, SIGNAL(magnitudeChanged(unsigned int, double)), this->dsoWidget, SLOT(updateSpectrumMagnitude(unsigned int))); + + // Set up the oscilloscope + for(unsigned int channel = 0; channel < this->settings->scope.physicalChannels; channel++) { + this->dsoControl->setCoupling(channel, (Dso::Coupling) this->settings->scope.voltage[channel].misc); + this->updateVoltageGain(channel); + this->updateOffset(channel); + this->dsoControl->setTriggerLevel(channel, this->settings->scope.voltage[channel].trigger); + } + this->updateUsed(this->settings->scope.physicalChannels); + this->dsoControl->setBufferSize(this->settings->scope.horizontal.samples); + this->updateTimebase(); + this->dsoControl->setTriggerMode(this->settings->scope.trigger.mode); + this->dsoControl->setTriggerPosition(this->settings->scope.trigger.position); + this->dsoControl->setTriggerSlope(this->settings->scope.trigger.slope); + this->dsoControl->setTriggerSource(this->settings->scope.trigger.special, this->settings->scope.trigger.source); + + this->dsoControl->connectDevice(); + this->dsoControl->startSampling(); +} + +/// \brief Cleans up the main window. +OpenHantekMainWindow::~OpenHantekMainWindow() { +} + +/// \brief Save the settings before exiting. +/// \param event The close event that should be handled. +void OpenHantekMainWindow::closeEvent(QCloseEvent *event) { + this->writeSettings(); + + QMainWindow::closeEvent(event); +} + +/// \brief Create the used actions. +void OpenHantekMainWindow::createActions() { + this->openAction = new QAction(QIcon(":actions/open.png"), tr("&Open..."), this); + this->openAction->setShortcut(tr("Ctrl+O")); + this->openAction->setStatusTip(tr("Open saved settings")); + connect(this->openAction, SIGNAL(triggered()), this, SLOT(open())); + + this->saveAction = new QAction(QIcon(":actions/save.png"), tr("&Save"), this); + this->saveAction->setShortcut(tr("Ctrl+S")); + this->saveAction->setStatusTip(tr("Save the current settings")); + connect(this->saveAction, SIGNAL(triggered()), this, SLOT(save())); + + this->saveAsAction = new QAction(QIcon(":actions/save-as.png"), tr("Save &as..."), this); + this->saveAsAction->setStatusTip(tr("Save the current settings to another file")); + connect(this->saveAsAction, SIGNAL(triggered()), this, SLOT(saveAs())); + + this->printAction = new QAction(QIcon(":actions/print.png"), tr("&Print..."), this); + this->saveAction->setShortcut(tr("Ctrl+P")); + this->printAction->setStatusTip(tr("Print the oscilloscope screen")); + connect(this->printAction, SIGNAL(triggered()), this->dsoWidget, SLOT(print())); + + this->exportAsAction = new QAction(QIcon(":actions/export-as.png"), tr("&Export as..."), this); + this->saveAction->setShortcut(tr("Ctrl+E")); + this->exportAsAction->setStatusTip(tr("Export the oscilloscope data to a file")); + connect(this->exportAsAction, SIGNAL(triggered()), this->dsoWidget, SLOT(exportAs())); + + this->exitAction = new QAction(tr("E&xit"), this); + this->exitAction->setShortcut(tr("Ctrl+Q")); + this->exitAction->setStatusTip(tr("Exit the application")); + connect(this->exitAction, SIGNAL(triggered()), this, SLOT(close())); + + this->configAction = new QAction(tr("&Settings"), this); + this->configAction->setShortcut(tr("Ctrl+S")); + this->configAction->setStatusTip(tr("Configure the oscilloscope")); + connect(this->configAction, SIGNAL(triggered()), this, SLOT(config())); + + this->startStopAction = new QAction(QIcon(":actions/stop.png"), tr("&Stop"), this); + this->startStopAction->setShortcut(tr("Space")); + this->startStopAction->setStatusTip(tr("Stop the oscilloscope")); + connect(this->startStopAction, SIGNAL(triggered()), this, SLOT(stop())); + + this->bufferSizeActionGroup = new QActionGroup(this); + connect(this->bufferSizeActionGroup, SIGNAL(selected(QAction *)), this, SLOT(bufferSizeSelected(QAction *))); + + this->bufferSizeSmallAction = new QAction(tr("&Small"), this); + this->bufferSizeSmallAction->setActionGroup(this->bufferSizeActionGroup); + this->bufferSizeSmallAction->setCheckable(true); + this->bufferSizeSmallAction->setChecked(this->settings->scope.horizontal.samples == Hantek::BUFFER_SMALL); + this->bufferSizeSmallAction->setStatusTip(tr("10240 Samples")); + + this->bufferSizeLargeAction = new QAction(tr("&Large"), this); + this->bufferSizeLargeAction->setActionGroup(this->bufferSizeActionGroup); + this->bufferSizeLargeAction->setCheckable(true); + this->bufferSizeLargeAction->setChecked(this->settings->scope.horizontal.samples == Hantek::BUFFER_LARGE); + this->bufferSizeLargeAction->setStatusTip(tr("32768 Samples")); + + this->digitalPhosphorAction = new QAction(QIcon(":actions/digitalphosphor.png"), tr("Digital &phosphor"), this); + this->digitalPhosphorAction->setCheckable(true); + this->digitalPhosphorAction->setChecked(this->settings->view.digitalPhosphor); + this->digitalPhosphor(this->settings->view.digitalPhosphor); + connect(this->digitalPhosphorAction, SIGNAL(toggled(bool)), this, SLOT(digitalPhosphor(bool))); + + this->zoomAction = new QAction(QIcon(":actions/zoom.png"), tr("&Zoom"), this); + this->zoomAction->setCheckable(true); + this->zoomAction->setChecked(this->settings->view.zoom); + this->zoom(this->settings->view.zoom); + connect(this->zoomAction, SIGNAL(toggled(bool)), this, SLOT(zoom(bool))); + connect(this->zoomAction, SIGNAL(toggled(bool)), this->dsoWidget, SLOT(updateZoom(bool))); + + this->aboutAction = new QAction(tr("&About"), this); + this->aboutAction->setStatusTip(tr("Show information about this program")); + connect(this->aboutAction, SIGNAL(triggered()), this, SLOT(about())); + + this->aboutQtAction = new QAction(tr("About &Qt"), this); + this->aboutQtAction->setStatusTip(tr("Show the Qt library's About box")); + connect(this->aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt())); +} + +/// \brief Create the menus and menuitems. +void OpenHantekMainWindow::createMenus() { + this->fileMenu = this->menuBar()->addMenu(tr("&File")); + this->fileMenu->addAction(this->openAction); + this->fileMenu->addAction(this->saveAction); + this->fileMenu->addAction(this->saveAsAction); + this->fileMenu->addSeparator(); + this->fileMenu->addAction(this->printAction); + this->fileMenu->addAction(this->exportAsAction); + this->fileMenu->addSeparator(); + this->fileMenu->addAction(this->exitAction); + + this->viewMenu = this->menuBar()->addMenu(tr("&View")); + this->viewMenu->addAction(this->digitalPhosphorAction); + this->viewMenu->addAction(this->zoomAction); + + this->oscilloscopeMenu = this->menuBar()->addMenu(tr("&Oscilloscope")); + this->oscilloscopeMenu->addAction(this->configAction); + this->oscilloscopeMenu->addSeparator(); + this->oscilloscopeMenu->addAction(this->startStopAction); + this->oscilloscopeMenu->addSeparator(); + this->bufferSizeMenu = this->oscilloscopeMenu->addMenu(tr("&Buffer size")); + this->bufferSizeMenu->addAction(this->bufferSizeSmallAction); + this->bufferSizeMenu->addAction(this->bufferSizeLargeAction); + + this->menuBar()->addSeparator(); + + this->helpMenu = this->menuBar()->addMenu(tr("&Help")); + this->helpMenu->addAction(this->aboutAction); + this->helpMenu->addAction(this->aboutQtAction); +} + +/// \brief Create the toolbars and their buttons. +void OpenHantekMainWindow::createToolBars() { + this->fileToolBar = this->addToolBar(tr("File")); + this->fileToolBar->addAction(this->openAction); + this->fileToolBar->addAction(this->saveAction); + this->fileToolBar->addAction(this->saveAsAction); + this->fileToolBar->addSeparator(); + this->fileToolBar->addAction(this->printAction); + this->fileToolBar->addAction(this->exportAsAction); + + this->oscilloscopeToolBar = this->addToolBar(tr("Oscilloscope")); + this->oscilloscopeToolBar->addAction(this->startStopAction); + + this->viewToolBar = this->addToolBar(tr("View")); + this->viewToolBar->addAction(this->digitalPhosphorAction); + this->viewToolBar->addAction(this->zoomAction); +} + +/// \brief Create the status bar. +void OpenHantekMainWindow::createStatusBar() { + // Progressbar inside the status bar + /*this->progressBar = new QProgressBar(); + this->progressBar->setMinimumWidth(100); + this->progressBar->setRange(0, 256); + this->progressBar->setValue(0); + this->progressBar->setTextVisible(true); + this->progressBar->hide();*/ + + //this->statusBar()->addPermanentWidget(this->progressBar); + + this->statusBar()->showMessage(tr("Ready")); +} + +/// \brief Create all docking windows. +void OpenHantekMainWindow::createDockWindows() +{ + this->horizontalDock = new HorizontalDock(this->settings); + this->triggerDock = new TriggerDock(this->settings, this->dsoControl->getSpecialTriggerSources()); + this->spectrumDock = new SpectrumDock(this->settings); + this->voltageDock = new VoltageDock(this->settings); + + this->addDockWidget(Qt::RightDockWidgetArea, this->horizontalDock); + this->addDockWidget(Qt::RightDockWidgetArea, this->triggerDock); + this->addDockWidget(Qt::RightDockWidgetArea, this->voltageDock); + this->addDockWidget(Qt::RightDockWidgetArea, this->spectrumDock); + + //viewMenu->addAction(this->horizontalDock->toggleViewAction()); +} + +/// \brief Read the settings from the last session. +/// \param fileName Optional filename to export the settings to a specific file. +void OpenHantekMainWindow::readSettings(const QString &fileName) { + // Use main configuration if the fileName wasn't set + QSettings *settingsLoader; + if(fileName.isEmpty()) + settingsLoader = new QSettings(this); + else + settingsLoader = new QSettings(fileName, QSettings::IniFormat, this); + + // Window size and position + settingsLoader->beginGroup("window"); + if(settingsLoader->contains("pos")) + this->move(settingsLoader->value("pos").toPoint()); + if(settingsLoader->contains("size")) + this->resize(settingsLoader->value("size").toSize()); + settingsLoader->endGroup(); + + // Oszilloskope settings + settingsLoader->beginGroup("scope"); + // Horizontal axis + settingsLoader->beginGroup("horizontal"); + if(settingsLoader->contains("format")) + this->settings->scope.horizontal.format = (Dso::GraphFormat) settingsLoader->value("format").toInt(); + if(settingsLoader->contains("frequencybase")) + this->settings->scope.horizontal.frequencybase = settingsLoader->value("frequencybase").toDouble(); + for(int marker = 0; marker < 2; marker++) { + QString name; + name = QString("marker%1").arg(marker); + if(settingsLoader->contains(name)) + this->settings->scope.horizontal.marker[marker] = settingsLoader->value(name).toDouble(); + } + if(settingsLoader->contains("timebase")) + this->settings->scope.horizontal.timebase = settingsLoader->value("timebase").toDouble(); + settingsLoader->endGroup(); + // Trigger + settingsLoader->beginGroup("trigger"); + if(settingsLoader->contains("filter")) + this->settings->scope.trigger.filter = settingsLoader->value("filter").toBool(); + if(settingsLoader->contains("mode")) + this->settings->scope.trigger.mode = (Dso::TriggerMode) settingsLoader->value("mode").toInt(); + if(settingsLoader->contains("position")) + this->settings->scope.trigger.position = settingsLoader->value("position").toDouble(); + if(settingsLoader->contains("slope")) + this->settings->scope.trigger.slope = (Dso::Slope) settingsLoader->value("slope").toInt(); + if(settingsLoader->contains("source")) + this->settings->scope.trigger.source = settingsLoader->value("source").toInt(); + if(settingsLoader->contains("special")) + this->settings->scope.trigger.special = settingsLoader->value("special").toInt(); + settingsLoader->endGroup(); + // Spectrum + for(int channel = 0; channel < this->settings->scope.spectrum.count(); channel++) { + settingsLoader->beginGroup(QString("spectrum%1").arg(channel)); + if(settingsLoader->contains("magnitude")) + this->settings->scope.spectrum[channel].magnitude = settingsLoader->value("magnitude").toDouble(); + if(settingsLoader->contains("offset")) + this->settings->scope.spectrum[channel].offset = settingsLoader->value("offset").toDouble(); + if(settingsLoader->contains("used")) + this->settings->scope.spectrum[channel].used = settingsLoader->value("used").toBool(); + settingsLoader->endGroup(); + } + // Vertical axis + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + settingsLoader->beginGroup(QString("vertical%1").arg(channel)); + if(settingsLoader->contains("gain")) + this->settings->scope.voltage[channel].gain = settingsLoader->value("gain").toDouble(); + if(settingsLoader->contains("misc")) + this->settings->scope.voltage[channel].misc = settingsLoader->value("misc").toInt(); + if(settingsLoader->contains("offset")) + this->settings->scope.voltage[channel].offset = settingsLoader->value("offset").toDouble(); + if(settingsLoader->contains("trigger")) + this->settings->scope.voltage[channel].trigger = settingsLoader->value("trigger").toDouble(); + if(settingsLoader->contains("used")) + this->settings->scope.voltage[channel].used = settingsLoader->value("used").toBool(); + settingsLoader->endGroup(); + } + if(settingsLoader->contains("spectrumLimit")) + this->settings->scope.spectrumLimit = settingsLoader->value("spectrumLimit").toDouble(); + if(settingsLoader->contains("spectrumReference")) + this->settings->scope.spectrumReference = settingsLoader->value("spectrumReference").toDouble(); + if(settingsLoader->contains("spectrumWindow")) + this->settings->scope.spectrumWindow = (Dso::WindowFunction) settingsLoader->value("spectrumWindow").toInt(); + settingsLoader->endGroup(); + + // View + settingsLoader->beginGroup("view"); + // Colors + settingsLoader->beginGroup("color"); + if(settingsLoader->contains("axes")) + this->settings->view.color.screen.axes = settingsLoader->value("axes").value(); + if(settingsLoader->contains("background")) + this->settings->view.color.screen.background = settingsLoader->value("background").value(); + if(settingsLoader->contains("border")) + this->settings->view.color.screen.border = settingsLoader->value("border").value(); + if(settingsLoader->contains("grid")) + this->settings->view.color.screen.grid = settingsLoader->value("grid").value(); + if(settingsLoader->contains("markers")) + this->settings->view.color.screen.markers = settingsLoader->value("markers").value(); + for(int channel = 0; channel < this->settings->scope.spectrum.count(); channel++) { + QString key = QString("spectrum%1").arg(channel); + if(settingsLoader->contains(key)) + this->settings->view.color.screen.spectrum[channel] = settingsLoader->value(key).value(); + } + if(settingsLoader->contains("text")) + this->settings->view.color.screen.text = settingsLoader->value("text").value(); + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + QString key = QString("voltage%1").arg(channel); + if(settingsLoader->contains(key)) + this->settings->view.color.screen.voltage[channel] = settingsLoader->value(key).value(); + } + settingsLoader->endGroup(); + // Other view settings + if(settingsLoader->contains("digitalPhosphor")) + this->settings->view.digitalPhosphor = settingsLoader->value("digitalPhosphor").toBool(); + if(settingsLoader->contains("interpolation")) + this->settings->view.interpolation = (GlInterpolationMode) settingsLoader->value("interpolation").toInt(); + if(settingsLoader->contains("screenColorImages")) + this->settings->view.screenColorImages = (GlInterpolationMode) settingsLoader->value("screenColorImages").toBool(); + if(settingsLoader->contains("zoom")) + this->settings->view.zoom = (GlInterpolationMode) settingsLoader->value("zoom").toBool(); + settingsLoader->endGroup(); + + delete settingsLoader; + + emit(settingsChanged()); +} + +/// \brief Save the settings to the harddisk. +void OpenHantekMainWindow::writeSettings(const QString &fileName) { + // Use main configuration and save everything if the fileName wasn't set + QSettings *settingsSaver; + bool complete = fileName.isEmpty(); + if(complete) + settingsSaver = new QSettings(this); + else + settingsSaver = new QSettings(fileName, QSettings::IniFormat, this); + + if(complete) { + // Window size and position + settingsSaver->beginGroup("window"); + settingsSaver->setValue("pos", this->pos()); + settingsSaver->setValue("size", this->size()); + settingsSaver->endGroup(); + } + // Oszilloskope settings + settingsSaver->beginGroup("scope"); + // Horizontal axis + settingsSaver->beginGroup("horizontal"); + settingsSaver->setValue("format", this->settings->scope.horizontal.format); + settingsSaver->setValue("frequencybase", this->settings->scope.horizontal.frequencybase); + for(int marker = 0; marker < 2; marker++) + settingsSaver->setValue(QString("marker%1").arg(marker), this->settings->scope.horizontal.marker[marker]); + settingsSaver->setValue("timebase", this->settings->scope.horizontal.timebase); + settingsSaver->endGroup(); + // Trigger + settingsSaver->beginGroup("trigger"); + settingsSaver->setValue("filter", this->settings->scope.trigger.filter); + settingsSaver->setValue("mode", this->settings->scope.trigger.mode); + settingsSaver->setValue("position", this->settings->scope.trigger.position); + settingsSaver->setValue("slope", this->settings->scope.trigger.slope); + settingsSaver->setValue("source", this->settings->scope.trigger.source); + settingsSaver->endGroup(); + // Spectrum + for(int channel = 0; channel < this->settings->scope.spectrum.count(); channel++) { + settingsSaver->beginGroup(QString("spectrum%1").arg(channel)); + settingsSaver->setValue("magnitude", this->settings->scope.spectrum[channel].magnitude); + settingsSaver->setValue("offset", this->settings->scope.spectrum[channel].offset); + settingsSaver->setValue("used", this->settings->scope.spectrum[channel].used); + settingsSaver->endGroup(); + } + // Vertical axis + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) { + settingsSaver->beginGroup(QString("vertical%1").arg(channel)); + settingsSaver->setValue("gain", this->settings->scope.voltage[channel].gain); + settingsSaver->setValue("misc", this->settings->scope.voltage[channel].misc); + settingsSaver->setValue("offset", this->settings->scope.voltage[channel].offset); + settingsSaver->setValue("trigger", this->settings->scope.voltage[channel].trigger); + settingsSaver->setValue("used", this->settings->scope.voltage[channel].used); + settingsSaver->endGroup(); + } + settingsSaver->setValue("spectrumLimit", this->settings->scope.spectrumLimit); + settingsSaver->setValue("spectrumReference", this->settings->scope.spectrumReference); + settingsSaver->setValue("spectrumWindow", this->settings->scope.spectrumWindow); + settingsSaver->endGroup(); + + // View + settingsSaver->beginGroup("view"); + // Colors + if(complete) { + settingsSaver->beginGroup("color"); + settingsSaver->setValue("axes", this->settings->view.color.screen.axes); + settingsSaver->setValue("background", this->settings->view.color.screen.background); + settingsSaver->setValue("border", this->settings->view.color.screen.border); + settingsSaver->setValue("grid", this->settings->view.color.screen.grid); + settingsSaver->setValue("markers", this->settings->view.color.screen.markers); + for(int channel = 0; channel < this->settings->scope.spectrum.count(); channel++) + settingsSaver->setValue(QString("spectrum%1").arg(channel), this->settings->view.color.screen.spectrum[channel]); + settingsSaver->setValue("text", this->settings->view.color.screen.text); + for(int channel = 0; channel < this->settings->scope.voltage.count(); channel++) + settingsSaver->setValue(QString("voltage%1").arg(channel), this->settings->view.color.screen.voltage[channel]); + settingsSaver->endGroup(); + } + // Other view settings + settingsSaver->setValue("digitalPhosphor", this->settings->view.digitalPhosphor); + if(complete) { + settingsSaver->setValue("interpolation", this->settings->view.interpolation); + settingsSaver->setValue("screenColorImages", this->settings->view.screenColorImages); + } + settingsSaver->setValue("zoom", this->settings->view.zoom); + settingsSaver->endGroup(); + + delete settingsSaver; +} + +/// \brief Open a existing file. +void OpenHantekMainWindow::open() { + QString fileName = QFileDialog::getOpenFileName(this, tr("Open file"), "", "*.xml"); + if(!fileName.isEmpty()) + return; // TODO +} + +/// \brief Save the file. +/// \return true if the file was saved, false if not. +bool OpenHantekMainWindow::save() { + if (this->currentFile.isEmpty()) { + return saveAs(); + } else { + return false; // TODO + } +} + +/// \brief Save the mapping to another filename. +/// \return true if the file was saved, false if not. +bool OpenHantekMainWindow::saveAs() { + QString fileName = QFileDialog::getSaveFileName(this, tr("Save settings..."), "", "*.xml"); + if (fileName.isEmpty()) + return false; + + return false; // TODO +} + +/// \brief Start the oscilloscope. +void OpenHantekMainWindow::start() { + this->startStopAction->setText(tr("&Stop")); + this->startStopAction->setIcon(QIcon(":actions/stop.png")); + this->startStopAction->setStatusTip(tr("Stop the oscilloscope")); + + disconnect(this->startStopAction, SIGNAL(triggered()), this, SLOT(start())); + connect(this->startStopAction, SIGNAL(triggered()), this, SLOT(stop())); + connect(this->dsoControl, SIGNAL(disconnected()), this, SLOT(stopped())); + + this->dsoControl->startSampling(); +} + +/// \brief Stop the oscilloscope. +void OpenHantekMainWindow::stop() { + this->startStopAction->setText(tr("&Start")); + this->startStopAction->setIcon(QIcon(":actions/start.png")); + this->startStopAction->setStatusTip(tr("Start the oscilloscope")); + + disconnect(this->startStopAction, SIGNAL(triggered()), this, SLOT(stop())); + disconnect(this->dsoWidget, SIGNAL(stopped()), this, SLOT(stopped())); + connect(this->startStopAction, SIGNAL(triggered()), this, SLOT(start())); + + this->dsoControl->stopSampling(); +} + +/// \brief Configure the oscilloscope. +void OpenHantekMainWindow::config() { + DsoConfigDialog configDialog(this->settings, this); + if(configDialog.exec() == QDialog::Accepted) + this->settingsChanged(); +} + +/// \brief Enable/disable digital phosphor. +void OpenHantekMainWindow::digitalPhosphor(bool enabled) { + this->settings->view.digitalPhosphor = enabled; + + if(this->settings->view.digitalPhosphor) + this->digitalPhosphorAction->setStatusTip(tr("Disable fading of previous graphs")); + else + this->digitalPhosphorAction->setStatusTip(tr("Enable fading of previous graphs")); +} + +/// \brief Show/hide the magnified scope. +void OpenHantekMainWindow::zoom(bool enabled) { + this->settings->view.zoom = enabled; + + if(this->settings->view.zoom) + this->zoomAction->setStatusTip(tr("Hide magnified scope")); + else + this->zoomAction->setStatusTip(tr("Show magnified scope")); +} + +/// \brief Show the about dialog. +void OpenHantekMainWindow::about() { + QMessageBox::about(this, tr("About OpenHantek %1").arg(VERSION), tr( + "

This is a open source software for Hantek USB oscilloscopes.

" + "

Copyright © 2010 Oliver Haag <oliver.haag@gmail.com>

" + )); +} + +/// \brief The settings have changed. +void OpenHantekMainWindow::applySettings() { +} + +/// \brief Apply new buffer size to settings. +/// \param action The selected buffer size menu item. +void OpenHantekMainWindow::bufferSizeSelected(QAction *action) { + this->settings->scope.horizontal.samples = (action == this->bufferSizeSmallAction) ? Hantek::BUFFER_SMALL : Hantek::BUFFER_LARGE; + this->dsoControl->setBufferSize(this->settings->scope.horizontal.samples); +} + +/// \brief Sets the offset of the oscilloscope for the given channel. +/// \param channel The channel that got a new offset. +void OpenHantekMainWindow::updateOffset(unsigned int channel) { + if(channel >= this->settings->scope.physicalChannels) + return; + + this->dsoControl->setOffset(channel, (this->settings->scope.voltage[channel].offset / DIVS_VOLTAGE) + 0.5); +} + +/// \brief Sets the samplerate of the oscilloscope. +void OpenHantekMainWindow::updateTimebase() { + this->settings->scope.horizontal.samplerate = this->dsoControl->setSamplerate(1e3 / this->settings->scope.horizontal.timebase); + this->dsoWidget->updateSamplerate(); +} + +/// \brief Sets the state of the given oscilloscope channel. +/// \param channel The channel whose state has changed. +void OpenHantekMainWindow::updateUsed(unsigned int channel) { + if(channel >= (unsigned int) this->settings->scope.voltage.count()) + return; + + bool mathUsed = this->settings->scope.voltage[this->settings->scope.physicalChannels].used | this->settings->scope.spectrum[this->settings->scope.physicalChannels].used; + + // Normal channel, check if voltage/spectrum or math channel is used + if(channel < this->settings->scope.physicalChannels) + this->dsoControl->setChannelUsed(channel, mathUsed | this->settings->scope.voltage[channel].used | this->settings->scope.spectrum[channel].used); + // Math channel, update all channels + else if(channel == this->settings->scope.physicalChannels) { + for(unsigned int channelCounter = 0; channelCounter < this->settings->scope.physicalChannels; channelCounter++) + this->dsoControl->setChannelUsed(channelCounter, mathUsed | this->settings->scope.voltage[channelCounter].used | this->settings->scope.spectrum[channelCounter].used); + } +} + +/// \brief Sets the gain of the oscilloscope for the given channel. +/// \param channel The channel that got a new gain value. +void OpenHantekMainWindow::updateVoltageGain(unsigned int channel) { + if(channel >= this->settings->scope.physicalChannels) + return; + + this->dsoControl->setGain(channel, this->settings->scope.voltage[channel].gain * DIVS_VOLTAGE); +} diff --git b/openhantek/src/openhantek.h a/openhantek/src/openhantek.h new file mode 100644 index 0000000..4ddca1e --- /dev/null +++ a/openhantek/src/openhantek.h @@ -0,0 +1,140 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file openhantek.h +/// \brief Declares the HantekDsoMainWindow class. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef HANTEKDSO_H +#define HANTEKDSO_H + + +#include + + +class QActionGroup; + +class DataAnalyzer; +class DsoControl; +class DsoSettings; +class DsoWidget; +class HorizontalDock; +class TriggerDock; +class SpectrumDock; +class VoltageDock; + + +//////////////////////////////////////////////////////////////////////////////// +/// \class OpenHantekMainWindow openhantek.h +/// \brief The main window of the application. +/// The main window contains the classic oszilloscope-screen and the gui +/// elements used to control the oszilloscope. +class OpenHantekMainWindow : public QMainWindow { + Q_OBJECT + + public: + OpenHantekMainWindow(QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~OpenHantekMainWindow(); + + protected: + void closeEvent(QCloseEvent *event); + + private: + // GUI creation + void createActions(); + void createMenus(); + void createToolBars(); + void createStatusBar(); + void createDockWindows(); + + // Settings + void readSettings(const QString &fileName = QString()); + void writeSettings(const QString &fileName = QString()); + + // Actions + QAction *newAction, *openAction, *saveAction, *saveAsAction; + QAction *printAction, *exportAsAction; + QAction *exitAction; + + QAction *configAction; + QAction *startStopAction; + QActionGroup *bufferSizeActionGroup; + QAction *bufferSizeSmallAction, *bufferSizeLargeAction; + QAction *digitalPhosphorAction, *zoomAction; + + QAction *aboutAction, *aboutQtAction; + + // Menus + QMenu *fileMenu; + QMenu *viewMenu; + QMenu *oscilloscopeMenu, *bufferSizeMenu; + QMenu *helpMenu; + + // Toolbars + QToolBar *fileToolBar, *oscilloscopeToolBar, *viewToolBar; + + // Docking windows + HorizontalDock *horizontalDock; + TriggerDock *triggerDock; + SpectrumDock *spectrumDock; + VoltageDock *voltageDock; + + // Central widgets + DsoWidget *dsoWidget; + + // Data handling classes + DataAnalyzer *dataAnalyzer; + DsoControl *dsoControl; + + // Other variables + QString currentFile; + + DsoSettings *settings; + + private slots: + // File operations + void open(); + bool save(); + bool saveAs(); + // View + void digitalPhosphor(bool enabled); + void zoom(bool enabled); + // Oscilloscope control + void start(); + void stop(); + // Other + void config(); + void about(); + + void applySettings(); + + void bufferSizeSelected(QAction *action); + void updateOffset(unsigned int channel); + void updateTimebase(); + void updateUsed(unsigned int channel); + void updateVoltageGain(unsigned int channel); + + signals: + void settingsChanged(); +}; + + +#endif diff --git b/openhantek/src/settings.cpp a/openhantek/src/settings.cpp new file mode 100644 index 0000000..7d4b8f4 --- /dev/null +++ a/openhantek/src/settings.cpp @@ -0,0 +1,174 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// settings.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#include + + +#include "settings.h" + +#include "constants.h" +#include "dsowidget.h" + + +//////////////////////////////////////////////////////////////////////////////// +// class DsoSettings +/// \brief Sets the values to their defaults. +DsoSettings::DsoSettings() { + // Options + this->options.alwaysSave = false; + + // Oscilloscope settings + // Horizontal axis + this->scope.horizontal.format = Dso::GRAPHFORMAT_TY; + this->scope.horizontal.frequencybase = 1e3; + this->scope.horizontal.marker[0] = -1.0; + this->scope.horizontal.marker[1] = 1.0; + this->scope.horizontal.timebase = 1e-3; + this->scope.horizontal.samples = 10240; + this->scope.horizontal.samplerate = 1e6; + // Trigger + this->scope.trigger.filter = true; + this->scope.trigger.mode = Dso::TRIGGERMODE_NORMAL; + this->scope.trigger.position = 0.0; + this->scope.trigger.slope = Dso::SLOPE_POSITIVE; + this->scope.trigger.source = 0; + this->scope.trigger.special = false; + // General + this->scope.physicalChannels = 0; + this->scope.spectrumLimit = 0.0; + this->scope.spectrumReference = 20.0; + this->scope.spectrumWindow = Dso::WINDOW_HANN; + + + // View + // Colors + // Screen + this->view.color.screen.axes = QColor(0xff, 0xff, 0xff, 0x7f); + this->view.color.screen.background = QColor(0x00, 0x00, 0x00, 0xff); + this->view.color.screen.border = QColor(0xff, 0xff, 0xff, 0xff); + this->view.color.screen.grid = QColor(0xff, 0xff, 0xff, 0x3f); + this->view.color.screen.markers = QColor(0xff, 0xff, 0xff, 0xbf); + this->view.color.screen.text = QColor(0xff, 0xff, 0xff, 0xff); + // Print + this->view.color.print.axes = QColor(0x00, 0x00, 0x00, 0xbf); + this->view.color.print.background = QColor(0x00, 0x00, 0x00, 0x00); + this->view.color.print.border = QColor(0x00, 0x00, 0x00, 0xff); + this->view.color.print.grid = QColor(0x00, 0x00, 0x00, 0x7f); + this->view.color.print.markers = QColor(0x00, 0x00, 0x00, 0xef); + this->view.color.print.text = QColor(0x00, 0x00, 0x00, 0xff); + // Other view settings + this->view.antialiasing = true; + this->view.digitalPhosphor = false; + this->view.digitalPhosphorDepth = 8; + this->view.interpolation = INTERPOLATION_LINEAR; + this->view.screenColorImages = false; + this->view.zoom = false; +} + +/// \brief Set the number of channels. +/// \param channels The new channel count, that will be applied to lists. +void DsoSettings::setChannelCount(unsigned int channels) { + this->scope.physicalChannels = channels; + // Always put the math channel at the end of the list + + // Remove list items for removed channels + for(int channel = this->scope.spectrum.count() - 2; channel >= (int) channels; channel--) + this->scope.spectrum.removeAt(channel); + for(int channel = this->scope.voltage.count() - 2; channel >= (int) channels; channel--) + this->scope.voltage.removeAt(channel); + for(int channel = this->scope.spectrum.count() - 2; channel >= (int) channels; channel--) + this->scope.spectrum.removeAt(channel); + for(int channel = this->scope.spectrum.count() - 2; channel >= (int) channels; channel--) + this->scope.spectrum.removeAt(channel); + + // Add new channels to the list + for(int channel = 0; channel < (int) channels; channel++) { + // Oscilloscope settings + // Spectrum + if(this->scope.spectrum.count() <= channel + 1) { + DsoSettingsScopeSpectrum newSpectrum; + newSpectrum.magnitude = 20.0; + newSpectrum.name = QApplication::tr("SP%1").arg(channel + 1); + newSpectrum.offset = 0.0; + newSpectrum.used = false; + this->scope.spectrum.insert(channel, newSpectrum); + } + // Voltage + if(this->scope.voltage.count() <= channel + 1) { + DsoSettingsScopeVoltage newVoltage; + newVoltage.gain = 1.0; + newVoltage.misc = Dso::COUPLING_DC; + newVoltage.name = QApplication::tr("CH%1").arg(channel + 1); + newVoltage.offset = 0.0; + newVoltage.trigger = 0.0; + newVoltage.used = (channel == 0); + this->scope.voltage.insert(channel, newVoltage); + } + + // View + // Colors + // Screen + if(this->view.color.screen.voltage.count() <= channel + 1) + this->view.color.screen.voltage.insert(channel, QColor::fromHsv(channel * 60, 0xff, 0xff)); + if(this->view.color.screen.spectrum.count() <= channel + 1) + this->view.color.screen.spectrum.insert(channel, this->view.color.screen.voltage[channel].lighter()); + // Print + if(this->view.color.print.voltage.count() <= channel + 1) + this->view.color.print.voltage.insert(channel, this->view.color.screen.voltage[channel]); + if(this->view.color.print.spectrum.count() <= channel + 1) + this->view.color.print.spectrum.insert(channel, this->view.color.print.voltage[channel].darker()); + } + + // Check if the math channel is missing + if(this->scope.spectrum.count() <= (int) channels) { + DsoSettingsScopeSpectrum newSpectrum; + newSpectrum.magnitude = 20.0; + newSpectrum.name = QApplication::tr("SPM"); + newSpectrum.offset = 0.0; + newSpectrum.used = false; + this->scope.spectrum.append(newSpectrum); + } + if(this->scope.voltage.count() <= (int) channels) { + DsoSettingsScopeVoltage newVoltage; + newVoltage.gain = 1.0; + newVoltage.misc = Dso::MATHMODE_1ADD2; + newVoltage.name = QApplication::tr("MATH"); + newVoltage.offset = 0.0; + newVoltage.trigger = 0.0; + newVoltage.used = false; + this->scope.voltage.append(newVoltage); + } + if(this->view.color.screen.voltage.count() <= (int) channels) + this->view.color.screen.voltage.append(QColor(0x7f, 0x7f, 0x7f, 0xff)); + if(this->view.color.screen.spectrum.count() <= (int) channels) + this->view.color.screen.spectrum.append(this->view.color.screen.voltage[channels].lighter()); + if(this->view.color.print.voltage.count() <= (int) channels) + this->view.color.print.voltage.append(this->view.color.screen.voltage[channels]); + if(this->view.color.print.spectrum.count() <= (int) channels) + this->view.color.print.spectrum.append(this->view.color.print.voltage[channels].darker()); +} + +/// \brief Cleans up. +DsoSettings::~DsoSettings() { +} diff --git b/openhantek/src/settings.h a/openhantek/src/settings.h new file mode 100644 index 0000000..a7a8659 --- /dev/null +++ a/openhantek/src/settings.h @@ -0,0 +1,157 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +/// \file settings.h +/// \brief Declares the DsoSettings class. +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef SETTINGS_H +#define SETTINGS_H + + +#include +#include +#include + + +#include "constants.h" + + +//////////////////////////////////////////////////////////////////////////////// +/// \struct DsoSettingsOptions settings.h +/// \brief Holds the general options of the program. +struct DsoSettingsOptions { + bool alwaysSave; ///< Always save the settings on exit +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct DsoSettingsScopeHorizontal settings.h +/// \brief Holds the settings for the horizontal axis. +struct DsoSettingsScopeHorizontal { + Dso::GraphFormat format; ///< Graph drawing mode of the scope + double frequencybase; ///< Frequencybase in Hz/div + double marker[2]; ///< Marker positions in div + double timebase; ///< Timebase in s/div + unsigned long int samples; ///< Sample count + unsigned long int samplerate; ///< The samplerate of the oscilloscope in S +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct DsoSettingsScopeTrigger settings.h +/// \brief Holds the settings for the trigger. +struct DsoSettingsScopeTrigger { + bool filter; ///< Not sure what this is good for... + Dso::TriggerMode mode; ///< Automatic, normal or single trigger + double position; ///< Horizontal position for pretrigger + Dso::Slope slope; ///< Rising or falling edge causes trigger + bool special; ///< true if the trigger source is not a standard channel + unsigned int source; ///< Channel that is used as trigger source +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct DsoSettingsScopeSpectrum settings.h +/// \brief Holds the settings for the spectrum analysis. +struct DsoSettingsScopeSpectrum { + double magnitude; ///< The vertical resolution in dB/div + QString name; ///< Name of this channel + double offset; ///< Vertical offset in divs + bool used; ///< true if the spectrum is turned on +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct DsoSettingsScopeVoltage settings.h +/// \brief Holds the settings for the normal voltage graphs. +struct DsoSettingsScopeVoltage { + double gain; ///< The vertical resolution in V/div + int misc; ///< Different enums, coupling for real- and mode for math-channels + QString name; ///< Name of this channel + double offset; ///< Vertical offset in divs + double trigger; ///< Trigger level in V + bool used; ///< true if this channel is enabled +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct DsoSettingsScope settings.h +/// \brief Holds the settings for the oscilloscope. +struct DsoSettingsScope { + DsoSettingsScopeHorizontal horizontal; ///< Settings for the horizontal axis + DsoSettingsScopeTrigger trigger; ///< Settings for the trigger + QList spectrum; ///< Spectrum analysis settings + QList voltage; ///< Settings for the normal graphs + + unsigned int physicalChannels; ///< Number of real channels (No math etc.) + Dso::WindowFunction spectrumWindow; ///< Window function for DFT + double spectrumReference; ///< Reference level for spectrum in dBm + double spectrumLimit; ///< Minimum magnitude of the spectrum (Avoids peaks) +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct DsoColorValues settings.h +/// \brief Holds the color values for the oscilloscope screen. +struct DsoSettingsColorValues { + QColor axes; ///< X- and Y-axis and subdiv lines on them + QColor background; ///< The scope background + QColor border; ///< The border of the scope screen + QColor grid; ///< The color of the grid + QColor markers; ///< The color of the markers + QList spectrum; ///< The colors of the spectrum graphs + QColor text; ///< The default text color + QList voltage; ///< The colors of the voltage graphs +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct DsoSettingsViewColor settings.h +/// \brief Holds the settings for the used colors on the screen and on paper. +struct DsoSettingsViewColor { + DsoSettingsColorValues screen; ///< Colors for the screen + DsoSettingsColorValues print; ///< Colors for printout +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct DsoSettingsView settings.h +/// \brief Holds all view settings. +struct DsoSettingsView { + DsoSettingsViewColor color; ///< Used colors + bool antialiasing; ///< Antialiasing for the graphs + bool digitalPhosphor; ///< true slowly fades out the previous graphs + int digitalPhosphorDepth; ///< Number of channels shown at one time + GlInterpolationMode interpolation; ///< Interpolation mode for the graph + bool screenColorImages; ///< true exports images with screen colors + bool zoom; ///< true if the magnified scope is enabled +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \class DsoSettings settings.h +/// \brief Holds the settings of the program. +class DsoSettings { + public: + DsoSettings(); + ~DsoSettings(); + + void setChannelCount(unsigned int channels); + + DsoSettingsOptions options; ///< General options of the program + DsoSettingsScope scope; ///< All oscilloscope related settings + DsoSettingsView view; ///< All view related settings +}; + + +#endif diff --git b/openhantek/translations/openhantek_de.qm a/openhantek/translations/openhantek_de.qm new file mode 100644 index 0000000..ae8b10e --- /dev/null +++ a/openhantek/translations/openhantek_de.qm diff --git b/openhantek/translations/openhantek_de.ts a/openhantek/translations/openhantek_de.ts new file mode 100644 index 0000000..d10b6c0 --- /dev/null +++ a/openhantek/translations/openhantek_de.ts @@ -0,0 +1,779 @@ + + + + + DsoConfigAnalysisPage + + + Rectangular + Rechteck + + + + Hamming + Hamming + + + + Hann + +Hann + + + + Cosine + Cosinus + + + + Bartlett + Bartlett + + + + Triangular + Dreieck + + + + Gauss + Gauss + + + + Bartlett-Hann + Bartlett-Hann + + + + Blackman + Blackman + + + + Nuttall + Nuttall + + + + Blackman-Harris + Blackman-Harris + + + + Blackman-Nuttall + Blackman-Nuttall + + + + Flat top + Flat Top + + + + Window function + Fensterfunktion + + + + Spectrum + Spektrum + + + + DsoConfigColorsPage + + + Axes + Achsen + + + + Background + Hintergrund + + + + Border + Rahmen + + + + Grid + Gitternetz + + + + Markers + Marker + + + + Text + Text + + + + Screen + Bildschirm + + + + Channel + Kanal + + + + Spectrum + Spektrum + + + + Graph + Graph + + + + DsoConfigDialog + + + Settings + Einstellungen + + + + &Ok + &Ok + + + + &Apply + &Anwenden + + + + &Cancel + &Abbrechen + + + + Analysis + Analyse + + + + Colors + Farben + + + + Scope + Oszilloskop + + + + DsoConfigScopePage + + + Off + Aus + + + + Linear + Linear + + + + Sinc + Sinc + + + + Antialiasing + Antialiasing + + + + Interpolation + Interpolation + + + + Graph + Graph + + + + DsoWidget + + + Zoom x%L1 + Zoom x%L1 + + + + + + + + + /div + /div + + + + Portable Document Format (*.pdf) + Portables Dokumentenformat (*.pdf) + + + + PostScript (*.ps) + PostScript (*.ps) + + + + Marker 1/2 + Marker 1/2 + + + + %L1% + %L1% + + + + %1 %2 %3 %4 + %1 %2 %3 %4 + + + + /s + /s + + + + %1 S + %1 S + + + + Image (*.png *.xpm *.jpg) + Bild (*.png *.xpm *.jpg) + + + + Comma-Separated Values (*.csv) + Kommagetrennte Werte (*.csv) + + + + Exporter + + + Print oscillograph + Oszillograph drucken + + + + HorizontalDock + + + Horizontal + Horizontal + + + + T - Y + T - Y + + + + X - Y + X - Y + + + + Timebase + Zeitbasis + + + + Frequencybase + Frequenzbasis + + + + Format + Format + + + + OpenHantekMainWindow + + + OpenHantek + OpenHantek + + + + &Open... + &Öffnen... + + + + Ctrl+O + Strg+O + + + + Open saved settings + Gespeicherte Einstellungen öffnen + + + + &Save + &Speichern + + + + + Ctrl+S + Strg+S + + + + Save the current settings + Aktuelle Einstellungen speichern + + + + Save &as... + Speichern &unter... + + + + Save the current settings to another file + Aktuelle Einstellungen in anderer Datei speichern + + + + &Print... + &Drucken... + + + + Ctrl+P + Strg+P + + + + Print the oscilloscope screen + Den Oscilloskopbildschirm drucken + + + + &Export as... + &Exportieren als... + + + + Ctrl+E + Strg+E + + + + Export the oscilloscope data to a file + Die Oszilloskopdaten in eine Datei exportieren + + + + E&xit + &Beenden + + + + Ctrl+Q + Strg+Q + + + + Exit the application + Anwendung beenden + + + + &Settings + &Einstellungen + + + + Configure the oscilloscope + Das Oszilloskop konfigurieren + + + + + &Stop + &Stop + + + + Space + Leertaste + + + + + Stop the oscilloscope + Das Oszilloskop anhalten + + + + &Small + &Klein + + + + 10240 Samples + 10240 Werte + + + + &Large + &Groß + + + + 32768 Samples + 32768 Werte + + + + Digital &phosphor + Digitaler &Phosphor + + + + &Zoom + &Zoom + + + + &About + &Über + + + + Show information about this program + Zeige Informationen über dieses Programm + + + + About &Qt + Über &Qt + + + + Show the Qt library's About box + Zeige Dialog zur Qt Bibliothek + + + + &File + &Datei + + + + &View + &Ansicht + + + + &Oscilloscope + &Oszilloskop + + + + &Buffer size + &Puffergröße + + + + &Help + &Hilfe + + + + File + Datei + + + + Oscilloscope + Oszilloskop + + + + View + Ansicht + + + + Ready + Bereit + + + + Open file + Datei öffnen + + + + Save settings... + Einstellungen speichern... + + + + &Start + &Start + + + + Start the oscilloscope + Startet das Oszilloskop + + + + Disable fading of previous graphs + Nachleuchten von vorigen Graphen deaktivieren + + + + Enable fading of previous graphs + Nachleuchten von vorigen Graphen aktivieren + + + + Hide magnified scope + Vergrößerte Anzeige ausblenden + + + + Show magnified scope + Vergrößerte Anzeige anzeigen + + + + About OpenHantek %1 + *ber OpenHantek %1 + + + + <p>This is a open source software for Hantek USB oscilloscopes.</p><p>Copyright &copy; 2010 Oliver Haag &lt;oliver.haag@gmail.com&gt;</p> + <p>Dies ist ein Open-Source Programm für Hantek USB Oszilloskope.</p><p>Copyright &copy; 2010 Oliver Haag &lt;oliver.haag@gmail.com&gt;</p> + + + + QApplication + + + %L1 mV + %L1 mV + + + + %L1 V + %L1 V + + + + %L1 dB + %L1 dB + + + + %L1 ns + %L1 ns + + + + %L1 µs + %L1 µs + + + + %L1 ms + %L1 ms + + + + %L1 s + %L1 s + + + + %L1 min + %L1 min + + + + %L1 h + %L1 h + + + + %L1 Hz + %L1 Hz + + + + %L1 kHz + %L1 kHz + + + + %L1 MHz + %L1 MHz + + + + %L1 S + %L1 S + + + + %L1 kS + %L1 kS + + + + %L1 MS + %L1 MS + + + + CH%1 + CH%1 + + + + SP%1 + SP%1 + + + + MATH + MATH + + + + SPM + SPM + + + + SpectrumDock + + + Spectrum + Spektrum + + + + TriggerDock + + + Trigger + Trigger + + + + Auto + Auto + + + + Normal + Normal + + + + Single + Einzel + + + + CH%1 + CH%1 + + + + ALT + ALT + + + + EXT + EXT + + + + EXT/10 + EXT/10 + + + + Mode + Modus + + + + Slope + Flanke + + + + Source + Quelle + + + + VerticalDock + + + Vertical + Vertikal + + + + AC + AC + + + + DC + DC + + + + GND + GND + + + + CH1 + CH2 + CH1 + CH2 + + + + CH1 - CH2 + CH1 - CH2 + + + + CH2 - CH1 + CH2 - CH1 + + +