Mercurial > hg > octave-lyh
changeset 13499:a8249623254e
Merge with Savannah
author | Jordi Gutiérrez Hermoso <jordigh@gmail.com> |
---|---|
date | Mon, 04 Jul 2011 13:59:55 -0400 |
parents | 6c1d0f03c331 (current diff) e05d6d17196e (diff) |
children | f2dde813214c |
files | |
diffstat | 176 files changed, 52947 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/gui//AUTHORS @@ -0,0 +1,1 @@ +e_k@users.sourceforge.net \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/gui//COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License.
new file mode 100644 --- /dev/null +++ b/gui//Changelog @@ -0,0 +1,19 @@ +31.07.2008 +Interface class from c-style conversions rewritten with pimpl support. + + +16.07.2008 +Added optional scrollbar + + +06.06.2008 +Some artefacts were removed, some added... +Also added support for color schemes, and 3 color schemes provided (classical - white on black, green on black, black on light yellow). Is it enough or not? + + +26.05.2008 +Added file release as an archive with source code. But preferrable way is still getting code from CVS, cause file release can be outdated. + + +11.05.2008 +Initial CVS import - first version comes with number 0.0.1 \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/gui//LineFont.src @@ -0,0 +1,786 @@ +#2500: single horizontal line +2500 + + +----- + + + +#2501: triple horizontal line +2501 + +----- +----- +----- + + +#2502: single vertical line +2502 + | + | + | + | + | + +#2503: triple vertical line +2503 + ||| + ||| + ||| + ||| + ||| + +#2504-250B are dashed - not handled + +#250C: top-left corner (lines on bottom + right) +250C + + + .-- + | + | + +#250D: as above, but top line triple-width +250D + + .-- + .-- + |-- + | + +#250E: now the vert line triple-width +250E + + + ..-- + ||| + ||| + +#250F: and now both lines triple-width +250F + + .___ + |.-- + ||._ + ||| + +#2510: top-right corner +2510 + + +--. + | + | + +2511 + +==. +==. +==| + | + +2512 + + +==.. + ||| + ||| + +2513 + +===. +==.| +=.|| + ||| + +#2514: bottom-left corner +2514 + | + | + .== + + + +2515 + | + |== + |== + === + + + +2516 + ||| + ||| + |.== + + + +2517 + ||| + ||.= + |.== + .=== + + +#2518: bottm-right corner +2518 + | + | +==. + + + +2519 + | +==| +==| +=== + + + +251A + ||| + ||| +==== + + + +251B + ||| +=.|| +==.| +===. + + +#251C: Join of vertical line and one from the right +251C + | + | + |== + | + | + +251D + | + |== + |== + |== + | + +251E + ||| + ||| + ||== + | + | + +251F + | + | + ||== + ||| + ||| + + +2520 + ||| + ||| + ||== + ||| + ||| + +2521 + ||| + |||= + ||== + .|== + | + +2522 + | + .|== + ||== + |||= + ||| + +2523 + ||| + ||.= + ||== + ||.= + ||| + +#2524: Join of vertical line and one from the left +2524 + | + | +==| + | + | + +2525 + | +==| +==| +==| + | + +2526 + ||| + ||| +==+| + | + | + +2527 + | + | +==+| + ||| + ||| + +2528 + ||| + ||| +==+| + ||| + ||| + +2529 + ||| +=+|| +==+| +===+ + | + +252A + | +=+|| +==+| +===+ + ||| + +252B + ||| +=+|| +==+| +=+|| + ||| + +#252C: horizontal line joined to from below +252C + + +===== + | + | + +252D + +=== +==|== +==| + | + +252E + + === +==|== + |== + | + +252F + +==+== +==|== +==|== + | + +2530 + +===== +===== +==|== + | + +2531 + +===| +==||= +=||| + ||| + +2532 + + |=== +=||== + ||== + || + +2533 + +===== +==|== +=+|+= + ||| + +#2534: bottom line, connected to from top +2534 + | + | +===== + + + +2535 + | +==| +===== +=== + + +2536 + | + |== +===== + === + + +2537 + | +==|== +===== +===== + + +2538 + ||| + ||| +===== + + + +2539 + ||| +==|| +===== +===| + + + +253A + ||| + ||== +=|=== + |=== + + +253B + ||| +==|== +===== +===== + + +#253C: vertical + horizontal lines intersecting +253C + | + | +===== + | + | + +253D + | +==| +===== +==| + | + +253E + | + |== +===== + |== + | + +253F + | +==|== +===== +==|== + | + +2540 + ||| + ||| +===== + | + | + +2541 + | + | +===== + ||| + ||| + +2542 + ||| + ||| +===== + ||| + ||| + +2543 + ||| +=||| +===== +==|+ + | + +2544 + ||| + ||== +===== + |== + | + +2545 + | +==|+ +===== +=||| + ||| + +2546 + | + |== +===== + ||== + ||| + +2547 + ||| +=|||= +===== +=|||= + | + +2548 + | +=|||= +===== +=|||= + ||| + +2549 + ||| +=||| +===== +=||| + ||| + +254A + ||| + |||= +===== + |||= + ||| + +254B + ||| +=|||= +===== +=|||= + ||| + +#254C-254F are dashed +2550 + +_____ + +_____ + + +2551 + | | + | | + | | + | | + | | + +2552 + + |-- + | + |-- + | + +2553 + + + ---- + | | + | | + +2554 + + +--- + | + + +- + | | + +2555 + +--+ + | +--+ + | + +2556 + + +-+-+ + | | + | | + +2557 + +---+ + | +-+ | + | | + +2558 + | + +-- + | + +-- + +2559 + | | + | | + +-+- + + + +255A + | | + | +- + | + +--- + + +255B + | +--+ + | +--+ + + +255C + | | + | | +-+-+ + + +255D + | | +-+ | + | +---+ + + +255E + | + +-- + | + +-- + | + +255F + | | + | | + | +- + | | + | | + +2560 + | | + | +- + | | + | +- + | | + +2561 + | +--+ + | +--+ + | + +2562 + | | + | | +-+ + + | | + | | + +2563 + | | +-+ | + | +-+ | + | | + +2564 + +----- + +--+-- + | + +2565 + + +-+-+- + | | + | | + +2566 + +----- + +-+ +- + | | + +2567 + | +--+-- + +----- + + +2568 + | | + | | +-+-+- + + + +2569 + | | +-+ +- + +----- + + +256A + | +--+-- + | +--+-- + | + +256B + | | + | | +-+-+- + | | + | | + +256C + | | +-+ +- + +-+ +- + | | + +#256F-2570 are curly, +#2571-2573 are slashes and X + +2574 + + +___ + + + +2575 + | + | + | + + + +2576 + + + ___ + + + +2577 + + + | + | + | + +2578 + +___ +___ +___ + + +2579 + ||| + ||| + ||| + + + +257A + + ___ + ___ + ___ + + +257B + + + ||| + ||| + ||| + +257C + + ___ +_____ + ___ + + +257D + | + | + ||| + ||| + ||| + +257E + +___ +_____ +___ + + +257F + ||| + ||| + ||| + | + |
new file mode 100644 --- /dev/null +++ b/gui//Quint.pro @@ -0,0 +1,113 @@ +# Quint - A graphical user interface for Octave +# Copyright (C) 2011 Jacob Dawid +# jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + + +# Basic settings: +QT += core gui webkit xml opengl # Qt modules +TEMPLATE = app # Build as application +TARGET = Quint # Name of the target binary + +DESTDIR = bin # Destination of the output +UI_DIR = ui-files # Folder for ui files +MOC_DIR = moc-files # Folder for moc files +OBJECTS_DIR = object-files # Folder for object files + +TRANSLATIONS += languages/german # Available translations + +# Includepaths and libraries to link against: +INCLUDEPATH += src +INCFLAGS += $$system(mkoctfile -p INCFLAGS) +LFLAGS += $$system(mkoctfile -p LFLAGS) \ + $$system(mkoctfile -p OCTAVE_LIBS) \ + $$system(mkoctfile -p LIBS) +QMAKE_LFLAGS += $$LFLAGS -lutil $$system(mkoctfile -p RLD_FLAG) +QMAKE_CXXFLAGS += $$INCFLAGS + +# Files associated with the project: +SOURCES +=\ + src/TerminalCharacterDecoder.cpp \ + src/KeyboardTranslator.cpp \ + src/Screen.cpp \ + src/History.cpp \ + src/BlockArray.cpp \ + src/konsole_wcwidth.cpp \ + src/ScreenWindow.cpp \ + src/Emulation.cpp \ + src/Vt102Emulation.cpp \ + src/TerminalDisplay.cpp \ + src/Filter.cpp \ + src/Pty.cpp \ + src/kpty.cpp \ + src/kptyprocess.cpp \ + src/kprocess.cpp \ + src/kptydevice.cpp \ + src/Session.cpp \ + src/ShellCommand.cpp \ + src/QTerminalWidget.cpp \ + src/MainWindow.cpp \ + src/Quint.cpp \ + src/OctaveLink.cpp \ + src/ProcessInfo.cpp \ + src/OctaveTerminal.cpp \ + src/VariablesDockWidget.cpp \ + src/HistoryDockWidget.cpp \ + src/FilesDockWidget.cpp \ + src/FileEditorDockWidget.cpp \ + src/SyntaxHighlighter.cpp \ + src/BrowserWidget.cpp \ + src/NumberedCodeEdit.cpp \ + src/SimpleEditor.cpp \ + src/ImageViewerDockWidget.cpp + +HEADERS += \ + src/TerminalCharacterDecoder.h \ + src/Character.h \ + src/CharacterColor.h \ + src/KeyboardTranslator.h \ + src/Screen.h \ + src/History.h \ + src/BlockArray.h \ + src/konsole_wcwidth.h \ + src/ScreenWindow.h \ + src/Emulation.h \ + src/Vt102Emulation.h \ + src/TerminalDisplay.h \ + src/Filter.h \ + src/LineFont.h \ + src/Pty.h \ + src/kpty.h \ + src/kpty_p.h \ + src/kptyprocess.h \ + src/kprocess.h \ + src/kprocess_p.h \ + src/kptydevice.h \ + src/Session.h \ + src/ShellCommand.h \ + src/QTerminalWidget.h \ + src/MainWindow.h \ + src/OctaveLink.h \ + src/ProcessInfo.h \ + src/OctaveTerminal.h \ + src/VariablesDockWidget.h \ + src/HistoryDockWidget.h \ + src/FilesDockWidget.h \ + src/FileEditorDockWidget.h \ + src/SyntaxHighlighter.h \ + src/BrowserWidget.h \ + src/NumberedCodeEdit.h \ + src/SimpleEditor.h \ + src/ImageViewerDockWidget.h
new file mode 100644 --- /dev/null +++ b/gui//README @@ -0,0 +1,46 @@ +Quint is using the QTermWidget to emulate a terminal, this enabling readline support for Octave. This is the original README file from the author: + +########################################################################################### +QTermWidget +version 0.1.0 + +QTermWidget is an opensource project based on KDE4 Konsole application. +The main goal of this project is to provide unicode-enabled, embeddable +QT widget for using as a built-in console (or terminal emulation widget). + +Of course I`m aware about embedding abilities of original Konsole, +but once I had Qt without KDE, and it was a serious obstacle. +I decided not to rely on a chance. I cannot find any interesting related project, +so I had to write it. + +The original Konsole`s code was rewritten entirely with QT4 only; also I have to +include in the project some parts of code from kde core library. All code dealing +with user interface parts and session managers was removed (maybe later I bring it +back somehow), and the result is quite useful, I suppose. + +This library was compiled and tested on three linux systems, +based on 2.4.32, 2.6.20, 2.6.23 kernels, x86 and amd64. +Please inform about its behaviour on other systems. +########################################################################################### + +If you cannot launch Quint because it fails to find octave's shared libraries and you are sure that you have them installed, you will need +to do an + +export LD_LIBRARY_PATH=/usr/lib/octave-x.x.x/:$LD_LIBRARY_PATH && bin/Quint + +and replace x.x.x by the specific octave version you have installed to run Quint. If you don't want to do that each time you launch Quint, +do the following: + +sudo gedit /etc/ld.so.conf + +and append the line: + +/usr/lib/octave-x.x.x/ + +Save the file and do: + +sudo ldconfig + +Now Quint should not complain about missing shared libraries. If you have any problems, suggestions or ideas, feel free to drop me a mail at +jacob.dawid@googlemail.com - Jacob Dawid +
new file mode 100644 --- /dev/null +++ b/gui//TODO @@ -0,0 +1,10 @@ +Global + - provide more compatibility for vttest + +Package + - migrate to autotools if needed + +Source + - provide more options for customization + - clean unused code + - add some QT3 support features if needed
new file mode 100644 --- /dev/null +++ b/gui//default.keytab @@ -0,0 +1,128 @@ +# [README.default.Keytab] Buildin Keyboard Table +# +# To customize your keyboard, copy this file to something +# ending with .keytab and change it to meet you needs. +# Please read the README.KeyTab and the README.keyboard +# in this case. +# +# -------------------------------------------------------------- + +keyboard "Default (XFree 4)" + +# -------------------------------------------------------------- +# +# Note that this particular table is a "risc" version made to +# ease customization without bothering with obsolete details. +# See VT100.keytab for the more hairy stuff. +# +# -------------------------------------------------------------- + +# common keys + +key Escape : "\E" + +key Tab -Shift : "\t" +key Tab +Shift+Ansi : "\E[Z" +key Tab +Shift-Ansi : "\t" +key Backtab +Ansi : "\E[Z" +key Backtab -Ansi : "\t" + +key Return-Shift-NewLine : "\r" +key Return-Shift+NewLine : "\r\n" + +key Return+Shift : "\EOM" + +# Backspace and Delete codes are preserving CTRL-H. + +key Backspace : "\x7f" + +# Arrow keys in VT52 mode +# shift up/down are reserved for scrolling. +# shift left/right are reserved for switching between tabs (this is hardcoded). + +key Up -Shift-Ansi : "\EA" +key Down -Shift-Ansi : "\EB" +key Right-Shift-Ansi : "\EC" +key Left -Shift-Ansi : "\ED" + +# Arrow keys in ANSI mode with Application - and Normal Cursor Mode) + +key Up -Shift-AnyMod+Ansi+AppCuKeys : "\EOA" +key Down -Shift-AnyMod+Ansi+AppCuKeys : "\EOB" +key Right -Shift-AnyMod+Ansi+AppCuKeys : "\EOC" +key Left -Shift-AnyMod+Ansi+AppCuKeys : "\EOD" + +key Up -Shift-AnyMod+Ansi-AppCuKeys : "\E[A" +key Down -Shift-AnyMod+Ansi-AppCuKeys : "\E[B" +key Right -Shift-AnyMod+Ansi-AppCuKeys : "\E[C" +key Left -Shift-AnyMod+Ansi-AppCuKeys : "\E[D" + +key Up -Shift+AnyMod+Ansi : "\E[1;*A" +key Down -Shift+AnyMod+Ansi : "\E[1;*B" +key Right -Shift+AnyMod+Ansi : "\E[1;*C" +key Left -Shift+AnyMod+Ansi : "\E[1;*D" + +# other grey PC keys + +key Enter+NewLine : "\r\n" +key Enter-NewLine : "\r" + +key Home -AnyMod -AppCuKeys : "\E[H" +key End -AnyMod -AppCuKeys : "\E[F" +key Home -AnyMod +AppCuKeys : "\EOH" +key End -AnyMod +AppCuKeys : "\EOF" +key Home +AnyMod : "\E[1;*H" +key End +AnyMod : "\E[1;*F" + +key Insert -AnyMod : "\E[2~" +key Delete -AnyMod : "\E[3~" +key Insert +AnyMod : "\E[2;*~" +key Delete +AnyMod : "\E[3;*~" + +key Prior -Shift-AnyMod : "\E[5~" +key Next -Shift-AnyMod : "\E[6~" +key Prior -Shift+AnyMod : "\E[5;*~" +key Next -Shift+AnyMod : "\E[6;*~" + +# Function keys +key F1 -AnyMod : "\EOP" +key F2 -AnyMod : "\EOQ" +key F3 -AnyMod : "\EOR" +key F4 -AnyMod : "\EOS" +key F5 -AnyMod : "\E[15~" +key F6 -AnyMod : "\E[17~" +key F7 -AnyMod : "\E[18~" +key F8 -AnyMod : "\E[19~" +key F9 -AnyMod : "\E[20~" +key F10 -AnyMod : "\E[21~" +key F11 -AnyMod : "\E[23~" +key F12 -AnyMod : "\E[24~" + +key F1 +AnyMod : "\EO*P" +key F2 +AnyMod : "\EO*Q" +key F3 +AnyMod : "\EO*R" +key F4 +AnyMod : "\EO*S" +key F5 +AnyMod : "\E[15;*~" +key F6 +AnyMod : "\E[17;*~" +key F7 +AnyMod : "\E[18;*~" +key F8 +AnyMod : "\E[19;*~" +key F9 +AnyMod : "\E[20;*~" +key F10 +AnyMod : "\E[21;*~" +key F11 +AnyMod : "\E[23;*~" +key F12 +AnyMod : "\E[24;*~" + +# Work around dead keys + +key Space +Control : "\x00" + +# Some keys are used by konsole to cause operations. +# The scroll* operations refer to the history buffer. + +key Up +Shift-AppScreen : scrollLineUp +key Prior +Shift-AppScreen : scrollPageUp +key Down +Shift-AppScreen : scrollLineDown +key Next +Shift-AppScreen : scrollPageDown + +key ScrollLock : scrollLock + +# keypad characters are not offered differently by Qt.
new file mode 100644 --- /dev/null +++ b/gui//kb-layouts/CVS/Entries @@ -0,0 +1,4 @@ +/default.keytab/1.1.1.1/Sat May 10 21:27:57 2008// +/linux.keytab/1.1.1.1/Sat May 10 21:27:57 2008// +/vt420pc.keytab/1.1.1.1/Sat May 10 21:27:57 2008// +D
new file mode 100644 --- /dev/null +++ b/gui//kb-layouts/CVS/Repository @@ -0,0 +1,1 @@ +qtermwidget/lib/kb-layouts
new file mode 100644 --- /dev/null +++ b/gui//kb-layouts/CVS/Root @@ -0,0 +1,1 @@ +:ext:e_k@qtermwidget.cvs.sourceforge.net:/cvsroot/qtermwidget
new file mode 100644 --- /dev/null +++ b/gui//kb-layouts/default.keytab @@ -0,0 +1,133 @@ +# [README.default.Keytab] Buildin Keyboard Table +# +# To customize your keyboard, copy this file to something +# ending with .keytab and change it to meet you needs. +# Please read the README.KeyTab and the README.keyboard +# in this case. +# +# -------------------------------------------------------------- + +keyboard "Default (XFree 4)" + +# -------------------------------------------------------------- +# +# Note that this particular table is a "risc" version made to +# ease customization without bothering with obsolete details. +# See VT100.keytab for the more hairy stuff. +# +# -------------------------------------------------------------- + +# common keys + +key Escape : "\E" + +key Tab -Shift : "\t" +key Tab +Shift+Ansi : "\E[Z" +key Tab +Shift-Ansi : "\t" +key Backtab +Ansi : "\E[Z" +key Backtab -Ansi : "\t" + +key Return-Shift-NewLine : "\r" +key Return-Shift+NewLine : "\r\n" + +key Return+Shift : "\EOM" + +# Backspace and Delete codes are preserving CTRL-H. + +key Backspace : "\x7f" + +# Arrow keys in VT52 mode +# shift up/down are reserved for scrolling. +# shift left/right are reserved for switching between tabs (this is hardcoded). + +key Up -Shift-Ansi : "\EA" +key Down -Shift-Ansi : "\EB" +key Right-Shift-Ansi : "\EC" +key Left -Shift-Ansi : "\ED" + +# Arrow keys in ANSI mode with Application - and Normal Cursor Mode) + +key Up -Shift-AnyMod+Ansi+AppCuKeys : "\EOA" +key Down -Shift-AnyMod+Ansi+AppCuKeys : "\EOB" +key Right -Shift-AnyMod+Ansi+AppCuKeys : "\EOC" +key Left -Shift-AnyMod+Ansi+AppCuKeys : "\EOD" + +key Up -Shift-AnyMod+Ansi-AppCuKeys : "\E[A" +key Down -Shift-AnyMod+Ansi-AppCuKeys : "\E[B" +key Right -Shift-AnyMod+Ansi-AppCuKeys : "\E[C" +key Left -Shift-AnyMod+Ansi-AppCuKeys : "\E[D" + +key Up -Shift+AnyMod+Ansi : "\E[1;*A" +key Down -Shift+AnyMod+Ansi : "\E[1;*B" +key Right -Shift+AnyMod+Ansi : "\E[1;*C" +key Left -Shift+AnyMod+Ansi : "\E[1;*D" + +# other grey PC keys + +key Enter+NewLine : "\r\n" +key Enter-NewLine : "\r" + +key Home -AnyMod -AppCuKeys : "\E[H" +key End -AnyMod -AppCuKeys : "\E[F" +key Home -AnyMod +AppCuKeys : "\EOH" +key End -AnyMod +AppCuKeys : "\EOF" +key Home +AnyMod : "\E[1;*H" +key End +AnyMod : "\E[1;*F" + +key Insert -AnyMod : "\E[2~" +key Delete -AnyMod : "\E[3~" +key Insert +AnyMod : "\E[2;*~" +key Delete +AnyMod : "\E[3;*~" + +key Prior -Shift-AnyMod : "\E[5~" +key Next -Shift-AnyMod : "\E[6~" +key Prior -Shift+AnyMod : "\E[5;*~" +key Next -Shift+AnyMod : "\E[6;*~" + +# Function keys +key F1 -AnyMod : "\EOP" +key F2 -AnyMod : "\EOQ" +key F3 -AnyMod : "\EOR" +key F4 -AnyMod : "\EOS" +key F5 -AnyMod : "\E[15~" +key F6 -AnyMod : "\E[17~" +key F7 -AnyMod : "\E[18~" +key F8 -AnyMod : "\E[19~" +key F9 -AnyMod : "\E[20~" +key F10 -AnyMod : "\E[21~" +key F11 -AnyMod : "\E[23~" +key F12 -AnyMod : "\E[24~" + +key F1 +AnyMod : "\EO*P" +key F2 +AnyMod : "\EO*Q" +key F3 +AnyMod : "\EO*R" +key F4 +AnyMod : "\EO*S" +key F5 +AnyMod : "\E[15;*~" +key F6 +AnyMod : "\E[17;*~" +key F7 +AnyMod : "\E[18;*~" +key F8 +AnyMod : "\E[19;*~" +key F9 +AnyMod : "\E[20;*~" +key F10 +AnyMod : "\E[21;*~" +key F11 +AnyMod : "\E[23;*~" +key F12 +AnyMod : "\E[24;*~" + +# Work around dead keys + +key Space +Control : "\x00" + +# Some keys are used by konsole to cause operations. +# The scroll* operations refer to the history buffer. + +key Up +Shift-AppScreen : scrollLineUp +key Prior +Shift-AppScreen : scrollPageUp +key Down +Shift-AppScreen : scrollLineDown +key Next +Shift-AppScreen : scrollPageDown + +#key Up +Shift : scrollLineUp +#key Prior +Shift : scrollPageUp +#key Down +Shift : scrollLineDown +#key Next +Shift : scrollPageDown + +key ScrollLock : scrollLock + +# keypad characters are not offered differently by Qt.
new file mode 100644 --- /dev/null +++ b/gui//kb-layouts/linux.keytab @@ -0,0 +1,133 @@ +# [linux.keytab] Konsole Keyboard Table (Linux console keys) +# +# -------------------------------------------------------------- + +# NOT TESTED, MAY NEED SOME CLEANUPS +keyboard "Linux console" + +# -------------------------------------------------------------- +# +# This configuration table allows to customize the +# meaning of the keys. +# +# The syntax is that each entry has the form : +# +# "key" Keyname { ("+"|"-") Modename } ":" (String|Operation) +# +# Keynames are those defined in <qnamespace.h> with the +# "Qt::Key_" removed. (We'd better insert the list here) +# +# Mode names are : +# +# - Shift +# - Alt +# - Control +# +# The VT100 emulation has two modes that can affect the +# sequences emitted by certain keys. These modes are +# under control of the client program. +# +# - Newline : effects Return and Enter key. +# - Application : effects Up and Down key. +# +# - Ansi : effects Up and Down key (This is for VT52, really). +# +# Operations are +# +# - scrollUpLine +# - scrollUpPage +# - scrollDownLine +# - scrollDownPage +# +# - emitSelection +# +# If the key is not found here, the text of the +# key event as provided by QT is emitted, possibly +# preceeded by ESC if the Alt key is pressed. +# +# -------------------------------------------------------------- + +key Escape : "\E" +key Tab : "\t" + +# VT100 can add an extra \n after return. +# The NewLine mode is set by an escape sequence. + +key Return-NewLine : "\r" +key Return+NewLine : "\r\n" + +# Some desperately try to save the ^H. + +key Backspace : "\x7f" +key Delete : "\E[3~" + +# These codes are for the VT52 mode of VT100 +# The Ansi mode (i.e. VT100 mode) is set by +# an escape sequence + +key Up -Shift-Ansi : "\EA" +key Down -Shift-Ansi : "\EB" +key Right-Shift-Ansi : "\EC" +key Left -Shift-Ansi : "\ED" + +# VT100 emits a mode bit together +# with the arrow keys.The AppCuKeys +# mode is set by an escape sequence. + +key Up -Shift+Ansi+AppCuKeys : "\EOA" +key Down -Shift+Ansi+AppCuKeys : "\EOB" +key Right-Shift+Ansi+AppCuKeys : "\EOC" +key Left -Shift+Ansi+AppCuKeys : "\EOD" + +key Up -Shift+Ansi-AppCuKeys : "\E[A" +key Down -Shift+Ansi-AppCuKeys : "\E[B" +key Right-Shift+Ansi-AppCuKeys : "\E[C" +key Left -Shift+Ansi-AppCuKeys : "\E[D" + +# linux functions keys F1-F5 differ from xterm + +key F1 : "\E[[A" +key F2 : "\E[[B" +key F3 : "\E[[C" +key F4 : "\E[[D" +key F5 : "\E[[E" + +key F6 : "\E[17~" +key F7 : "\E[18~" +key F8 : "\E[19~" +key F9 : "\E[20~" +key F10 : "\E[21~" +key F11 : "\E[23~" +key F12 : "\E[24~" + +key Home : "\E[1~" +key End : "\E[4~" + +key Prior -Shift : "\E[5~" +key Next -Shift : "\E[6~" +key Insert-Shift : "\E[2~" + +# Keypad-Enter. See comment on Return above. + +key Enter+NewLine : "\r\n" +key Enter-NewLine : "\r" + +key Space +Control : "\x00" + +# some of keys are used by konsole. + +key Up +Shift : scrollLineUp +key Prior +Shift : scrollPageUp +key Down +Shift : scrollLineDown +key Next +Shift : scrollPageDown + +key ScrollLock : scrollLock + +#---------------------------------------------------------- + +# keypad characters as offered by Qt +# cannot be recognized as such. + +#---------------------------------------------------------- + +# Following other strings as emitted by konsole.
new file mode 100644 --- /dev/null +++ b/gui//kb-layouts/vt420pc.keytab @@ -0,0 +1,163 @@ +# [vt420pc.keytab] Konsole Keyboard Table (VT420pc keys) +# adapted by ferdinand gassauer f.gassauer@aon.at +# Nov 2000 +# +################################################################ +# +# The escape sequences emmited by the +# keys Shift+F1 to Shift+F12 might not fit your needs +# +################# IMPORTANT NOTICE ############################# +# the key bindings (Kcontrol -> look and feel -> keybindgs) +# overrule the settings in this file. The key bindings might be +# changed by the user WITHOUT notification of the maintainer of +# the keytab file. Konsole will not work as expected by +# the maintainer of the keytab file. +################################################################ +# +# -------------------------------------------------------------- + +keyboard "DEC VT420 Terminal" + +# -------------------------------------------------------------- +# +# This configuration table allows to customize the +# meaning of the keys. +# +# The syntax is that each entry has the form : +# +# "key" Keyname { ("+"|"-") Modename } ":" (String|Operation) +# +# Keynames are those defined in <qnamespace.h> with the +# "Qt::Key_" removed. (We'd better insert the list here) +# +# Mode names are : +# +# - Shift +# - Alt +# - Control +# +# The VT100 emulation has two modes that can affect the +# sequences emitted by certain keys. These modes are +# under control of the client program. +# +# - Newline : effects Return and Enter key. +# - Application : effects Up and Down key. +# +# - Ansi : effects Up and Down key (This is for VT52, really). +# +# Operations are +# +# - scrollUpLine +# - scrollUpPage +# - scrollDownLine +# - scrollDownPage +# +# - emitSelection +# +# If the key is not found here, the text of the +# key event as provided by QT is emitted, possibly +# preceeded by ESC if the Alt key is pressed. +# +# -------------------------------------------------------------- + +key Escape : "\E" +key Tab : "\t" +key Backtab: "\E[Z" + +# VT100 can add an extra \n after return. +# The NewLine mode is set by an escape sequence. + +key Return-NewLine : "\r" +key Return+NewLine : "\r\n" + +# Some desperately try to save the ^H. +# may be not everyone wants this + +key Backspace : "\x08" # Control H +key Delete : "\x7f" + +# These codes are for the VT420pc +# The Ansi mode (i.e. VT100 mode) is set by +# an escape sequence + +key Up -Shift-Ansi : "\EA" +key Down -Shift-Ansi : "\EB" +key Right-Shift-Ansi : "\EC" +key Left -Shift-Ansi : "\ED" + +# VT100 emits a mode bit together +# with the arrow keys.The AppCuKeys +# mode is set by an escape sequence. + +key Up -Shift+Ansi+AppCuKeys : "\EOA" +key Down -Shift+Ansi+AppCuKeys : "\EOB" +key Right-Shift+Ansi+AppCuKeys : "\EOC" +key Left -Shift+Ansi+AppCuKeys : "\EOD" + +key Up -Shift+Ansi-AppCuKeys : "\E[A" +key Down -Shift+Ansi-AppCuKeys : "\E[B" +key Right-Shift+Ansi-AppCuKeys : "\E[C" +key Left -Shift+Ansi-AppCuKeys : "\E[D" + +# function keys + +key F1 -Shift : "\E[11~" +key F2 -Shift : "\E[12~" +key F3 -Shift : "\E[13~" +key F4 -Shift : "\E[14~" +key F5 -Shift : "\E[15~" +key F6 -Shift : "\E[17~" +key F7 -Shift : "\E[18~" +key F8 -Shift : "\E[19~" +key F9 -Shift : "\E[20~" +key F10-Shift : "\E[21~" +key F11-Shift : "\E[23~" +key F12-Shift : "\E[24~" +# +# Shift F1-F12 +# +key F1 +Shift : "\E[11;2~" +key F2 +Shift : "\E[12;2~" +key F3 +Shift : "\E[13;2~" +key F4 +Shift : "\E[14;2~" +key F5 +Shift : "\E[15;2~" +key F6 +Shift : "\E[17;2~" +key F7 +Shift : "\E[18;2~" +key F8 +Shift : "\E[19;2~" +key F9 +Shift : "\E[20;2~" +key F10+Shift : "\E[21;2~" +key F11+Shift : "\E[23;2~" +key F12+Shift : "\E[24;2~" + +key Home : "\E[H" +key End : "\E[F" + +key Prior -Shift : "\E[5~" +key Next -Shift : "\E[6~" +key Insert-Shift : "\E[2~" + +# Keypad-Enter. See comment on Return above. + +key Enter+NewLine : "\r\n" +key Enter-NewLine : "\r" + +key Space +Control : "\x00" + +# some of keys are used by konsole. + +key Up +Shift : scrollLineUp +key Prior +Shift : scrollPageUp +key Down +Shift : scrollLineDown +key Next +Shift : scrollPageDown + +key ScrollLock : scrollLock + +#---------------------------------------------------------- + +# keypad characters as offered by Qt +# cannot be recognized as such. + +#---------------------------------------------------------- + +# Following other strings as emitted by konsole.
new file mode 100644 --- /dev/null +++ b/gui//src/BlockArray.cpp @@ -0,0 +1,332 @@ +/* + This file is part of Konsole, an X terminal. + Copyright 2000 by Stephan Kulow <coolo@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "BlockArray.h" + +// System +#include <assert.h> +#include <sys/mman.h> +#include <sys/param.h> +#include <unistd.h> +#include <stdio.h> + +#define KDE_fseek ::fseek +#define KDE_lseek ::lseek + +static int blocksize = 0; + +BlockArray::BlockArray() + : size(0), + current(size_t(-1)), + index(size_t(-1)), + lastmap(0), + lastmap_index(size_t(-1)), + lastblock(0), ion(-1), + length(0) +{ + // lastmap_index = index = current = size_t(-1); + if (blocksize == 0) + blocksize = ((sizeof(Block) / getpagesize()) + 1) * getpagesize(); + +} + +BlockArray::~BlockArray() +{ + setHistorySize(0); + assert(!lastblock); +} + +size_t BlockArray::append(Block *block) +{ + if (!size) + return size_t(-1); + + ++current; + if (current >= size) current = 0; + + int rc; + rc = KDE_lseek(ion, current * blocksize, SEEK_SET); if (rc < 0) { perror("HistoryBuffer::add.seek"); setHistorySize(0); return size_t(-1); } + rc = write(ion, block, blocksize); if (rc < 0) { perror("HistoryBuffer::add.write"); setHistorySize(0); return size_t(-1); } + + length++; + if (length > size) length = size; + + ++index; + + delete block; + return current; +} + +size_t BlockArray::newBlock() +{ + if (!size) + return size_t(-1); + append(lastblock); + + lastblock = new Block(); + return index + 1; +} + +Block *BlockArray::lastBlock() const +{ + return lastblock; +} + +bool BlockArray::has(size_t i) const +{ + if (i == index + 1) + return true; + + if (i > index) + return false; + if (index - i >= length) + return false; + return true; +} + +const Block* BlockArray::at(size_t i) +{ + if (i == index + 1) + return lastblock; + + if (i == lastmap_index) + return lastmap; + + if (i > index) { + //kDebug(1211) << "BlockArray::at() i > index\n"; + return 0; + } + +// if (index - i >= length) { +// kDebug(1211) << "BlockArray::at() index - i >= length\n"; +// return 0; +// } + + size_t j = i; // (current - (index - i) + (index/size+1)*size) % size ; + + assert(j < size); + unmap(); + + Block *block = (Block*)mmap(0, blocksize, PROT_READ, MAP_PRIVATE, ion, j * blocksize); + + if (block == (Block*)-1) { perror("mmap"); return 0; } + + lastmap = block; + lastmap_index = i; + + return block; +} + +void BlockArray::unmap() +{ + if (lastmap) { + int res = munmap((char*)lastmap, blocksize); + if (res < 0) perror("munmap"); + } + lastmap = 0; + lastmap_index = size_t(-1); +} + +bool BlockArray::setSize(size_t newsize) +{ + return setHistorySize(newsize * 1024 / blocksize); +} + +bool BlockArray::setHistorySize(size_t newsize) +{ +// kDebug(1211) << "setHistorySize " << size << " " << newsize; + + if (size == newsize) + return false; + + unmap(); + + if (!newsize) { + delete lastblock; + lastblock = 0; + if (ion >= 0) close(ion); + ion = -1; + current = size_t(-1); + return true; + } + + if (!size) { + FILE* tmp = tmpfile(); + if (!tmp) { + perror("konsole: cannot open temp file.\n"); + } else { + ion = dup(fileno(tmp)); + if (ion<0) { + perror("konsole: cannot dup temp file.\n"); + fclose(tmp); + } + } + if (ion < 0) + return false; + + assert(!lastblock); + + lastblock = new Block(); + size = newsize; + return false; + } + + if (newsize > size) { + increaseBuffer(); + size = newsize; + return false; + } else { + decreaseBuffer(newsize); + if (ftruncate(ion, length*blocksize) == -1) + perror("ftruncate"); + size = newsize; + + return true; + } +} + +void moveBlock(FILE *fion, int cursor, int newpos, char *buffer2) +{ + int res = KDE_fseek(fion, cursor * blocksize, SEEK_SET); + if (res) + perror("fseek"); + res = fread(buffer2, blocksize, 1, fion); + if (res != 1) + perror("fread"); + + res = KDE_fseek(fion, newpos * blocksize, SEEK_SET); + if (res) + perror("fseek"); + res = fwrite(buffer2, blocksize, 1, fion); + if (res != 1) + perror("fwrite"); +} + +void BlockArray::decreaseBuffer(size_t newsize) +{ + if (index < newsize) // still fits in whole + return; + + int offset = (current - (newsize - 1) + size) % size; + + if (!offset) + return; + + // The Block constructor could do somthing in future... + char *buffer1 = new char[blocksize]; + + FILE *fion = fdopen(dup(ion), "w+b"); + if (!fion) { + delete [] buffer1; + perror("fdopen/dup"); + return; + } + + int firstblock; + if (current <= newsize) { + firstblock = current + 1; + } else { + firstblock = 0; + } + + size_t oldpos; + for (size_t i = 0, cursor=firstblock; i < newsize; i++) { + oldpos = (size + cursor + offset) % size; + moveBlock(fion, oldpos, cursor, buffer1); + if (oldpos < newsize) { + cursor = oldpos; + } else + cursor++; + } + + current = newsize - 1; + length = newsize; + + delete [] buffer1; + + fclose(fion); + +} + +void BlockArray::increaseBuffer() +{ + if (index < size) // not even wrapped once + return; + + int offset = (current + size + 1) % size; + if (!offset) // no moving needed + return; + + // The Block constructor could do somthing in future... + char *buffer1 = new char[blocksize]; + char *buffer2 = new char[blocksize]; + + int runs = 1; + int bpr = size; // blocks per run + + if (size % offset == 0) { + bpr = size / offset; + runs = offset; + } + + FILE *fion = fdopen(dup(ion), "w+b"); + if (!fion) { + perror("fdopen/dup"); + delete [] buffer1; + delete [] buffer2; + return; + } + + int res; + for (int i = 0; i < runs; i++) + { + // free one block in chain + int firstblock = (offset + i) % size; + res = KDE_fseek(fion, firstblock * blocksize, SEEK_SET); + if (res) + perror("fseek"); + res = fread(buffer1, blocksize, 1, fion); + if (res != 1) + perror("fread"); + int newpos = 0; + for (int j = 1, cursor=firstblock; j < bpr; j++) + { + cursor = (cursor + offset) % size; + newpos = (cursor - offset + size) % size; + moveBlock(fion, cursor, newpos, buffer2); + } + res = KDE_fseek(fion, i * blocksize, SEEK_SET); + if (res) + perror("fseek"); + res = fwrite(buffer1, blocksize, 1, fion); + if (res != 1) + perror("fwrite"); + } + current = size - 1; + length = size; + + delete [] buffer1; + delete [] buffer2; + + fclose(fion); + +} +
new file mode 100644 --- /dev/null +++ b/gui//src/BlockArray.h @@ -0,0 +1,115 @@ +/* + This file is part of Konsole, an X terminal. + Copyright 2000 by Stephan Kulow <coolo@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef BLOCKARRAY_H +#define BLOCKARRAY_H + +#include <unistd.h> + +#define BlockSize (1 << 12) +#define ENTRIES ((BlockSize - sizeof(size_t) ) / sizeof(unsigned char)) + +struct Block { + Block() { size = 0; } + unsigned char data[ENTRIES]; + size_t size; +}; + +// /////////////////////////////////////////////////////// + +class BlockArray { +public: + /** + * Creates a history file for holding + * maximal size blocks. If more blocks + * are requested, then it drops earlier + * added ones. + */ + BlockArray(); + + /// destructor + ~BlockArray(); + + /** + * adds the Block at the end of history. + * This may drop other blocks. + * + * The ownership on the block is transferred. + * An unique index number is returned for accessing + * it later (if not yet dropped then) + * + * Note, that the block may be dropped completely + * if history is turned off. + */ + size_t append(Block *block); + + /** + * gets the block at the index. Function may return + * 0 if the block isn't available any more. + * + * The returned block is strictly readonly as only + * maped in memory - and will be invalid on the next + * operation on this class. + */ + const Block *at(size_t index); + + /** + * reorders blocks as needed. If newsize is null, + * the history is emptied completely. The indices + * returned on append won't change their semantic, + * but they may not be valid after this call. + */ + bool setHistorySize(size_t newsize); + + size_t newBlock(); + + Block *lastBlock() const; + + /** + * Convenient function to set the size in KBytes + * instead of blocks + */ + bool setSize(size_t newsize); + + size_t len() const { return length; } + + bool has(size_t index) const; + + size_t getCurrent() const { return current; } + +private: + void unmap(); + void increaseBuffer(); + void decreaseBuffer(size_t newsize); + + size_t size; + // current always shows to the last inserted block + size_t current; + size_t index; + + Block *lastmap; + size_t lastmap_index; + Block *lastblock; + + int ion; + size_t length; + +}; +#endif
new file mode 100644 --- /dev/null +++ b/gui//src/BrowserWidget.cpp @@ -0,0 +1,90 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include "BrowserWidget.h" +#include <QVBoxLayout> +#include <QAction> +#include <QStyle> +#include <QApplication> + +BrowserDockWidget::BrowserDockWidget(QWidget *parent, BrowserWidget *browserWidget) + : QDockWidget(parent) { + m_browserWidget = browserWidget; + setWidget(m_browserWidget); +} + +BrowserDockWidget::~BrowserDockWidget() { +} + +BrowserWidget *BrowserDockWidget::browserWidget() { + return m_browserWidget; +} + +BrowserWidget::BrowserWidget(QWidget *parent) + : QWidget(parent) { + construct(); +} + +void BrowserWidget::construct() { + QStyle *style = QApplication::style(); + m_navigationToolBar = new QToolBar(this); + m_webView = new QWebView(this); + m_urlLineEdit = new QLineEdit(this); + m_statusBar = new QStatusBar(this); + + QAction *backAction = new QAction(style->standardIcon(QStyle::SP_ArrowLeft), + "", m_navigationToolBar); + QAction *forwardAction = new QAction(style->standardIcon(QStyle::SP_ArrowRight), + "", m_navigationToolBar); + + m_navigationToolBar->addAction(backAction); + m_navigationToolBar->addAction(forwardAction); + m_navigationToolBar->addWidget(m_urlLineEdit); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(m_navigationToolBar); + layout->addWidget(m_webView); + layout->addWidget(m_statusBar); + layout->setMargin(2); + setLayout(layout); + + connect(backAction, SIGNAL(triggered()), m_webView, SLOT(back())); + connect(forwardAction, SIGNAL(triggered()), m_webView, SLOT(forward())); + connect(m_webView, SIGNAL(urlChanged(QUrl)), this, SLOT(setUrl(QUrl))); + connect(m_urlLineEdit, SIGNAL(returnPressed()), this, SLOT(jumpToWebsite())); + //connect(m_webView, SIGNAL(statusBarMessage(QString)), this, SLOT(showMessage(QString))); +} + +void BrowserWidget::setUrl(QUrl url) { + m_urlLineEdit->setText(url.toString()); +} + +void BrowserWidget::jumpToWebsite() { + QString url = m_urlLineEdit->text(); + if(!url.startsWith("http://")) + url = "http://" + url; + load(url); +} + +void BrowserWidget::showStatusMessage(QString message) { + m_statusBar->showMessage(message, 1000); +} + +void BrowserWidget::load(QUrl url) { + m_webView->load(url); +}
new file mode 100644 --- /dev/null +++ b/gui//src/BrowserWidget.h @@ -0,0 +1,61 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#ifndef BROWSERMDISUBWINDOW_H +#define BROWSERMDISUBWINDOW_H + +#include <QWidget> +#include <QToolBar> +#include <QLineEdit> +#include <QtWebKit/QWebView> +#include <QStatusBar> +#include <QDockWidget> + +class BrowserWidget; +class BrowserDockWidget : public QDockWidget { +public: + BrowserDockWidget(QWidget *parent, BrowserWidget *browserWidget); + ~BrowserDockWidget(); + + BrowserWidget *browserWidget(); + +private: + BrowserWidget *m_browserWidget; +}; + +class BrowserWidget : public QWidget { + Q_OBJECT +public: + BrowserWidget(QWidget *parent = 0); + void load(QUrl url); + +public slots: + void setUrl(QUrl url); + void jumpToWebsite(); + void showStatusMessage(QString message); + +private: + void construct(); + + QLineEdit *m_urlLineEdit; + QToolBar *m_navigationToolBar; + QWebView *m_webView; + QStatusBar *m_statusBar; +}; + +#endif // BROWSERMDISUBWINDOW_H
new file mode 100644 --- /dev/null +++ b/gui//src/Character.h @@ -0,0 +1,220 @@ +/* + This file is part of Konsole, KDE's terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef CHARACTER_H +#define CHARACTER_H + +// Qt +#include <QtCore/QHash> + +// Local +#include "CharacterColor.h" + +typedef unsigned char LineProperty; + +static const int LINE_DEFAULT = 0; +static const int LINE_WRAPPED = (1 << 0); +static const int LINE_DOUBLEWIDTH = (1 << 1); +static const int LINE_DOUBLEHEIGHT = (1 << 2); + +#define DEFAULT_RENDITION 0 +#define RE_BOLD (1 << 0) +#define RE_BLINK (1 << 1) +#define RE_UNDERLINE (1 << 2) +#define RE_REVERSE (1 << 3) // Screen only +#define RE_INTENSIVE (1 << 3) // Widget only +#define RE_CURSOR (1 << 4) +#define RE_EXTENDED_CHAR (1 << 5) + +/** + * A single character in the terminal which consists of a unicode character + * value, foreground and background colors and a set of rendition attributes + * which specify how it should be drawn. + */ +class Character +{ +public: + /** + * Constructs a new character. + * + * @param _c The unicode character value of this character. + * @param _f The foreground color used to draw the character. + * @param _b The color used to draw the character's background. + * @param _r A set of rendition flags which specify how this character is to be drawn. + */ + inline Character(quint16 _c = ' ', + CharacterColor _f = CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR), + CharacterColor _b = CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR), + quint8 _r = DEFAULT_RENDITION) + : character(_c), rendition(_r), foregroundColor(_f), backgroundColor(_b) {} + + union + { + /** The unicode character value for this character. */ + quint16 character; + /** + * Experimental addition which allows a single Character instance to contain more than + * one unicode character. + * + * charSequence is a hash code which can be used to look up the unicode + * character sequence in the ExtendedCharTable used to create the sequence. + */ + quint16 charSequence; + }; + + /** A combination of RENDITION flags which specify options for drawing the character. */ + quint8 rendition; + + /** The foreground color used to draw this character. */ + CharacterColor foregroundColor; + /** The color used to draw this character's background. */ + CharacterColor backgroundColor; + + /** + * Returns true if this character has a transparent background when + * it is drawn with the specified @p palette. + */ + bool isTransparent(const ColorEntry* palette) const; + /** + * Returns true if this character should always be drawn in bold when + * it is drawn with the specified @p palette, independent of whether + * or not the character has the RE_BOLD rendition flag. + */ + ColorEntry::FontWeight fontWeight(const ColorEntry* base) const; + + /** + * returns true if the format (color, rendition flag) of the compared characters is equal + */ + bool equalsFormat(const Character &other) const; + + /** + * Compares two characters and returns true if they have the same unicode character value, + * rendition and colors. + */ + friend bool operator == (const Character& a, const Character& b); + /** + * Compares two characters and returns true if they have different unicode character values, + * renditions or colors. + */ + friend bool operator != (const Character& a, const Character& b); +}; + +inline bool operator == (const Character& a, const Character& b) +{ + return a.character == b.character && + a.rendition == b.rendition && + a.foregroundColor == b.foregroundColor && + a.backgroundColor == b.backgroundColor; +} + +inline bool operator != (const Character& a, const Character& b) +{ + return a.character != b.character || + a.rendition != b.rendition || + a.foregroundColor != b.foregroundColor || + a.backgroundColor != b.backgroundColor; +} + +inline bool Character::isTransparent(const ColorEntry* base) const +{ + return ((backgroundColor._colorSpace == COLOR_SPACE_DEFAULT) && + base[backgroundColor._u+0+(backgroundColor._v?BASE_COLORS:0)].transparent) + || ((backgroundColor._colorSpace == COLOR_SPACE_SYSTEM) && + base[backgroundColor._u+2+(backgroundColor._v?BASE_COLORS:0)].transparent); +} + +inline bool Character::equalsFormat(const Character& other) const +{ + return + backgroundColor==other.backgroundColor && + foregroundColor==other.foregroundColor && + rendition==other.rendition; +} + +inline ColorEntry::FontWeight Character::fontWeight(const ColorEntry* base) const +{ + if (backgroundColor._colorSpace == COLOR_SPACE_DEFAULT) + return base[backgroundColor._u+0+(backgroundColor._v?BASE_COLORS:0)].fontWeight; + else if (backgroundColor._colorSpace == COLOR_SPACE_SYSTEM) + return base[backgroundColor._u+2+(backgroundColor._v?BASE_COLORS:0)].fontWeight; + else + return ColorEntry::UseCurrentFormat; +} + +extern unsigned short vt100_graphics[32]; + + +/** + * A table which stores sequences of unicode characters, referenced + * by hash keys. The hash key itself is the same size as a unicode + * character ( ushort ) so that it can occupy the same space in + * a structure. + */ +class ExtendedCharTable +{ +public: + /** Constructs a new character table. */ + ExtendedCharTable(); + ~ExtendedCharTable(); + + /** + * Adds a sequences of unicode characters to the table and returns + * a hash code which can be used later to look up the sequence + * using lookupExtendedChar() + * + * If the same sequence already exists in the table, the hash + * of the existing sequence will be returned. + * + * @param unicodePoints An array of unicode character points + * @param length Length of @p unicodePoints + */ + ushort createExtendedChar(ushort* unicodePoints , ushort length); + /** + * Looks up and returns a pointer to a sequence of unicode characters + * which was added to the table using createExtendedChar(). + * + * @param hash The hash key returned by createExtendedChar() + * @param length This variable is set to the length of the + * character sequence. + * + * @return A unicode character sequence of size @p length. + */ + ushort* lookupExtendedChar(ushort hash , ushort& length) const; + + /** The global ExtendedCharTable instance. */ + static ExtendedCharTable instance; +private: + // calculates the hash key of a sequence of unicode points of size 'length' + ushort extendedCharHash(ushort* unicodePoints , ushort length) const; + // tests whether the entry in the table specified by 'hash' matches the + // character sequence 'unicodePoints' of size 'length' + bool extendedCharMatch(ushort hash , ushort* unicodePoints , ushort length) const; + // internal, maps hash keys to character sequence buffers. The first ushort + // in each value is the length of the buffer, followed by the ushorts in the buffer + // themselves. + QHash<ushort,ushort*> extendedCharTable; +}; + +Q_DECLARE_TYPEINFO(Character, Q_MOVABLE_TYPE); + +#endif // CHARACTER_H +
new file mode 100644 --- /dev/null +++ b/gui//src/CharacterColor.h @@ -0,0 +1,290 @@ +/* + This file is part of Konsole, KDE's terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef CHARACTERCOLOR_H +#define CHARACTERCOLOR_H + +// Qt +#include <QtGui/QColor> + +/** + * An entry in a terminal display's color palette. + * + * A color palette is an array of 16 ColorEntry instances which map + * system color indexes (from 0 to 15) into actual colors. + * + * Each entry can be set as bold, in which case any text + * drawn using the color should be drawn in bold. + * + * Each entry can also be transparent, in which case the terminal + * display should avoid drawing the background for any characters + * using the entry as a background. + */ +class ColorEntry +{ +public: + /** Specifies the weight to use when drawing text with this color. */ + enum FontWeight + { + /** Always draw text in this color with a bold weight. */ + Bold, + /** Always draw text in this color with a normal weight. */ + Normal, + /** + * Use the current font weight set by the terminal application. + * This is the default behavior. + */ + UseCurrentFormat + }; + + /** + * Constructs a new color palette entry. + * + * @param c The color value for this entry. + * @param tr Specifies that the color should be transparent when used as a background color. + * @param weight Specifies the font weight to use when drawing text with this color. + */ + ColorEntry(QColor c, bool tr, FontWeight weight = UseCurrentFormat) + : color(c), transparent(tr), fontWeight(weight) {} + + /** + * Constructs a new color palette entry with an undefined color, and + * with the transparent and bold flags set to false. + */ + ColorEntry() : transparent(false), fontWeight(UseCurrentFormat) {} + + /** + * Sets the color, transparency and boldness of this color to those of @p rhs. + */ + void operator=(const ColorEntry& rhs) + { + color = rhs.color; + transparent = rhs.transparent; + fontWeight = rhs.fontWeight; + } + + /** The color value of this entry for display. */ + QColor color; + + /** + * If true character backgrounds using this color should be transparent. + * This is not applicable when the color is used to render text. + */ + bool transparent; + /** + * Specifies the font weight to use when drawing text with this color. + * This is not applicable when the color is used to draw a character's background. + */ + FontWeight fontWeight; +}; + + +// Attributed Character Representations /////////////////////////////// + +// Colors + +#define BASE_COLORS (2+8) +#define INTENSITIES 2 +#define TABLE_COLORS (INTENSITIES*BASE_COLORS) + +#define DEFAULT_FORE_COLOR 0 +#define DEFAULT_BACK_COLOR 1 + +//a standard set of colors using black text on a white background. +//defined in TerminalDisplay.cpp + +extern const ColorEntry base_color_table[TABLE_COLORS]; + +/* CharacterColor is a union of the various color spaces. + + Assignment is as follows: + + Type - Space - Values + + 0 - Undefined - u: 0, v:0 w:0 + 1 - Default - u: 0..1 v:intense w:0 + 2 - System - u: 0..7 v:intense w:0 + 3 - Index(256) - u: 16..255 v:0 w:0 + 4 - RGB - u: 0..255 v:0..256 w:0..256 + + Default colour space has two separate colours, namely + default foreground and default background colour. +*/ + +#define COLOR_SPACE_UNDEFINED 0 +#define COLOR_SPACE_DEFAULT 1 +#define COLOR_SPACE_SYSTEM 2 +#define COLOR_SPACE_256 3 +#define COLOR_SPACE_RGB 4 + +/** + * Describes the color of a single character in the terminal. + */ +class CharacterColor +{ + friend class Character; + +public: + /** Constructs a new CharacterColor whoose color and color space are undefined. */ + CharacterColor() + : _colorSpace(COLOR_SPACE_UNDEFINED), + _u(0), + _v(0), + _w(0) + {} + + /** + * Constructs a new CharacterColor using the specified @p colorSpace and with + * color value @p co + * + * The meaning of @p co depends on the @p colorSpace used. + * + * TODO : Document how @p co relates to @p colorSpace + * + * TODO : Add documentation about available color spaces. + */ + CharacterColor(quint8 colorSpace, int co) + : _colorSpace(colorSpace), + _u(0), + _v(0), + _w(0) + { + switch (colorSpace) + { + case COLOR_SPACE_DEFAULT: + _u = co & 1; + break; + case COLOR_SPACE_SYSTEM: + _u = co & 7; + _v = (co >> 3) & 1; + break; + case COLOR_SPACE_256: + _u = co & 255; + break; + case COLOR_SPACE_RGB: + _u = co >> 16; + _v = co >> 8; + _w = co; + break; + default: + _colorSpace = COLOR_SPACE_UNDEFINED; + } + } + + /** + * Returns true if this character color entry is valid. + */ + bool isValid() + { + return _colorSpace != COLOR_SPACE_UNDEFINED; + } + + /** + * Toggles the value of this color between a normal system color and the corresponding intensive + * system color. + * + * This is only applicable if the color is using the COLOR_SPACE_DEFAULT or COLOR_SPACE_SYSTEM + * color spaces. + */ + void toggleIntensive(); + + /** + * Returns the color within the specified color @p palette + * + * The @p palette is only used if this color is one of the 16 system colors, otherwise + * it is ignored. + */ + QColor color(const ColorEntry* palette) const; + + /** + * Compares two colors and returns true if they represent the same color value and + * use the same color space. + */ + friend bool operator == (const CharacterColor& a, const CharacterColor& b); + /** + * Compares two colors and returns true if they represent different color values + * or use different color spaces. + */ + friend bool operator != (const CharacterColor& a, const CharacterColor& b); + +private: + quint8 _colorSpace; + + // bytes storing the character color + quint8 _u; + quint8 _v; + quint8 _w; +}; + +inline bool operator == (const CharacterColor& a, const CharacterColor& b) +{ + return a._colorSpace == b._colorSpace && + a._u == b._u && + a._v == b._v && + a._w == b._w; +} +inline bool operator != (const CharacterColor& a, const CharacterColor& b) +{ + return !operator==(a,b); +} + +inline const QColor color256(quint8 u, const ColorEntry* base) +{ + // 0.. 16: system colors + if (u < 8) return base[u+2 ].color; u -= 8; + if (u < 8) return base[u+2+BASE_COLORS].color; u -= 8; + + // 16..231: 6x6x6 rgb color cube + if (u < 216) return QColor(((u/36)%6) ? (40*((u/36)%6)+55) : 0, + ((u/ 6)%6) ? (40*((u/ 6)%6)+55) : 0, + ((u/ 1)%6) ? (40*((u/ 1)%6)+55) : 0); u -= 216; + + // 232..255: gray, leaving out black and white + int gray = u*10+8; return QColor(gray,gray,gray); +} + +inline QColor CharacterColor::color(const ColorEntry* base) const +{ + switch (_colorSpace) + { + case COLOR_SPACE_DEFAULT: return base[_u+0+(_v?BASE_COLORS:0)].color; + case COLOR_SPACE_SYSTEM: return base[_u+2+(_v?BASE_COLORS:0)].color; + case COLOR_SPACE_256: return color256(_u,base); + case COLOR_SPACE_RGB: return QColor(_u,_v,_w); + case COLOR_SPACE_UNDEFINED: return QColor(); + } + + Q_ASSERT(false); // invalid color space + + return QColor(); +} + +inline void CharacterColor::toggleIntensive() +{ + if (_colorSpace == COLOR_SPACE_SYSTEM || _colorSpace == COLOR_SPACE_DEFAULT) + { + _v = !_v; + } +} + +#endif // CHARACTERCOLOR_H +
new file mode 100644 --- /dev/null +++ b/gui//src/ColorTables.h @@ -0,0 +1,56 @@ +#ifndef _COLOR_TABLE_H +#define _COLOR_TABLE_H + +#include "CharacterColor.h" + +static const ColorEntry whiteonblack_color_table[TABLE_COLORS] = +{ + // normal + ColorEntry(QColor(0xFF,0xFF,0xFF), 0, 0 ), ColorEntry( QColor(0x00,0x00,0x00), 1, 0 ), // Dfore, Dback + ColorEntry(QColor(0x00,0x00,0x00), 0, 0 ), ColorEntry( QColor(0xB2,0x18,0x18), 0, 0 ), // Black, Red + ColorEntry(QColor(0x18,0xB2,0x18), 0, 0 ), ColorEntry( QColor(0xB2,0x68,0x18), 0, 0 ), // Green, Yellow + ColorEntry(QColor(0x18,0x18,0xB2), 0, 0 ), ColorEntry( QColor(0xB2,0x18,0xB2), 0, 0 ), // Blue, Magenta + ColorEntry(QColor(0x18,0xB2,0xB2), 0, 0 ), ColorEntry( QColor(0xB2,0xB2,0xB2), 0, 0 ), // Cyan, White + // intensiv + ColorEntry(QColor(0x00,0x00,0x00), 0, 1 ), ColorEntry( QColor(0xFF,0xFF,0xFF), 1, 0 ), + ColorEntry(QColor(0x68,0x68,0x68), 0, 0 ), ColorEntry( QColor(0xFF,0x54,0x54), 0, 0 ), + ColorEntry(QColor(0x54,0xFF,0x54), 0, 0 ), ColorEntry( QColor(0xFF,0xFF,0x54), 0, 0 ), + ColorEntry(QColor(0x54,0x54,0xFF), 0, 0 ), ColorEntry( QColor(0xFF,0x54,0xFF), 0, 0 ), + ColorEntry(QColor(0x54,0xFF,0xFF), 0, 0 ), ColorEntry( QColor(0xFF,0xFF,0xFF), 0, 0 ) +}; + +static const ColorEntry greenonblack_color_table[TABLE_COLORS] = +{ + ColorEntry(QColor( 24, 240, 24), 0, 0), ColorEntry(QColor( 0, 0, 0), 1, 0), + ColorEntry(QColor( 0, 0, 0), 0, 0), ColorEntry(QColor( 178, 24, 24), 0, 0), + ColorEntry(QColor( 24, 178, 24), 0, 0), ColorEntry(QColor( 178, 104, 24), 0, 0), + ColorEntry(QColor( 24, 24, 178), 0, 0), ColorEntry(QColor( 178, 24, 178), 0, 0), + ColorEntry(QColor( 24, 178, 178), 0, 0), ColorEntry(QColor( 178, 178, 178), 0, 0), + // intensive colors + ColorEntry(QColor( 24, 240, 24), 0, 1 ), ColorEntry(QColor( 0, 0, 0), 1, 0 ), + ColorEntry(QColor( 104, 104, 104), 0, 0 ), ColorEntry(QColor( 255, 84, 84), 0, 0 ), + ColorEntry(QColor( 84, 255, 84), 0, 0 ), ColorEntry(QColor( 255, 255, 84), 0, 0 ), + ColorEntry(QColor( 84, 84, 255), 0, 0 ), ColorEntry(QColor( 255, 84, 255), 0, 0 ), + ColorEntry(QColor( 84, 255, 255), 0, 0 ), ColorEntry(QColor( 255, 255, 255), 0, 0 ) +}; + +static const ColorEntry blackonlightyellow_color_table[TABLE_COLORS] = +{ + ColorEntry(QColor( 0, 0, 0), 0, 0), ColorEntry(QColor( 255, 255, 221), 1, 0), + ColorEntry(QColor( 0, 0, 0), 0, 0), ColorEntry(QColor( 178, 24, 24), 0, 0), + ColorEntry(QColor( 24, 178, 24), 0, 0), ColorEntry(QColor( 178, 104, 24), 0, 0), + ColorEntry(QColor( 24, 24, 178), 0, 0), ColorEntry(QColor( 178, 24, 178), 0, 0), + ColorEntry(QColor( 24, 178, 178), 0, 0), ColorEntry(QColor( 178, 178, 178), 0, 0), + ColorEntry(QColor( 0, 0, 0), 0, 1), ColorEntry(QColor( 255, 255, 221), 1, 0), + ColorEntry(QColor(104, 104, 104), 0, 0), ColorEntry(QColor( 255, 84, 84), 0, 0), + ColorEntry(QColor( 84, 255, 84), 0, 0), ColorEntry(QColor( 255, 255, 84), 0, 0), + ColorEntry(QColor( 84, 84, 255), 0, 0), ColorEntry(QColor( 255, 84, 255), 0, 0), + ColorEntry(QColor( 84, 255, 255), 0, 0), ColorEntry(QColor( 255, 255, 255), 0, 0) +}; + + + + + +#endif +
new file mode 100644 --- /dev/null +++ b/gui//src/DefaultTranslatorText.h @@ -0,0 +1,2 @@ +"keyboard \"Fallback Key Translator\"\n" +"key Tab : \"\\t\" \0"
new file mode 100644 --- /dev/null +++ b/gui//src/Emulation.cpp @@ -0,0 +1,404 @@ +/* + Copyright 2007-2008 Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + Copyright 1996 by Matthias Ettrich <ettrich@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "Emulation.h" + +// System +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +// Qt +#include <QtGui/QApplication> +#include <QtGui/QClipboard> +#include <QtCore/QHash> +#include <QtGui/QKeyEvent> +#include <QtCore/QRegExp> +#include <QtCore/QTextStream> +#include <QtCore/QThread> + +#include <QtCore/QTime> + +// Konsole +#include "KeyboardTranslator.h" +#include "Screen.h" +#include "TerminalCharacterDecoder.h" +#include "ScreenWindow.h" + +Emulation::Emulation() : + _currentScreen(0), + _codec(0), + _decoder(0), + _keyTranslator(0), + _usesMouse(false) +{ + // create screens with a default size + _screen[0] = new Screen(40,80); + _screen[1] = new Screen(40,80); + _currentScreen = _screen[0]; + + QObject::connect(&_bulkTimer1, SIGNAL(timeout()), this, SLOT(showBulk()) ); + QObject::connect(&_bulkTimer2, SIGNAL(timeout()), this, SLOT(showBulk()) ); + + // listen for mouse status changes + connect( this , SIGNAL(programUsesMouseChanged(bool)) , + SLOT(usesMouseChanged(bool)) ); +} + +bool Emulation::programUsesMouse() const +{ + return _usesMouse; +} + +void Emulation::usesMouseChanged(bool usesMouse) +{ + _usesMouse = usesMouse; +} + +ScreenWindow* Emulation::createWindow() +{ + ScreenWindow* window = new ScreenWindow(); + window->setScreen(_currentScreen); + _windows << window; + + connect(window , SIGNAL(selectionChanged()), + this , SLOT(bufferedUpdate())); + + connect(this , SIGNAL(outputChanged()), + window , SLOT(notifyOutputChanged()) ); + return window; +} + +Emulation::~Emulation() +{ + QListIterator<ScreenWindow*> windowIter(_windows); + + while (windowIter.hasNext()) + { + delete windowIter.next(); + } + + delete _screen[0]; + delete _screen[1]; + delete _decoder; +} + +void Emulation::setScreen(int n) +{ + Screen *old = _currentScreen; + _currentScreen = _screen[n & 1]; + if (_currentScreen != old) + { + // tell all windows onto this emulation to switch to the newly active screen + foreach(ScreenWindow* window,_windows) + window->setScreen(_currentScreen); + } +} + +void Emulation::clearHistory() +{ + _screen[0]->setScroll( _screen[0]->getScroll() , false ); +} +void Emulation::setHistory(const HistoryType& t) +{ + _screen[0]->setScroll(t); + + showBulk(); +} + +const HistoryType& Emulation::history() const +{ + return _screen[0]->getScroll(); +} + +void Emulation::setCodec(const QTextCodec * qtc) +{ + if (qtc) + _codec = qtc; + else + setCodec(LocaleCodec); + + delete _decoder; + _decoder = _codec->makeDecoder(); + + emit useUtf8Request(utf8()); +} + +void Emulation::setCodec(EmulationCodec codec) +{ + if ( codec == Utf8Codec ) + setCodec( QTextCodec::codecForName("utf8") ); + else if ( codec == LocaleCodec ) + setCodec( QTextCodec::codecForLocale() ); +} + +void Emulation::setKeyBindings(const QString& name) +{ + _keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name); + if (!_keyTranslator) + { + _keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator(); + } +} + +QString Emulation::keyBindings() const +{ + return _keyTranslator->name(); +} + +void Emulation::receiveChar(int c) +// process application unicode input to terminal +// this is a trivial scanner +{ + c &= 0xff; + switch (c) + { + case '\b' : _currentScreen->backspace(); break; + case '\t' : _currentScreen->tab(); break; + case '\n' : _currentScreen->newLine(); break; + case '\r' : _currentScreen->toStartOfLine(); break; + case 0x07 : emit stateSet(NOTIFYBELL); + break; + default : _currentScreen->displayCharacter(c); break; + }; +} + +void Emulation::sendKeyEvent( QKeyEvent* ev ) +{ + emit stateSet(NOTIFYNORMAL); + + if (!ev->text().isEmpty()) + { // A block of text + // Note that the text is proper unicode. + // We should do a conversion here + emit sendData(ev->text().toUtf8(), ev->text().length()); + } +} + +void Emulation::sendString(const char*,int) +{ + // default implementation does nothing +} + +void Emulation::sendMouseEvent(int /*buttons*/, int /*column*/, int /*row*/, int /*eventType*/) +{ + // default implementation does nothing +} + +/* + We are doing code conversion from locale to unicode first. +TODO: Character composition from the old code. See #96536 +*/ + +void Emulation::receiveData(const char* text, int length) +{ + emit stateSet(NOTIFYACTIVITY); + + bufferedUpdate(); + + QString unicodeText = _decoder->toUnicode(text,length); + + //send characters to terminal emulator + for (int i=0;i<unicodeText.length();i++) + receiveChar(unicodeText[i].unicode()); + + //look for z-modem indicator + //-- someone who understands more about z-modems that I do may be able to move + //this check into the above for loop? + for (int i=0;i<length;i++) + { + if (text[i] == '\030') + { + if ((length-i-1 > 3) && (strncmp(text+i+1, "B00", 3) == 0)) + emit zmodemDetected(); + } + } +} + +void Emulation::writeToStream( TerminalCharacterDecoder* _decoder , + int startLine , + int endLine) +{ + _currentScreen->writeLinesToStream(_decoder,startLine,endLine); +} + +int Emulation::lineCount() const +{ + // sum number of lines currently on _screen plus number of lines in history + return _currentScreen->getLines() + _currentScreen->getHistLines(); +} + +#define BULK_TIMEOUT1 10 +#define BULK_TIMEOUT2 40 + +void Emulation::showBulk() +{ + _bulkTimer1.stop(); + _bulkTimer2.stop(); + + emit outputChanged(); + + _currentScreen->resetScrolledLines(); + _currentScreen->resetDroppedLines(); +} + +void Emulation::bufferedUpdate() +{ + _bulkTimer1.setSingleShot(true); + _bulkTimer1.start(BULK_TIMEOUT1); + if (!_bulkTimer2.isActive()) + { + _bulkTimer2.setSingleShot(true); + _bulkTimer2.start(BULK_TIMEOUT2); + } +} + +char Emulation::eraseChar() const +{ + return '\b'; +} + +void Emulation::setImageSize(int lines, int columns) +{ + if ((lines < 1) || (columns < 1)) + return; + + QSize screenSize[2] = { QSize(_screen[0]->getColumns(), + _screen[0]->getLines()), + QSize(_screen[1]->getColumns(), + _screen[1]->getLines()) }; + QSize newSize(columns,lines); + + if (newSize == screenSize[0] && newSize == screenSize[1]) + return; + + _screen[0]->resizeImage(lines,columns); + _screen[1]->resizeImage(lines,columns); + + emit imageSizeChanged(lines,columns); + + bufferedUpdate(); +} + +QSize Emulation::imageSize() const +{ + return QSize(_currentScreen->getColumns(), _currentScreen->getLines()); +} + +ushort ExtendedCharTable::extendedCharHash(ushort* unicodePoints , ushort length) const +{ + ushort hash = 0; + for ( ushort i = 0 ; i < length ; i++ ) + { + hash = 31*hash + unicodePoints[i]; + } + return hash; +} +bool ExtendedCharTable::extendedCharMatch(ushort hash , ushort* unicodePoints , ushort length) const +{ + ushort* entry = extendedCharTable[hash]; + + // compare given length with stored sequence length ( given as the first ushort in the + // stored buffer ) + if ( entry == 0 || entry[0] != length ) + return false; + // if the lengths match, each character must be checked. the stored buffer starts at + // entry[1] + for ( int i = 0 ; i < length ; i++ ) + { + if ( entry[i+1] != unicodePoints[i] ) + return false; + } + return true; +} +ushort ExtendedCharTable::createExtendedChar(ushort* unicodePoints , ushort length) +{ + // look for this sequence of points in the table + ushort hash = extendedCharHash(unicodePoints,length); + + // check existing entry for match + while ( extendedCharTable.contains(hash) ) + { + if ( extendedCharMatch(hash,unicodePoints,length) ) + { + // this sequence already has an entry in the table, + // return its hash + return hash; + } + else + { + // if hash is already used by another, different sequence of unicode character + // points then try next hash + hash++; + } + } + + + // add the new sequence to the table and + // return that index + ushort* buffer = new ushort[length+1]; + buffer[0] = length; + for ( int i = 0 ; i < length ; i++ ) + buffer[i+1] = unicodePoints[i]; + + extendedCharTable.insert(hash,buffer); + + return hash; +} + +ushort* ExtendedCharTable::lookupExtendedChar(ushort hash , ushort& length) const +{ + // lookup index in table and if found, set the length + // argument and return a pointer to the character sequence + + ushort* buffer = extendedCharTable[hash]; + if ( buffer ) + { + length = buffer[0]; + return buffer+1; + } + else + { + length = 0; + return 0; + } +} + +ExtendedCharTable::ExtendedCharTable() +{ +} +ExtendedCharTable::~ExtendedCharTable() +{ + // free all allocated character buffers + QHashIterator<ushort,ushort*> iter(extendedCharTable); + while ( iter.hasNext() ) + { + iter.next(); + delete[] iter.value(); + } +} + +// global instance +ExtendedCharTable ExtendedCharTable::instance; + +
new file mode 100644 --- /dev/null +++ b/gui//src/Emulation.h @@ -0,0 +1,462 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef EMULATION_H +#define EMULATION_H + +// System +#include <stdio.h> + +// Qt +#include <QtGui/QKeyEvent> +#include <QtCore/QTextCodec> +#include <QtCore/QTextStream> +#include <QtCore/QTimer> + + +class KeyboardTranslator; +class HistoryType; +class Screen; +class ScreenWindow; +class TerminalCharacterDecoder; + +/** + * This enum describes the available states which + * the terminal emulation may be set to. + * + * These are the values used by Emulation::stateChanged() + */ +enum +{ + /** The emulation is currently receiving user input. */ + NOTIFYNORMAL=0, + /** + * The terminal program has triggered a bell event + * to get the user's attention. + */ + NOTIFYBELL=1, + /** + * The emulation is currently receiving data from its + * terminal input. + */ + NOTIFYACTIVITY=2, + + // unused here? + NOTIFYSILENCE=3 +}; + +/** + * Base class for terminal emulation back-ends. + * + * The back-end is responsible for decoding an incoming character stream and + * producing an output image of characters. + * + * When input from the terminal is received, the receiveData() slot should be called with + * the data which has arrived. The emulation will process the data and update the + * screen image accordingly. The codec used to decode the incoming character stream + * into the unicode characters used internally can be specified using setCodec() + * + * The size of the screen image can be specified by calling setImageSize() with the + * desired number of lines and columns. When new lines are added, old content + * is moved into a history store, which can be set by calling setHistory(). + * + * The screen image can be accessed by creating a ScreenWindow onto this emulation + * by calling createWindow(). Screen windows provide access to a section of the + * output. Each screen window covers the same number of lines and columns as the + * image size returned by imageSize(). The screen window can be moved up and down + * and provides transparent access to both the current on-screen image and the + * previous output. The screen windows emit an outputChanged signal + * when the section of the image they are looking at changes. + * Graphical views can then render the contents of a screen window, listening for notifications + * of output changes from the screen window which they are associated with and updating + * accordingly. + * + * The emulation also is also responsible for converting input from the connected views such + * as keypresses and mouse activity into a character string which can be sent + * to the terminal program. Key presses can be processed by calling the sendKeyEvent() slot, + * while mouse events can be processed using the sendMouseEvent() slot. When the character + * stream has been produced, the emulation will emit a sendData() signal with a pointer + * to the character buffer. This data should be fed to the standard input of the terminal + * process. The translation of key presses into an output character stream is performed + * using a lookup in a set of key bindings which map key sequences to output + * character sequences. The name of the key bindings set used can be specified using + * setKeyBindings() + * + * The emulation maintains certain state information which changes depending on the + * input received. The emulation can be reset back to its starting state by calling + * reset(). + * + * The emulation also maintains an activity state, which specifies whether + * terminal is currently active ( when data is received ), normal + * ( when the terminal is idle or receiving user input ) or trying + * to alert the user ( also known as a "Bell" event ). The stateSet() signal + * is emitted whenever the activity state is set. This can be used to determine + * how long the emulation has been active/idle for and also respond to + * a 'bell' event in different ways. + */ +class Emulation : public QObject +{ +Q_OBJECT + +public: + + /** Constructs a new terminal emulation */ + Emulation(); + ~Emulation(); + + /** + * Creates a new window onto the output from this emulation. The contents + * of the window are then rendered by views which are set to use this window using the + * TerminalDisplay::setScreenWindow() method. + */ + ScreenWindow* createWindow(); + + /** Returns the size of the screen image which the emulation produces */ + QSize imageSize() const; + + /** + * Returns the total number of lines, including those stored in the history. + */ + int lineCount() const; + + /** + * Sets the history store used by this emulation. When new lines + * are added to the output, older lines at the top of the screen are transferred to a history + * store. + * + * The number of lines which are kept and the storage location depend on the + * type of store. + */ + void setHistory(const HistoryType&); + /** Returns the history store used by this emulation. See setHistory() */ + const HistoryType& history() const; + /** Clears the history scroll. */ + void clearHistory(); + + /** + * Copies the output history from @p startLine to @p endLine + * into @p stream, using @p decoder to convert the terminal + * characters into text. + * + * @param decoder A decoder which converts lines of terminal characters with + * appearance attributes into output text. PlainTextDecoder is the most commonly + * used decoder. + * @param startLine Index of first line to copy + * @param endLine Index of last line to copy + */ + virtual void writeToStream(TerminalCharacterDecoder* decoder,int startLine,int endLine); + + /** Returns the codec used to decode incoming characters. See setCodec() */ + const QTextCodec* codec() const { return _codec; } + /** Sets the codec used to decode incoming characters. */ + void setCodec(const QTextCodec*); + + /** + * Convenience method. + * Returns true if the current codec used to decode incoming + * characters is UTF-8 + */ + bool utf8() const + { Q_ASSERT(_codec); return _codec->mibEnum() == 106; } + + + /** TODO Document me */ + virtual char eraseChar() const; + + /** + * Sets the key bindings used to key events + * ( received through sendKeyEvent() ) into character + * streams to send to the terminal. + */ + void setKeyBindings(const QString& name); + /** + * Returns the name of the emulation's current key bindings. + * See setKeyBindings() + */ + QString keyBindings() const; + + /** + * Copies the current image into the history and clears the screen. + */ + virtual void clearEntireScreen() =0; + + /** Resets the state of the terminal. */ + virtual void reset() =0; + + /** + * Returns true if the active terminal program wants + * mouse input events. + * + * The programUsesMouseChanged() signal is emitted when this + * changes. + */ + bool programUsesMouse() const; + +public slots: + + /** Change the size of the emulation's image */ + virtual void setImageSize(int lines, int columns); + + /** + * Interprets a sequence of characters and sends the result to the terminal. + * This is equivalent to calling sendKeyEvent() for each character in @p text in succession. + */ + virtual void sendText(const QString& text) = 0; + + /** + * Interprets a key press event and emits the sendData() signal with + * the resulting character stream. + */ + virtual void sendKeyEvent(QKeyEvent*); + + /** + * Converts information about a mouse event into an xterm-compatible escape + * sequence and emits the character sequence via sendData() + */ + virtual void sendMouseEvent(int buttons, int column, int line, int eventType); + + /** + * Sends a string of characters to the foreground terminal process. + * + * @param string The characters to send. + * @param length Length of @p string or if set to a negative value, @p string will + * be treated as a null-terminated string and its length will be determined automatically. + */ + virtual void sendString(const char* string, int length = -1) = 0; + + /** + * Processes an incoming stream of characters. receiveData() decodes the incoming + * character buffer using the current codec(), and then calls receiveChar() for + * each unicode character in the resulting buffer. + * + * receiveData() also starts a timer which causes the outputChanged() signal + * to be emitted when it expires. The timer allows multiple updates in quick + * succession to be buffered into a single outputChanged() signal emission. + * + * @param buffer A string of characters received from the terminal program. + * @param len The length of @p buffer + */ + void receiveData(const char* buffer,int len); + +signals: + + /** + * Emitted when a buffer of data is ready to send to the + * standard input of the terminal. + * + * @param data The buffer of data ready to be sent + * @param len The length of @p data in bytes + */ + void sendData(const char* data,int len); + + /** + * Requests that sending of input to the emulation + * from the terminal process be suspended or resumed. + * + * @param suspend If true, requests that sending of + * input from the terminal process' stdout be + * suspended. Otherwise requests that sending of + * input be resumed. + */ + void lockPtyRequest(bool suspend); + + /** + * Requests that the pty used by the terminal process + * be set to UTF 8 mode. + * + * TODO: More documentation + */ + void useUtf8Request(bool); + + /** + * Emitted when the activity state of the emulation is set. + * + * @param state The new activity state, one of NOTIFYNORMAL, NOTIFYACTIVITY + * or NOTIFYBELL + */ + void stateSet(int state); + + /** TODO Document me */ + void zmodemDetected(); + + + /** + * Requests that the color of the text used + * to represent the tabs associated with this + * emulation be changed. This is a Konsole-specific + * extension from pre-KDE 4 times. + * + * TODO: Document how the parameter works. + */ + void changeTabTextColorRequest(int color); + + /** + * This is emitted when the program running in the shell indicates whether or + * not it is interested in mouse events. + * + * @param usesMouse This will be true if the program wants to be informed about + * mouse events or false otherwise. + */ + void programUsesMouseChanged(bool usesMouse); + + /** + * Emitted when the contents of the screen image change. + * The emulation buffers the updates from successive image changes, + * and only emits outputChanged() at sensible intervals when + * there is a lot of terminal activity. + * + * Normally there is no need for objects other than the screen windows + * created with createWindow() to listen for this signal. + * + * ScreenWindow objects created using createWindow() will emit their + * own outputChanged() signal in response to this signal. + */ + void outputChanged(); + + /** + * Emitted when the program running in the terminal wishes to update the + * session's title. This also allows terminal programs to customize other + * aspects of the terminal emulation display. + * + * This signal is emitted when the escape sequence "\033]ARG;VALUE\007" + * is received in the input string, where ARG is a number specifying what + * should change and VALUE is a string specifying the new value. + * + * TODO: The name of this method is not very accurate since this method + * is used to perform a whole range of tasks besides just setting + * the user-title of the session. + * + * @param title Specifies what to change. + * <ul> + * <li>0 - Set window icon text and session title to @p newTitle</li> + * <li>1 - Set window icon text to @p newTitle</li> + * <li>2 - Set session title to @p newTitle</li> + * <li>11 - Set the session's default background color to @p newTitle, + * where @p newTitle can be an HTML-style string ("#RRGGBB") or a named + * color (eg 'red', 'blue'). + * See http://doc.trolltech.com/4.2/qcolor.html#setNamedColor for more + * details. + * </li> + * <li>31 - Supposedly treats @p newTitle as a URL and opens it (NOT IMPLEMENTED)</li> + * <li>32 - Sets the icon associated with the session. @p newTitle is the name + * of the icon to use, which can be the name of any icon in the current KDE icon + * theme (eg: 'konsole', 'kate', 'folder_home')</li> + * </ul> + * @param newTitle Specifies the new title + */ + + void titleChanged(int title,const QString& newTitle); + + /** + * Emitted when the program running in the terminal changes the + * screen size. + */ + void imageSizeChanged(int lineCount , int columnCount); + + /** + * Emitted when the terminal program requests to change various properties + * of the terminal display. + * + * A profile change command occurs when a special escape sequence, followed + * by a string containing a series of name and value pairs is received. + * This string can be parsed using a ProfileCommandParser instance. + * + * @param text A string expected to contain a series of key and value pairs in + * the form: name=value;name2=value2 ... + */ + void profileChangeCommandReceived(const QString& text); + + /** + * Emitted when a flow control key combination ( Ctrl+S or Ctrl+Q ) is pressed. + * @param suspendKeyPressed True if Ctrl+S was pressed to suspend output or Ctrl+Q to + * resume output. + */ + void flowControlKeyPressed(bool suspendKeyPressed); + +protected: + virtual void setMode(int mode) = 0; + virtual void resetMode(int mode) = 0; + + /** + * Processes an incoming character. See receiveData() + * @p ch A unicode character code. + */ + virtual void receiveChar(int ch); + + /** + * Sets the active screen. The terminal has two screens, primary and alternate. + * The primary screen is used by default. When certain interactive programs such + * as Vim are run, they trigger a switch to the alternate screen. + * + * @param index 0 to switch to the primary screen, or 1 to switch to the alternate screen + */ + void setScreen(int index); + + enum EmulationCodec + { + LocaleCodec = 0, + Utf8Codec = 1 + }; + void setCodec(EmulationCodec codec); // codec number, 0 = locale, 1=utf8 + + + QList<ScreenWindow*> _windows; + + Screen* _currentScreen; // pointer to the screen which is currently active, + // this is one of the elements in the screen[] array + + Screen* _screen[2]; // 0 = primary screen ( used by most programs, including the shell + // scrollbars are enabled in this mode ) + // 1 = alternate ( used by vi , emacs etc. + // scrollbars are not enabled in this mode ) + + + //decodes an incoming C-style character stream into a unicode QString using + //the current text codec. (this allows for rendering of non-ASCII characters in text files etc.) + const QTextCodec* _codec; + QTextDecoder* _decoder; + const KeyboardTranslator* _keyTranslator; // the keyboard layout + +protected slots: + /** + * Schedules an update of attached views. + * Repeated calls to bufferedUpdate() in close succession will result in only a single update, + * much like the Qt buffered update of widgets. + */ + void bufferedUpdate(); + +private slots: + + // triggered by timer, causes the emulation to send an updated screen image to each + // view + void showBulk(); + + void usesMouseChanged(bool usesMouse); + +private: + bool _usesMouse; + QTimer _bulkTimer1; + QTimer _bulkTimer2; + +}; + +#endif // ifndef EMULATION_H
new file mode 100644 --- /dev/null +++ b/gui//src/FileEditorMdiSubWindow.cpp @@ -0,0 +1,152 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include "FileEditorMdiSubWindow.h" +#include <QVBoxLayout> +#include <QApplication> +#include <QFile> +#include <QFileDialog> +#include <QMessageBox> +#include <QAction> + +FileEditorMdiSubWindow::FileEditorMdiSubWindow(QWidget *parent) + : QMdiSubWindow(parent) { + construct(); +} + +void FileEditorMdiSubWindow::loadFile(QString fileName) { + m_fileName = fileName; + setWindowTitle(fileName); + m_simpleEditor->load(fileName); +} + +void FileEditorMdiSubWindow::installEventFilter(QObject *object) { + QMdiSubWindow::installEventFilter(object); + m_numberedTextView->installEventFilter(object); + m_simpleEditor->installEventFilter(object); +} + +void FileEditorMdiSubWindow::newFile() { + if(m_modified) { + int decision + = QMessageBox::question(this, + "Open New File", + "Do you want to save the current file?", + QMessageBox::Yes, QMessageBox::No); + + if(decision == QMessageBox::Yes) { + saveFile(); + if(m_modified) { + // If the user attempted to save the file, but it's still + // modified, then probably something went wrong, so we quit here. + return; + } + } + } + + m_fileName = "<unnamed>"; + setWindowTitle(m_fileName); + m_simpleEditor->setPlainText(""); +} + +void FileEditorMdiSubWindow::saveFile() { + QString saveFileName = QFileDialog::getSaveFileName(this, "Save File", m_fileName); + if(saveFileName.isEmpty()) + return; + + QFile file(saveFileName); + file.open(QFile::WriteOnly); + + if(file.write(m_simpleEditor->toPlainText().toLocal8Bit()) == -1) { + QMessageBox::warning(this, + "Error Saving File", + QString("The file could not be saved: %1.").arg(file.errorString())); + } else { + m_simpleEditor->document()->setModified(false); + } + + file.close(); +} + +void FileEditorMdiSubWindow::showToolTipNew() { + m_statusBar->showMessage("Create a new file.", 2000); +} + +void FileEditorMdiSubWindow::showToolTipSave() { + m_statusBar->showMessage("Save the file.", 2000); +} + +void FileEditorMdiSubWindow::showToolTipUndo() { + m_statusBar->showMessage("Revert previous changes.", 2000); +} + +void FileEditorMdiSubWindow::showToolTipRedo() { + m_statusBar->showMessage("Append previous changes.", 2000); +} + +void FileEditorMdiSubWindow::registerModified(bool modified) { + m_modified = modified; +} + +void FileEditorMdiSubWindow::construct() { + QStyle *style = QApplication::style(); + setWidget(new QWidget()); + m_toolBar = new QToolBar(this); + m_simpleEditor = new SimpleEditor(this); + m_statusBar = new QStatusBar(this); + m_numberedTextView = new NumberedCodeEdit(this, m_simpleEditor); + + m_simpleEditor->setFont(QFont("Courier")); + m_simpleEditor->setLineWrapMode(QPlainTextEdit::NoWrap); + + QAction *newAction = new QAction(style->standardIcon(QStyle::SP_FileIcon), + "", m_toolBar); + QAction *saveAction = new QAction(style->standardIcon(QStyle::SP_DriveHDIcon), + "", m_toolBar); + QAction *undoAction = new QAction(style->standardIcon(QStyle::SP_ArrowLeft), + "", m_toolBar); + QAction *redoAction = new QAction(style->standardIcon(QStyle::SP_ArrowRight), + "", m_toolBar); + + m_toolBar->addAction(newAction); + m_toolBar->addAction(saveAction); + m_toolBar->addAction(undoAction); + m_toolBar->addAction(redoAction); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(m_toolBar); + layout->addWidget(m_numberedTextView); + layout->addWidget(m_statusBar); + layout->setMargin(2); + widget()->setLayout(layout); + + connect(newAction, SIGNAL(triggered()), this, SLOT(newFile())); + connect(undoAction, SIGNAL(triggered()), m_simpleEditor, SLOT(undo())); + connect(redoAction, SIGNAL(triggered()), m_simpleEditor, SLOT(redo())); + connect(saveAction, SIGNAL(triggered()), this, SLOT(saveFile())); + + connect(newAction, SIGNAL(hovered()), this, SLOT(showToolTipNew())); + connect(undoAction, SIGNAL(hovered()), this, SLOT(showToolTipUndo())); + connect(redoAction, SIGNAL(hovered()), this, SLOT(showToolTipRedo())); + connect(saveAction, SIGNAL(hovered()), this, SLOT(showToolTipSave())); + + connect(m_simpleEditor, SIGNAL(modificationChanged(bool)), this, SLOT(registerModified(bool))); + + m_fileName = ""; + setWindowTitle(m_fileName); +}
new file mode 100644 --- /dev/null +++ b/gui//src/FileEditorMdiSubWindow.h @@ -0,0 +1,56 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#ifndef FILEEDITORMDISUBWINDOW_H +#define FILEEDITORMDISUBWINDOW_H + +#include <QMdiSubWindow> +#include <QToolBar> +#include <QStatusBar> +#include "SimpleEditor.h" +#include "NumberedCodeEdit.h" + +class FileEditorMdiSubWindow : public QMdiSubWindow { + Q_OBJECT +public: + FileEditorMdiSubWindow(QWidget *parent = 0); + void loadFile(QString fileName); + + void installEventFilter(QObject *object); + +public slots: + void newFile(); + void saveFile(); + + void showToolTipNew(); + void showToolTipSave(); + void showToolTipUndo(); + void showToolTipRedo(); + + void registerModified(bool modified); +private: + void construct(); + QToolBar *m_toolBar; + SimpleEditor *m_simpleEditor; + NumberedCodeEdit *m_numberedTextView; + QStatusBar *m_statusBar; + QString m_fileName; + bool m_modified; +}; + +#endif // FILEEDITORMDISUBWINDOW_H
new file mode 100644 --- /dev/null +++ b/gui//src/FilesDockWidget.cpp @@ -0,0 +1,98 @@ +#include "FilesDockWidget.h" + +#include <QApplication> +#include <QFileInfo> + +FilesDockWidget::FilesDockWidget(QWidget *parent) + : QDockWidget(parent) { + setObjectName("FilesDockWidget"); + setWindowTitle(tr("Current Folder")); + setWidget(new QWidget(this)); + + // Create a toolbar + m_navigationToolBar = new QToolBar("", widget()); + m_navigationToolBar->setAllowedAreas(Qt::TopToolBarArea); + m_navigationToolBar->setMovable(false); + m_navigationToolBar->setIconSize(QSize (20,20)); + + // Add a button to the toolbar with the QT standard icon for up-directory + // TODO: Maybe change this to be an up-directory icon that is OS specific??? + QStyle *style = QApplication::style(); + m_directoryIcon = style->standardIcon(QStyle::SP_FileDialogToParent); + m_directoryUpAction = new QAction(m_directoryIcon, "", m_navigationToolBar); + m_currentDirectory = new QLineEdit(m_navigationToolBar); + + m_navigationToolBar->addAction(m_directoryUpAction); + m_navigationToolBar->addWidget(m_currentDirectory); + connect(m_directoryUpAction, SIGNAL(triggered()), this, SLOT(onUpDirectory())); + + // TODO: Add other buttons for creating directories + + // Create the QFileSystemModel starting in the home directory + QString homePath = QDir::homePath(); + // TODO: This should occur after Octave has been initialized and the startup directory of Octave is established + + m_fileSystemModel = new QFileSystemModel(this); + m_fileSystemModel->setFilter(QDir::NoDotAndDotDot | QDir::AllEntries); + QModelIndex rootPathIndex = m_fileSystemModel->setRootPath(homePath); + + // Attach the model to the QTreeView and set the root index + m_fileTreeView = new QTreeView(widget()); + m_fileTreeView->setModel(m_fileSystemModel); + m_fileTreeView->setRootIndex(rootPathIndex); + m_fileTreeView->setSortingEnabled(true); + m_fileTreeView->setAlternatingRowColors(true); + m_fileTreeView->setAnimated(true); + setCurrentDirectory(m_fileSystemModel->fileInfo(rootPathIndex).absoluteFilePath()); + + connect(m_fileTreeView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &))); + + // Layout the widgets vertically with the toolbar on top + QVBoxLayout *layout = new QVBoxLayout(); + layout->setSpacing(0); + layout->addWidget(m_navigationToolBar); + layout->addWidget(m_fileTreeView); + widget()->setLayout(layout); + // TODO: Add right-click contextual menus for copying, pasting, deleting files (and others) + + connect(m_currentDirectory, SIGNAL(returnPressed()), this, SLOT(currentDirectoryEntered())); + //m_currentDirectory->setEnabled(false); +} + +void FilesDockWidget::itemDoubleClicked(const QModelIndex &index) +{ + QFileInfo fileInfo = m_fileSystemModel->fileInfo(index); + if (fileInfo.isDir()) { + m_fileSystemModel->setRootPath(fileInfo.absolutePath()); + m_fileTreeView->setRootIndex(index); + setCurrentDirectory(m_fileSystemModel->fileInfo(index).absoluteFilePath()); + } else { + QFileInfo fileInfo = m_fileSystemModel->fileInfo(index); + emit openFile(fileInfo.filePath()); + } +} + +void FilesDockWidget::setCurrentDirectory(QString currentDirectory) { + m_currentDirectory->setText(currentDirectory); +} + +void FilesDockWidget::onUpDirectory(void) { + // Move up an inm_fileTreeView->setRootIndex(m_fileSystemModel->index(dir.absolutePath()));dex node + QDir dir = QDir(m_fileSystemModel->filePath(m_fileTreeView->rootIndex())); + dir.cdUp(); + m_fileSystemModel->setRootPath(dir.absolutePath()); + m_fileTreeView->setRootIndex(m_fileSystemModel->index(dir.absolutePath())); + setCurrentDirectory(dir.absolutePath()); +} + +void FilesDockWidget::currentDirectoryEntered() { + QFileInfo fileInfo(m_currentDirectory->text()); + if (fileInfo.isDir()) { + m_fileTreeView->setRootIndex(m_fileSystemModel->index(fileInfo.absolutePath())); + m_fileSystemModel->setRootPath(fileInfo.absolutePath()); + setCurrentDirectory(fileInfo.absoluteFilePath()); + } else { + if(QFile::exists(fileInfo.absoluteFilePath())) + emit openFile(fileInfo.absoluteFilePath()); + } +}
new file mode 100644 --- /dev/null +++ b/gui//src/FilesDockWidget.h @@ -0,0 +1,86 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 John P. Swensen, Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#ifndef FILESDOCKWIDGET_H +#define FILESDOCKWIDGET_H + +#include <QListView> +#include <QDate> +#include <QObject> +#include <QWidget> +#include <QListWidget> +#include <QFileSystemModel> +#include <QToolBar> +#include <QToolButton> +#include <QVBoxLayout> +#include <QAction> +#include <QTreeView> + +#include <vector> +#include <string> + +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#include "octave/config.h" +#include "octave/octave.h" +#include "octave/str-vec.h" +#include "octave/cmd-hist.h" +#include <QDockWidget> +#include <QLineEdit> + +class FilesDockWidget : public QDockWidget { + Q_OBJECT +public : + FilesDockWidget(QWidget *parent = 0); +public slots: + /** Slot for handling a change in directory via double click. */ + void itemDoubleClicked(const QModelIndex &index); + + /** Slot for handling the up-directory button in the toolbar. */ + void onUpDirectory(); + + void setCurrentDirectory(QString currentDirectory); + + void currentDirectoryEntered(); + +signals: + void openFile(QString fileName); + +private: + // TODO: Add toolbar with buttons for navigating the path, creating dirs, etc + + /** Toolbar for file and directory manipulation. */ + QToolBar *m_navigationToolBar; + + /** Variables for the up-directory action. */ + QIcon m_directoryIcon; + QAction *m_directoryUpAction; + QToolButton *upDirectoryButton; + + /** The file system model. */ + QFileSystemModel *m_fileSystemModel; + + /** The file system view. */ + QTreeView *m_fileTreeView; + QLineEdit *m_currentDirectory; +}; + +#endif // FILESDOCKWIDGET_H
new file mode 100644 --- /dev/null +++ b/gui//src/Filter.cpp @@ -0,0 +1,534 @@ +/* + Copyright 2007-2008 by Robert Knight <robertknight@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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "Filter.h" + +// System +#include <iostream> + +// Qt +#include <QtGui/QAction> +#include <QtGui/QApplication> +#include <QtGui/QClipboard> +#include <QtCore/QString> +#include <QtCore/QTextStream> +#include <QtCore/QSharedData> +#include <QtCore/QFile> + +// Konsole +#include "TerminalCharacterDecoder.h" +#include "konsole_wcwidth.h" +#include "konsole_export.h" + +FilterChain::~FilterChain() +{ + QMutableListIterator<Filter*> iter(*this); + + while ( iter.hasNext() ) + { + Filter* filter = iter.next(); + iter.remove(); + delete filter; + } +} + +void FilterChain::addFilter(Filter* filter) +{ + append(filter); +} +void FilterChain::removeFilter(Filter* filter) +{ + removeAll(filter); +} +bool FilterChain::containsFilter(Filter* filter) +{ + return contains(filter); +} +void FilterChain::reset() +{ + QListIterator<Filter*> iter(*this); + while (iter.hasNext()) + iter.next()->reset(); +} +void FilterChain::setBuffer(const QString* buffer , const QList<int>* linePositions) +{ + QListIterator<Filter*> iter(*this); + while (iter.hasNext()) + iter.next()->setBuffer(buffer,linePositions); +} +void FilterChain::process() +{ + QListIterator<Filter*> iter(*this); + while (iter.hasNext()) + iter.next()->process(); +} +void FilterChain::clear() +{ + QList<Filter*>::clear(); +} +Filter::HotSpot* FilterChain::hotSpotAt(int line , int column) const +{ + QListIterator<Filter*> iter(*this); + while (iter.hasNext()) + { + Filter* filter = iter.next(); + Filter::HotSpot* spot = filter->hotSpotAt(line,column); + if ( spot != 0 ) + { + return spot; + } + } + + return 0; +} + +QList<Filter::HotSpot*> FilterChain::hotSpots() const +{ + QList<Filter::HotSpot*> list; + QListIterator<Filter*> iter(*this); + while (iter.hasNext()) + { + Filter* filter = iter.next(); + list << filter->hotSpots(); + } + return list; +} +//QList<Filter::HotSpot*> FilterChain::hotSpotsAtLine(int line) const; + +TerminalImageFilterChain::TerminalImageFilterChain() +: _buffer(0) +, _linePositions(0) +{ +} + +TerminalImageFilterChain::~TerminalImageFilterChain() +{ + delete _buffer; + delete _linePositions; +} + +void TerminalImageFilterChain::setImage(const Character* const image , int lines , int columns, const QVector<LineProperty>& lineProperties) +{ + if (empty()) + return; + + // reset all filters and hotspots + reset(); + + PlainTextDecoder decoder; + decoder.setTrailingWhitespace(false); + + // setup new shared buffers for the filters to process on + QString* newBuffer = new QString(); + QList<int>* newLinePositions = new QList<int>(); + setBuffer( newBuffer , newLinePositions ); + + // free the old buffers + delete _buffer; + delete _linePositions; + + _buffer = newBuffer; + _linePositions = newLinePositions; + + QTextStream lineStream(_buffer); + decoder.begin(&lineStream); + + for (int i=0 ; i < lines ; i++) + { + _linePositions->append(_buffer->length()); + decoder.decodeLine(image + i*columns,columns,LINE_DEFAULT); + + // pretend that each line ends with a newline character. + // this prevents a link that occurs at the end of one line + // being treated as part of a link that occurs at the start of the next line + // + // the downside is that links which are spread over more than one line are not + // highlighted. + // + // TODO - Use the "line wrapped" attribute associated with lines in a + // terminal image to avoid adding this imaginary character for wrapped + // lines + if ( !(lineProperties.value(i,LINE_DEFAULT) & LINE_WRAPPED) ) + lineStream << QChar('\n'); + } + decoder.end(); +} + +Filter::Filter() : +_linePositions(0), +_buffer(0) +{ +} + +Filter::~Filter() +{ + QListIterator<HotSpot*> iter(_hotspotList); + while (iter.hasNext()) + { + delete iter.next(); + } +} +void Filter::reset() +{ + _hotspots.clear(); + _hotspotList.clear(); +} + +void Filter::setBuffer(const QString* buffer , const QList<int>* linePositions) +{ + _buffer = buffer; + _linePositions = linePositions; +} + +void Filter::getLineColumn(int position , int& startLine , int& startColumn) +{ + Q_ASSERT( _linePositions ); + Q_ASSERT( _buffer ); + + + for (int i = 0 ; i < _linePositions->count() ; i++) + { + int nextLine = 0; + + if ( i == _linePositions->count()-1 ) + nextLine = _buffer->length() + 1; + else + nextLine = _linePositions->value(i+1); + + if ( _linePositions->value(i) <= position && position < nextLine ) + { + startLine = i; + startColumn = string_width(buffer()->mid(_linePositions->value(i),position - _linePositions->value(i))); + return; + } + } +} + + +/*void Filter::addLine(const QString& text) +{ + _linePositions << _buffer.length(); + _buffer.append(text); +}*/ + +const QString* Filter::buffer() +{ + return _buffer; +} +Filter::HotSpot::~HotSpot() +{ +} +void Filter::addHotSpot(HotSpot* spot) +{ + _hotspotList << spot; + + for (int line = spot->startLine() ; line <= spot->endLine() ; line++) + { + _hotspots.insert(line,spot); + } +} +QList<Filter::HotSpot*> Filter::hotSpots() const +{ + return _hotspotList; +} +QList<Filter::HotSpot*> Filter::hotSpotsAtLine(int line) const +{ + return _hotspots.values(line); +} + +Filter::HotSpot* Filter::hotSpotAt(int line , int column) const +{ + QListIterator<HotSpot*> spotIter(_hotspots.values(line)); + + while (spotIter.hasNext()) + { + HotSpot* spot = spotIter.next(); + + if ( spot->startLine() == line && spot->startColumn() > column ) + continue; + if ( spot->endLine() == line && spot->endColumn() < column ) + continue; + + return spot; + } + + return 0; +} + +Filter::HotSpot::HotSpot(int startLine , int startColumn , int endLine , int endColumn) + : _startLine(startLine) + , _startColumn(startColumn) + , _endLine(endLine) + , _endColumn(endColumn) + , _type(NotSpecified) +{ +} +QString Filter::HotSpot::tooltip() const +{ + return QString(); +} +QList<QAction*> Filter::HotSpot::actions() +{ + return QList<QAction*>(); +} +int Filter::HotSpot::startLine() const +{ + return _startLine; +} +int Filter::HotSpot::endLine() const +{ + return _endLine; +} +int Filter::HotSpot::startColumn() const +{ + return _startColumn; +} +int Filter::HotSpot::endColumn() const +{ + return _endColumn; +} +Filter::HotSpot::Type Filter::HotSpot::type() const +{ + return _type; +} +void Filter::HotSpot::setType(Type type) +{ + _type = type; +} + +RegExpFilter::RegExpFilter() +{ +} + +RegExpFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn) + : Filter::HotSpot(startLine,startColumn,endLine,endColumn) +{ + setType(Marker); +} + +void RegExpFilter::HotSpot::activate(QObject*) +{ +} + +void RegExpFilter::HotSpot::setCapturedTexts(const QStringList& texts) +{ + _capturedTexts = texts; +} +QStringList RegExpFilter::HotSpot::capturedTexts() const +{ + return _capturedTexts; +} + +void RegExpFilter::setRegExp(const QRegExp& regExp) +{ + _searchText = regExp; +} +QRegExp RegExpFilter::regExp() const +{ + return _searchText; +} +/*void RegExpFilter::reset(int) +{ + _buffer = QString(); +}*/ +void RegExpFilter::process() +{ + int pos = 0; + const QString* text = buffer(); + + Q_ASSERT( text ); + + // ignore any regular expressions which match an empty string. + // otherwise the while loop below will run indefinitely + static const QString emptyString(""); + if ( _searchText.exactMatch(emptyString) ) + return; + + while(pos >= 0) + { + pos = _searchText.indexIn(*text,pos); + + if ( pos >= 0 ) + { + int startLine = 0; + int endLine = 0; + int startColumn = 0; + int endColumn = 0; + + getLineColumn(pos,startLine,startColumn); + getLineColumn(pos + _searchText.matchedLength(),endLine,endColumn); + + RegExpFilter::HotSpot* spot = newHotSpot(startLine,startColumn, + endLine,endColumn); + spot->setCapturedTexts(_searchText.capturedTexts()); + + addHotSpot( spot ); + pos += _searchText.matchedLength(); + + // if matchedLength == 0, the program will get stuck in an infinite loop + if ( _searchText.matchedLength() == 0 ) + pos = -1; + } + } +} + +RegExpFilter::HotSpot* RegExpFilter::newHotSpot(int startLine,int startColumn, + int endLine,int endColumn) +{ + return new RegExpFilter::HotSpot(startLine,startColumn, + endLine,endColumn); +} +RegExpFilter::HotSpot* UrlFilter::newHotSpot(int startLine,int startColumn,int endLine, + int endColumn) +{ + return new UrlFilter::HotSpot(startLine,startColumn, + endLine,endColumn); +} +UrlFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn) +: RegExpFilter::HotSpot(startLine,startColumn,endLine,endColumn) +, _urlObject(new FilterObject(this)) +{ + setType(Link); +} +QString UrlFilter::HotSpot::tooltip() const +{ + QString url = capturedTexts().first(); + + const UrlType kind = urlType(); + + if ( kind == StandardUrl ) + return QString(); + else if ( kind == Email ) + return QString(); + else + return QString(); +} +UrlFilter::HotSpot::UrlType UrlFilter::HotSpot::urlType() const +{ + QString url = capturedTexts().first(); + + if ( FullUrlRegExp.exactMatch(url) ) + return StandardUrl; + else if ( EmailAddressRegExp.exactMatch(url) ) + return Email; + else + return Unknown; +} + +void UrlFilter::HotSpot::activate(QObject* object) +{ + QString url = capturedTexts().first(); + + const UrlType kind = urlType(); + + const QString& actionName = object ? object->objectName() : QString(); + + if ( actionName == "copy-action" ) + { + QApplication::clipboard()->setText(url); + return; + } + + if ( !object || actionName == "open-action" ) + { + if ( kind == StandardUrl ) + { + // if the URL path does not include the protocol ( eg. "www.kde.org" ) then + // prepend http:// ( eg. "www.kde.org" --> "http://www.kde.org" ) + if (!url.contains("://")) + { + url.prepend("http://"); + } + } + else if ( kind == Email ) + { + url.prepend("mailto:"); + } + + //new KRun(url,QApplication::activeWindow()); + } +} + +// Note: Altering these regular expressions can have a major effect on the performance of the filters +// used for finding URLs in the text, especially if they are very general and could match very long +// pieces of text. +// Please be careful when altering them. + +//regexp matches: +// full url: +// protocolname:// or www. followed by anything other than whitespaces, <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, comma and dot +const QRegExp UrlFilter::FullUrlRegExp("(www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]]"); +// email address: +// [word chars, dots or dashes]@[word chars, dots or dashes].[word chars] +const QRegExp UrlFilter::EmailAddressRegExp("\\b(\\w|\\.|-)+@(\\w|\\.|-)+\\.\\w+\\b"); + +// matches full url or email address +const QRegExp UrlFilter::CompleteUrlRegExp('('+FullUrlRegExp.pattern()+'|'+ + EmailAddressRegExp.pattern()+')'); + +UrlFilter::UrlFilter() +{ + setRegExp( CompleteUrlRegExp ); +} +UrlFilter::HotSpot::~HotSpot() +{ + delete _urlObject; +} +void FilterObject::activated() +{ + _filter->activate(sender()); +} +QList<QAction*> UrlFilter::HotSpot::actions() +{ + QList<QAction*> list; + + const UrlType kind = urlType(); + + QAction* openAction = new QAction(_urlObject); + QAction* copyAction = new QAction(_urlObject);; + + Q_ASSERT( kind == StandardUrl || kind == Email ); + + if ( kind == StandardUrl ) + { + openAction->setText(i18n("Open Link")); + copyAction->setText(i18n("Copy Link Address")); + } + else if ( kind == Email ) + { + openAction->setText(i18n("Send Email To...")); + copyAction->setText(i18n("Copy Email Address")); + } + + // object names are set here so that the hotspot performs the + // correct action when activated() is called with the triggered + // action passed as a parameter. + openAction->setObjectName( QLatin1String("open-action" )); + copyAction->setObjectName( QLatin1String("copy-action" )); + + QObject::connect( openAction , SIGNAL(triggered()) , _urlObject , SLOT(activated()) ); + QObject::connect( copyAction , SIGNAL(triggered()) , _urlObject , SLOT(activated()) ); + + list << openAction; + list << copyAction; + + return list; +} +
new file mode 100644 --- /dev/null +++ b/gui//src/Filter.h @@ -0,0 +1,379 @@ +/* + Copyright 2007-2008 by Robert Knight <robertknight@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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef FILTER_H +#define FILTER_H + +// Qt +#include <QtGui/QAction> +#include <QtCore/QList> +#include <QtCore/QObject> +#include <QtCore/QStringList> +#include <QtCore/QHash> +#include <QtCore/QRegExp> + +// Local +#include "Character.h" + + +/** + * A filter processes blocks of text looking for certain patterns (such as URLs or keywords from a list) + * and marks the areas which match the filter's patterns as 'hotspots'. + * + * Each hotspot has a type identifier associated with it ( such as a link or a highlighted section ), + * and an action. When the user performs some activity such as a mouse-click in a hotspot area ( the exact + * action will depend on what is displaying the block of text which the filter is processing ), the hotspot's + * activate() method should be called. Depending on the type of hotspot this will trigger a suitable response. + * + * For example, if a hotspot represents a URL then a suitable action would be opening that URL in a web browser. + * Hotspots may have more than one action, in which case the list of actions can be obtained using the + * actions() method. + * + * Different subclasses of filter will return different types of hotspot. + * Subclasses must reimplement the process() method to examine a block of text and identify sections of interest. + * When processing the text they should create instances of Filter::HotSpot subclasses for sections of interest + * and add them to the filter's list of hotspots using addHotSpot() + */ +class Filter +{ +public: + /** + * Represents an area of text which matched the pattern a particular filter has been looking for. + * + * Each hotspot has a type identifier associated with it ( such as a link or a highlighted section ), + * and an action. When the user performs some activity such as a mouse-click in a hotspot area ( the exact + * action will depend on what is displaying the block of text which the filter is processing ), the hotspot's + * activate() method should be called. Depending on the type of hotspot this will trigger a suitable response. + * + * For example, if a hotspot represents a URL then a suitable action would be opening that URL in a web browser. + * Hotspots may have more than one action, in which case the list of actions can be obtained using the + * actions() method. These actions may then be displayed in a popup menu or toolbar for example. + */ + class HotSpot + { + public: + /** + * Constructs a new hotspot which covers the area from (@p startLine,@p startColumn) to (@p endLine,@p endColumn) + * in a block of text. + */ + HotSpot(int startLine , int startColumn , int endLine , int endColumn); + virtual ~HotSpot(); + + enum Type + { + // the type of the hotspot is not specified + NotSpecified, + // this hotspot represents a clickable link + Link, + // this hotspot represents a marker + Marker + }; + + /** Returns the line when the hotspot area starts */ + int startLine() const; + /** Returns the line where the hotspot area ends */ + int endLine() const; + /** Returns the column on startLine() where the hotspot area starts */ + int startColumn() const; + /** Returns the column on endLine() where the hotspot area ends */ + int endColumn() const; + /** + * Returns the type of the hotspot. This is usually used as a hint for views on how to represent + * the hotspot graphically. eg. Link hotspots are typically underlined when the user mouses over them + */ + Type type() const; + /** + * Causes the an action associated with a hotspot to be triggered. + * + * @param object The object which caused the hotspot to be triggered. This is + * typically null ( in which case the default action should be performed ) or + * one of the objects from the actions() list. In which case the associated + * action should be performed. + */ + virtual void activate(QObject* object = 0) = 0; + /** + * Returns a list of actions associated with the hotspot which can be used in a + * menu or toolbar + */ + virtual QList<QAction*> actions(); + + /** + * Returns the text of a tooltip to be shown when the mouse moves over the hotspot, or + * an empty string if there is no tooltip associated with this hotspot. + * + * The default implementation returns an empty string. + */ + virtual QString tooltip() const; + + protected: + /** Sets the type of a hotspot. This should only be set once */ + void setType(Type type); + + private: + int _startLine; + int _startColumn; + int _endLine; + int _endColumn; + Type _type; + + }; + + /** Constructs a new filter. */ + Filter(); + virtual ~Filter(); + + /** Causes the filter to process the block of text currently in its internal buffer */ + virtual void process() = 0; + + /** + * Empties the filters internal buffer and resets the line count back to 0. + * All hotspots are deleted. + */ + void reset(); + + /** Adds a new line of text to the filter and increments the line count */ + //void addLine(const QString& string); + + /** Returns the hotspot which covers the given @p line and @p column, or 0 if no hotspot covers that area */ + HotSpot* hotSpotAt(int line , int column) const; + + /** Returns the list of hotspots identified by the filter */ + QList<HotSpot*> hotSpots() const; + + /** Returns the list of hotspots identified by the filter which occur on a given line */ + QList<HotSpot*> hotSpotsAtLine(int line) const; + + /** + * TODO: Document me + */ + void setBuffer(const QString* buffer , const QList<int>* linePositions); + +protected: + /** Adds a new hotspot to the list */ + void addHotSpot(HotSpot*); + /** Returns the internal buffer */ + const QString* buffer(); + /** Converts a character position within buffer() to a line and column */ + void getLineColumn(int position , int& startLine , int& startColumn); + +private: + QMultiHash<int,HotSpot*> _hotspots; + QList<HotSpot*> _hotspotList; + + const QList<int>* _linePositions; + const QString* _buffer; +}; + +/** + * A filter which searches for sections of text matching a regular expression and creates a new RegExpFilter::HotSpot + * instance for them. + * + * Subclasses can reimplement newHotSpot() to return custom hotspot types when matches for the regular expression + * are found. + */ +class RegExpFilter : public Filter +{ +public: + /** + * Type of hotspot created by RegExpFilter. The capturedTexts() method can be used to find the text + * matched by the filter's regular expression. + */ + class HotSpot : public Filter::HotSpot + { + public: + HotSpot(int startLine, int startColumn, int endLine , int endColumn); + virtual void activate(QObject* object = 0); + + /** Sets the captured texts associated with this hotspot */ + void setCapturedTexts(const QStringList& texts); + /** Returns the texts found by the filter when matching the filter's regular expression */ + QStringList capturedTexts() const; + private: + QStringList _capturedTexts; + }; + + /** Constructs a new regular expression filter */ + RegExpFilter(); + + /** + * Sets the regular expression which the filter searches for in blocks of text. + * + * Regular expressions which match the empty string are treated as not matching + * anything. + */ + void setRegExp(const QRegExp& text); + /** Returns the regular expression which the filter searches for in blocks of text */ + QRegExp regExp() const; + + /** + * Reimplemented to search the filter's text buffer for text matching regExp() + * + * If regexp matches the empty string, then process() will return immediately + * without finding results. + */ + virtual void process(); + +protected: + /** + * Called when a match for the regular expression is encountered. Subclasses should reimplement this + * to return custom hotspot types + */ + virtual RegExpFilter::HotSpot* newHotSpot(int startLine,int startColumn, + int endLine,int endColumn); + +private: + QRegExp _searchText; +}; + +class FilterObject; + +/** A filter which matches URLs in blocks of text */ +class UrlFilter : public RegExpFilter +{ +public: + /** + * Hotspot type created by UrlFilter instances. The activate() method opens a web browser + * at the given URL when called. + */ + class HotSpot : public RegExpFilter::HotSpot + { + public: + HotSpot(int startLine,int startColumn,int endLine,int endColumn); + virtual ~HotSpot(); + + virtual QList<QAction*> actions(); + + /** + * Open a web browser at the current URL. The url itself can be determined using + * the capturedTexts() method. + */ + virtual void activate(QObject* object = 0); + + virtual QString tooltip() const; + private: + enum UrlType + { + StandardUrl, + Email, + Unknown + }; + UrlType urlType() const; + + FilterObject* _urlObject; + }; + + UrlFilter(); + +protected: + virtual RegExpFilter::HotSpot* newHotSpot(int,int,int,int); + +private: + + static const QRegExp FullUrlRegExp; + static const QRegExp EmailAddressRegExp; + + // combined OR of FullUrlRegExp and EmailAddressRegExp + static const QRegExp CompleteUrlRegExp; +}; + +class FilterObject : public QObject +{ +Q_OBJECT +public: + FilterObject(Filter::HotSpot* filter) : _filter(filter) {} +private slots: + void activated(); +private: + Filter::HotSpot* _filter; +}; + +/** + * A chain which allows a group of filters to be processed as one. + * The chain owns the filters added to it and deletes them when the chain itself is destroyed. + * + * Use addFilter() to add a new filter to the chain. + * When new text to be filtered arrives, use addLine() to add each additional + * line of text which needs to be processed and then after adding the last line, use + * process() to cause each filter in the chain to process the text. + * + * After processing a block of text, the reset() method can be used to set the filter chain's + * internal cursor back to the first line. + * + * The hotSpotAt() method will return the first hotspot which covers a given position. + * + * The hotSpots() and hotSpotsAtLine() method return all of the hotspots in the text and on + * a given line respectively. + */ +class FilterChain : protected QList<Filter*> +{ +public: + virtual ~FilterChain(); + + /** Adds a new filter to the chain. The chain will delete this filter when it is destroyed */ + void addFilter(Filter* filter); + /** Removes a filter from the chain. The chain will no longer delete the filter when destroyed */ + void removeFilter(Filter* filter); + /** Returns true if the chain contains @p filter */ + bool containsFilter(Filter* filter); + /** Removes all filters from the chain */ + void clear(); + + /** Resets each filter in the chain */ + void reset(); + /** + * Processes each filter in the chain + */ + void process(); + + /** Sets the buffer for each filter in the chain to process. */ + void setBuffer(const QString* buffer , const QList<int>* linePositions); + + /** Returns the first hotspot which occurs at @p line, @p column or 0 if no hotspot was found */ + Filter::HotSpot* hotSpotAt(int line , int column) const; + /** Returns a list of all the hotspots in all the chain's filters */ + QList<Filter::HotSpot*> hotSpots() const; + /** Returns a list of all hotspots at the given line in all the chain's filters */ + QList<Filter::HotSpot> hotSpotsAtLine(int line) const; + +}; + +/** A filter chain which processes character images from terminal displays */ +class TerminalImageFilterChain : public FilterChain +{ +public: + TerminalImageFilterChain(); + virtual ~TerminalImageFilterChain(); + + /** + * Set the current terminal image to @p image. + * + * @param image The terminal image + * @param lines The number of lines in the terminal image + * @param columns The number of columns in the terminal image + * @param lineProperties The line properties to set for image + */ + void setImage(const Character* const image , int lines , int columns, + const QVector<LineProperty>& lineProperties); + +private: + QString* _buffer; + QList<int>* _linePositions; +}; + +#endif //FILTER_H
new file mode 100644 --- /dev/null +++ b/gui//src/History.cpp @@ -0,0 +1,696 @@ +/* + This file is part of Konsole, an X terminal. + Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "History.h" + +// System +#include <iostream> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <unistd.h> +#include <errno.h> + + +// Reasonable line size +#define LINE_SIZE 1024 + +/* + An arbitrary long scroll. + + One can modify the scroll only by adding either cells + or newlines, but access it randomly. + + The model is that of an arbitrary wide typewriter scroll + in that the scroll is a serie of lines and each line is + a serie of cells with no overwriting permitted. + + The implementation provides arbitrary length and numbers + of cells and line/column indexed read access to the scroll + at constant costs. + +KDE4: Can we use QTemporaryFile here, instead of KTempFile? + +FIXME: some complain about the history buffer comsuming the + memory of their machines. This problem is critical + since the history does not behave gracefully in cases + where the memory is used up completely. + + I put in a workaround that should handle it problem + now gracefully. I'm not satisfied with the solution. + +FIXME: Terminating the history is not properly indicated + in the menu. We should throw a signal. + +FIXME: There is noticeable decrease in speed, also. Perhaps, + there whole feature needs to be revisited therefore. + Disadvantage of a more elaborated, say block-oriented + scheme with wrap around would be it's complexity. +*/ + +//FIXME: tempory replacement for tmpfile +// this is here one for debugging purpose. + +//#define tmpfile xTmpFile + +// History File /////////////////////////////////////////// + +/* + A Row(X) data type which allows adding elements to the end. +*/ + +HistoryFile::HistoryFile() + : ion(-1), + length(0), + fileMap(0) +{ + if (tmpFile.open()) + { + tmpFile.setAutoRemove(true); + ion = tmpFile.handle(); + } +} + +HistoryFile::~HistoryFile() +{ + if (fileMap) + unmap(); +} + +//TODO: Mapping the entire file in will cause problems if the history file becomes exceedingly large, +//(ie. larger than available memory). HistoryFile::map() should only map in sections of the file at a time, +//to avoid this. +void HistoryFile::map() +{ + assert( fileMap == 0 ); + + fileMap = (char*)mmap( 0 , length , PROT_READ , MAP_PRIVATE , ion , 0 ); + + //if mmap'ing fails, fall back to the read-lseek combination + if ( fileMap == MAP_FAILED ) + { + readWriteBalance = 0; + fileMap = 0; + qDebug() << ": mmap'ing history failed. errno = " << errno; + } +} + +void HistoryFile::unmap() +{ + int result = munmap( fileMap , length ); + assert( result == 0 ); + + fileMap = 0; +} + +bool HistoryFile::isMapped() +{ + return (fileMap != 0); +} + +void HistoryFile::add(const unsigned char* bytes, int len) +{ + if ( fileMap ) + unmap(); + + readWriteBalance++; + + int rc = 0; + + rc = lseek(ion,length,SEEK_SET); if (rc < 0) { perror("HistoryFile::add.seek"); return; } + rc = write(ion,bytes,len); if (rc < 0) { perror("HistoryFile::add.write"); return; } + length += rc; +} + +void HistoryFile::get(unsigned char* bytes, int len, int loc) +{ + //count number of get() calls vs. number of add() calls. + //If there are many more get() calls compared with add() + //calls (decided by using MAP_THRESHOLD) then mmap the log + //file to improve performance. + readWriteBalance--; + if ( !fileMap && readWriteBalance < MAP_THRESHOLD ) + map(); + + if ( fileMap ) + { + for (int i=0;i<len;i++) + bytes[i]=fileMap[loc+i]; + } + else + { + int rc = 0; + + if (loc < 0 || len < 0 || loc + len > length) + fprintf(stderr,"getHist(...,%d,%d): invalid args.\n",len,loc); + rc = lseek(ion,loc,SEEK_SET); if (rc < 0) { perror("HistoryFile::get.seek"); return; } + rc = read(ion,bytes,len); if (rc < 0) { perror("HistoryFile::get.read"); return; } + } +} + +int HistoryFile::len() +{ + return length; +} + + +// History Scroll abstract base class ////////////////////////////////////// + + +HistoryScroll::HistoryScroll(HistoryType* t) + : m_histType(t) +{ +} + +HistoryScroll::~HistoryScroll() +{ + delete m_histType; +} + +bool HistoryScroll::hasScroll() +{ + return true; +} + +// History Scroll File ////////////////////////////////////// + +/* + The history scroll makes a Row(Row(Cell)) from + two history buffers. The index buffer contains + start of line positions which refere to the cells + buffer. + + Note that index[0] addresses the second line + (line #1), while the first line (line #0) starts + at 0 in cells. +*/ + +HistoryScrollFile::HistoryScrollFile(const QString &logFileName) + : HistoryScroll(new HistoryTypeFile(logFileName)), + m_logFileName(logFileName) +{ +} + +HistoryScrollFile::~HistoryScrollFile() +{ +} + +int HistoryScrollFile::getLines() +{ + return index.len() / sizeof(int); +} + +int HistoryScrollFile::getLineLen(int lineno) +{ + return (startOfLine(lineno+1) - startOfLine(lineno)) / sizeof(Character); +} + +bool HistoryScrollFile::isWrappedLine(int lineno) +{ + if (lineno>=0 && lineno <= getLines()) { + unsigned char flag; + lineflags.get((unsigned char*)&flag,sizeof(unsigned char),(lineno)*sizeof(unsigned char)); + return flag; + } + return false; +} + +int HistoryScrollFile::startOfLine(int lineno) +{ + if (lineno <= 0) return 0; + if (lineno <= getLines()) + { + + if (!index.isMapped()) + index.map(); + + int res; + index.get((unsigned char*)&res,sizeof(int),(lineno-1)*sizeof(int)); + return res; + } + return cells.len(); +} + +void HistoryScrollFile::getCells(int lineno, int colno, int count, Character res[]) +{ + cells.get((unsigned char*)res,count*sizeof(Character),startOfLine(lineno)+colno*sizeof(Character)); +} + +void HistoryScrollFile::addCells(const Character text[], int count) +{ + cells.add((unsigned char*)text,count*sizeof(Character)); +} + +void HistoryScrollFile::addLine(bool previousWrapped) +{ + if (index.isMapped()) + index.unmap(); + + int locn = cells.len(); + index.add((unsigned char*)&locn,sizeof(int)); + unsigned char flags = previousWrapped ? 0x01 : 0x00; + lineflags.add((unsigned char*)&flags,sizeof(unsigned char)); +} + + +// History Scroll Buffer ////////////////////////////////////// +HistoryScrollBuffer::HistoryScrollBuffer(unsigned int maxLineCount) + : HistoryScroll(new HistoryTypeBuffer(maxLineCount)) + ,_historyBuffer() + ,_maxLineCount(0) + ,_usedLines(0) + ,_head(0) +{ + setMaxNbLines(maxLineCount); +} + +HistoryScrollBuffer::~HistoryScrollBuffer() +{ + delete[] _historyBuffer; +} + +void HistoryScrollBuffer::addCellsVector(const QVector<Character>& cells) +{ + _head++; + if ( _usedLines < _maxLineCount ) + _usedLines++; + + if ( _head >= _maxLineCount ) + { + _head = 0; + } + + _historyBuffer[bufferIndex(_usedLines-1)] = cells; + _wrappedLine[bufferIndex(_usedLines-1)] = false; +} +void HistoryScrollBuffer::addCells(const Character a[], int count) +{ + HistoryLine newLine(count); + qCopy(a,a+count,newLine.begin()); + + addCellsVector(newLine); +} + +void HistoryScrollBuffer::addLine(bool previousWrapped) +{ + _wrappedLine[bufferIndex(_usedLines-1)] = previousWrapped; +} + +int HistoryScrollBuffer::getLines() +{ + return _usedLines; +} + +int HistoryScrollBuffer::getLineLen(int lineNumber) +{ + Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount ); + + if ( lineNumber < _usedLines ) + { + return _historyBuffer[bufferIndex(lineNumber)].size(); + } + else + { + return 0; + } +} + +bool HistoryScrollBuffer::isWrappedLine(int lineNumber) +{ + Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount ); + + if (lineNumber < _usedLines) + { + //kDebug() << "Line" << lineNumber << "wrapped is" << _wrappedLine[bufferIndex(lineNumber)]; + return _wrappedLine[bufferIndex(lineNumber)]; + } + else + return false; +} + +void HistoryScrollBuffer::getCells(int lineNumber, int startColumn, int count, Character* buffer) +{ + if ( count == 0 ) return; + + Q_ASSERT( lineNumber < _maxLineCount ); + + if (lineNumber >= _usedLines) + { + memset(buffer, 0, count * sizeof(Character)); + return; + } + + const HistoryLine& line = _historyBuffer[bufferIndex(lineNumber)]; + + //kDebug() << "startCol " << startColumn; + //kDebug() << "line.size() " << line.size(); + //kDebug() << "count " << count; + + Q_ASSERT( startColumn <= line.size() - count ); + + memcpy(buffer, line.constData() + startColumn , count * sizeof(Character)); +} + +void HistoryScrollBuffer::setMaxNbLines(unsigned int lineCount) +{ + HistoryLine* oldBuffer = _historyBuffer; + HistoryLine* newBuffer = new HistoryLine[lineCount]; + + for ( int i = 0 ; i < qMin(_usedLines,(int)lineCount) ; i++ ) + { + newBuffer[i] = oldBuffer[bufferIndex(i)]; + } + + _usedLines = qMin(_usedLines,(int)lineCount); + _maxLineCount = lineCount; + _head = ( _usedLines == _maxLineCount ) ? 0 : _usedLines-1; + + _historyBuffer = newBuffer; + delete[] oldBuffer; + + _wrappedLine.resize(lineCount); +} + +int HistoryScrollBuffer::bufferIndex(int lineNumber) +{ + Q_ASSERT( lineNumber >= 0 ); + Q_ASSERT( lineNumber < _maxLineCount ); + Q_ASSERT( (_usedLines == _maxLineCount) || lineNumber <= _head ); + + if ( _usedLines == _maxLineCount ) + { + return (_head+lineNumber+1) % _maxLineCount; + } + else + { + return lineNumber; + } +} + + +// History Scroll None ////////////////////////////////////// + +HistoryScrollNone::HistoryScrollNone() + : HistoryScroll(new HistoryTypeNone()) +{ +} + +HistoryScrollNone::~HistoryScrollNone() +{ +} + +bool HistoryScrollNone::hasScroll() +{ + return false; +} + +int HistoryScrollNone::getLines() +{ + return 0; +} + +int HistoryScrollNone::getLineLen(int) +{ + return 0; +} + +bool HistoryScrollNone::isWrappedLine(int /*lineno*/) +{ + return false; +} + +void HistoryScrollNone::getCells(int, int, int, Character []) +{ +} + +void HistoryScrollNone::addCells(const Character [], int) +{ +} + +void HistoryScrollNone::addLine(bool) +{ +} + +// History Scroll BlockArray ////////////////////////////////////// + +HistoryScrollBlockArray::HistoryScrollBlockArray(size_t size) + : HistoryScroll(new HistoryTypeBlockArray(size)) +{ + m_blockArray.setHistorySize(size); // nb. of lines. +} + +HistoryScrollBlockArray::~HistoryScrollBlockArray() +{ +} + +int HistoryScrollBlockArray::getLines() +{ + return m_lineLengths.count(); +} + +int HistoryScrollBlockArray::getLineLen(int lineno) +{ + if ( m_lineLengths.contains(lineno) ) + return m_lineLengths[lineno]; + else + return 0; +} + +bool HistoryScrollBlockArray::isWrappedLine(int /*lineno*/) +{ + return false; +} + +void HistoryScrollBlockArray::getCells(int lineno, int colno, + int count, Character res[]) +{ + if (!count) return; + + const Block *b = m_blockArray.at(lineno); + + if (!b) { + memset(res, 0, count * sizeof(Character)); // still better than random data + return; + } + + assert(((colno + count) * sizeof(Character)) < ENTRIES); + memcpy(res, b->data + (colno * sizeof(Character)), count * sizeof(Character)); +} + +void HistoryScrollBlockArray::addCells(const Character a[], int count) +{ + Block *b = m_blockArray.lastBlock(); + + if (!b) return; + + // put cells in block's data + assert((count * sizeof(Character)) < ENTRIES); + + memset(b->data, 0, ENTRIES); + + memcpy(b->data, a, count * sizeof(Character)); + b->size = count * sizeof(Character); + + size_t res = m_blockArray.newBlock(); + assert (res > 0); + Q_UNUSED( res ); + + m_lineLengths.insert(m_blockArray.getCurrent(), count); +} + +void HistoryScrollBlockArray::addLine(bool) +{ +} + +////////////////////////////////////////////////////////////////////// +// History Types +////////////////////////////////////////////////////////////////////// + +HistoryType::HistoryType() +{ +} + +HistoryType::~HistoryType() +{ +} + +////////////////////////////// + +HistoryTypeNone::HistoryTypeNone() +{ +} + +bool HistoryTypeNone::isEnabled() const +{ + return false; +} + +HistoryScroll* HistoryTypeNone::scroll(HistoryScroll *old) const +{ + delete old; + return new HistoryScrollNone(); +} + +int HistoryTypeNone::maximumLineCount() const +{ + return 0; +} + +////////////////////////////// + +HistoryTypeBlockArray::HistoryTypeBlockArray(size_t size) + : m_size(size) +{ +} + +bool HistoryTypeBlockArray::isEnabled() const +{ + return true; +} + +int HistoryTypeBlockArray::maximumLineCount() const +{ + return m_size; +} + +HistoryScroll* HistoryTypeBlockArray::scroll(HistoryScroll *old) const +{ + delete old; + return new HistoryScrollBlockArray(m_size); +} + + +////////////////////////////// + +HistoryTypeBuffer::HistoryTypeBuffer(unsigned int nbLines) + : m_nbLines(nbLines) +{ +} + +bool HistoryTypeBuffer::isEnabled() const +{ + return true; +} + +int HistoryTypeBuffer::maximumLineCount() const +{ + return m_nbLines; +} + +HistoryScroll* HistoryTypeBuffer::scroll(HistoryScroll *old) const +{ + if (old) + { + HistoryScrollBuffer *oldBuffer = dynamic_cast<HistoryScrollBuffer*>(old); + if (oldBuffer) + { + oldBuffer->setMaxNbLines(m_nbLines); + return oldBuffer; + } + + HistoryScroll *newScroll = new HistoryScrollBuffer(m_nbLines); + int lines = old->getLines(); + int startLine = 0; + if (lines > (int) m_nbLines) + startLine = lines - m_nbLines; + + Character line[LINE_SIZE]; + for(int i = startLine; i < lines; i++) + { + int size = old->getLineLen(i); + if (size > LINE_SIZE) + { + Character *tmp_line = new Character[size]; + old->getCells(i, 0, size, tmp_line); + newScroll->addCells(tmp_line, size); + newScroll->addLine(old->isWrappedLine(i)); + delete [] tmp_line; + } + else + { + old->getCells(i, 0, size, line); + newScroll->addCells(line, size); + newScroll->addLine(old->isWrappedLine(i)); + } + } + delete old; + return newScroll; + } + return new HistoryScrollBuffer(m_nbLines); +} + +////////////////////////////// + +HistoryTypeFile::HistoryTypeFile(const QString& fileName) + : m_fileName(fileName) +{ +} + +bool HistoryTypeFile::isEnabled() const +{ + return true; +} + +const QString& HistoryTypeFile::getFileName() const +{ + return m_fileName; +} + +HistoryScroll* HistoryTypeFile::scroll(HistoryScroll *old) const +{ + if (dynamic_cast<HistoryFile *>(old)) + return old; // Unchanged. + + HistoryScroll *newScroll = new HistoryScrollFile(m_fileName); + + Character line[LINE_SIZE]; + int lines = (old != 0) ? old->getLines() : 0; + for(int i = 0; i < lines; i++) + { + int size = old->getLineLen(i); + if (size > LINE_SIZE) + { + Character *tmp_line = new Character[size]; + old->getCells(i, 0, size, tmp_line); + newScroll->addCells(tmp_line, size); + newScroll->addLine(old->isWrappedLine(i)); + delete [] tmp_line; + } + else + { + old->getCells(i, 0, size, line); + newScroll->addCells(line, size); + newScroll->addLine(old->isWrappedLine(i)); + } + } + + delete old; + return newScroll; +} + +int HistoryTypeFile::maximumLineCount() const +{ + return 0; +}
new file mode 100644 --- /dev/null +++ b/gui//src/History.h @@ -0,0 +1,307 @@ +/* + This file is part of Konsole, an X terminal. + Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef HISTORY_H +#define HISTORY_H + +// Qt +#include <QtCore/QBitRef> +#include <QtCore/QHash> +#include <QtCore> + +// Konsole +#include "BlockArray.h" +#include "Character.h" + +class HistoryFile +{ +public: + HistoryFile(); + virtual ~HistoryFile(); + + virtual void add(const unsigned char* bytes, int len); + virtual void get(unsigned char* bytes, int len, int loc); + virtual int len(); + + //mmaps the file in read-only mode + void map(); + //un-mmaps the file + void unmap(); + //returns true if the file is mmap'ed + bool isMapped(); + + +private: + int ion; + int length; + QTemporaryFile tmpFile; + + //pointer to start of mmap'ed file data, or 0 if the file is not mmap'ed + char* fileMap; + + //incremented whenver 'add' is called and decremented whenever + //'get' is called. + //this is used to detect when a large number of lines are being read and processed from the history + //and automatically mmap the file for better performance (saves the overhead of many lseek-read calls). + int readWriteBalance; + + //when readWriteBalance goes below this threshold, the file will be mmap'ed automatically + static const int MAP_THRESHOLD = -1000; +}; + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// +// Abstract base class for file and buffer versions +////////////////////////////////////////////////////////////////////// +class HistoryType; + +class HistoryScroll +{ +public: + HistoryScroll(HistoryType*); + virtual ~HistoryScroll(); + + virtual bool hasScroll(); + + // access to history + virtual int getLines() = 0; + virtual int getLineLen(int lineno) = 0; + virtual void getCells(int lineno, int colno, int count, Character res[]) = 0; + virtual bool isWrappedLine(int lineno) = 0; + + // backward compatibility (obsolete) + Character getCell(int lineno, int colno) { Character res; getCells(lineno,colno,1,&res); return res; } + + // adding lines. + virtual void addCells(const Character a[], int count) = 0; + // convenience method - this is virtual so that subclasses can take advantage + // of QVector's implicit copying + virtual void addCellsVector(const QVector<Character>& cells) + { + addCells(cells.data(),cells.size()); + } + + virtual void addLine(bool previousWrapped=false) = 0; + + // + // FIXME: Passing around constant references to HistoryType instances + // is very unsafe, because those references will no longer + // be valid if the history scroll is deleted. + // + const HistoryType& getType() { return *m_histType; } + +protected: + HistoryType* m_histType; + +}; + + + +////////////////////////////////////////////////////////////////////// +// File-based history (e.g. file log, no limitation in length) +////////////////////////////////////////////////////////////////////// + +class HistoryScrollFile : public HistoryScroll +{ +public: + HistoryScrollFile(const QString &logFileName); + virtual ~HistoryScrollFile(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addLine(bool previousWrapped=false); + +private: + int startOfLine(int lineno); + + QString m_logFileName; + HistoryFile index; // lines Row(int) + HistoryFile cells; // text Row(Character) + HistoryFile lineflags; // flags Row(unsigned char) +}; + + +////////////////////////////////////////////////////////////////////// +// Buffer-based history (limited to a fixed nb of lines) +////////////////////////////////////////////////////////////////////// +class HistoryScrollBuffer : public HistoryScroll +{ +public: + typedef QVector<Character> HistoryLine; + + HistoryScrollBuffer(unsigned int maxNbLines = 1000); + virtual ~HistoryScrollBuffer(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addCellsVector(const QVector<Character>& cells); + virtual void addLine(bool previousWrapped=false); + + void setMaxNbLines(unsigned int nbLines); + unsigned int maxNbLines() { return _maxLineCount; } + + +private: + int bufferIndex(int lineNumber); + + HistoryLine* _historyBuffer; + QBitArray _wrappedLine; + int _maxLineCount; + int _usedLines; + int _head; +}; + +////////////////////////////////////////////////////////////////////// +// Nothing-based history (no history :-) +////////////////////////////////////////////////////////////////////// +class HistoryScrollNone : public HistoryScroll +{ +public: + HistoryScrollNone(); + virtual ~HistoryScrollNone(); + + virtual bool hasScroll(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addLine(bool previousWrapped=false); +}; + +////////////////////////////////////////////////////////////////////// +// BlockArray-based history +////////////////////////////////////////////////////////////////////// +class HistoryScrollBlockArray : public HistoryScroll +{ +public: + HistoryScrollBlockArray(size_t size); + virtual ~HistoryScrollBlockArray(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addLine(bool previousWrapped=false); + +protected: + BlockArray m_blockArray; + QHash<int,size_t> m_lineLengths; +}; + +////////////////////////////////////////////////////////////////////// +// History type +////////////////////////////////////////////////////////////////////// + +class HistoryType +{ +public: + HistoryType(); + virtual ~HistoryType(); + + /** + * Returns true if the history is enabled ( can store lines of output ) + * or false otherwise. + */ + virtual bool isEnabled() const = 0; + /** + * Returns true if the history size is unlimited. + */ + bool isUnlimited() const { return maximumLineCount() == 0; } + /** + * Returns the maximum number of lines which this history type + * can store or 0 if the history can store an unlimited number of lines. + */ + virtual int maximumLineCount() const = 0; + + virtual HistoryScroll* scroll(HistoryScroll *) const = 0; +}; + +class HistoryTypeNone : public HistoryType +{ +public: + HistoryTypeNone(); + + virtual bool isEnabled() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; +}; + +class HistoryTypeBlockArray : public HistoryType +{ +public: + HistoryTypeBlockArray(size_t size); + + virtual bool isEnabled() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; + +protected: + size_t m_size; +}; + +class HistoryTypeFile : public HistoryType +{ +public: + HistoryTypeFile(const QString& fileName=QString()); + + virtual bool isEnabled() const; + virtual const QString& getFileName() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; + +protected: + QString m_fileName; +}; + + +class HistoryTypeBuffer : public HistoryType +{ +public: + HistoryTypeBuffer(unsigned int nbLines); + + virtual bool isEnabled() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; + +protected: + unsigned int m_nbLines; +}; + +#endif // HISTORY_H
new file mode 100644 --- /dev/null +++ b/gui//src/HistoryDockWidget.cpp @@ -0,0 +1,61 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include "HistoryDockWidget.h" +#include <QHBoxLayout> + +HistoryDockWidget::HistoryDockWidget(QWidget *parent) + : QDockWidget(parent) { + setObjectName("HistoryDockWidget"); + construct(); +} + +void HistoryDockWidget::handleListViewItemDoubleClicked(QModelIndex modelIndex) { + QString command = m_historyListModel->data(modelIndex, 0).toString(); + emit commandDoubleClicked(command); +} + +void HistoryDockWidget::construct() { + m_historyListModel = new QStringListModel(); + m_historyListView = new QListView(this); + m_historyListView->setModel(m_historyListModel); + m_historyListView->setAlternatingRowColors(true); + m_historyListView->setEditTriggers(QAbstractItemView::NoEditTriggers); + QHBoxLayout *layout = new QHBoxLayout(); + + setWindowTitle(tr("Command History")); + setWidget(new QWidget()); + + layout->addWidget(m_historyListView); + layout->setMargin(2); + + widget()->setLayout(layout); + connect(m_historyListView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(handleListViewItemDoubleClicked(QModelIndex))); +} + +void HistoryDockWidget::updateHistory(string_vector historyEntries) { + QStringList stringList = m_historyListModel->stringList(); + for(int i = 0; i < historyEntries.length(); i++) { + QString command(historyEntries[i].c_str()); + if(!command.startsWith("#")) { + stringList.push_front(command); + } + } + m_historyListModel->setStringList(stringList); + emit information(tr("History updated.")); +}
new file mode 100644 --- /dev/null +++ b/gui//src/HistoryDockWidget.h @@ -0,0 +1,55 @@ +#ifndef HISTORYDOCKWIDGET_H +#define HISTORYDOCKWIDGET_H + +#include <QDockWidget> +#include <QListView> +#include <QStringListModel> + +// Octave includes +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef PACKAGE_URL +#include "octave/config.h" + +#include "octave/debug.h" +#include "octave/octave.h" +#include "octave/symtab.h" +#include "octave/parse.h" +#include "octave/unwind-prot.h" +#include "octave/toplev.h" +#include "octave/load-path.h" +#include "octave/error.h" +#include "octave/quit.h" +#include "octave/variables.h" +#include "octave/sighandlers.h" +#include "octave/sysdep.h" +#include "octave/str-vec.h" +#include "octave/cmd-hist.h" +#include "octave/cmd-edit.h" +#include "octave/oct-env.h" +#include "octave/symtab.h" +#include "cmd-edit.h" + +class HistoryDockWidget : public QDockWidget { + Q_OBJECT +public: + HistoryDockWidget(QWidget *parent = 0); + void updateHistory(string_vector historyEntries); + +signals: + void information(QString message); + void commandDoubleClicked(QString command); + +private slots: + void handleListViewItemDoubleClicked(QModelIndex modelIndex); + +private: + void construct(); + QListView *m_historyListView; + QStringListModel *m_historyListModel; +}; + +#endif // HISTORYDOCKWIDGET_H
new file mode 100644 --- /dev/null +++ b/gui//src/ImageViewerMdiSubWindow.cpp @@ -0,0 +1,23 @@ +#include "ImageViewerMdiSubWindow.h" +#include <QLabel> +#include <QPixmap> +#include <QScrollArea> + +ImageViewerMdiSubWindow::ImageViewerMdiSubWindow(QPixmap pixmap, QWidget *parent) + : QMdiSubWindow(parent), + m_pixmap(pixmap) { + construct(); +} + +void ImageViewerMdiSubWindow::construct() { + QLabel *label = new QLabel(); + label->setBackgroundRole(QPalette::Base); + label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + label->setScaledContents(true); + label->setPixmap(m_pixmap); + + QScrollArea *scrollArea = new QScrollArea(this); + scrollArea->setBackgroundRole(QPalette::Dark); + scrollArea->setWidget(label); + setWidget(scrollArea); +}
new file mode 100644 --- /dev/null +++ b/gui//src/ImageViewerMdiSubWindow.h @@ -0,0 +1,16 @@ +#ifndef IMAGEVIEWERMDISUBWINDOW_H +#define IMAGEVIEWERMDISUBWINDOW_H + +#include <QMdiSubWindow> + +class ImageViewerMdiSubWindow : public QMdiSubWindow +{ +public: + ImageViewerMdiSubWindow(QPixmap pixmap, QWidget *parent = 0); + +private: + void construct(); + QPixmap m_pixmap; +}; + +#endif // IMAGEVIEWERMDISUBWINDOW_H
new file mode 100644 --- /dev/null +++ b/gui//src/KeyboardTranslator.cpp @@ -0,0 +1,970 @@ +/* + This source file is part of Konsole, a terminal emulator. + + Copyright 2007-2008 by Robert Knight <robertknight@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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "KeyboardTranslator.h" + +// System +#include <ctype.h> +#include <stdio.h> + +// Qt +#include <QtCore/QBuffer> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QTextStream> +#include <QtGui/QKeySequence> +#include <QtCore/QDir> +#include <QtCore/QStringList> +#include <QtCore/QDebug> +#include <QtCore/QDataStream> + +const QByteArray KeyboardTranslatorManager::defaultTranslatorText( +"keyboard \"Fallback Key Translator\"\n" +"key Tab : \"\\t\"" +); + +KeyboardTranslatorManager::KeyboardTranslatorManager() + : _haveLoadedAll(false) +{ +} +KeyboardTranslatorManager::~KeyboardTranslatorManager() +{ + qDeleteAll(_translators); +} +QString KeyboardTranslatorManager::findTranslatorPath(const QString& name) +{ + return QString("../kb-layouts/" + name + ".keytab"); + // return KGlobal::dirs()->findResource("data","konsole/"+name+".keytab"); +} +void KeyboardTranslatorManager::findTranslators() +{ + //QStringList list = KGlobal::dirs()->findAllResources("data", + // "konsole/*.keytab", + // KStandardDirs::NoDuplicates); + + QDir dir("../kb-layouts/"); + QStringList filters; + filters << "*.keytab"; + dir.setNameFilters(filters); + QStringList list = dir.entryList(filters); + + // add the name of each translator to the list and associated + // the name with a null pointer to indicate that the translator + // has not yet been loaded from disk + QStringListIterator listIter(list); + while (listIter.hasNext()) + { + QString translatorPath = listIter.next(); + + QString name = QFileInfo(translatorPath).baseName(); + + if ( !_translators.contains(name) ) + _translators.insert(name,0); + } + + _haveLoadedAll = true; +} + +const KeyboardTranslator* KeyboardTranslatorManager::findTranslator(const QString& name) +{ + if ( name.isEmpty() ) + return defaultTranslator(); + + if ( _translators.contains(name) && _translators[name] != 0 ) + return _translators[name]; + + KeyboardTranslator* translator = loadTranslator(name); + + if ( translator != 0 ) + _translators[name] = translator; + //else if ( !name.isEmpty() ) + // kWarning() << "Unable to load translator" << name; + + return translator; +} + +bool KeyboardTranslatorManager::saveTranslator(const KeyboardTranslator* translator) +{ + //const QString path = KGlobal::dirs()->saveLocation("data","konsole/")+translator->name() + // +".keytab"; + const QString path = ".keytab"; + + //kDebug() << "Saving translator to" << path; + + QFile destination(path); + if (!destination.open(QIODevice::WriteOnly | QIODevice::Text)) + { + //kWarning() << "Unable to save keyboard translation:" + // << destination.errorString(); + return false; + } + + { + KeyboardTranslatorWriter writer(&destination); + writer.writeHeader(translator->description()); + + QListIterator<KeyboardTranslator::Entry> iter(translator->entries()); + while ( iter.hasNext() ) + writer.writeEntry(iter.next()); + } + + destination.close(); + + return true; +} + +KeyboardTranslator* KeyboardTranslatorManager::loadTranslator(const QString& name) +{ + const QString& path = findTranslatorPath(name); + + QFile source(path); + if (name.isEmpty() || !source.open(QIODevice::ReadOnly | QIODevice::Text)) + return 0; + + return loadTranslator(&source,name); +} + +const KeyboardTranslator* KeyboardTranslatorManager::defaultTranslator() +{ + // Try to find the default.keytab file if it exists, otherwise + // fall back to the hard-coded one + const KeyboardTranslator* translator = findTranslator("default"); + if (!translator) + { + QBuffer textBuffer; + textBuffer.setData(defaultTranslatorText); + textBuffer.open(QIODevice::ReadOnly); + translator = loadTranslator(&textBuffer,"fallback"); + } + return translator; +} + +KeyboardTranslator* KeyboardTranslatorManager::loadTranslator(QIODevice* source,const QString& name) +{ + KeyboardTranslator* translator = new KeyboardTranslator(name); + KeyboardTranslatorReader reader(source); + translator->setDescription( reader.description() ); + while ( reader.hasNextEntry() ) + translator->addEntry(reader.nextEntry()); + + source->close(); + + if ( !reader.parseError() ) + { + return translator; + } + else + { + delete translator; + return 0; + } +} + +KeyboardTranslatorWriter::KeyboardTranslatorWriter(QIODevice* destination) +: _destination(destination) +{ + Q_ASSERT( destination && destination->isWritable() ); + + _writer = new QTextStream(_destination); +} +KeyboardTranslatorWriter::~KeyboardTranslatorWriter() +{ + delete _writer; +} +void KeyboardTranslatorWriter::writeHeader( const QString& description ) +{ + *_writer << "keyboard \"" << description << '\"' << '\n'; +} +void KeyboardTranslatorWriter::writeEntry( const KeyboardTranslator::Entry& entry ) +{ + QString result; + if ( entry.command() != KeyboardTranslator::NoCommand ) + result = entry.resultToString(); + else + result = '\"' + entry.resultToString() + '\"'; + + *_writer << "key " << entry.conditionToString() << " : " << result << '\n'; +} + + +// each line of the keyboard translation file is one of: +// +// - keyboard "name" +// - key KeySequence : "characters" +// - key KeySequence : CommandName +// +// KeySequence begins with the name of the key ( taken from the Qt::Key enum ) +// and is followed by the keyboard modifiers and state flags ( with + or - in front +// of each modifier or flag to indicate whether it is required ). All keyboard modifiers +// and flags are optional, if a particular modifier or state is not specified it is +// assumed not to be a part of the sequence. The key sequence may contain whitespace +// +// eg: "key Up+Shift : scrollLineUp" +// "key Next-Shift : "\E[6~" +// +// (lines containing only whitespace are ignored, parseLine assumes that comments have +// already been removed) +// + +KeyboardTranslatorReader::KeyboardTranslatorReader( QIODevice* source ) + : _source(source) + , _hasNext(false) +{ + // read input until we find the description + while ( _description.isEmpty() && !source->atEnd() ) + { + QList<Token> tokens = tokenize( QString(source->readLine()) ); + if ( !tokens.isEmpty() && tokens.first().type == Token::TitleKeyword ) + _description = QString(tokens[1].text.toLatin1().data()); + } + // read first entry (if any) + readNext(); +} +void KeyboardTranslatorReader::readNext() +{ + // find next entry + while ( !_source->atEnd() ) + { + const QList<Token>& tokens = tokenize( QString(_source->readLine()) ); + if ( !tokens.isEmpty() && tokens.first().type == Token::KeyKeyword ) + { + KeyboardTranslator::States flags = KeyboardTranslator::NoState; + KeyboardTranslator::States flagMask = KeyboardTranslator::NoState; + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + Qt::KeyboardModifiers modifierMask = Qt::NoModifier; + + int keyCode = Qt::Key_unknown; + + decodeSequence(tokens[1].text.toLower(), + keyCode, + modifiers, + modifierMask, + flags, + flagMask); + + KeyboardTranslator::Command command = KeyboardTranslator::NoCommand; + QByteArray text; + + // get text or command + if ( tokens[2].type == Token::OutputText ) + { + text = tokens[2].text.toLocal8Bit(); + } + else if ( tokens[2].type == Token::Command ) + { + // identify command + // if (!parseAsCommand(tokens[2].text,command)) + // kWarning() << "Command" << tokens[2].text << "not understood."; + } + + KeyboardTranslator::Entry newEntry; + newEntry.setKeyCode( keyCode ); + newEntry.setState( flags ); + newEntry.setStateMask( flagMask ); + newEntry.setModifiers( modifiers ); + newEntry.setModifierMask( modifierMask ); + newEntry.setText( text ); + newEntry.setCommand( command ); + + _nextEntry = newEntry; + + _hasNext = true; + + return; + } + } + + _hasNext = false; +} + +bool KeyboardTranslatorReader::parseAsCommand(const QString& text,KeyboardTranslator::Command& command) +{ + if ( text.compare("erase",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::EraseCommand; + else if ( text.compare("scrollpageup",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollPageUpCommand; + else if ( text.compare("scrollpagedown",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollPageDownCommand; + else if ( text.compare("scrolllineup",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollLineUpCommand; + else if ( text.compare("scrolllinedown",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollLineDownCommand; + else if ( text.compare("scrolllock",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollLockCommand; + else + return false; + + return true; +} + +bool KeyboardTranslatorReader::decodeSequence(const QString& text, + int& keyCode, + Qt::KeyboardModifiers& modifiers, + Qt::KeyboardModifiers& modifierMask, + KeyboardTranslator::States& flags, + KeyboardTranslator::States& flagMask) +{ + bool isWanted = true; + bool endOfItem = false; + QString buffer; + + Qt::KeyboardModifiers tempModifiers = modifiers; + Qt::KeyboardModifiers tempModifierMask = modifierMask; + KeyboardTranslator::States tempFlags = flags; + KeyboardTranslator::States tempFlagMask = flagMask; + + for ( int i = 0 ; i < text.count() ; i++ ) + { + const QChar& ch = text[i]; + bool isFirstLetter = i == 0; + bool isLastLetter = ( i == text.count()-1 ); + endOfItem = true; + if ( ch.isLetterOrNumber() ) + { + endOfItem = false; + buffer.append(ch); + } else if ( isFirstLetter ) + { + buffer.append(ch); + } + + if ( (endOfItem || isLastLetter) && !buffer.isEmpty() ) + { + Qt::KeyboardModifier itemModifier = Qt::NoModifier; + int itemKeyCode = 0; + KeyboardTranslator::State itemFlag = KeyboardTranslator::NoState; + + if ( parseAsModifier(buffer,itemModifier) ) + { + tempModifierMask |= itemModifier; + + if ( isWanted ) + tempModifiers |= itemModifier; + } + else if ( parseAsStateFlag(buffer,itemFlag) ) + { + tempFlagMask |= itemFlag; + + if ( isWanted ) + tempFlags |= itemFlag; + } + else if ( parseAsKeyCode(buffer,itemKeyCode) ) + keyCode = itemKeyCode; + //else + // kWarning() << "Unable to parse key binding item:" << buffer; + + buffer.clear(); + } + + // check if this is a wanted / not-wanted flag and update the + // state ready for the next item + if ( ch == '+' ) + isWanted = true; + else if ( ch == '-' ) + isWanted = false; + } + + modifiers = tempModifiers; + modifierMask = tempModifierMask; + flags = tempFlags; + flagMask = tempFlagMask; + + return true; +} + +bool KeyboardTranslatorReader::parseAsModifier(const QString& item , Qt::KeyboardModifier& modifier) +{ + if ( item == "shift" ) + modifier = Qt::ShiftModifier; + else if ( item == "ctrl" || item == "control" ) + modifier = Qt::ControlModifier; + else if ( item == "alt" ) + modifier = Qt::AltModifier; + else if ( item == "meta" ) + modifier = Qt::MetaModifier; + else if ( item == "keypad" ) + modifier = Qt::KeypadModifier; + else + return false; + + return true; +} +bool KeyboardTranslatorReader::parseAsStateFlag(const QString& item , KeyboardTranslator::State& flag) +{ + if ( item == "appcukeys" || item == "appcursorkeys" ) + flag = KeyboardTranslator::CursorKeysState; + else if ( item == "ansi" ) + flag = KeyboardTranslator::AnsiState; + else if ( item == "newline" ) + flag = KeyboardTranslator::NewLineState; + else if ( item == "appscreen" ) + flag = KeyboardTranslator::AlternateScreenState; + else if ( item == "anymod" || item == "anymodifier" ) + flag = KeyboardTranslator::AnyModifierState; + else if ( item == "appkeypad" ) + flag = KeyboardTranslator::ApplicationKeypadState; + else + return false; + + return true; +} +bool KeyboardTranslatorReader::parseAsKeyCode(const QString& item , int& keyCode) +{ + QKeySequence sequence = QKeySequence::fromString(item); + if ( !sequence.isEmpty() ) + { + keyCode = sequence[0]; + + if ( sequence.count() > 1 ) + { + //kWarning() << "Unhandled key codes in sequence: " << item; + } + } + // additional cases implemented for backwards compatibility with KDE 3 + else if ( item == "prior" ) + keyCode = Qt::Key_PageUp; + else if ( item == "next" ) + keyCode = Qt::Key_PageDown; + else + return false; + + return true; +} + +QString KeyboardTranslatorReader::description() const +{ + return _description; +} +bool KeyboardTranslatorReader::hasNextEntry() +{ + return _hasNext; +} +KeyboardTranslator::Entry KeyboardTranslatorReader::createEntry( const QString& condition , + const QString& result ) +{ + QString entryString("keyboard \"temporary\"\nkey "); + entryString.append(condition); + entryString.append(" : "); + + // if 'result' is the name of a command then the entry result will be that command, + // otherwise the result will be treated as a string to echo when the key sequence + // specified by 'condition' is pressed + KeyboardTranslator::Command command; + if (parseAsCommand(result,command)) + entryString.append(result); + else + entryString.append('\"' + result + '\"'); + + QByteArray array = entryString.toUtf8(); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); + KeyboardTranslatorReader reader(&buffer); + + KeyboardTranslator::Entry entry; + if ( reader.hasNextEntry() ) + entry = reader.nextEntry(); + + return entry; +} + +KeyboardTranslator::Entry KeyboardTranslatorReader::nextEntry() +{ + Q_ASSERT( _hasNext ); + KeyboardTranslator::Entry entry = _nextEntry; + readNext(); + return entry; +} +bool KeyboardTranslatorReader::parseError() +{ + return false; +} +QList<KeyboardTranslatorReader::Token> KeyboardTranslatorReader::tokenize(const QString& line) +{ + QString text = line; + + // remove comments + bool inQuotes = false; + int commentPos = -1; + for (int i=text.length()-1;i>=0;i--) + { + QChar ch = text[i]; + if (ch == '\"') + inQuotes = !inQuotes; + else if (ch == '#' && !inQuotes) + commentPos = i; + } + if (commentPos != -1) + text.remove(commentPos,text.length()); + + text = text.simplified(); + + // title line: keyboard "title" + static QRegExp title("keyboard\\s+\"(.*)\""); + // key line: key KeySequence : "output" + // key line: key KeySequence : command + static QRegExp key("key\\s+([\\w\\+\\s\\-\\*\\.]+)\\s*:\\s*(\"(.*)\"|\\w+)"); + + QList<Token> list; + if ( text.isEmpty() ) + { + return list; + } + + if ( title.exactMatch(text) ) + { + Token titleToken = { Token::TitleKeyword , QString() }; + Token textToken = { Token::TitleText , title.capturedTexts()[1] }; + + list << titleToken << textToken; + } + else if ( key.exactMatch(text) ) + { + Token keyToken = { Token::KeyKeyword , QString() }; + Token sequenceToken = { Token::KeySequence , key.capturedTexts()[1].remove(' ') }; + + list << keyToken << sequenceToken; + + if ( key.capturedTexts()[3].isEmpty() ) + { + // capturedTexts()[2] is a command + Token commandToken = { Token::Command , key.capturedTexts()[2] }; + list << commandToken; + } + else + { + // capturedTexts()[3] is the output string + Token outputToken = { Token::OutputText , key.capturedTexts()[3] }; + list << outputToken; + } + } + else + { + //kWarning() << "Line in keyboard translator file could not be understood:" << text; + } + + return list; +} + +QList<QString> KeyboardTranslatorManager::allTranslators() +{ + if ( !_haveLoadedAll ) + { + findTranslators(); + } + + return _translators.keys(); +} + +KeyboardTranslator::Entry::Entry() +: _keyCode(0) +, _modifiers(Qt::NoModifier) +, _modifierMask(Qt::NoModifier) +, _state(NoState) +, _stateMask(NoState) +, _command(NoCommand) +{ +} + +bool KeyboardTranslator::Entry::operator==(const Entry& rhs) const +{ + return _keyCode == rhs._keyCode && + _modifiers == rhs._modifiers && + _modifierMask == rhs._modifierMask && + _state == rhs._state && + _stateMask == rhs._stateMask && + _command == rhs._command && + _text == rhs._text; +} + +bool KeyboardTranslator::Entry::matches(int keyCode , + Qt::KeyboardModifiers modifiers, + States testState) const +{ + if ( _keyCode != keyCode ) + return false; + + if ( (modifiers & _modifierMask) != (_modifiers & _modifierMask) ) + return false; + + // if modifiers is non-zero, the 'any modifier' state is implicit + if ( modifiers != 0 ) + testState |= AnyModifierState; + + if ( (testState & _stateMask) != (_state & _stateMask) ) + return false; + + // special handling for the 'Any Modifier' state, which checks for the presence of + // any or no modifiers. In this context, the 'keypad' modifier does not count. + bool anyModifiersSet = modifiers != 0 && modifiers != Qt::KeypadModifier; + bool wantAnyModifier = _state & KeyboardTranslator::AnyModifierState; + if ( _stateMask & KeyboardTranslator::AnyModifierState ) + { + if ( wantAnyModifier != anyModifiersSet ) + return false; + } + + return true; +} +QByteArray KeyboardTranslator::Entry::escapedText(bool expandWildCards,Qt::KeyboardModifiers modifiers) const +{ + QByteArray result(text(expandWildCards,modifiers)); + + for ( int i = 0 ; i < result.count() ; i++ ) + { + char ch = result[i]; + char replacement = 0; + + switch ( ch ) + { + case 27 : replacement = 'E'; break; + case 8 : replacement = 'b'; break; + case 12 : replacement = 'f'; break; + case 9 : replacement = 't'; break; + case 13 : replacement = 'r'; break; + case 10 : replacement = 'n'; break; + default: + // any character which is not printable is replaced by an equivalent + // \xhh escape sequence (where 'hh' are the corresponding hex digits) + if ( !QChar(ch).isPrint() ) + replacement = 'x'; + } + + if ( replacement == 'x' ) + { + result.replace(i,1,"\\x"+QByteArray(1,ch).toHex()); + } else if ( replacement != 0 ) + { + result.remove(i,1); + result.insert(i,'\\'); + result.insert(i+1,replacement); + } + } + + return result; +} +QByteArray KeyboardTranslator::Entry::unescape(const QByteArray& input) const +{ + QByteArray result(input); + + for ( int i = 0 ; i < result.count()-1 ; i++ ) + { + + QByteRef ch = result[i]; + if ( ch == '\\' ) + { + char replacement[2] = {0,0}; + int charsToRemove = 2; + bool escapedChar = true; + + switch ( result[i+1] ) + { + case 'E' : replacement[0] = 27; break; + case 'b' : replacement[0] = 8 ; break; + case 'f' : replacement[0] = 12; break; + case 't' : replacement[0] = 9 ; break; + case 'r' : replacement[0] = 13; break; + case 'n' : replacement[0] = 10; break; + case 'x' : + { + // format is \xh or \xhh where 'h' is a hexadecimal + // digit from 0-9 or A-F which should be replaced + // with the corresponding character value + char hexDigits[3] = {0}; + + if ( (i < result.count()-2) && isxdigit(result[i+2]) ) + hexDigits[0] = result[i+2]; + if ( (i < result.count()-3) && isxdigit(result[i+3]) ) + hexDigits[1] = result[i+3]; + + unsigned charValue = 0; + sscanf(hexDigits,"%x",&charValue); + + replacement[0] = (char)charValue; + charsToRemove = 2 + strlen(hexDigits); + } + break; + default: + escapedChar = false; + } + + if ( escapedChar ) + result.replace(i,charsToRemove,replacement); + } + } + + return result; +} + +void KeyboardTranslator::Entry::insertModifier( QString& item , int modifier ) const +{ + if ( !(modifier & _modifierMask) ) + return; + + if ( modifier & _modifiers ) + item += '+'; + else + item += '-'; + + if ( modifier == Qt::ShiftModifier ) + item += "Shift"; + else if ( modifier == Qt::ControlModifier ) + item += "Ctrl"; + else if ( modifier == Qt::AltModifier ) + item += "Alt"; + else if ( modifier == Qt::MetaModifier ) + item += "Meta"; + else if ( modifier == Qt::KeypadModifier ) + item += "KeyPad"; +} +void KeyboardTranslator::Entry::insertState( QString& item , int state ) const +{ + if ( !(state & _stateMask) ) + return; + + if ( state & _state ) + item += '+' ; + else + item += '-' ; + + if ( state == KeyboardTranslator::AlternateScreenState ) + item += "AppScreen"; + else if ( state == KeyboardTranslator::NewLineState ) + item += "NewLine"; + else if ( state == KeyboardTranslator::AnsiState ) + item += "Ansi"; + else if ( state == KeyboardTranslator::CursorKeysState ) + item += "AppCursorKeys"; + else if ( state == KeyboardTranslator::AnyModifierState ) + item += "AnyModifier"; + else if ( state == KeyboardTranslator::ApplicationKeypadState ) + item += "AppKeypad"; +} +QString KeyboardTranslator::Entry::resultToString(bool expandWildCards,Qt::KeyboardModifiers modifiers) const +{ + if ( !_text.isEmpty() ) + return escapedText(expandWildCards,modifiers); + else if ( _command == EraseCommand ) + return "Erase"; + else if ( _command == ScrollPageUpCommand ) + return "ScrollPageUp"; + else if ( _command == ScrollPageDownCommand ) + return "ScrollPageDown"; + else if ( _command == ScrollLineUpCommand ) + return "ScrollLineUp"; + else if ( _command == ScrollLineDownCommand ) + return "ScrollLineDown"; + else if ( _command == ScrollLockCommand ) + return "ScrollLock"; + + return QString(); +} +QString KeyboardTranslator::Entry::conditionToString() const +{ + QString result = QKeySequence(_keyCode).toString(); + + insertModifier( result , Qt::ShiftModifier ); + insertModifier( result , Qt::ControlModifier ); + insertModifier( result , Qt::AltModifier ); + insertModifier( result , Qt::MetaModifier ); + insertModifier( result , Qt::KeypadModifier ); + + insertState( result , KeyboardTranslator::AlternateScreenState ); + insertState( result , KeyboardTranslator::NewLineState ); + insertState( result , KeyboardTranslator::AnsiState ); + insertState( result , KeyboardTranslator::CursorKeysState ); + insertState( result , KeyboardTranslator::AnyModifierState ); + insertState( result , KeyboardTranslator::ApplicationKeypadState ); + + return result; +} + +KeyboardTranslator::KeyboardTranslator(const QString& name) +: _name(name) +{ +} + +void KeyboardTranslator::setDescription(const QString& description) +{ + _description = description; +} +QString KeyboardTranslator::description() const +{ + return _description; +} +void KeyboardTranslator::setName(const QString& name) +{ + _name = name; +} +QString KeyboardTranslator::name() const +{ + return _name; +} + +QList<KeyboardTranslator::Entry> KeyboardTranslator::entries() const +{ + return _entries.values(); +} + +void KeyboardTranslator::addEntry(const Entry& entry) +{ + const int keyCode = entry.keyCode(); + _entries.insert(keyCode,entry); +} +void KeyboardTranslator::replaceEntry(const Entry& existing , const Entry& replacement) +{ + if ( !existing.isNull() ) + _entries.remove(existing.keyCode(),existing); + _entries.insert(replacement.keyCode(),replacement); +} +void KeyboardTranslator::removeEntry(const Entry& entry) +{ + _entries.remove(entry.keyCode(),entry); +} +KeyboardTranslator::Entry KeyboardTranslator::findEntry(int keyCode, Qt::KeyboardModifiers modifiers, States state) const +{ + foreach(const Entry& entry, _entries.values(keyCode)) + { + if ( entry.matches(keyCode,modifiers,state) ) + return entry; + } + return Entry(); // entry not found +} +void KeyboardTranslatorManager::addTranslator(KeyboardTranslator* translator) +{ + _translators.insert(translator->name(),translator); + + // if ( !saveTranslator(translator) ) + // kWarning() << "Unable to save translator" << translator->name() + // << "to disk."; +} +bool KeyboardTranslatorManager::deleteTranslator(const QString& name) +{ + Q_ASSERT( _translators.contains(name) ); + + // locate and delete + QString path = findTranslatorPath(name); + if ( QFile::remove(path) ) + { + _translators.remove(name); + return true; + } + else + { + //kWarning() << "Failed to remove translator - " << path; + return false; + } +} + +/** + * @internal + */ +typedef void (*KdeCleanUpFunction)(); + +/** + * @internal + * + * Helper class for K_GLOBAL_STATIC to clean up the object on library unload or application + * shutdown. + */ +class KCleanUpGlobalStatic +{ + public: + KdeCleanUpFunction func; + + inline ~KCleanUpGlobalStatic() { func(); } +}; + + + +#ifdef Q_CC_MSVC +/** + * @internal + * + * MSVC seems to give anonymous structs the same name which fails at link time. So instead we name + * the struct and hope that by adding the line number to the name it's unique enough to never clash. + */ +# define K_GLOBAL_STATIC_STRUCT_NAME(NAME) _k_##NAME##__LINE__ +#else +/** + * @internal + * + * Make the struct of the K_GLOBAL_STATIC anonymous. + */ +# define K_GLOBAL_STATIC_STRUCT_NAME(NAME) +#endif + + + +#define K_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \ +static QBasicAtomicPointer<TYPE > _k_static_##NAME = Q_BASIC_ATOMIC_INITIALIZER(0); \ +static bool _k_static_##NAME##_destroyed; \ +static struct K_GLOBAL_STATIC_STRUCT_NAME(NAME) \ +{ \ + inline bool isDestroyed() const \ + { \ + return _k_static_##NAME##_destroyed; \ + } \ + inline bool exists() const \ + { \ + return _k_static_##NAME != 0; \ + } \ + inline operator TYPE*() \ + { \ + return operator->(); \ + } \ + inline TYPE *operator->() \ + { \ + if (!_k_static_##NAME) { \ + if (isDestroyed()) { \ + qFatal("Fatal Error: Accessed global static '%s *%s()' after destruction. " \ + "Defined at %s:%d", #TYPE, #NAME, __FILE__, __LINE__); \ + } \ + TYPE *x = new TYPE ARGS; \ + if (!_k_static_##NAME.testAndSetOrdered(0, x) \ + && _k_static_##NAME != x ) { \ + delete x; \ + } else { \ + static KCleanUpGlobalStatic cleanUpObject = { destroy }; \ + } \ + } \ + return _k_static_##NAME; \ + } \ + inline TYPE &operator*() \ + { \ + return *operator->(); \ + } \ + static void destroy() \ + { \ + _k_static_##NAME##_destroyed = true; \ + TYPE *x = _k_static_##NAME; \ + _k_static_##NAME = 0; \ + delete x; \ + } \ +} NAME; + +#define K_GLOBAL_STATIC(TYPE, NAME) K_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ()) + +K_GLOBAL_STATIC( KeyboardTranslatorManager , theKeyboardTranslatorManager ) +KeyboardTranslatorManager* KeyboardTranslatorManager::instance() +{ + return theKeyboardTranslatorManager; +}
new file mode 100644 --- /dev/null +++ b/gui//src/KeyboardTranslator.h @@ -0,0 +1,577 @@ +/* + This source file is part of Konsole, a terminal emulator. + + Copyright 2007-2008 by Robert Knight <robertknight@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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef KEYBOARDTRANSLATOR_H +#define KEYBOARDTRANSLATOR_H + +// Qt +#include <QtCore/QHash> +#include <QtCore/QList> +#include <QtGui/QKeySequence> +#include <QtCore/QMetaType> +#include <QtCore/QVarLengthArray> + +class QIODevice; +class QTextStream; + +/** + * A convertor which maps between key sequences pressed by the user and the + * character strings which should be sent to the terminal and commands + * which should be invoked when those character sequences are pressed. + * + * Konsole supports multiple keyboard translators, allowing the user to + * specify the character sequences which are sent to the terminal + * when particular key sequences are pressed. + * + * A key sequence is defined as a key code, associated keyboard modifiers + * (Shift,Ctrl,Alt,Meta etc.) and state flags which indicate the state + * which the terminal must be in for the key sequence to apply. + */ +class KeyboardTranslator +{ +public: + /** + * The meaning of a particular key sequence may depend upon the state which + * the terminal emulation is in. Therefore findEntry() may return a different + * Entry depending upon the state flags supplied. + * + * This enum describes the states which may be associated with with a particular + * entry in the keyboard translation entry. + */ + enum State + { + /** Indicates that no special state is active */ + NoState = 0, + /** + * TODO More documentation + */ + NewLineState = 1, + /** + * Indicates that the terminal is in 'Ansi' mode. + * TODO: More documentation + */ + AnsiState = 2, + /** + * TODO More documentation + */ + CursorKeysState = 4, + /** + * Indicates that the alternate screen ( typically used by interactive programs + * such as screen or vim ) is active + */ + AlternateScreenState = 8, + /** Indicates that any of the modifier keys is active. */ + AnyModifierState = 16, + /** Indicates that the numpad is in application mode. */ + ApplicationKeypadState = 32 + }; + Q_DECLARE_FLAGS(States,State) + + /** + * This enum describes commands which are associated with particular key sequences. + */ + enum Command + { + /** Indicates that no command is associated with this command sequence */ + NoCommand = 0, + /** TODO Document me */ + SendCommand = 1, + /** Scroll the terminal display up one page */ + ScrollPageUpCommand = 2, + /** Scroll the terminal display down one page */ + ScrollPageDownCommand = 4, + /** Scroll the terminal display up one line */ + ScrollLineUpCommand = 8, + /** Scroll the terminal display down one line */ + ScrollLineDownCommand = 16, + /** Toggles scroll lock mode */ + ScrollLockCommand = 32, + /** Echos the operating system specific erase character. */ + EraseCommand = 64 + }; + Q_DECLARE_FLAGS(Commands,Command) + + /** + * Represents an association between a key sequence pressed by the user + * and the character sequence and commands associated with it for a particular + * KeyboardTranslator. + */ + class Entry + { + public: + /** + * Constructs a new entry for a keyboard translator. + */ + Entry(); + + /** + * Returns true if this entry is null. + * This is true for newly constructed entries which have no properties set. + */ + bool isNull() const; + + /** Returns the commands associated with this entry */ + Command command() const; + /** Sets the command associated with this entry. */ + void setCommand(Command command); + + /** + * Returns the character sequence associated with this entry, optionally replacing + * wildcard '*' characters with numbers to indicate the keyboard modifiers being pressed. + * + * TODO: The numbers used to replace '*' characters are taken from the Konsole/KDE 3 code. + * Document them. + * + * @param expandWildCards Specifies whether wild cards (occurrences of the '*' character) in + * the entry should be replaced with a number to indicate the modifier keys being pressed. + * + * @param modifiers The keyboard modifiers being pressed. + */ + QByteArray text(bool expandWildCards = false, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; + + /** Sets the character sequence associated with this entry */ + void setText(const QByteArray& text); + + /** + * Returns the character sequence associated with this entry, + * with any non-printable characters replaced with escape sequences. + * + * eg. \\E for Escape, \\t for tab, \\n for new line. + * + * @param expandWildCards See text() + * @param modifiers See text() + */ + QByteArray escapedText(bool expandWildCards = false, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; + + /** Returns the character code ( from the Qt::Key enum ) associated with this entry */ + int keyCode() const; + /** Sets the character code associated with this entry */ + void setKeyCode(int keyCode); + + /** + * Returns a bitwise-OR of the enabled keyboard modifiers associated with this entry. + * If a modifier is set in modifierMask() but not in modifiers(), this means that the entry + * only matches when that modifier is NOT pressed. + * + * If a modifier is not set in modifierMask() then the entry matches whether the modifier + * is pressed or not. + */ + Qt::KeyboardModifiers modifiers() const; + + /** Returns the keyboard modifiers which are valid in this entry. See modifiers() */ + Qt::KeyboardModifiers modifierMask() const; + + /** See modifiers() */ + void setModifiers( Qt::KeyboardModifiers modifiers ); + /** See modifierMask() and modifiers() */ + void setModifierMask( Qt::KeyboardModifiers modifiers ); + + /** + * Returns a bitwise-OR of the enabled state flags associated with this entry. + * If flag is set in stateMask() but not in state(), this means that the entry only + * matches when the terminal is NOT in that state. + * + * If a state is not set in stateMask() then the entry matches whether the terminal + * is in that state or not. + */ + States state() const; + + /** Returns the state flags which are valid in this entry. See state() */ + States stateMask() const; + + /** See state() */ + void setState( States state ); + /** See stateMask() */ + void setStateMask( States mask ); + + /** + * Returns the key code and modifiers associated with this entry + * as a QKeySequence + */ + //QKeySequence keySequence() const; + + /** + * Returns this entry's conditions ( ie. its key code, modifier and state criteria ) + * as a string. + */ + QString conditionToString() const; + + /** + * Returns this entry's result ( ie. its command or character sequence ) + * as a string. + * + * @param expandWildCards See text() + * @param modifiers See text() + */ + QString resultToString(bool expandWildCards = false, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; + + /** + * Returns true if this entry matches the given key sequence, specified + * as a combination of @p keyCode , @p modifiers and @p state. + */ + bool matches( int keyCode , + Qt::KeyboardModifiers modifiers , + States flags ) const; + + bool operator==(const Entry& rhs) const; + + private: + void insertModifier( QString& item , int modifier ) const; + void insertState( QString& item , int state ) const; + QByteArray unescape(const QByteArray& text) const; + + int _keyCode; + Qt::KeyboardModifiers _modifiers; + Qt::KeyboardModifiers _modifierMask; + States _state; + States _stateMask; + + Command _command; + QByteArray _text; + }; + + /** Constructs a new keyboard translator with the given @p name */ + KeyboardTranslator(const QString& name); + + //KeyboardTranslator(const KeyboardTranslator& other); + + /** Returns the name of this keyboard translator */ + QString name() const; + + /** Sets the name of this keyboard translator */ + void setName(const QString& name); + + /** Returns the descriptive name of this keyboard translator */ + QString description() const; + + /** Sets the descriptive name of this keyboard translator */ + void setDescription(const QString& description); + + /** + * Looks for an entry in this keyboard translator which matches the given + * key code, keyboard modifiers and state flags. + * + * Returns the matching entry if found or a null Entry otherwise ( ie. + * entry.isNull() will return true ) + * + * @param keyCode A key code from the Qt::Key enum + * @param modifiers A combination of modifiers + * @param state Optional flags which specify the current state of the terminal + */ + Entry findEntry(int keyCode , + Qt::KeyboardModifiers modifiers , + States state = NoState) const; + + /** + * Adds an entry to this keyboard translator's table. Entries can be looked up according + * to their key sequence using findEntry() + */ + void addEntry(const Entry& entry); + + /** + * Replaces an entry in the translator. If the @p existing entry is null, + * then this is equivalent to calling addEntry(@p replacement) + */ + void replaceEntry(const Entry& existing , const Entry& replacement); + + /** + * Removes an entry from the table. + */ + void removeEntry(const Entry& entry); + + /** Returns a list of all entries in the translator. */ + QList<Entry> entries() const; + +private: + + QMultiHash<int,Entry> _entries; // entries in this keyboard translation, + // entries are indexed according to + // their keycode + QString _name; + QString _description; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(KeyboardTranslator::States) +Q_DECLARE_OPERATORS_FOR_FLAGS(KeyboardTranslator::Commands) + +/** + * Parses the contents of a Keyboard Translator (.keytab) file and + * returns the entries found in it. + * + * Usage example: + * + * @code + * QFile source( "/path/to/keytab" ); + * source.open( QIODevice::ReadOnly ); + * + * KeyboardTranslator* translator = new KeyboardTranslator( "name-of-translator" ); + * + * KeyboardTranslatorReader reader(source); + * while ( reader.hasNextEntry() ) + * translator->addEntry(reader.nextEntry()); + * + * source.close(); + * + * if ( !reader.parseError() ) + * { + * // parsing succeeded, do something with the translator + * } + * else + * { + * // parsing failed + * } + * @endcode + */ +class KeyboardTranslatorReader +{ +public: + /** Constructs a new reader which parses the given @p source */ + KeyboardTranslatorReader( QIODevice* source ); + + /** + * Returns the description text. + * TODO: More documentation + */ + QString description() const; + + /** Returns true if there is another entry in the source stream */ + bool hasNextEntry(); + /** Returns the next entry found in the source stream */ + KeyboardTranslator::Entry nextEntry(); + + /** + * Returns true if an error occurred whilst parsing the input or + * false if no error occurred. + */ + bool parseError(); + + /** + * Parses a condition and result string for a translator entry + * and produces a keyboard translator entry. + * + * The condition and result strings are in the same format as in + */ + static KeyboardTranslator::Entry createEntry( const QString& condition , + const QString& result ); +private: + struct Token + { + enum Type + { + TitleKeyword, + TitleText, + KeyKeyword, + KeySequence, + Command, + OutputText + }; + Type type; + QString text; + }; + QList<Token> tokenize(const QString&); + void readNext(); + bool decodeSequence(const QString& , + int& keyCode, + Qt::KeyboardModifiers& modifiers, + Qt::KeyboardModifiers& modifierMask, + KeyboardTranslator::States& state, + KeyboardTranslator::States& stateFlags); + + static bool parseAsModifier(const QString& item , Qt::KeyboardModifier& modifier); + static bool parseAsStateFlag(const QString& item , KeyboardTranslator::State& state); + static bool parseAsKeyCode(const QString& item , int& keyCode); + static bool parseAsCommand(const QString& text , KeyboardTranslator::Command& command); + + QIODevice* _source; + QString _description; + KeyboardTranslator::Entry _nextEntry; + bool _hasNext; +}; + +/** Writes a keyboard translation to disk. */ +class KeyboardTranslatorWriter +{ +public: + /** + * Constructs a new writer which saves data into @p destination. + * The caller is responsible for closing the device when writing is complete. + */ + KeyboardTranslatorWriter(QIODevice* destination); + ~KeyboardTranslatorWriter(); + + /** + * Writes the header for the keyboard translator. + * @param description Description of the keyboard translator. + */ + void writeHeader( const QString& description ); + /** Writes a translator entry. */ + void writeEntry( const KeyboardTranslator::Entry& entry ); + +private: + QIODevice* _destination; + QTextStream* _writer; +}; + +/** + * Manages the keyboard translations available for use by terminal sessions, + * see KeyboardTranslator. + */ +class KeyboardTranslatorManager +{ +public: + /** + * Constructs a new KeyboardTranslatorManager and loads the list of + * available keyboard translations. + * + * The keyboard translations themselves are not loaded until they are + * first requested via a call to findTranslator() + */ + KeyboardTranslatorManager(); + ~KeyboardTranslatorManager(); + + /** + * Adds a new translator. If a translator with the same name + * already exists, it will be replaced by the new translator. + * + * TODO: More documentation. + */ + void addTranslator(KeyboardTranslator* translator); + + /** + * Deletes a translator. Returns true on successful deletion or false otherwise. + * + * TODO: More documentation + */ + bool deleteTranslator(const QString& name); + + /** Returns the default translator for Konsole. */ + const KeyboardTranslator* defaultTranslator(); + + /** + * Returns the keyboard translator with the given name or 0 if no translator + * with that name exists. + * + * The first time that a translator with a particular name is requested, + * the on-disk .keyboard file is loaded and parsed. + */ + const KeyboardTranslator* findTranslator(const QString& name); + /** + * Returns a list of the names of available keyboard translators. + * + * The first time this is called, a search for available + * translators is started. + */ + QList<QString> allTranslators(); + + /** Returns the global KeyboardTranslatorManager instance. */ + static KeyboardTranslatorManager* instance(); + +private: + static const QByteArray defaultTranslatorText; + + void findTranslators(); // locate the available translators + KeyboardTranslator* loadTranslator(const QString& name); // loads the translator + // with the given name + KeyboardTranslator* loadTranslator(QIODevice* device,const QString& name); + + bool saveTranslator(const KeyboardTranslator* translator); + QString findTranslatorPath(const QString& name); + + QHash<QString,KeyboardTranslator*> _translators; // maps translator-name -> KeyboardTranslator + // instance + bool _haveLoadedAll; +}; + +inline int KeyboardTranslator::Entry::keyCode() const { return _keyCode; } +inline void KeyboardTranslator::Entry::setKeyCode(int keyCode) { _keyCode = keyCode; } + +inline void KeyboardTranslator::Entry::setModifiers( Qt::KeyboardModifiers modifier ) +{ + _modifiers = modifier; +} +inline Qt::KeyboardModifiers KeyboardTranslator::Entry::modifiers() const { return _modifiers; } + +inline void KeyboardTranslator::Entry::setModifierMask( Qt::KeyboardModifiers mask ) +{ + _modifierMask = mask; +} +inline Qt::KeyboardModifiers KeyboardTranslator::Entry::modifierMask() const { return _modifierMask; } + +inline bool KeyboardTranslator::Entry::isNull() const +{ + return ( *this == Entry() ); +} + +inline void KeyboardTranslator::Entry::setCommand( Command command ) +{ + _command = command; +} +inline KeyboardTranslator::Command KeyboardTranslator::Entry::command() const { return _command; } + +inline void KeyboardTranslator::Entry::setText( const QByteArray& text ) +{ + _text = unescape(text); +} +inline int oneOrZero(int value) +{ + return value ? 1 : 0; +} +inline QByteArray KeyboardTranslator::Entry::text(bool expandWildCards,Qt::KeyboardModifiers modifiers) const +{ + QByteArray expandedText = _text; + + if (expandWildCards) + { + int modifierValue = 1; + modifierValue += oneOrZero(modifiers & Qt::ShiftModifier); + modifierValue += oneOrZero(modifiers & Qt::AltModifier) << 1; + modifierValue += oneOrZero(modifiers & Qt::ControlModifier) << 2; + + for (int i=0;i<_text.length();i++) + { + if (expandedText[i] == '*') + expandedText[i] = '0' + modifierValue; + } + } + + return expandedText; +} + +inline void KeyboardTranslator::Entry::setState( States state ) +{ + _state = state; +} +inline KeyboardTranslator::States KeyboardTranslator::Entry::state() const { return _state; } + +inline void KeyboardTranslator::Entry::setStateMask( States stateMask ) +{ + _stateMask = stateMask; +} +inline KeyboardTranslator::States KeyboardTranslator::Entry::stateMask() const { return _stateMask; } + + +Q_DECLARE_METATYPE(KeyboardTranslator::Entry) +Q_DECLARE_METATYPE(const KeyboardTranslator*) + +#endif // KEYBOARDTRANSLATOR_H +
new file mode 100644 --- /dev/null +++ b/gui//src/LineFont.h @@ -0,0 +1,21 @@ +// WARNING: Autogenerated by "fontembedder ./linefont.src". +// You probably do not want to hand-edit this! + +static const quint32 LineChars[] = { + 0x00007c00, 0x000fffe0, 0x00421084, 0x00e739ce, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00427000, 0x004e7380, 0x00e77800, 0x00ef7bc0, + 0x00421c00, 0x00439ce0, 0x00e73c00, 0x00e7bde0, 0x00007084, 0x000e7384, 0x000079ce, 0x000f7bce, + 0x00001c84, 0x00039ce4, 0x00003dce, 0x0007bdee, 0x00427084, 0x004e7384, 0x004279ce, 0x00e77884, + 0x00e779ce, 0x004f7bce, 0x00ef7bc4, 0x00ef7bce, 0x00421c84, 0x00439ce4, 0x00423dce, 0x00e73c84, + 0x00e73dce, 0x0047bdee, 0x00e7bde4, 0x00e7bdee, 0x00427c00, 0x0043fce0, 0x004e7f80, 0x004fffe0, + 0x004fffe0, 0x00e7fde0, 0x006f7fc0, 0x00efffe0, 0x00007c84, 0x0003fce4, 0x000e7f84, 0x000fffe4, + 0x00007dce, 0x0007fdee, 0x000f7fce, 0x000fffee, 0x00427c84, 0x0043fce4, 0x004e7f84, 0x004fffe4, + 0x00427dce, 0x00e77c84, 0x00e77dce, 0x0047fdee, 0x004e7fce, 0x00e7fde4, 0x00ef7f84, 0x004fffee, + 0x00efffe4, 0x00e7fdee, 0x00ef7fce, 0x00efffee, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x000f83e0, 0x00a5294a, 0x004e1380, 0x00a57800, 0x00ad0bc0, 0x004390e0, 0x00a53c00, 0x00a5a1e0, + 0x000e1384, 0x0000794a, 0x000f0b4a, 0x000390e4, 0x00003d4a, 0x0007a16a, 0x004e1384, 0x00a5694a, + 0x00ad2b4a, 0x004390e4, 0x00a52d4a, 0x00a5a16a, 0x004f83e0, 0x00a57c00, 0x00ad83e0, 0x000f83e4, + 0x00007d4a, 0x000f836a, 0x004f93e4, 0x00a57d4a, 0x00ad836a, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00001c00, 0x00001084, 0x00007000, 0x00421000, + 0x00039ce0, 0x000039ce, 0x000e7380, 0x00e73800, 0x000e7f80, 0x00e73884, 0x0003fce0, 0x004239ce +};
new file mode 100644 --- /dev/null +++ b/gui//src/MainWindow.cpp @@ -0,0 +1,190 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include <QMenuBar> +#include <QMenu> +#include <QAction> +#include <QSettings> +#include <QDesktopServices> +#include <QFileDialog> +#include "MainWindow.h" +#include "FileEditorDockWidget.h" +#include "ImageViewerDockWidget.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), + m_isRunning(true) { + setObjectName("MainWindow"); + + QDesktopServices desktopServices; + m_settingsFile = desktopServices.storageLocation(QDesktopServices::HomeLocation) + "/.quint/settings.ini"; + construct(); + establishOctaveLink(); +} + +MainWindow::~MainWindow() { +} + +void MainWindow::handleOpenFileRequest(QString fileName) { + reportStatusMessage(tr("Opening file.")); + QPixmap pixmap; + if(pixmap.load(fileName)) { + ImageViewerDockWidget *imageViewerDockWidget = new ImageViewerDockWidget(pixmap, this); + imageViewerDockWidget->setWindowTitle(fileName); + addDockWidget(Qt::RightDockWidgetArea, imageViewerDockWidget); + } else { + FileEditorDockWidget *fileEditorDockWidget = new FileEditorDockWidget(this); + fileEditorDockWidget->loadFile(fileName); + addDockWidget(Qt::RightDockWidgetArea, fileEditorDockWidget); + } +} + +void MainWindow::reportStatusMessage(QString statusMessage) { + m_statusBar->showMessage(statusMessage, 1000); +} + +void MainWindow::handleSaveWorkspaceRequest() { + QDesktopServices desktopServices; + QString selectedFile = QFileDialog::getSaveFileName(this, tr("Save Workspace"), + desktopServices.storageLocation(QDesktopServices::HomeLocation) + "/.quint/workspace"); + m_octaveTerminalDockWidget->octaveTerminal()->sendText(QString("save \'%1\'\n").arg(selectedFile)); + m_octaveTerminalDockWidget->octaveTerminal()->setFocus(); +} + +void MainWindow::handleLoadWorkspaceRequest() { + QDesktopServices desktopServices; + QString selectedFile = QFileDialog::getOpenFileName(this, tr("Load Workspace"), + desktopServices.storageLocation(QDesktopServices::HomeLocation) + "/.quint/workspace"); + m_octaveTerminalDockWidget->octaveTerminal()->sendText(QString("load \'%1\'\n").arg(selectedFile)); + m_octaveTerminalDockWidget->octaveTerminal()->setFocus(); +} + +void MainWindow::handleClearWorkspaceRequest() { + m_octaveTerminalDockWidget->octaveTerminal()->sendText("clear\n"); + m_octaveTerminalDockWidget->octaveTerminal()->setFocus(); +} + +void MainWindow::handleCommandDoubleClicked(QString command) { + m_octaveTerminalDockWidget->octaveTerminal()->sendText(command); + m_octaveTerminalDockWidget->octaveTerminal()->setFocus(); +} + +void MainWindow::closeEvent(QCloseEvent *closeEvent) { + m_isRunning = false; + reportStatusMessage(tr("Saving data and shutting down.")); + writeSettings(); + + m_octaveCallbackThread->terminate(); + m_octaveCallbackThread->wait(); + + m_octaveMainThread->terminate(); + QMainWindow::closeEvent(closeEvent); +} + +void MainWindow::readSettings() { + QSettings settings(m_settingsFile, QSettings::IniFormat); + restoreGeometry(settings.value("MainWindow/geometry").toByteArray()); + restoreState(settings.value("MainWindow/windowState").toByteArray()); +} + +void MainWindow::writeSettings() { + QSettings settings(m_settingsFile, QSettings::IniFormat); + settings.setValue("MainWindow/geometry", saveGeometry()); + settings.setValue("MainWindow/windowState", saveState()); +} + +void MainWindow::construct() { + setWindowTitle("Octave"); + setWindowIcon(QIcon("../media/quint_icon_small.png")); + QStyle *style = QApplication::style(); + resize(800, 600); + + m_octaveTerminalDockWidget = new OctaveTerminalDockWidget(this, new OctaveTerminal(this)); + m_variablesDockWidget = new VariablesDockWidget(this); + m_historyDockWidget = new HistoryDockWidget(this); + m_filesDockWidget = new FilesDockWidget(this); + m_browserDockWidget = new BrowserDockWidget(this, new BrowserWidget(this)); + m_serviceDockWidget = new BrowserDockWidget(this, new BrowserWidget(this)); + + m_browserDockWidget->setObjectName("BrowserWidget"); + m_browserDockWidget->setWindowTitle(tr("Documentation")); + m_serviceDockWidget->setObjectName("ServiceWidget"); + m_serviceDockWidget->setWindowTitle(tr("Service")); + + // This is needed, since a QMainWindow without a central widget is not supported. + setCentralWidget(new QWidget(this)); + centralWidget()->setObjectName("CentralWidget"); + centralWidget()->hide(); + + setDockOptions(QMainWindow::AllowTabbedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AnimatedDocks); + + addDockWidget(Qt::RightDockWidgetArea, m_octaveTerminalDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_variablesDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_historyDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_filesDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_browserDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_serviceDockWidget); + + // TODO: Add meaningfull toolbar items. + m_generalPurposeToolbar = new QToolBar(tr("Octave Toolbar"), this); + QAction *commandAction = new QAction(style->standardIcon(QStyle::SP_CommandLink), + "", m_generalPurposeToolbar); + QAction *computerAction = new QAction(style->standardIcon(QStyle::SP_ComputerIcon), + "", m_generalPurposeToolbar); + m_generalPurposeToolbar->addAction(commandAction); + m_generalPurposeToolbar->addAction(computerAction); + addToolBar(m_generalPurposeToolbar); + + // Create status bar. + + m_statusBar = new QStatusBar(this); + setStatusBar(m_statusBar); + + readSettings(); + + connect(m_filesDockWidget, SIGNAL(openFile(QString)), this, SLOT(handleOpenFileRequest(QString))); + connect(m_historyDockWidget, SIGNAL(information(QString)), this, SLOT(reportStatusMessage(QString))); + connect(m_historyDockWidget, SIGNAL(commandDoubleClicked(QString)), this, SLOT(handleCommandDoubleClicked(QString))); + connect(m_variablesDockWidget, SIGNAL(saveWorkspace()), this, SLOT(handleSaveWorkspaceRequest())); + connect(m_variablesDockWidget, SIGNAL(loadWorkspace()), this, SLOT(handleLoadWorkspaceRequest())); + connect(m_variablesDockWidget, SIGNAL(clearWorkspace()), this, SLOT(handleClearWorkspaceRequest())); + + m_browserDockWidget->browserWidget()->load(QUrl("http://www.gnu.org/software/octave/doc/interpreter/")); + m_serviceDockWidget->browserWidget()->load(QUrl("http://powerup.ath.cx/bugtracker")); + +} + +void MainWindow::establishOctaveLink() { + m_octaveMainThread = new OctaveMainThread(this); + m_octaveMainThread->start(); + + m_octaveCallbackThread = new OctaveCallbackThread(this, this); + m_octaveCallbackThread->start(); + + command_editor::add_event_hook(OctaveLink::readlineEventHook); + + int fdm, fds; + if(openpty(&fdm, &fds, 0, 0, 0) < 0) { + assert(0); + } + dup2 (fds, 0); + dup2 (fds, 1); + dup2 (fds, 2); + m_octaveTerminalDockWidget->octaveTerminal()->openTeletype(fdm); + reportStatusMessage(tr("Established link to Octave.")); +}
new file mode 100644 --- /dev/null +++ b/gui//src/MainWindow.h @@ -0,0 +1,180 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QtGui/QMainWindow> +#include <QThread> +#include <QTabWidget> +#include <QMdiArea> +#include <QStatusBar> +#include <QToolBar> +#include <QQueue> +#include "OctaveTerminal.h" +#include "OctaveLink.h" +#include "VariablesDockWidget.h" +#include "HistoryDockWidget.h" +#include "FilesDockWidget.h" +#include "SimpleEditor.h" +#include "BrowserWidget.h" + +// Octave includes +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef PACKAGE_URL +#include "octave/config.h" + +#include "octave/debug.h" +#include "octave/octave.h" +#include "octave/symtab.h" +#include "octave/parse.h" +#include "octave/unwind-prot.h" +#include "octave/toplev.h" +#include "octave/load-path.h" +#include "octave/error.h" +#include "octave/quit.h" +#include "octave/variables.h" +#include "octave/sighandlers.h" +#include "octave/sysdep.h" +#include "octave/str-vec.h" +#include "octave/cmd-hist.h" +#include "octave/cmd-edit.h" +#include "octave/oct-env.h" +#include "octave/symtab.h" +#include "cmd-edit.h" + +typedef struct yy_buffer_state *YY_BUFFER_STATE; +extern OCTINTERP_API YY_BUFFER_STATE create_buffer (FILE *f); +extern OCTINTERP_API void switch_to_buffer (YY_BUFFER_STATE buf); +extern OCTINTERP_API FILE *get_input_from_stdin (void); + +// System +#include <termios.h> +#include <sys/types.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <iostream> +#include <vector> +#include "pty.h" + +class OctaveMainThread; +class OctaveCallbackThread; + +/** + * \class MainWindow + * + * Represents the main window. + */ +class MainWindow : public QMainWindow { + Q_OBJECT +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + + bool isRunning() { return m_isRunning; } + OctaveTerminal *octaveTerminal() { return m_octaveTerminalDockWidget->octaveTerminal(); } + VariablesDockWidget *variablesDockWidget() { return m_variablesDockWidget; } + HistoryDockWidget *historyDockWidget() { return m_historyDockWidget; } + FilesDockWidget *filesDockWidget() { return m_filesDockWidget; } + +public slots: + void handleOpenFileRequest(QString fileName); + void reportStatusMessage(QString statusMessage); + void handleSaveWorkspaceRequest(); + void handleLoadWorkspaceRequest(); + void handleClearWorkspaceRequest(); + void handleCommandDoubleClicked(QString command); + +protected: + void closeEvent(QCloseEvent *closeEvent); + void readSettings(); + void writeSettings(); + +private: + void construct(); + void establishOctaveLink(); + OctaveTerminalDockWidget *m_octaveTerminalDockWidget; + VariablesDockWidget *m_variablesDockWidget; + HistoryDockWidget *m_historyDockWidget; + FilesDockWidget *m_filesDockWidget; + BrowserDockWidget *m_browserDockWidget; + BrowserDockWidget *m_serviceDockWidget; + + QStatusBar *m_statusBar; + QToolBar *m_generalPurposeToolbar; + QString m_settingsFile; + + // Threads for running octave and managing the data interaction. + OctaveMainThread *m_octaveMainThread; + OctaveCallbackThread *m_octaveCallbackThread; + bool m_isRunning; +}; + +class OctaveMainThread : public QThread { + Q_OBJECT +public: + OctaveMainThread(QObject *parent) + : QThread(parent) { + } +protected: + void run() { + int argc = 3; + const char* argv[] = {"octave", "--interactive", "--line-editing"}; + octave_main(argc, (char**)argv, 1); + main_loop(); + clean_up_and_exit(0); + } +}; + +class OctaveCallbackThread : public QThread { + Q_OBJECT +public: + OctaveCallbackThread(QObject *parent, MainWindow *mainWindow) + : QThread(parent), + m_mainWindow(mainWindow) { + } + +protected: + void run() { + while(m_mainWindow->isRunning()) { + + // Get a full variable list. + QList<SymbolRecord> symbolTable = OctaveLink::instance()->currentSymbolTable(); + if(symbolTable.size()) { + m_mainWindow->variablesDockWidget()->setVariablesList(symbolTable); + } + + // Collect history list. + string_vector historyList = OctaveLink::instance()->currentHistory(); + if(historyList.length()) { + m_mainWindow->historyDockWidget()->updateHistory(historyList); + } + + usleep(100000); + } + } +private: + MainWindow *m_mainWindow; +}; + +#endif // MAINWINDOW_H
new file mode 100644 --- /dev/null +++ b/gui//src/NumberedCodeEdit.cpp @@ -0,0 +1,591 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005, 2006 KJSEmbed Authors + See included AUTHORS file. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include <QTextDocument> +#include <QTextBlock> +#include <QHBoxLayout> +#include <QScrollBar> +#include <QPainter> +#include <QAbstractTextDocumentLayout> +#include <QToolTip> +#include <QTextStream> +#include <QProcess> +#include <QRegExp> +#include <QMessageBox> +#include <QFileInfo> + +#include "NumberedCodeEdit.h" +#include "config.h" + +NumberBar::NumberBar( QWidget *parent ) + : QWidget( parent ), edit(0), currentLine(-1), bugLine(-1) +{ + // Make room for 4 digits and the breakpoint icon + setFixedWidth( fontMetrics().width( QString("0000") + 10 + 32 ) ); + stopMarker = QPixmap();// QString(ICON_PATH) + "/stop.png" ); + currentMarker = QPixmap();// QString(ICON_PATH) + "/bookmark.png" ); + bugMarker = QPixmap();// QString(ICON_PATH) + "/bug.png" ); +} + +NumberBar::~NumberBar() +{ +} + +void NumberBar::setCurrentLine( int lineno ) +{ + currentLine = lineno; + update(); +} + +void NumberBar::setBugLine( int lineno ) +{ + bugLine = lineno; +} + +void NumberBar::toggleBreakpoint( int lineno ) +{ + if(lineno > 0) + { + int i = breakpoints.indexOf(lineno); + + if(i > -1) + breakpoints.removeAt(i); + else + breakpoints.push_back(lineno); + } + update(); +} + +void NumberBar::setTextEdit( SimpleEditor *edit ) +{ + this->edit = edit; + setFixedWidth( edit->fontMetrics().width( QString("0000") + 10 + 32 ) ); + connect( edit->document()->documentLayout(), SIGNAL( update(const QRectF &) ), + this, SLOT( update() ) ); + connect( edit->verticalScrollBar(), SIGNAL(valueChanged(int) ), + this, SLOT( update() ) ); +} + +void NumberBar::paintEvent( QPaintEvent * ) +{ + QVector<qreal> lines_list; + int first_line_no; + edit->publicBlockBoundingRectList(lines_list, first_line_no); + + const QFontMetrics fm = edit->fontMetrics(); + const int ascent = fontMetrics().ascent(); // height = ascent + descent + + QPainter p(this); + p.setPen(palette().windowText().color()); + + bugRect = QRect(); + stopRect = QRect(); + currentRect = QRect(); + + int position_y; + int lineCount; + + const int lines_list_size=lines_list.size(); + + for(int i=0;i<lines_list_size;i++) + { + position_y=qRound( lines_list[i] ); + lineCount=first_line_no+i; + + const QString txt = QString::number( lineCount ); + p.drawText( width() - fm.width(txt)- 2, position_y+ascent, txt ); + + // Bug marker + if ( bugLine == lineCount ) { + p.drawPixmap( 1, position_y, bugMarker ); + bugRect = QRect( 19, position_y, bugMarker.width(), bugMarker.height() ); + } + + // Stop marker + if ( breakpoints.contains(lineCount) ) { + p.drawPixmap( 1, position_y, stopMarker ); + stopRect = QRect( 1, position_y,stopMarker.width(), stopMarker.height() ); + } + + // Current line marker + if ( currentLine == lineCount ) { + p.drawPixmap( 1, position_y, currentMarker ); + currentRect = QRect( 1, position_y, currentMarker.width(), currentMarker.height() ); + } + } + + /* + + int contentsY = edit->verticalScrollBar()->value(); + qreal pageBottom = contentsY + edit->viewport()->height(); + const QFontMetrics fm = fontMetrics(); + const int ascent = fontMetrics().ascent() + 1; // height = ascent + descent + 1 + int lineCount = 1; + + QPainter p(this); + p.setPen(palette().windowText().color()); + + bugRect = QRect(); + stopRect = QRect(); + currentRect = QRect(); + + for ( QTextBlock block = edit->document()->begin(); + block.isValid(); block = block.next(), ++lineCount ) { + + const QRectF boundingRect = edit->publicBlockBoundingRect( block ); + + QPointF position = boundingRect.topLeft(); + if ( position.y() + boundingRect.height() < contentsY ) + continue; + if ( position.y() > pageBottom ) + break; + + const QString txt = QString::number( lineCount ); + p.drawText( width() - fm.width(txt), qRound( position.y() ) - contentsY + ascent, txt ); + + // Bug marker + if ( bugLine == lineCount ) { + p.drawPixmap( 1, qRound( position.y() ) - contentsY, bugMarker ); + bugRect = QRect( 1, qRound( position.y() ) - contentsY, bugMarker.width(), bugMarker.height() ); + } + + // Stop marker + if ( breakpoints.contains(lineCount) ) { + p.drawPixmap( 19, qRound( position.y() ) - contentsY, stopMarker ); + stopRect = QRect( 19, qRound( position.y() ) - contentsY, stopMarker.width(), stopMarker.height() ); + } + + // Current line marker + if ( currentLine == lineCount ) { + p.drawPixmap( 19, qRound( position.y() ) - contentsY, currentMarker ); + currentRect = QRect( 19, qRound( position.y() ) - contentsY, currentMarker.width(), currentMarker.height() ); + } + } + */ +} + +bool NumberBar::event( QEvent *event ) +{ + if ( event->type() == QEvent::ToolTip ) { + QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); + + if ( stopRect.contains( helpEvent->pos() ) ) { + QToolTip::showText( helpEvent->globalPos(), tr("Stop Here")); + } + else if ( currentRect.contains( helpEvent->pos() ) ) { + QToolTip::showText( helpEvent->globalPos(), tr("Current Line")); + } + else if ( bugRect.contains( helpEvent->pos() ) ) { + QToolTip::showText( helpEvent->globalPos(), tr("Error Line" )); + } + } + + return QWidget::event(event); +} + +QList<int> *NumberBar::getBreakpoints() +{ + return &breakpoints; +} + + + +NumberedCodeEdit::NumberedCodeEdit( QWidget *parent, SimpleEditor *textEdit ) + : QFrame( parent ) +{ + setFrameStyle( QFrame::StyledPanel | QFrame::Sunken ); + setLineWidth( 2 ); + + view=textEdit; + view->installEventFilter( this ); + + connect( view->document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(textChanged(int,int,int)) ); + + connect( view, SIGNAL(cursorPositionChanged()), this, SLOT(cursor_moved_cb()) ); + + // Setup the line number pane + + numbers = new NumberBar( this ); + numbers->setTextEdit( view ); + //numbers=NULL; + + + vbox = new QVBoxLayout(this); + vbox->setSpacing( 0 ); + vbox->setMargin( 0 ); + + hbox = new QHBoxLayout; + vbox->addLayout(hbox); + + hbox->setSpacing( 0 ); + hbox->setMargin( 0 ); + hbox->addWidget( numbers ); + hbox->addWidget( view ); + + textModifiedOk=false; + + QHBoxLayout *messages_layout= new QHBoxLayout; + vbox->addLayout(messages_layout); + messages_layout->setSpacing( 0 ); + messages_layout->setMargin( 0 ); + } + + +NumberedCodeEdit::~NumberedCodeEdit() +{ + hide(); + //printf("Borrado ntv\n"); +} + +void NumberedCodeEdit::setCurrentLine( int lineno ) +{ + currentLine = lineno; + if(numbers!=NULL) numbers->setCurrentLine( lineno ); + + //Move cursor to lineno + if(lineno>-1) + { + QTextCursor cursor=textEdit()->textCursor(); + + cursor.movePosition(QTextCursor::Start); + + for(int i=1;i<lineno;i++) + cursor.movePosition(QTextCursor::NextBlock); + + textEdit()->setTextCursor(cursor); + } + + textChanged( 0, 0, 1 ); +} + +void NumberedCodeEdit::toggleBreakpoint( int lineno ) +{ + if(numbers!=NULL) numbers->toggleBreakpoint( lineno ); +} + +void NumberedCodeEdit::setBugLine( int lineno ) +{ + if(numbers!=NULL) numbers->setBugLine( lineno ); +} + +void NumberedCodeEdit::textChanged( int /*pos*/, int removed, int added ) +{ + //Q_UNUSED( pos ); + + if ( removed == 0 && added == 0 ) + return; + + //QTextBlock block = highlight.block(); + //QTextBlock block = view->document()->begin(); + //QTextBlockFormat fmt = block.blockFormat(); + //QColor bg = view->palette().base().color(); + //fmt.setBackground( bg ); + //highlight.setBlockFormat( fmt ); + /* + QTextBlockFormat fmt; + + int lineCount = 1; + for ( QTextBlock block = view->document()->begin(); + block.isValid() && block!=view->document()->end(); block = block.next(), ++lineCount ) { + + if ( lineCount == currentLine ) + { + fmt = block.blockFormat(); + QColor bg = view->palette().highlight().color(); + fmt.setBackground( bg ); + + highlight = QTextCursor( block ); + highlight.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor ); + highlight.setBlockFormat( fmt ); + + break; + } + } + */ + + if( !textModifiedOk && view->document()->isModified() ) + { + textModifiedOk=true; + emit textModified(); + } +} + +bool NumberedCodeEdit::eventFilter( QObject *obj, QEvent *event ) +{ + if ( obj != view ) + return QFrame::eventFilter(obj, event); + + if ( event->type() == QEvent::ToolTip ) { + QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); + + QTextCursor cursor = view->cursorForPosition( helpEvent->pos() ); + cursor.movePosition( QTextCursor::StartOfWord, QTextCursor::MoveAnchor ); + cursor.movePosition( QTextCursor::EndOfWord, QTextCursor::KeepAnchor ); + + QString word = cursor.selectedText(); + emit mouseHover( word ); + emit mouseHover( helpEvent->pos(), word ); + + // QToolTip::showText( helpEvent->globalPos(), word ); // For testing + } + + return false; +} + +QList<int> *NumberedCodeEdit::getBreakpoints() +{ + QList<int> *br=NULL; + if(numbers!=NULL) br=numbers->getBreakpoints(); + return br; +} + +void NumberedCodeEdit::open(QString path) +{ + FILE *fl; + + fl = fopen(path.toLocal8Bit().constData(), "rt"); + if(fl) + { + fclose(fl); + filePath = path; + + textEdit()->load(path); + + textModifiedOk=false; + textEdit()->document()->setModified(false); + }else{ + throw path; + } +} + +bool NumberedCodeEdit::save(QString path) +{ + FILE *fl; + + if(path.isEmpty()) path = filePath; + QRegExp re("[A-Za-z_][A-Za-z0-9_]*\\.m"); + + if( ! re.exactMatch( QFileInfo(path).fileName() ) ) + { + QMessageBox msgBox; + msgBox.setText( tr("This file name is not valid.") ); + msgBox.setInformativeText(tr("Octave doesn't understand this file name:\n")+path+tr("\nPlease, change it.\n Do you want to save your changes?")); + msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Save); + int ret = msgBox.exec(); + switch (ret) + { + case QMessageBox::Save: + // Save was clicked + break; + case QMessageBox::Cancel: + // Cancel was clicked + return false; + break; + default: + // should never be reached + break; + } + } + + + fl = fopen(path.toLocal8Bit().constData(), "wt"); + if(fl) + { + filePath = path; + QTextStream *stream = new QTextStream(fl); + (*stream) << textEdit()->document()->toPlainText(); + delete stream; + fclose(fl); + textModifiedOk=false; + view->document()->setModified(false); + }else{ + return false; + } + + return true; +} + +QString NumberedCodeEdit::path() +{ + return filePath; +} + +void NumberedCodeEdit::setPath(QString path) +{ + filePath=path; + textEdit()->setFile(path); +} + +void NumberedCodeEdit::setModified(bool modify) +{ + textModifiedOk=modify; +} + +bool NumberedCodeEdit::modified() +{ + return textModifiedOk; +} + +void NumberedCodeEdit::cursor_moved_cb() +{ + QTextCursor cursor=view->textCursor(); + QTextBlock actual_block=cursor.block(); + int lineCount=1; + QTextBlock block = view->document()->begin(); + + for ( ;block.isValid() && actual_block!=block; block = block.next()) lineCount++ ; +} + +static QString startLineInsertText(QString str, QString textToInsert) +{ + str.replace(QChar(0x2029), "\n"); + //printf("str=%s\n", str.toLocal8Bit().data() ); + + QStringList list = str.split("\n"); + + for(int i=0;i<list.size();i++) + { + QString s=list[i]; + + int x; + + for(x=0;x<s.size();x++) + { + if( s.at(x)!=' ' && s.at(x)!='\t' ) break; + } + + QString s1=s.left(x); + QString s2=s.right(s.size()-x); + list[i]=s1+textToInsert+s2; + } + + return list.join("\n"); +} + +static QString startLineRemoveText(QString str, QStringList textToRemove) +{ + str.replace(QChar(0x2029), "\n"); + + QStringList list = str.split("\n"); + + for(int i=0;i<list.size();i++) + { + QString s=list[i]; + + int x; + + for(x=0;x<s.size();x++) + { + if( s.at(x)!=' ' && s.at(x)!='\t' ) break; + } + + QString s1=s.left(x); + QString s2=s.right(s.size()-x); + + for(int k=0;k<textToRemove.size();k++) + { + if(s1.endsWith(textToRemove[k])) + { + s1=s1.left(s1.size()-textToRemove[k].size()); + break; + } + else if(s2.startsWith(textToRemove[k])) + { + s2=s2.right(s2.size()-textToRemove[k].size()); + break; + } + } + + //printf("s1=%s s2=%s \n", s1.toLocal8Bit().data(), s2.toLocal8Bit().data()); + list[i]=s1+s2; + } + + return list.join("\n"); +} + +void NumberedCodeEdit::indent() +{ + QTextCursor cursor(textEdit()->textCursor()); + + if( !cursor.hasSelection() ) return; + + QString str=cursor.selectedText(); + + str=startLineInsertText(str, "\t"); + + cursor.insertText(str); + cursor.setPosition(cursor.position()-str.size(), QTextCursor::KeepAnchor); + textEdit()->setTextCursor(cursor); +} + +void NumberedCodeEdit::unindent() +{ + //QTextDocument *doc=textEdit()->document(); + + QTextCursor cursor(textEdit()->textCursor()); + + if( !cursor.hasSelection() ) return; + + QString str=cursor.selectedText(); + + QStringList textToRemove; + textToRemove << "\t" << " "; + str=startLineRemoveText(str, textToRemove); + + cursor.insertText(str); + cursor.setPosition(cursor.position()-str.size(), QTextCursor::KeepAnchor); + textEdit()->setTextCursor(cursor); +} + +void NumberedCodeEdit::comment() +{ + //QTextDocument *doc=textEdit()->document(); + + QTextCursor cursor(textEdit()->textCursor()); + + if( !cursor.hasSelection() ) return; + + QString str=cursor.selectedText(); + + str=startLineInsertText(str, "%"); + + cursor.insertText(str); + cursor.setPosition(cursor.position()-str.size(), QTextCursor::KeepAnchor); + textEdit()->setTextCursor(cursor); +} + +void NumberedCodeEdit::uncomment() +{ + //QTextDocument *doc=textEdit()->document(); + + QTextCursor cursor(textEdit()->textCursor()); + + if( !cursor.hasSelection() ) return; + + QString str=cursor.selectedText(); + + QStringList textToRemove; + textToRemove << "%" << "#"; + str=startLineRemoveText(str, textToRemove); + + cursor.insertText(str); + cursor.setPosition(cursor.position()-str.size(), QTextCursor::KeepAnchor); + textEdit()->setTextCursor(cursor); +}
new file mode 100644 --- /dev/null +++ b/gui//src/NumberedCodeEdit.h @@ -0,0 +1,169 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005, 2006 KJSEmbed Authors + See included AUTHORS file. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +// -*- c++ -*- +#ifndef NUMBERED_TEXT_VIEW_H +#define NUMBERED_TEXT_VIEW_H + +#include <QFrame> +#include <QPixmap> +#include <QTextCursor> +#include <QLabel> +#include <QVBoxLayout> +#include <QHBoxLayout> +#include "SimpleEditor.h" + +class SimpleEditor; +class QHBoxLayout; + +/** + * @internal Used to display the numbers. + */ +class NumberBar : public QWidget +{ + Q_OBJECT + +public: + NumberBar( QWidget *parent ); + ~NumberBar(); + + void setCurrentLine( int lineno ); + void setBugLine( int lineno ); + void toggleBreakpoint( int lineno ); + QList<int> *getBreakpoints(); + + void setTextEdit( SimpleEditor *edit ); + void paintEvent( QPaintEvent *ev ); + +protected: + bool event( QEvent *ev ); + +private: + SimpleEditor *edit; + QPixmap stopMarker; + QPixmap currentMarker; + QPixmap bugMarker; + QList<int> breakpoints; + int currentLine; + int bugLine; + QRect stopRect; + QRect currentRect; + QRect bugRect; +}; + +/** + * Displays a CodeEdit with line numbers. + */ +class NumberedCodeEdit : public QFrame +{ + Q_OBJECT + +public: + NumberedCodeEdit( QWidget *parent = 0 , SimpleEditor *textEdit=new SimpleEditor() ); + ~NumberedCodeEdit(); + + QList<int> *getBreakpoints(); + + void open(QString path); + + /**Saves file to path. @return true if all is OK.*/ + bool save(QString path = QString()); + + QString path(); + void setPath(QString path); + + bool modified(); + void setModified(bool modify); + + /** Returns the CodeEdit of the main view. */ + SimpleEditor *textEdit() const { return view; } + + /** + * Sets the line that should have the current line indicator. + * A value of -1 indicates no line should show the indicator. + */ + void setCurrentLine( int lineno ); + + /** + * Toggle breakpoint + */ + void toggleBreakpoint( int lineno ); + + /** + * Sets the line that should have the bug line indicator. + * A value of -1 indicates no line should show the indicator. + */ + void setBugLine( int lineno ); + + /** @internal Used to get tooltip events from the view for the hover signal. */ + bool eventFilter( QObject *obj, QEvent *event ); + + /**Indent selected text.*/ + void indent(); + + /**UnIndent selected text.*/ + void unindent(); + + /**Comment selected text.*/ + void comment(); + + /**UnComment selected text.*/ + void uncomment(); + +signals: + /** + * Emitted when the mouse is hovered over the text edit component. + * @param word The word under the mouse pointer + */ + void mouseHover( const QString &word ); + + /** + * Emitted when the mouse is hovered over the text edit component. + * @param pos The position of the mouse pointer. + * @param word The word under the mouse pointer + */ + void mouseHover( const QPoint &pos, const QString &word ); + + /** + * Emitted when file is changed. + */ + void textModified(); + +protected slots: + /** @internal Used to update the highlight on the current line. */ + void textChanged( int pos, int added, int removed ); +public slots: + void cursor_moved_cb(); + +private: + QString filePath; + QLabel *line_column_label; + SimpleEditor *view; + NumberBar *numbers; + QHBoxLayout *hbox; + QVBoxLayout *vbox; + int currentLine; + QTextCursor highlight; + bool textModifiedOk; +}; + + +#endif // NUMBERED_TEXT_VIEW_H + +
new file mode 100644 --- /dev/null +++ b/gui//src/OctaveLink.cpp @@ -0,0 +1,132 @@ +/* + +Copyright (C) 2007,2008,2009 John P. Swensen + +Octave is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. + +Octave 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 Octave; see the file COPYING. If not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ + +// Born July 13, 2007. + +#include "OctaveLink.h" + +OctaveLink OctaveLink::m_singleton; + + +OctaveLink::OctaveLink() + : QObject(), + m_previousHistoryLength(0) { + m_symbolTableSemaphore = new QSemaphore(1); + m_historySemaphore = new QSemaphore(1); +} + +OctaveLink::~OctaveLink() { +} + +int OctaveLink::readlineEventHook() { + OctaveLink::instance()->processOctaveServerData(); + return 0; +} + +QString OctaveLink::octaveValueAsQString(OctaveValue octaveValue) { + // Convert single qouted string. + if(octaveValue.is_sq_string()) { + return QString("\'%1\'").arg(octaveValue.string_value().c_str()); + + // Convert double qouted string. + } else if(octaveValue.is_dq_string()) { + return QString("\"%1\"").arg(octaveValue.string_value().c_str()); + + // Convert real scalar. + } else if(octaveValue.is_real_scalar()) { + return QString("%1").arg(octaveValue.scalar_value()); + + // Convert complex scalar. + } else if(octaveValue.is_complex_scalar()) { + return QString("%1 + %2i").arg(octaveValue.scalar_value()).arg(octaveValue.complex_value().imag()); + + // Convert range. + } else if(octaveValue.is_range()) { + return QString("%1 : %2 : %3").arg(octaveValue.range_value().base()) + .arg(octaveValue.range_value().inc()) + .arg(octaveValue.range_value().limit()); + + // Convert real matrix. + } else if(octaveValue.is_real_matrix()) { + // TODO: Convert real matrix into a string. + return QString("{matrix}"); + + // Convert complex matrix. + } else if(octaveValue.is_complex_matrix()) { + // TODO: Convert complex matrix into a string. + return QString("{complex matrix}"); + + // If everything else does not fit, we could not recognize the type. + } else { + return QString("<Type not recognized>"); + } +} + +void OctaveLink::fetchSymbolTable() { + m_symbolTableSemaphore->acquire(); + m_symbolTableBuffer.clear(); + std::list<SymbolRecord> allVariables = symbol_table::all_variables(); + std::list<SymbolRecord>::iterator iterator; + for(iterator = allVariables.begin(); iterator != allVariables.end(); iterator++) + m_symbolTableBuffer.append(*iterator); + m_symbolTableSemaphore->release(); + emit symbolTableChanged(); +} + + +void OctaveLink::fetchHistory() { + int currentLen = command_history::length(); + if(currentLen != m_previousHistoryLength) { + m_historySemaphore->acquire(); + for(int i = m_previousHistoryLength; i < currentLen; i++) { + m_historyBuffer.append(command_history::get_entry(i)); + } + m_historySemaphore->release(); + m_previousHistoryLength = currentLen; + emit historyChanged(); + } +} + +QList<SymbolRecord> OctaveLink::currentSymbolTable() { + QList<SymbolRecord> m_symbolTableCopy; + + // Generate a deep copy of the current symbol table. + m_symbolTableSemaphore->acquire(); + foreach(SymbolRecord symbolRecord, m_symbolTableBuffer) + m_symbolTableCopy.append(symbolRecord); + m_symbolTableSemaphore->release(); + + return m_symbolTableCopy; +} + +string_vector OctaveLink::currentHistory() { + m_historySemaphore->acquire(); + string_vector retval(m_historyBuffer); + m_historyBuffer = string_vector(); + m_historySemaphore->release(); + return retval; +} + +void OctaveLink::processOctaveServerData() { + fetchSymbolTable(); + fetchHistory(); +} +
new file mode 100644 --- /dev/null +++ b/gui//src/OctaveLink.h @@ -0,0 +1,139 @@ +/* + * + * Copyright (C) 2007, 2008, 2009 John P. Swensen + * + * This file is as a part of OctaveDE. + * + * Octave is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * Octave 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 Octave; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * */ +#ifndef OCTAVELINK_H +#define OCTAVELINK_H + +// Octave includes +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef PACKAGE_URL +#include <octave/config.h> +#include "octave/cmd-edit.h" +#include "octave/error.h" +#include "octave/file-io.h" +#include "octave/input.h" +#include "octave/lex.h" +#include "octave/load-path.h" +#include "octave/octave.h" +#include "octave/oct-hist.h" +#include "octave/oct-map.h" +#include "octave/oct-obj.h" +#include "octave/ops.h" +#include "octave/ov.h" +#include "octave/ov-usr-fcn.h" +#include "octave/symtab.h" +#include "octave/pt.h" +#include "octave/pt-eval.h" +#include "octave/config.h" +#include "octave/Range.h" +#include "octave/toplev.h" +#include "octave/procstream.h" +#include "octave/sighandlers.h" +#include "octave/debug.h" +#include "octave/sysdep.h" +#include "octave/ov.h" +#include "octave/unwind-prot.h" +#include "octave/utils.h" +#include "octave/variables.h" + +// Standard includes +#include <iostream> +#include <string> +#include <vector> +#include <readline/readline.h> + +// Qt includes +#include <QMutexLocker> +#include <QMutex> +#include <QFileInfo> +#include <QList> +#include <QString> +#include <QVector> +#include <QSemaphore> +#include <QObject> + +typedef symbol_table::symbol_record SymbolRecord; +typedef octave_value OctaveValue; + +/** + * \class OctaveLink + * Manages a link to an octave instance. + */ +class OctaveLink : QObject +{ + Q_OBJECT +public: + static OctaveLink *instance() { return &m_singleton; } + static int readlineEventHook(void); + static QString octaveValueAsQString(OctaveValue octaveValue); + + /** + * Returns a copy of the current symbol table buffer. + * \return Copy of the current symbol table buffer. + */ + QList<SymbolRecord> currentSymbolTable(); + + /** + * Returns a copy of the current history buffer. + * \return Copy of the current history buffer. + */ + string_vector currentHistory(); + + void processOctaveServerData(); + + /** + * Updates the current symbol table with new data + * from octave. + */ + void fetchSymbolTable(); + + /** + * Updates the current history buffer with new data + * from octave. + */ + void fetchHistory(); + +signals: + void symbolTableChanged(); + void historyChanged(); + +private: + OctaveLink(); + ~OctaveLink(); + + /** Variable related member variables. */ + QSemaphore *m_symbolTableSemaphore; + QList<SymbolRecord> m_symbolTableBuffer; + + /** History related member variables. */ + QSemaphore *m_historySemaphore; + string_vector m_historyBuffer; + int m_previousHistoryLength; + + static OctaveLink m_singleton; +}; +#endif // OCTAVELINK_H +
new file mode 100644 --- /dev/null +++ b/gui//src/OctaveTerminal.cpp @@ -0,0 +1,52 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include "OctaveTerminal.h" +#include <QHBoxLayout> +#include <QVBoxLayout> +#include <QStringListModel> +#include <QStringList> + +OctaveTerminalDockWidget::OctaveTerminalDockWidget(QWidget *parent, OctaveTerminal *octaveTerminal) + : QDockWidget(parent) { + setObjectName("OctaveTerminalDockWidget"); + setWindowTitle(tr("Octave terminal")); + m_octaveTerminal = octaveTerminal; + setWidget(m_octaveTerminal); +} + +OctaveTerminalDockWidget::~OctaveTerminalDockWidget() { +} + +OctaveTerminal *OctaveTerminalDockWidget::octaveTerminal() { + return m_octaveTerminal; +} + +OctaveTerminal::OctaveTerminal(QWidget *parent) + : QTerminalWidget(0, parent) { + construct(); +} + +OctaveTerminal::~OctaveTerminal() { +} + +void OctaveTerminal::construct() { + setScrollBarPosition(QTerminalWidget::ScrollBarRight); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} +
new file mode 100644 --- /dev/null +++ b/gui//src/OctaveTerminal.h @@ -0,0 +1,46 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#ifndef OCTAVETERMINAL_H +#define OCTAVETERMINAL_H + +#include "QTerminalWidget.h" +#include <QDockWidget> + +class OctaveTerminal; +class OctaveTerminalDockWidget : public QDockWidget { +public: + OctaveTerminalDockWidget(QWidget *parent, OctaveTerminal *octaveTerminal); + ~OctaveTerminalDockWidget(); + + OctaveTerminal *octaveTerminal(); + +private: + OctaveTerminal *m_octaveTerminal; +}; + +class OctaveTerminal : public QTerminalWidget { + Q_OBJECT +public: + OctaveTerminal(QWidget *parent = 0); + ~OctaveTerminal(); + +private: + void construct(); +}; +#endif // OCTAVETERMINAL_H
new file mode 100644 --- /dev/null +++ b/gui//src/ProcessInfo.cpp @@ -0,0 +1,1054 @@ +/* + Copyright 2007-2008 by Robert Knight <robertknight@gmail.countm> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "ProcessInfo.h" + +// Unix +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <pwd.h> + +// Qt +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QRegExp> +#include <QtCore/QTextStream> +#include <QtCore/QStringList> +#include <QtCore/QSet> + +// KDE +#include "konsole_export.h" + +#if defined(Q_OS_MAC) +#include <sys/sysctl.h> +#include <libproc.h> +#ifdef HAVE_SYS_PROC_INFO_H +#include <sys/proc_info.h> +#endif +#ifdef HAVE_SYS_PROC_H +#include <sys/proc.h> +#endif +//#include <kde_file.h> +#define KDE_struct_stat struct stat +#define KDE_stat ::stat +#endif + +#if defined(Q_OS_FREEBSD) +#include <sys/sysctl.h> //krazy:exclude=includes +#include <sys/types.h> +#include <sys/user.h> +#include <sys/syslimits.h> +#include <libutil.h> +#endif + +ProcessInfo::ProcessInfo(int pid , bool enableEnvironmentRead) + : _fields( ARGUMENTS | ENVIRONMENT ) // arguments and environments + // are currently always valid, + // they just return an empty + // vector / map respectively + // if no arguments + // or environment bindings + // have been explicitly set + , _enableEnvironmentRead(enableEnvironmentRead) + , _pid(pid) + , _parentPid(0) + , _foregroundPid(0) + , _userId(0) + , _lastError(NoError) + , _userName(QString()) + , _userHomeDir(QString()) +{ +} + +ProcessInfo::Error ProcessInfo::error() const { return _lastError; } +void ProcessInfo::setError(Error error) { _lastError = error; } + +void ProcessInfo::update() +{ + readProcessInfo(_pid,_enableEnvironmentRead); +} + +QString ProcessInfo::validCurrentDir() const +{ + bool ok = false; + + // read current dir, if an error occurs try the parent as the next + // best option + int currentPid = parentPid(&ok); + QString dir = currentDir(&ok); + while ( !ok && currentPid != 0 ) + { + ProcessInfo* current = ProcessInfo::newInstance(currentPid); + current->update(); + currentPid = current->parentPid(&ok); + dir = current->currentDir(&ok); + delete current; + } + + return dir; +} + +QString ProcessInfo::format(const QString& input) const +{ + bool ok = false; + + QString output(input); + + // search for and replace known marker + output.replace("%u",userName()); + output.replace("%n",name(&ok)); + output.replace("%c",formatCommand(name(&ok),arguments(&ok),ShortCommandFormat)); + output.replace("%C",formatCommand(name(&ok),arguments(&ok),LongCommandFormat)); + + QString dir = validCurrentDir(); + if (output.contains("%D")) + { + QString homeDir = userHomeDir(); + QString tempDir = dir; + // Change User's Home Dir w/ ~ only at the beginning + if (tempDir.startsWith(homeDir)) + { + tempDir.remove(0, homeDir.length()); + tempDir.prepend('~'); + } + output.replace("%D", tempDir); + } + output.replace("%d", dir); + + // remove any remaining %[LETTER] sequences + // output.replace(QRegExp("%\\w"), QString()); + + return output; +} + +QString ProcessInfo::formatCommand(const QString& name, + const QVector<QString>& arguments, + CommandFormat format) const +{ + Q_UNUSED(name); + Q_UNUSED(format); + + // TODO Implement me + return QStringList(QList<QString>::fromVector(arguments)).join(" "); +} + +QVector<QString> ProcessInfo::arguments(bool* ok) const +{ + *ok = _fields & ARGUMENTS; + + return _arguments; +} + +QMap<QString,QString> ProcessInfo::environment(bool* ok) const +{ + *ok = _fields & ENVIRONMENT; + + return _environment; +} + +bool ProcessInfo::isValid() const +{ + return _fields & PROCESS_ID; +} + +int ProcessInfo::pid(bool* ok) const +{ + *ok = _fields & PROCESS_ID; + + return _pid; +} + +int ProcessInfo::parentPid(bool* ok) const +{ + *ok = _fields & PARENT_PID; + + return _parentPid; +} + +int ProcessInfo::foregroundPid(bool* ok) const +{ + *ok = _fields & FOREGROUND_PID; + + return _foregroundPid; +} + +QString ProcessInfo::name(bool* ok) const +{ + *ok = _fields & NAME; + + return _name; +} + +int ProcessInfo::userId(bool* ok) const +{ + *ok = _fields & UID; + + return _userId; +} + +QString ProcessInfo::userName() const +{ + return _userName; +} + +QString ProcessInfo::userHomeDir() const +{ + return _userHomeDir; +} + +void ProcessInfo::setPid(int pid) +{ + _pid = pid; + _fields |= PROCESS_ID; +} + +void ProcessInfo::setUserId(int uid) +{ + _userId = uid; + _fields |= UID; +} + +void ProcessInfo::setUserName(const QString& name) +{ + _userName = name; + setUserHomeDir(); +} + +void ProcessInfo::setUserHomeDir() +{ + QString usersName = userName(); + // JPS: I don't know a good QT replacement + //if (!usersName.isEmpty()) + // _userHomeDir = KUser(usersName).homeDir(); + //else + _userHomeDir = QDir::homePath(); +} + +void ProcessInfo::setParentPid(int pid) +{ + _parentPid = pid; + _fields |= PARENT_PID; +} +void ProcessInfo::setForegroundPid(int pid) +{ + _foregroundPid = pid; + _fields |= FOREGROUND_PID; +} + +QString ProcessInfo::currentDir(bool* ok) const +{ + if (ok) + *ok = _fields & CURRENT_DIR; + + return _currentDir; +} +void ProcessInfo::setCurrentDir(const QString& dir) +{ + _fields |= CURRENT_DIR; + _currentDir = dir; +} + +void ProcessInfo::setName(const QString& name) +{ + _name = name; + _fields |= NAME; +} +void ProcessInfo::addArgument(const QString& argument) +{ + _arguments << argument; +} + +void ProcessInfo::addEnvironmentBinding(const QString& name , const QString& value) +{ + _environment.insert(name,value); +} + +void ProcessInfo::setFileError( QFile::FileError error ) +{ + switch ( error ) + { + case PermissionsError: + setError( PermissionsError ); + break; + case NoError: + setError( NoError ); + break; + default: + setError( UnknownError ); + } +} + +// +// ProcessInfo::newInstance() is way at the bottom so it can see all of the +// implementations of the UnixProcessInfo abstract class. +// + +NullProcessInfo::NullProcessInfo(int pid,bool enableEnvironmentRead) + : ProcessInfo(pid,enableEnvironmentRead) +{ +} + +bool NullProcessInfo::readProcessInfo(int /*pid*/ , bool /*enableEnvironmentRead*/) +{ + return false; +} + +void NullProcessInfo::readUserName() +{ +} + +UnixProcessInfo::UnixProcessInfo(int pid,bool enableEnvironmentRead) + : ProcessInfo(pid,enableEnvironmentRead) +{ +} + +bool UnixProcessInfo::readProcessInfo(int pid , bool enableEnvironmentRead) +{ + bool ok = readProcInfo(pid); + if (ok) + { + ok |= readArguments(pid); + ok |= readCurrentDir(pid); + if ( enableEnvironmentRead ) + { + ok |= readEnvironment(pid); + } + } + return ok; +} + +void UnixProcessInfo::readUserName() +{ + bool ok = false; + int uid = userId(&ok); + if (!ok) return; + + struct passwd passwdStruct; + struct passwd *getpwResult; + char *getpwBuffer; + long getpwBufferSize; + int getpwStatus; + + getpwBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (getpwBufferSize == -1) + getpwBufferSize = 16384; + + getpwBuffer = new char[getpwBufferSize]; + if (getpwBuffer == NULL) + return; + getpwStatus = getpwuid_r(uid, &passwdStruct, getpwBuffer, getpwBufferSize, &getpwResult); + if (getpwResult != NULL) + setUserName(QString(passwdStruct.pw_name)); + else + setUserName(QString()); + delete [] getpwBuffer; +} + + +class LinuxProcessInfo : public UnixProcessInfo +{ +public: + LinuxProcessInfo(int pid, bool env) : + UnixProcessInfo(pid,env) + { + } + +private: + virtual bool readProcInfo(int pid) + { + // indicies of various fields within the process status file which + // contain various information about the process + const int PARENT_PID_FIELD = 3; + const int PROCESS_NAME_FIELD = 1; + const int GROUP_PROCESS_FIELD = 7; + + QString parentPidString; + QString processNameString; + QString foregroundPidString; + QString uidLine; + QString uidString; + QStringList uidStrings; + + // For user id read process status file ( /proc/<pid>/status ) + // Can not use getuid() due to it does not work for 'su' + QFile statusInfo( QString("/proc/%1/status").arg(pid) ); + if ( statusInfo.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&statusInfo); + QString statusLine; + do { + statusLine = stream.readLine(0); + if (statusLine.startsWith(QLatin1String("Uid:"))) + uidLine = statusLine; + } while (!statusLine.isNull() && uidLine.isNull()); + + uidStrings << uidLine.split('\t', QString::SkipEmptyParts); + // Must be 5 entries: 'Uid: %d %d %d %d' and + // uid string must be less than 5 chars (uint) + if (uidStrings.size() == 5) + uidString = uidStrings[1]; + if (uidString.size() > 5) + uidString.clear(); + + bool ok = false; + int uid = uidString.toInt(&ok); + if (ok) + setUserId(uid); + readUserName(); + } + else + { + setFileError( statusInfo.error() ); + return false; + } + + + // read process status file ( /proc/<pid/stat ) + // + // the expected file format is a list of fields separated by spaces, using + // parenthesies to escape fields such as the process name which may itself contain + // spaces: + // + // FIELD FIELD (FIELD WITH SPACES) FIELD FIELD + // + QFile processInfo( QString("/proc/%1/stat").arg(pid) ); + if ( processInfo.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&processInfo); + QString data = stream.readAll(); + + int stack = 0; + int field = 0; + int pos = 0; + + while (pos < data.count()) + { + QChar c = data[pos]; + + if ( c == '(' ) + stack++; + else if ( c == ')' ) + stack--; + else if ( stack == 0 && c == ' ' ) + field++; + else + { + switch ( field ) + { + case PARENT_PID_FIELD: + parentPidString.append(c); + break; + case PROCESS_NAME_FIELD: + processNameString.append(c); + break; + case GROUP_PROCESS_FIELD: + foregroundPidString.append(c); + break; + } + } + + pos++; + } + } + else + { + setFileError( processInfo.error() ); + return false; + } + + // check that data was read successfully + bool ok = false; + int foregroundPid = foregroundPidString.toInt(&ok); + if (ok) + setForegroundPid(foregroundPid); + + int parentPid = parentPidString.toInt(&ok); + if (ok) + setParentPid(parentPid); + + if (!processNameString.isEmpty()) + setName(processNameString); + + // update object state + setPid(pid); + + return ok; + } + + virtual bool readArguments(int pid) + { + // read command-line arguments file found at /proc/<pid>/cmdline + // the expected format is a list of strings delimited by null characters, + // and ending in a double null character pair. + + QFile argumentsFile( QString("/proc/%1/cmdline").arg(pid) ); + if ( argumentsFile.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&argumentsFile); + QString data = stream.readAll(); + + QStringList argList = data.split( QChar('\0') ); + + foreach ( const QString &entry , argList ) + { + if (!entry.isEmpty()) + addArgument(entry); + } + } + else + { + setFileError( argumentsFile.error() ); + } + + return true; + } + + virtual bool readCurrentDir(int pid) + { + QFileInfo info( QString("/proc/%1/cwd").arg(pid) ); + + const bool readable = info.isReadable(); + + if ( readable && info.isSymLink() ) + { + setCurrentDir( info.symLinkTarget() ); + return true; + } + else + { + if ( !readable ) + setError( PermissionsError ); + else + setError( UnknownError ); + + return false; + } + } + + virtual bool readEnvironment(int pid) + { + // read environment bindings file found at /proc/<pid>/environ + // the expected format is a list of KEY=VALUE strings delimited by null + // characters and ending in a double null character pair. + + QFile environmentFile( QString("/proc/%1/environ").arg(pid) ); + if ( environmentFile.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&environmentFile); + QString data = stream.readAll(); + + QStringList bindingList = data.split( QChar('\0') ); + + foreach( const QString &entry , bindingList ) + { + QString name; + QString value; + + int splitPos = entry.indexOf('='); + + if ( splitPos != -1 ) + { + name = entry.mid(0,splitPos); + value = entry.mid(splitPos+1,-1); + + addEnvironmentBinding(name,value); + } + } + } + else + { + setFileError( environmentFile.error() ); + } + + return true; + } +} ; + +#if defined(Q_OS_FREEBSD) +class FreeBSDProcessInfo : public UnixProcessInfo +{ +public: + FreeBSDProcessInfo(int pid, bool readEnvironment) : + UnixProcessInfo(pid, readEnvironment) + { + } + +private: + virtual bool readProcInfo(int pid) + { + int managementInfoBase[4]; + size_t mibLength; + struct kinfo_proc* kInfoProc; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_PID; + managementInfoBase[3] = pid; + + if (sysctl(managementInfoBase, 4, NULL, &mibLength, NULL, 0) == -1) + return false; + + kInfoProc = new struct kinfo_proc [mibLength]; + + if (sysctl(managementInfoBase, 4, kInfoProc, &mibLength, NULL, 0) == -1) + { + delete [] kInfoProc; + return false; + } + +#if defined(__DragonFly__) + setName(kInfoProc->kp_comm); + setPid(kInfoProc->kp_pid); + setParentPid(kInfoProc->kp_ppid); + setForegroundPid(kInfoProc->kp_pgid); + setUserId(kInfoProc->kp_uid); +#else + setName(kInfoProc->ki_comm); + setPid(kInfoProc->ki_pid); + setParentPid(kInfoProc->ki_ppid); + setForegroundPid(kInfoProc->ki_pgid); + setUserId(kInfoProc->ki_uid); +#endif + + readUserName(); + + delete [] kInfoProc; + return true; + } + + virtual bool readArguments(int pid) + { + char args[ARG_MAX]; + int managementInfoBase[4]; + size_t len; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_PID; + managementInfoBase[3] = pid; + + len = sizeof(args); + if (sysctl(managementInfoBase, 4, args, &len, NULL, 0) == -1) + return false; + + const QStringList argumentList = QString(args).split(QChar('\0')); + + for (QStringList::const_iterator it = argumentList.begin(); it != argumentList.end(); ++it) + { + addArgument(*it); + } + + return true; + } + + virtual bool readEnvironment(int pid) + { + // Not supported in FreeBSD? + return false; + } + + virtual bool readCurrentDir(int pid) + { +#if defined(__DragonFly__) + char buf[PATH_MAX]; + int managementInfoBase[4]; + size_t len; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_CWD; + managementInfoBase[3] = pid; + + len = sizeof(buf); + if (sysctl(managementInfoBase, 4, buf, &len, NULL, 0) == -1) + return false; + + setCurrentDir(buf); + + return true; +#else + int numrecords; + struct kinfo_file* info = 0; + + info = kinfo_getfile(pid, &numrecords); + + if (!info) + return false; + + for (int i = 0; i < numrecords; ++i) + { + if (info[i].kf_fd == KF_FD_TYPE_CWD) + { + setCurrentDir(info[i].kf_path); + + free(info); + return true; + } + } + + free(info); + return false; +#endif + } +} ; +#endif + +#if defined(Q_OS_MAC) +class MacProcessInfo : public UnixProcessInfo +{ +public: + MacProcessInfo(int pid, bool env) : + UnixProcessInfo(pid, env) + { + } + +private: + virtual bool readProcInfo(int pid) + { + int managementInfoBase[4]; + size_t mibLength; + struct kinfo_proc* kInfoProc; + KDE_struct_stat statInfo; + + // Find the tty device of 'pid' (Example: /dev/ttys001) + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_PID; + managementInfoBase[3] = pid; + + if (sysctl(managementInfoBase, 4, NULL, &mibLength, NULL, 0) == -1) + { + return false; + } + else + { + kInfoProc = new struct kinfo_proc [mibLength]; + if (sysctl(managementInfoBase, 4, kInfoProc, &mibLength, NULL, 0) == -1) + { + delete [] kInfoProc; + return false; + } + else + { + QString deviceNumber = QString(devname(((&kInfoProc->kp_eproc)->e_tdev), S_IFCHR)); + QString fullDeviceName = QString("/dev/") + deviceNumber.rightJustified(3, '0'); + delete [] kInfoProc; + + QByteArray deviceName = fullDeviceName.toLatin1(); + const char* ttyName = deviceName.data(); + + if (KDE_stat(ttyName, &statInfo) != 0) + return false; + + // Find all processes attached to ttyName + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_TTY; + managementInfoBase[3] = statInfo.st_rdev; + + mibLength = 0; + if (sysctl(managementInfoBase, sizeof(managementInfoBase)/sizeof(int), NULL, &mibLength, NULL, 0) == -1) + return false; + + kInfoProc = new struct kinfo_proc [mibLength]; + if (sysctl(managementInfoBase, sizeof(managementInfoBase)/sizeof(int), kInfoProc, &mibLength, NULL, 0) == -1) + return false; + + // The foreground program is the first one + setName(QString(kInfoProc->kp_proc.p_comm)); + + delete [] kInfoProc; + } + } + return true; + } + + virtual bool readArguments(int pid) + { + Q_UNUSED(pid); + return false; + } + virtual bool readCurrentDir(int pid) + { + struct proc_vnodepathinfo vpi; + int nb = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi)); + if (nb == sizeof(vpi)) + { + setCurrentDir(QString(vpi.pvi_cdir.vip_path)); + return true; + } + return false; + } + virtual bool readEnvironment(int pid) + { + Q_UNUSED(pid); + return false; + } +} ; +#endif + +#ifdef Q_OS_SOLARIS + // The procfs structure definition requires off_t to be + // 32 bits, which only applies if FILE_OFFSET_BITS=32. + // Futz around here to get it to compile regardless, + // although some of the structure sizes might be wrong. + // Fortunately, the structures we actually use don't use + // off_t, and we're safe. + #if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS==64) + #undef _FILE_OFFSET_BITS + #endif + #include <procfs.h> +#else + // On non-Solaris platforms, define a fake psinfo structure + // so that the SolarisProcessInfo class can be compiled + // + // That avoids the trap where you change the API and + // don't notice it in #ifdeffed platform-specific parts + // of the code. + struct psinfo { + int pr_ppid; + int pr_pgid; + char* pr_fname; + char* pr_psargs; + } ; + static const int PRARGSZ=1; +#endif + +class SolarisProcessInfo : public UnixProcessInfo +{ +public: + SolarisProcessInfo(int pid, bool readEnvironment) + : UnixProcessInfo(pid,readEnvironment) + { + } +private: + virtual bool readProcInfo(int pid) + { + QFile psinfo( QString("/proc/%1/psinfo").arg(pid) ); + if ( psinfo.open( QIODevice::ReadOnly ) ) + { + struct psinfo info; + if (psinfo.read((char *)&info,sizeof(info)) != sizeof(info)) + { + return false; + } + + setParentPid(info.pr_ppid); + setForegroundPid(info.pr_pgid); + setName(info.pr_fname); + setPid(pid); + + // Bogus, because we're treating the arguments as one single string + info.pr_psargs[PRARGSZ-1]=0; + addArgument(info.pr_psargs); + } + return true; + } + + virtual bool readArguments(int /*pid*/) + { + // Handled in readProcInfo() + return false; + } + + virtual bool readEnvironment(int /*pid*/) + { + // Not supported in Solaris + return false; + } + + virtual bool readCurrentDir(int pid) + { + QFileInfo info( QString("/proc/%1/path/cwd").arg(pid) ); + const bool readable = info.isReadable(); + + if ( readable && info.isSymLink() ) + { + setCurrentDir( info.symLinkTarget() ); + return true; + } + else + { + if ( !readable ) + setError( PermissionsError ); + else + setError( UnknownError ); + + return false; + } + } +} ; + +SSHProcessInfo::SSHProcessInfo(const ProcessInfo& process) + : _process(process) +{ + bool ok = false; + + // check that this is a SSH process + const QString& name = _process.name(&ok); + + if ( !ok || name != "ssh" ) + { + //if ( !ok ) + // kWarning() << "Could not read process info"; + //else + // kWarning() << "Process is not a SSH process"; + + return; + } + + // read arguments + const QVector<QString>& args = _process.arguments(&ok); + + // SSH options + // these are taken from the SSH manual ( accessed via 'man ssh' ) + + // options which take no arguments + static const QString noOptionsArguments("1246AaCfgkMNnqsTtVvXxY"); + // options which take one argument + static const QString singleOptionArguments("bcDeFiLlmOopRSw"); + + if ( ok ) + { + // find the username, host and command arguments + // + // the username/host is assumed to be the first argument + // which is not an option + // ( ie. does not start with a dash '-' character ) + // or an argument to a previous option. + // + // the command, if specified, is assumed to be the argument following + // the username and host + // + // note that we skip the argument at index 0 because that is the + // program name ( expected to be 'ssh' in this case ) + for ( int i = 1 ; i < args.count() ; i++ ) + { + // if this argument is an option then skip it, plus any + // following arguments which refer to this option + if ( args[i].startsWith('-') ) + { + QChar argChar = ( args[i].length() > 1 ) ? args[i][1] : '\0'; + + if ( noOptionsArguments.contains(argChar) ) + continue; + else if ( singleOptionArguments.contains(argChar) ) + { + i++; + continue; + } + } + + // check whether the host has been found yet + // if not, this must be the username/host argument + if ( _host.isEmpty() ) + { + // check to see if only a hostname is specified, or whether + // both a username and host are specified ( in which case they + // are separated by an '@' character: username@host ) + + int separatorPosition = args[i].indexOf('@'); + if ( separatorPosition != -1 ) + { + // username and host specified + _user = args[i].left(separatorPosition); + _host = args[i].mid(separatorPosition+1); + } + else + { + // just the host specified + _host = args[i]; + } + } + else + { + // host has already been found, this must be the command argument + _command = args[i]; + } + + } + } + else + { + //kWarning() << "Could not read arguments"; + + return; + } +} + +QString SSHProcessInfo::userName() const +{ + return _user; +} +QString SSHProcessInfo::host() const +{ + return _host; +} +QString SSHProcessInfo::command() const +{ + return _command; +} +QString SSHProcessInfo::format(const QString& input) const +{ + QString output(input); + + // test whether host is an ip address + // in which case 'short host' and 'full host' + // markers in the input string are replaced with + // the full address + bool isIpAddress = false; + + struct in_addr address; + if ( inet_aton(_host.toLocal8Bit().constData(),&address) != 0 ) + isIpAddress = true; + else + isIpAddress = false; + + // search for and replace known markers + output.replace("%u",_user); + + if ( isIpAddress ) + output.replace("%h",_host); + else + output.replace("%h",_host.left(_host.indexOf('.'))); + + output.replace("%H",_host); + output.replace("%c",_command); + + return output; +} + +ProcessInfo* ProcessInfo::newInstance(int pid,bool enableEnvironmentRead) +{ +#ifdef Q_OS_LINUX + return new LinuxProcessInfo(pid,enableEnvironmentRead); +#elif defined(Q_OS_SOLARIS) + return new SolarisProcessInfo(pid,enableEnvironmentRead); +#elif defined(Q_OS_MAC) + return new MacProcessInfo(pid,enableEnvironmentRead); +#elif defined(Q_OS_FREEBSD) + return new FreeBSDProcessInfo(pid,enableEnvironmentRead); +#else + return new NullProcessInfo(pid,enableEnvironmentRead); +#endif +} +
new file mode 100644 --- /dev/null +++ b/gui//src/ProcessInfo.h @@ -0,0 +1,454 @@ +/* + Copyright 2007-2008 by Robert Knight <robertknight@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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef PROCESSINFO_H +#define PROCESSINFO_H + +// Qt +#include <QtCore/QFile> +#include <QtCore/QMap> +#include <QtCore/QString> +#include <QtCore/QVector> + +/** + * Takes a snapshot of the state of a process and provides access to + * information such as the process name, parent process, + * the foreground process in the controlling terminal, + * the arguments with which the process was started and the + * environment. + * + * To create a new snapshot, construct a new ProcessInfo instance, + * using ProcessInfo::newInstance(), + * passing the process identifier of the process you are interested in. + * + * After creating a new instance, call the update() method to take a + * snapshot of the current state of the process. + * + * Before calling any additional methods, check that the process state + * was read successfully using the isValid() method. + * + * Each accessor method which provides information about the process state ( such as pid(), + * currentDir(), name() ) takes a pointer to a boolean as an argument. If the information + * requested was read successfully then the boolean is set to true, otherwise it is set + * to false, in which case the return value from the function should be ignored. + * If this boolean is set to false, it may indicate an error reading the process information, + * or it may indicate that the information is not available on the current platform. + * + * eg. + * + * @code + * ProcessInfo* info = ProcessInfo::newInstance(pid); + * info->update(); + * + * if ( info->isValid() ) + * { + * bool ok; + * QString value = info->name(&ok); + * + * if ( ok ) kDebug() << "process name - " << name; + * int parentPid = info->parentPid(&ok); + * if ( ok ) kDebug() << "parent process - " << parentPid; + * int foregroundPid = info->foregroundColororegroundPid(&ok); + * if ( ok ) kDebug() << "foreground process - " << foregroundPid; + * } + * @endcode + */ +class ProcessInfo +{ +public: + /** + * Constructs a new instance of a suitable ProcessInfo sub-class for + * the current platform which provides information about a given process. + * + * @param pid The pid of the process to examine + * @param readEnvironment Specifies whether environment bindings should + * be read. If this is false, then environment() calls will + * always fail. This is an optimization to avoid the overhead + * of reading the (potentially large) environment data when it + * is not required. + */ + static ProcessInfo* newInstance(int pid,bool readEnvironment = false); + + virtual ~ProcessInfo() {} + + /** + * Updates the information about the process. This must + * be called before attempting to use any of the accessor methods. + */ + void update(); + + /** Returns true if the process state was read successfully. */ + bool isValid() const; + /** + * Returns the process id. + * + * @param ok Set to true if the process id was read successfully or false otherwise + */ + int pid(bool* ok) const; + /** + * Returns the id of the parent process id was read successfully or false otherwise + * + * @param ok Set to true if the parent process id + */ + int parentPid(bool* ok) const; + + /** + * Returns the id of the current foreground process + * + * NOTE: Using the foregroundProcessGroup() method of the Pty + * instance associated with the terminal of interest is preferred + * over using this method. + * + * @param ok Set to true if the foreground process id was read successfully or false otherwise + */ + int foregroundPid(bool* ok) const; + + /* Returns the user id of the process */ + int userId(bool* ok) const; + + /** Returns the user's name of the process */ + QString userName() const; + + /** Returns the user's home directory of the process */ + QString userHomeDir() const; + + /** Returns the name of the current process */ + QString name(bool* ok) const; + + /** + * Returns the command-line arguments which the process + * was started with. + * + * The first argument is the name used to launch the process. + * + * @param ok Set to true if the arguments were read successfully or false otherwise. + */ + QVector<QString> arguments(bool* ok) const; + /** + * Returns the environment bindings which the process + * was started with. + * In the returned map, the key is the name of the environment variable, + * and the value is the corresponding value. + * + * @param ok Set to true if the environment bindings were read successfully or false otherwise + */ + QMap<QString,QString> environment(bool* ok) const; + + /** + * Returns the current working directory of the process + * + * @param ok Set to true if the current working directory was read successfully or false otherwise + */ + QString currentDir(bool* ok) const; + + /** + * Returns the current working directory of the process (or its parent) + */ + QString validCurrentDir() const; + + /** Forces the user home directory to be calculated */ + void setUserHomeDir(); + + /** + * Parses an input string, looking for markers beginning with a '%' + * character and returns a string with the markers replaced + * with information from this process description. + * <br> + * The markers recognised are: + * <ul> + * <li> %u - Name of the user which owns the process. </li> + * <li> %n - Replaced with the name of the process. </li> + * <li> %d - Replaced with the last part of the path name of the + * process' current working directory. + * + * (eg. if the current directory is '/home/bob' then + * 'bob' would be returned) + * </li> + * <li> %D - Replaced with the current working directory of the process. </li> + * </ul> + */ + QString format(const QString& text) const; + + /** + * This enum describes the errors which can occur when trying to read + * a process's information. + */ + enum Error + { + /** No error occurred. */ + NoError, + /** The nature of the error is unknown. */ + UnknownError, + /** Konsole does not have permission to obtain the process information. */ + PermissionsError + }; + + /** + * Returns the last error which occurred. + */ + Error error() const; + +protected: + /** + * Constructs a new process instance. You should not call the constructor + * of ProcessInfo or its subclasses directly. Instead use the + * static ProcessInfo::newInstance() method which will return + * a suitable ProcessInfo instance for the current platform. + */ + explicit ProcessInfo(int pid , bool readEnvironment = false); + + /** + * This is called on construction to read the process state + * Subclasses should reimplement this function to provide + * platform-specific process state reading functionality. + * + * When called, readProcessInfo() should attempt to read all + * of the necessary state information. If the attempt is successful, + * it should set the process id using setPid(), and update + * the other relevant information using setParentPid(), setName(), + * setArguments() etc. + * + * Calls to isValid() will return true only if the process id + * has been set using setPid() + * + * @param pid The process id of the process to read + * @param readEnvironment Specifies whether the environment bindings + * for the process should be read + */ + virtual bool readProcessInfo(int pid , bool readEnvironment) = 0; + + /* Read the user name */ + virtual void readUserName(void) = 0; + + /** Sets the process id associated with this ProcessInfo instance */ + void setPid(int pid); + /** Sets the parent process id as returned by parentPid() */ + void setParentPid(int pid); + /** Sets the foreground process id as returend by foregroundPid() */ + void setForegroundPid(int pid); + /** Sets the user id associated with this ProcessInfo instance */ + void setUserId(int uid); + /** Sets the user name of the process as set by readUserName() */ + void setUserName(const QString& name); + /** Sets the name of the process as returned by name() */ + void setName(const QString& name); + /** Sets the current working directory for the process */ + void setCurrentDir(const QString& dir); + + /** Sets the error */ + void setError( Error error ); + + /** Convenience method. Sets the error based on a QFile error code. */ + void setFileError( QFile::FileError error ); + + /** + * Adds a commandline argument for the process, as returned + * by arguments() + */ + void addArgument(const QString& argument); + /** + * Adds an environment binding for the process, as returned by + * environment() + * + * @param name The name of the environment variable, eg. "PATH" + * @param value The value of the environment variable, eg. "/bin" + */ + void addEnvironmentBinding(const QString& name , const QString& value); + +private: + enum CommandFormat + { + ShortCommandFormat, + LongCommandFormat + }; + // takes a process name and its arguments and produces formatted output + QString formatCommand(const QString& name , const QVector<QString>& arguments , + CommandFormat format) const; + + // valid bits for _fields variable, ensure that + // _fields is changed to an int if more than 8 fields are added + enum FIELD_BITS + { + PROCESS_ID = 1, + PARENT_PID = 2, + FOREGROUND_PID = 4, + ARGUMENTS = 8, + ENVIRONMENT = 16, + NAME = 32, + CURRENT_DIR = 64, + UID =128 + }; + + char _fields; // a bitmap indicating which fields are valid + // used to set the "ok" parameters for the public + // accessor functions + + bool _enableEnvironmentRead; // specifies whether to read the environment + // bindings when update() is called + int _pid; + int _parentPid; + int _foregroundPid; + int _userId; + + Error _lastError; + + QString _name; + QString _userName; + QString _userHomeDir; + QString _currentDir; + + QVector<QString> _arguments; + QMap<QString,QString> _environment; +}; + +/** + * Implementation of ProcessInfo which does nothing. + * Used on platforms where a suitable ProcessInfo subclass is not + * available. + * + * isValid() will always return false for instances of NullProcessInfo + */ +class NullProcessInfo : public ProcessInfo +{ +public: + /** + * Constructs a new NullProcessInfo instance. + * See ProcessInfo::newInstance() + */ + explicit NullProcessInfo(int pid,bool readEnvironment = false); +protected: + virtual bool readProcessInfo(int pid,bool readEnvironment); + virtual void readUserName(void); +}; + +/** + * Implementation of ProcessInfo for Unix platforms which uses + * the /proc filesystem + */ +class UnixProcessInfo : public ProcessInfo +{ +public: + /** + * Constructs a new instance of UnixProcessInfo. + * See ProcessInfo::newInstance() + */ + explicit UnixProcessInfo(int pid,bool readEnvironment = false); + +protected: + /** + * Implementation of ProcessInfo::readProcessInfo(); calls the + * four private methods below in turn. + */ + virtual bool readProcessInfo(int pid , bool readEnvironment); + + virtual void readUserName(void); + +private: + /** + * Read the standard process information -- PID, parent PID, foreground PID. + * @param pid process ID to use + * @return true on success + */ + virtual bool readProcInfo(int pid)=0; + + /** + * Read the environment of the process. Sets _environment. + * @param pid process ID to use + * @return true on success + */ + virtual bool readEnvironment(int pid)=0; + + /** + * Determine what arguments were passed to the process. Sets _arguments. + * @param pid process ID to use + * @return true on success + */ + virtual bool readArguments(int pid)=0; + + /** + * Determine the current directory of the process. + * @param pid process ID to use + * @return true on success + */ + virtual bool readCurrentDir(int pid)=0; +}; + +/** + * Lightweight class which provides additional information about SSH processes. + */ +class SSHProcessInfo +{ +public: + /** + * Constructs a new SSHProcessInfo instance which provides additional + * information about the specified SSH process. + * + * @param process A ProcessInfo instance for a SSH process. + */ + SSHProcessInfo(const ProcessInfo& process); + + /** + * Returns the user name which the user initially logged into on + * the remote computer. + */ + QString userName() const; + + /** + * Returns the host which the user has connected to. + */ + QString host() const; + + /** + * Returns the command which the user specified to execute on the + * remote computer when starting the SSH process. + */ + QString command() const; + + /** + * Operates in the same way as ProcessInfo::format(), except + * that the set of markers understood is different: + * + * %u - Replaced with user name which the user initially logged + * into on the remote computer. + * %h - Replaced with the first part of the host name which + * is connected to. + * %H - Replaced with the full host name of the computer which + * is connected to. + * %c - Replaced with the command which the user specified + * to execute when starting the SSH process. + */ + QString format(const QString& input) const; + +private: + const ProcessInfo& _process; + QString _user; + QString _host; + QString _command; +}; + +#endif //PROCESSINFO_H + +/* + Local Variables: + mode: c++ + c-file-style: "stroustrup" + indent-tabs-mode: nil + tab-width: 4 + End: +*/
new file mode 100644 --- /dev/null +++ b/gui//src/Pty.cpp @@ -0,0 +1,310 @@ +/* + This file is part of Konsole, an X terminal. + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "kprocess_p.h" +#include "kptyprocess.h" +#include "Pty.h" + +// System +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <termios.h> +#include <signal.h> + +// Qt +#include <QtCore/QStringList> + +#include "kpty.h" +#include "kptydevice.h" + + +void Pty::setWindowSize(int lines, int cols) +{ + _windowColumns = cols; + _windowLines = lines; + + if (pty()->masterFd() >= 0) + pty()->setWinSize(lines, cols); +} +QSize Pty::windowSize() const +{ + return QSize(_windowColumns,_windowLines); +} + +void Pty::setFlowControlEnabled(bool enable) +{ + _xonXoff = enable; + + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + if (!enable) + ttmode.c_iflag &= ~(IXOFF | IXON); + else + ttmode.c_iflag |= (IXOFF | IXON); + //if (!pty()->tcSetAttr(&ttmode)) + // kWarning() << "Unable to set terminal attributes."; + } +} +bool Pty::flowControlEnabled() const +{ + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + return ttmode.c_iflag & IXOFF && + ttmode.c_iflag & IXON; + } + //kWarning() << "Unable to get flow control status, terminal not connected."; + return false; +} + +void Pty::setUtf8Mode(bool enable) +{ +#ifdef IUTF8 // XXX not a reasonable place to check it. + _utf8 = enable; + + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + if (!enable) + ttmode.c_iflag &= ~IUTF8; + else + ttmode.c_iflag |= IUTF8; + // if (!pty()->tcSetAttr(&ttmode)) + // kWarning() << "Unable to set terminal attributes."; + } +#endif +} + +void Pty::setErase(char erase) +{ + _eraseChar = erase; + + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + ttmode.c_cc[VERASE] = erase; + //if (!pty()->tcSetAttr(&ttmode)) + // kWarning() << "Unable to set terminal attributes."; + } +} + +char Pty::erase() const +{ + if (pty()->masterFd() >= 0) + { + struct ::termios ttyAttributes; + pty()->tcGetAttr(&ttyAttributes); + return ttyAttributes.c_cc[VERASE]; + } + + return _eraseChar; +} + +void Pty::addEnvironmentVariables(const QStringList& environment) +{ + QListIterator<QString> iter(environment); + while (iter.hasNext()) + { + QString pair = iter.next(); + + // split on the first '=' character + int pos = pair.indexOf('='); + + if ( pos >= 0 ) + { + QString variable = pair.left(pos); + QString value = pair.mid(pos+1); + + setEnv(variable,value); + } + } +} + +int Pty::start(const QString& program, + const QStringList& programArguments, + const QStringList& environment, + ulong winid, + bool addToUtmp, + const QString& dbusService, + const QString& dbusSession) +{ + clearProgram(); + + // For historical reasons, the first argument in programArguments is the + // name of the program to execute, so create a list consisting of all + // but the first argument to pass to setProgram() + Q_ASSERT(programArguments.count() >= 1); + setProgram(program.toLatin1(),programArguments.mid(1)); + + addEnvironmentVariables(environment); + + if ( !dbusService.isEmpty() ) + setEnv("KONSOLE_DBUS_SERVICE",dbusService); + if ( !dbusSession.isEmpty() ) + setEnv("KONSOLE_DBUS_SESSION", dbusSession); + + setEnv("WINDOWID", QString::number(winid)); + + // unless the LANGUAGE environment variable has been set explicitly + // set it to a null string + // this fixes the problem where KCatalog sets the LANGUAGE environment + // variable during the application's startup to something which + // differs from LANG,LC_* etc. and causes programs run from + // the terminal to display messages in the wrong language + // + // this can happen if LANG contains a language which KDE + // does not have a translation for + // + // BR:149300 + setEnv("LANGUAGE",QString(),false /* do not overwrite existing value if any */); + + setUseUtmp(addToUtmp); + + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + if (!_xonXoff) + ttmode.c_iflag &= ~(IXOFF | IXON); + else + ttmode.c_iflag |= (IXOFF | IXON); +#ifdef IUTF8 // XXX not a reasonable place to check it. + if (!_utf8) + ttmode.c_iflag &= ~IUTF8; + else + ttmode.c_iflag |= IUTF8; +#endif + + if (_eraseChar != 0) + ttmode.c_cc[VERASE] = _eraseChar; + + //if (!pty()->tcSetAttr(&ttmode)) + // kWarning() << "Unable to set terminal attributes."; + + pty()->setWinSize(_windowLines, _windowColumns); + + KProcess::start(); + + if (!waitForStarted()) + return -1; + + return 0; +} + +void Pty::setWriteable(bool writeable) +{ + //KDE_struct_stat sbuf; + struct stat sbuf; + //KDE_stat(pty()->ttyName(), &sbuf); + ::stat(pty()->ttyName(), &sbuf); + if (writeable) + chmod(pty()->ttyName(), sbuf.st_mode | S_IWGRP); + else + chmod(pty()->ttyName(), sbuf.st_mode & ~(S_IWGRP|S_IWOTH)); +} + +Pty::Pty(int masterFd, QObject* parent) + : KPtyProcess(masterFd,parent) +{ + init(); +} +Pty::Pty(QObject* parent) + : KPtyProcess(parent) +{ + init(); +} +void Pty::init() +{ + _windowColumns = 0; + _windowLines = 0; + _eraseChar = 0; + _xonXoff = true; + _utf8 =true; + + connect(pty(), SIGNAL(readyRead()) , this , SLOT(dataReceived())); + setPtyChannels(KPtyProcess::AllChannels); +} + +Pty::~Pty() +{ +} + +void Pty::sendData(const char* data, int length) +{ + if (!length) + return; + + if (!pty()->write(data,length)) + { + //kWarning() << "Pty::doSendJobs - Could not send input data to terminal process."; + return; + } +} + +void Pty::dataReceived() +{ + QByteArray data = pty()->readAll(); + emit receivedData(data.constData(),data.count()); +} + +void Pty::lockPty(bool lock) +{ + Q_UNUSED(lock); + +// TODO: Support for locking the Pty + //if (lock) + //suspend(); + //else + //resume(); +} + +int Pty::foregroundProcessGroup() const +{ + int pid = tcgetpgrp(pty()->masterFd()); + + if ( pid != -1 ) + { + return pid; + } + + return 0; +} + +void Pty::setupChildProcess() +{ + KPtyProcess::setupChildProcess(); + + // reset all signal handlers + // this ensures that terminal applications respond to + // signals generated via key sequences such as Ctrl+C + // (which sends SIGINT) + struct sigaction action; + sigemptyset(&action.sa_mask); + action.sa_handler = SIG_DFL; + action.sa_flags = 0; + for (int signal=1;signal < NSIG; signal++) + sigaction(signal,&action,0L); +}
new file mode 100644 --- /dev/null +++ b/gui//src/Pty.h @@ -0,0 +1,201 @@ +/* + This file is part of Konsole, KDE's terminal emulator. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef PTY_H +#define PTY_H + +// Qt +#include <QtCore/QStringList> +#include <QtCore/QVector> +#include <QtCore/QList> +#include <QtCore/QSize> + +// KDE +#include "kprocess.h" +#include "kptyprocess.h" + +/** + * The Pty class is used to start the terminal process, + * send data to it, receive data from it and manipulate + * various properties of the pseudo-teletype interface + * used to communicate with the process. + * + * To use this class, construct an instance and connect + * to the sendData slot and receivedData signal to + * send data to or receive data from the process. + * + * To start the terminal process, call the start() method + * with the program name and appropriate arguments. + */ +//class KONSOLEPRIVATE_EXPORT Pty: public KPtyProcess +class Pty: public KPtyProcess +{ +Q_OBJECT + + public: + + /** + * Constructs a new Pty. + * + * Connect to the sendData() slot and receivedData() signal to prepare + * for sending and receiving data from the terminal process. + * + * To start the terminal process, call the run() method with the + * name of the program to start and appropriate arguments. + */ + explicit Pty(QObject* parent = 0); + + /** + * Construct a process using an open pty master. + * See KPtyProcess::KPtyProcess() + */ + explicit Pty(int ptyMasterFd, QObject* parent = 0); + + ~Pty(); + + /** + * Starts the terminal process. + * + * Returns 0 if the process was started successfully or non-zero + * otherwise. + * + * @param program Path to the program to start + * @param arguments Arguments to pass to the program being started + * @param environment A list of key=value pairs which will be added + * to the environment for the new process. At the very least this + * should include an assignment for the TERM environment variable. + * @param winid Specifies the value of the WINDOWID environment variable + * in the process's environment. + * @param addToUtmp Specifies whether a utmp entry should be created for + * the pty used. See K3Process::setUsePty() + * @param dbusService Specifies the value of the KONSOLE_DBUS_SERVICE + * environment variable in the process's environment. + * @param dbusSession Specifies the value of the KONSOLE_DBUS_SESSION + * environment variable in the process's environment. + */ + int start( const QString& program, + const QStringList& arguments, + const QStringList& environment, + ulong winid, + bool addToUtmp, + const QString& dbusService, + const QString& dbusSession + ); + + /** TODO: Document me */ + void setWriteable(bool writeable); + + /** + * Enables or disables Xon/Xoff flow control. The flow control setting + * may be changed later by a terminal application, so flowControlEnabled() + * may not equal the value of @p on in the previous call to setFlowControlEnabled() + */ + void setFlowControlEnabled(bool on); + + /** Queries the terminal state and returns true if Xon/Xoff flow control is enabled. */ + bool flowControlEnabled() const; + + /** + * Sets the size of the window (in lines and columns of characters) + * used by this teletype. + */ + void setWindowSize(int lines, int cols); + + /** Returns the size of the window used by this teletype. See setWindowSize() */ + QSize windowSize() const; + + /** TODO Document me */ + void setErase(char erase); + + /** */ + char erase() const; + + /** + * Returns the process id of the teletype's current foreground + * process. This is the process which is currently reading + * input sent to the terminal via. sendData() + * + * If there is a problem reading the foreground process group, + * 0 will be returned. + */ + int foregroundProcessGroup() const; + + public slots: + + /** + * Put the pty into UTF-8 mode on systems which support it. + */ + void setUtf8Mode(bool on); + + /** + * Suspend or resume processing of data from the standard + * output of the terminal process. + * + * See K3Process::suspend() and K3Process::resume() + * + * @param lock If true, processing of output is suspended, + * otherwise processing is resumed. + */ + void lockPty(bool lock); + + /** + * Sends data to the process currently controlling the + * teletype ( whose id is returned by foregroundProcessGroup() ) + * + * @param buffer Pointer to the data to send. + * @param length Length of @p buffer. + */ + void sendData(const char* buffer, int length); + + signals: + + /** + * Emitted when a new block of data is received from + * the teletype. + * + * @param buffer Pointer to the data received. + * @param length Length of @p buffer + */ + void receivedData(const char* buffer, int length); + + protected: + void setupChildProcess(); + + private slots: + // called when data is received from the terminal process + void dataReceived(); + + private: + void init(); + + // takes a list of key=value pairs and adds them + // to the environment for the process + void addEnvironmentVariables(const QStringList& environment); + + int _windowColumns; + int _windowLines; + char _eraseChar; + bool _xonXoff; + bool _utf8; +}; + +#endif // PTY_H
new file mode 100644 --- /dev/null +++ b/gui//src/QTerminalWidget.cpp @@ -0,0 +1,191 @@ +/* Copyright (C) 2008 e_k (e_k@users.sourceforge.net) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "QTerminalWidget.h" +#include "Session.h" +#include "TerminalDisplay.h" + +struct TermWidgetImpl +{ + TermWidgetImpl(QWidget* parent = 0); + + TerminalDisplay *m_terminalDisplay; + Session *m_session; + Session* createSession(); + TerminalDisplay* createTerminalDisplay(Session *session, QWidget* parent); +}; + +TermWidgetImpl::TermWidgetImpl(QWidget* parent) +{ + QPalette palette = QApplication::palette(); + m_session = createSession(); + m_terminalDisplay = createTerminalDisplay(this->m_session, parent); + m_terminalDisplay->setBackgroundColor(palette.color(QPalette::Base)); + m_terminalDisplay->setForegroundColor(palette.color(QPalette::Text)); +} + +Session *TermWidgetImpl::createSession() +{ + Session *session = new Session(); + session->setTitle(Session::NameRole, "QTerminalWidget"); + session->setProgram("/bin/bash"); + session->setArguments(QStringList()); + session->setAutoClose(true); + session->setCodec(QTextCodec::codecForName("UTF-8")); + session->setFlowControlEnabled(true); + session->setHistoryType(HistoryTypeBuffer(1000)); + session->setDarkBackground(true); + session->setKeyBindings(""); + return session; +} + +TerminalDisplay *TermWidgetImpl::createTerminalDisplay(Session *session, QWidget* parent) +{ + TerminalDisplay* display = new TerminalDisplay(parent); + display->setBellMode(TerminalDisplay::NotifyBell); + display->setTerminalSizeHint(true); + display->setTripleClickMode(TerminalDisplay::SelectWholeLine); + display->setTerminalSizeStartup(true); + display->setRandomSeed(session->sessionId() * 31); + return display; +} + +QTerminalWidget::QTerminalWidget(int startnow, QWidget *parent) + :QWidget(parent) +{ + m_impl = new TermWidgetImpl(this); + + initialize(); + + if(startnow && m_impl->m_session) { + m_impl->m_session->run(); + } + + setFocus(Qt::OtherFocusReason); + m_impl->m_terminalDisplay->resize(this->size()); + setFocusProxy(m_impl->m_terminalDisplay); +} + +void QTerminalWidget::startShellProgram() +{ + if(m_impl->m_session->isRunning()) + return; + + m_impl->m_session->run(); +} + +void QTerminalWidget::initialize() +{ + m_impl->m_terminalDisplay->setSize(80, 40); + + QFont font = QApplication::font(); + font.setFamily("Monospace"); + font.setPointSize(10); + font.setStyleHint(QFont::TypeWriter); + setTerminalFont(font); + setScrollBarPosition(NoScrollBar); + + m_impl->m_session->addView(m_impl->m_terminalDisplay); + + connect(m_impl->m_session, SIGNAL(finished()), this, SLOT(sessionFinished())); +} + +QTerminalWidget::~QTerminalWidget() +{ + emit destroyed(); +} + +void QTerminalWidget::setTerminalFont(QFont &font) +{ + if (!m_impl->m_terminalDisplay) + return; + m_impl->m_terminalDisplay->setVTFont(font); +} + +void QTerminalWidget::setShellProgram(QString progname) +{ + if (!m_impl->m_session) + return; + m_impl->m_session->setProgram(progname); +} + +void QTerminalWidget::openTeletype(int fd) +{ + if ( m_impl->m_session->isRunning() ) + return; + + m_impl->m_session->openTeletype(fd); +} + +void QTerminalWidget::setArgs(QStringList &args) +{ + if (!m_impl->m_session) + return; + m_impl->m_session->setArguments(args); +} + +void QTerminalWidget::setTextCodec(QTextCodec *codec) +{ + if (!m_impl->m_session) + return; + m_impl->m_session->setCodec(codec); +} + +void QTerminalWidget::setSize(int h, int v) +{ + if (!m_impl->m_terminalDisplay) + return; + m_impl->m_terminalDisplay->setSize(h, v); +} + +void QTerminalWidget::setHistorySize(int lines) +{ + if (lines < 0) + m_impl->m_session->setHistoryType(HistoryTypeFile()); + else + m_impl->m_session->setHistoryType(HistoryTypeBuffer(lines)); +} + +void QTerminalWidget::setScrollBarPosition(ScrollBarPosition pos) +{ + if (!m_impl->m_terminalDisplay) + return; + m_impl->m_terminalDisplay->setScrollBarPosition((TerminalDisplay::ScrollBarPosition)pos); +} + +void QTerminalWidget::sendText(const QString &text) +{ + m_impl->m_session->sendText(text); +} + +void QTerminalWidget::installEventFilterOnDisplay(QObject *object) { + m_impl->m_terminalDisplay->installEventFilter(object); +} + +void QTerminalWidget::resizeEvent(QResizeEvent*) +{ + m_impl->m_terminalDisplay->resize(this->size()); + m_impl->m_terminalDisplay->update(); +} + +void QTerminalWidget::sessionFinished() +{ + emit finished(); +} + +
new file mode 100644 --- /dev/null +++ b/gui//src/QTerminalWidget.h @@ -0,0 +1,93 @@ +/* Copyright (C) 2008 e_k (e_k@users.sourceforge.net) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef QTERMINALWIDGET_H +#define QTERMINALWIDGET_H + +#include <QtGui> + +struct TermWidgetImpl; +/** + * \class QTerminalWidget + * This class forms a widget class that can be inserted into other widgets. + */ +class QTerminalWidget : public QWidget +{ + Q_OBJECT +public: + /** + * \enum ScrollBarPosition + * Defines the scrollbar position of the terminal. + */ + enum ScrollBarPosition + { + NoScrollBar, + ScrollBarLeft, + ScrollBarRight + }; + + QTerminalWidget(int startnow = 1, QWidget *parent = 0); + ~QTerminalWidget(); + + void startShellProgram(); + void openTeletype(int fd); + + /** Default is application font with family Monospace, size 10. */ + void setTerminalFont(QFont &font); + + /** Shell program, default is /bin/bash. */ + void setShellProgram(QString progname); + + /** Shell program args, default is none. */ + void setArgs(QStringList &args); + + /** Text codec, default is UTF-8. */ + void setTextCodec(QTextCodec *codec); + + /** Resize terminal widget. */ + void setSize(int h, int v); + + /** History size for scrolling, values below zero mean infinite. */ + void setHistorySize(int lines); + + /** Presence of scrollbar. By default, there is no scrollbar present. */ + void setScrollBarPosition(ScrollBarPosition); + + /** Send some text to the terminal. */ + void sendText(const QString &text); + + /** Installs an event filter onto the display. */ + void installEventFilterOnDisplay(QObject *object); + +signals: + /** Emitted, when the current program has finished. */ + void finished(); + +protected: + virtual void resizeEvent(QResizeEvent *); + +protected slots: + void sessionFinished(); + +private: + /** Performs initial operations on this widget. */ + void initialize(); + TermWidgetImpl *m_impl; +}; + +#endif // QTERMINALWIDGET_H
new file mode 100644 --- /dev/null +++ b/gui//src/Quint.cpp @@ -0,0 +1,41 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include <QtGui/QApplication> +#include <QTranslator> +#include <QSettings> +#include "MainWindow.h" + +int main(int argc, char *argv[]) +{ + QApplication application(argc, argv); + + QDesktopServices desktopServices; + QSettings settings( + desktopServices.storageLocation(QDesktopServices::HomeLocation) + + "/.quint/settings.ini", QSettings::IniFormat); + + QTranslator translator; + translator.load(QString("../languages/%1.qm").arg(settings.value("application/language").toString())); + application.installTranslator(&translator); + + MainWindow w; + w.show(); + + return application.exec(); +}
new file mode 100644 --- /dev/null +++ b/gui//src/Screen.cpp @@ -0,0 +1,1356 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight <robert.knight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + */ + +// Own +#include "Screen.h" + +// Standard +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include <ctype.h> + +// Qt +#include <QtCore/QTextStream> +#include <QtCore/QDate> + + +// Konsole +#include "konsole_wcwidth.h" +#include "TerminalCharacterDecoder.h" + +//FIXME: this is emulation specific. Use false for xterm, true for ANSI. +//FIXME: see if we can get this from terminfo. +#define BS_CLEARS false + +//Macro to convert x,y position on screen to position within an image. +// +//Originally the image was stored as one large contiguous block of +//memory, so a position within the image could be represented as an +//offset from the beginning of the block. For efficiency reasons this +//is no longer the case. +//Many internal parts of this class still use this representation for parameters and so on, +//notably moveImage() and clearImage(). +//This macro converts from an X,Y position into an image offset. +#ifndef loc +#define loc(X,Y) ((Y)*columns+(X)) +#endif + + +Character Screen::defaultChar = Character(' ', + CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR), + CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR), + DEFAULT_RENDITION); + +//#define REVERSE_WRAPPED_LINES // for wrapped line debug + + Screen::Screen(int l, int c) +: lines(l), + columns(c), + screenLines(new ImageLine[lines+1] ), + _scrolledLines(0), + _droppedLines(0), + history(new HistoryScrollNone()), + cuX(0), cuY(0), + currentRendition(0), + _topMargin(0), _bottomMargin(0), + selBegin(0), selTopLeft(0), selBottomRight(0), + blockSelectionMode(false), + effectiveForeground(CharacterColor()), effectiveBackground(CharacterColor()), effectiveRendition(0), + lastPos(-1) +{ + lineProperties.resize(lines+1); + for (int i=0;i<lines+1;i++) + lineProperties[i]=LINE_DEFAULT; + + initTabStops(); + clearSelection(); + reset(); +} + +/*! Destructor +*/ + +Screen::~Screen() +{ + delete[] screenLines; + delete history; +} + +void Screen::cursorUp(int n) + //=CUU +{ + if (n == 0) n = 1; // Default + int stop = cuY < _topMargin ? 0 : _topMargin; + cuX = qMin(columns-1,cuX); // nowrap! + cuY = qMax(stop,cuY-n); +} + +void Screen::cursorDown(int n) + //=CUD +{ + if (n == 0) n = 1; // Default + int stop = cuY > _bottomMargin ? lines-1 : _bottomMargin; + cuX = qMin(columns-1,cuX); // nowrap! + cuY = qMin(stop,cuY+n); +} + +void Screen::cursorLeft(int n) + //=CUB +{ + if (n == 0) n = 1; // Default + cuX = qMin(columns-1,cuX); // nowrap! + cuX = qMax(0,cuX-n); +} + +void Screen::cursorRight(int n) + //=CUF +{ + if (n == 0) n = 1; // Default + cuX = qMin(columns-1,cuX+n); +} + +void Screen::setMargins(int top, int bot) + //=STBM +{ + if (top == 0) top = 1; // Default + if (bot == 0) bot = lines; // Default + top = top - 1; // Adjust to internal lineno + bot = bot - 1; // Adjust to internal lineno + if ( !( 0 <= top && top < bot && bot < lines ) ) + { //Debug()<<" setRegion("<<top<<","<<bot<<") : bad range."; + return; // Default error action: ignore + } + _topMargin = top; + _bottomMargin = bot; + cuX = 0; + cuY = getMode(MODE_Origin) ? top : 0; + +} + +int Screen::topMargin() const +{ + return _topMargin; +} +int Screen::bottomMargin() const +{ + return _bottomMargin; +} + +void Screen::index() + //=IND +{ + if (cuY == _bottomMargin) + scrollUp(1); + else if (cuY < lines-1) + cuY += 1; +} + +void Screen::reverseIndex() + //=RI +{ + if (cuY == _topMargin) + scrollDown(_topMargin,1); + else if (cuY > 0) + cuY -= 1; +} + +void Screen::nextLine() + //=NEL +{ + toStartOfLine(); index(); +} + +void Screen::eraseChars(int n) +{ + if (n == 0) n = 1; // Default + int p = qMax(0,qMin(cuX+n-1,columns-1)); + clearImage(loc(cuX,cuY),loc(p,cuY),' '); +} + +void Screen::deleteChars(int n) +{ + Q_ASSERT( n >= 0 ); + + // always delete at least one char + if (n == 0) + n = 1; + + // if cursor is beyond the end of the line there is nothing to do + if ( cuX >= screenLines[cuY].count() ) + return; + + if ( cuX+n > screenLines[cuY].count() ) + n = screenLines[cuY].count() - cuX; + + Q_ASSERT( n >= 0 ); + Q_ASSERT( cuX+n <= screenLines[cuY].count() ); + + screenLines[cuY].remove(cuX,n); +} + +void Screen::insertChars(int n) +{ + if (n == 0) n = 1; // Default + + if ( screenLines[cuY].size() < cuX ) + screenLines[cuY].resize(cuX); + + screenLines[cuY].insert(cuX,n,' '); + + if ( screenLines[cuY].count() > columns ) + screenLines[cuY].resize(columns); +} + +void Screen::deleteLines(int n) +{ + if (n == 0) n = 1; // Default + scrollUp(cuY,n); +} + +void Screen::insertLines(int n) +{ + if (n == 0) n = 1; // Default + scrollDown(cuY,n); +} + +void Screen::setMode(int m) +{ + currentModes[m] = true; + switch(m) + { + case MODE_Origin : cuX = 0; cuY = _topMargin; break; //FIXME: home + } +} + +void Screen::resetMode(int m) +{ + currentModes[m] = false; + switch(m) + { + case MODE_Origin : cuX = 0; cuY = 0; break; //FIXME: home + } +} + +void Screen::saveMode(int m) +{ + savedModes[m] = currentModes[m]; +} + +void Screen::restoreMode(int m) +{ + currentModes[m] = savedModes[m]; +} + +bool Screen::getMode(int m) const +{ + return currentModes[m]; +} + +void Screen::saveCursor() +{ + savedState.cursorColumn = cuX; + savedState.cursorLine = cuY; + savedState.rendition = currentRendition; + savedState.foreground = currentForeground; + savedState.background = currentBackground; +} + +void Screen::restoreCursor() +{ + cuX = qMin(savedState.cursorColumn,columns-1); + cuY = qMin(savedState.cursorLine,lines-1); + currentRendition = savedState.rendition; + currentForeground = savedState.foreground; + currentBackground = savedState.background; + updateEffectiveRendition(); +} + +void Screen::resizeImage(int new_lines, int new_columns) +{ + if ((new_lines==lines) && (new_columns==columns)) return; + + if (cuY > new_lines-1) + { // attempt to preserve focus and lines + _bottomMargin = lines-1; //FIXME: margin lost + for (int i = 0; i < cuY-(new_lines-1); i++) + { + addHistLine(); scrollUp(0,1); + } + } + + // create new screen lines and copy from old to new + + ImageLine* newScreenLines = new ImageLine[new_lines+1]; + for (int i=0; i < qMin(lines-1,new_lines+1) ;i++) + newScreenLines[i]=screenLines[i]; + for (int i=lines;(i > 0) && (i<new_lines+1);i++) + newScreenLines[i].resize( new_columns ); + + lineProperties.resize(new_lines+1); + for (int i=lines;(i > 0) && (i<new_lines+1);i++) + lineProperties[i] = LINE_DEFAULT; + + clearSelection(); + + delete[] screenLines; + screenLines = newScreenLines; + + lines = new_lines; + columns = new_columns; + cuX = qMin(cuX,columns-1); + cuY = qMin(cuY,lines-1); + + // FIXME: try to keep values, evtl. + _topMargin=0; + _bottomMargin=lines-1; + initTabStops(); + clearSelection(); +} + +void Screen::setDefaultMargins() +{ + _topMargin = 0; + _bottomMargin = lines-1; +} + + +/* + Clarifying rendition here and in the display. + + currently, the display's color table is + 0 1 2 .. 9 10 .. 17 + dft_fg, dft_bg, dim 0..7, intensive 0..7 + + currentForeground, currentBackground contain values 0..8; + - 0 = default color + - 1..8 = ansi specified color + + re_fg, re_bg contain values 0..17 + due to the TerminalDisplay's color table + + rendition attributes are + + attr widget screen + -------------- ------ ------ + RE_UNDERLINE XX XX affects foreground only + RE_BLINK XX XX affects foreground only + RE_BOLD XX XX affects foreground only + RE_REVERSE -- XX + RE_TRANSPARENT XX -- affects background only + RE_INTENSIVE XX -- affects foreground only + + Note that RE_BOLD is used in both widget + and screen rendition. Since xterm/vt102 + is to poor to distinguish between bold + (which is a font attribute) and intensive + (which is a color attribute), we translate + this and RE_BOLD in falls eventually appart + into RE_BOLD and RE_INTENSIVE. + */ + +void Screen::reverseRendition(Character& p) const +{ + CharacterColor f = p.foregroundColor; + CharacterColor b = p.backgroundColor; + + p.foregroundColor = b; + p.backgroundColor = f; //p->r &= ~RE_TRANSPARENT; +} + +void Screen::updateEffectiveRendition() +{ + effectiveRendition = currentRendition; + if (currentRendition & RE_REVERSE) + { + effectiveForeground = currentBackground; + effectiveBackground = currentForeground; + } + else + { + effectiveForeground = currentForeground; + effectiveBackground = currentBackground; + } + + if (currentRendition & RE_BOLD) + effectiveForeground.toggleIntensive(); +} + +void Screen::copyFromHistory(Character* dest, int startLine, int count) const +{ + Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= history->getLines() ); + + for (int line = startLine; line < startLine + count; line++) + { + const int length = qMin(columns,history->getLineLen(line)); + const int destLineOffset = (line-startLine)*columns; + + history->getCells(line,0,length,dest + destLineOffset); + + for (int column = length; column < columns; column++) + dest[destLineOffset+column] = defaultChar; + + // invert selected text + if (selBegin !=-1) + { + for (int column = 0; column < columns; column++) + { + if (isSelected(column,line)) + { + reverseRendition(dest[destLineOffset + column]); + } + } + } + } +} + +void Screen::copyFromScreen(Character* dest , int startLine , int count) const +{ + Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= lines ); + + for (int line = startLine; line < (startLine+count) ; line++) + { + int srcLineStartIndex = line*columns; + int destLineStartIndex = (line-startLine)*columns; + + for (int column = 0; column < columns; column++) + { + int srcIndex = srcLineStartIndex + column; + int destIndex = destLineStartIndex + column; + + dest[destIndex] = screenLines[srcIndex/columns].value(srcIndex%columns,defaultChar); + + // invert selected text + if (selBegin != -1 && isSelected(column,line + history->getLines())) + reverseRendition(dest[destIndex]); + } + + } +} + +void Screen::getImage( Character* dest, int size, int startLine, int endLine ) const +{ + Q_ASSERT( startLine >= 0 ); + Q_ASSERT( endLine >= startLine && endLine < history->getLines() + lines ); + + const int mergedLines = endLine - startLine + 1; + + Q_ASSERT( size >= mergedLines * columns ); + Q_UNUSED( size ); + + const int linesInHistoryBuffer = qBound(0,history->getLines()-startLine,mergedLines); + const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer; + + // copy lines from history buffer + if (linesInHistoryBuffer > 0) + copyFromHistory(dest,startLine,linesInHistoryBuffer); + + // copy lines from screen buffer + if (linesInScreenBuffer > 0) + copyFromScreen(dest + linesInHistoryBuffer*columns, + startLine + linesInHistoryBuffer - history->getLines(), + linesInScreenBuffer); + + // invert display when in screen mode + if (getMode(MODE_Screen)) + { + for (int i = 0; i < mergedLines*columns; i++) + reverseRendition(dest[i]); // for reverse display + } + + // mark the character at the current cursor position + int cursorIndex = loc(cuX, cuY + linesInHistoryBuffer); + if(getMode(MODE_Cursor) && cursorIndex < columns*mergedLines) + dest[cursorIndex].rendition |= RE_CURSOR; +} + +QVector<LineProperty> Screen::getLineProperties( int startLine , int endLine ) const +{ + Q_ASSERT( startLine >= 0 ); + Q_ASSERT( endLine >= startLine && endLine < history->getLines() + lines ); + + const int mergedLines = endLine-startLine+1; + const int linesInHistory = qBound(0,history->getLines()-startLine,mergedLines); + const int linesInScreen = mergedLines - linesInHistory; + + QVector<LineProperty> result(mergedLines); + int index = 0; + + // copy properties for lines in history + for (int line = startLine; line < startLine + linesInHistory; line++) + { + //TODO Support for line properties other than wrapped lines + if (history->isWrappedLine(line)) + { + result[index] = (LineProperty)(result[index] | LINE_WRAPPED); + } + index++; + } + + // copy properties for lines in screen buffer + const int firstScreenLine = startLine + linesInHistory - history->getLines(); + for (int line = firstScreenLine; line < firstScreenLine+linesInScreen; line++) + { + result[index]=lineProperties[line]; + index++; + } + + return result; +} + +void Screen::reset(bool clearScreen) +{ + setMode(MODE_Wrap ); saveMode(MODE_Wrap ); // wrap at end of margin + resetMode(MODE_Origin); saveMode(MODE_Origin); // position refere to [1,1] + resetMode(MODE_Insert); saveMode(MODE_Insert); // overstroke + setMode(MODE_Cursor); // cursor visible + resetMode(MODE_Screen); // screen not inverse + resetMode(MODE_NewLine); + + _topMargin=0; + _bottomMargin=lines-1; + + setDefaultRendition(); + saveCursor(); + + if ( clearScreen ) + clear(); +} + +void Screen::clear() +{ + clearEntireScreen(); + home(); +} + +void Screen::backspace() +{ + cuX = qMin(columns-1,cuX); // nowrap! + cuX = qMax(0,cuX-1); + + if (screenLines[cuY].size() < cuX+1) + screenLines[cuY].resize(cuX+1); + + if (BS_CLEARS) + screenLines[cuY][cuX].character = ' '; +} + +void Screen::tab(int n) +{ + // note that TAB is a format effector (does not write ' '); + if (n == 0) n = 1; + while((n > 0) && (cuX < columns-1)) + { + cursorRight(1); + while((cuX < columns-1) && !tabStops[cuX]) + cursorRight(1); + n--; + } +} + +void Screen::backtab(int n) +{ + // note that TAB is a format effector (does not write ' '); + if (n == 0) n = 1; + while((n > 0) && (cuX > 0)) + { + cursorLeft(1); while((cuX > 0) && !tabStops[cuX]) cursorLeft(1); + n--; + } +} + +void Screen::clearTabStops() +{ + for (int i = 0; i < columns; i++) tabStops[i] = false; +} + +void Screen::changeTabStop(bool set) +{ + if (cuX >= columns) return; + tabStops[cuX] = set; +} + +void Screen::initTabStops() +{ + tabStops.resize(columns); + + // Arrg! The 1st tabstop has to be one longer than the other. + // i.e. the kids start counting from 0 instead of 1. + // Other programs might behave correctly. Be aware. + for (int i = 0; i < columns; i++) + tabStops[i] = (i%8 == 0 && i != 0); +} + +void Screen::newLine() +{ + if (getMode(MODE_NewLine)) + toStartOfLine(); + index(); +} + +void Screen::checkSelection(int from, int to) +{ + if (selBegin == -1) + return; + int scr_TL = loc(0, history->getLines()); + //Clear entire selection if it overlaps region [from, to] + if ( (selBottomRight >= (from+scr_TL)) && (selTopLeft <= (to+scr_TL)) ) + clearSelection(); +} + +void Screen::displayCharacter(unsigned short c) +{ + // Note that VT100 does wrapping BEFORE putting the character. + // This has impact on the assumption of valid cursor positions. + // We indicate the fact that a newline has to be triggered by + // putting the cursor one right to the last column of the screen. + + int w = konsole_wcwidth(c); + if (w <= 0) + return; + + if (cuX+w > columns) { + if (getMode(MODE_Wrap)) { + lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | LINE_WRAPPED); + nextLine(); + } + else + cuX = columns-w; + } + + // ensure current line vector has enough elements + int size = screenLines[cuY].size(); + if (size < cuX+w) + { + screenLines[cuY].resize(cuX+w); + } + + if (getMode(MODE_Insert)) insertChars(w); + + lastPos = loc(cuX,cuY); + + // check if selection is still valid. + checkSelection(lastPos, lastPos); + + Character& currentChar = screenLines[cuY][cuX]; + + currentChar.character = c; + currentChar.foregroundColor = effectiveForeground; + currentChar.backgroundColor = effectiveBackground; + currentChar.rendition = effectiveRendition; + + int i = 0; + int newCursorX = cuX + w--; + while(w) + { + i++; + + if ( screenLines[cuY].size() < cuX + i + 1 ) + screenLines[cuY].resize(cuX+i+1); + + Character& ch = screenLines[cuY][cuX + i]; + ch.character = 0; + ch.foregroundColor = effectiveForeground; + ch.backgroundColor = effectiveBackground; + ch.rendition = effectiveRendition; + + w--; + } + cuX = newCursorX; +} + +void Screen::compose(const QString& /*compose*/) +{ + Q_ASSERT( 0 /*Not implemented yet*/ ); + + /* if (lastPos == -1) + return; + + QChar c(image[lastPos].character); + compose.prepend(c); + //compose.compose(); ### FIXME! + image[lastPos].character = compose[0].unicode();*/ +} + +int Screen::scrolledLines() const +{ + return _scrolledLines; +} +int Screen::droppedLines() const +{ + return _droppedLines; +} +void Screen::resetDroppedLines() +{ + _droppedLines = 0; +} +void Screen::resetScrolledLines() +{ + _scrolledLines = 0; +} + +void Screen::scrollUp(int n) +{ + if (n == 0) n = 1; // Default + if (_topMargin == 0) addHistLine(); // history.history + scrollUp(_topMargin, n); +} + +QRect Screen::lastScrolledRegion() const +{ + return _lastScrolledRegion; +} + +void Screen::scrollUp(int from, int n) +{ + if (n <= 0 || from + n > _bottomMargin) return; + + _scrolledLines -= n; + _lastScrolledRegion = QRect(0,_topMargin,columns-1,(_bottomMargin-_topMargin)); + + //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. + moveImage(loc(0,from),loc(0,from+n),loc(columns-1,_bottomMargin)); + clearImage(loc(0,_bottomMargin-n+1),loc(columns-1,_bottomMargin),' '); +} + +void Screen::scrollDown(int n) +{ + if (n == 0) n = 1; // Default + scrollDown(_topMargin, n); +} + +void Screen::scrollDown(int from, int n) +{ + _scrolledLines += n; + + //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. + if (n <= 0) + return; + if (from > _bottomMargin) + return; + if (from + n > _bottomMargin) + n = _bottomMargin - from; + moveImage(loc(0,from+n),loc(0,from),loc(columns-1,_bottomMargin-n)); + clearImage(loc(0,from),loc(columns-1,from+n-1),' '); +} + +void Screen::setCursorYX(int y, int x) +{ + setCursorY(y); setCursorX(x); +} + +void Screen::setCursorX(int x) +{ + if (x == 0) x = 1; // Default + x -= 1; // Adjust + cuX = qMax(0,qMin(columns-1, x)); +} + +void Screen::setCursorY(int y) +{ + if (y == 0) y = 1; // Default + y -= 1; // Adjust + cuY = qMax(0,qMin(lines -1, y + (getMode(MODE_Origin) ? _topMargin : 0) )); +} + +void Screen::home() +{ + cuX = 0; + cuY = 0; +} + +void Screen::toStartOfLine() +{ + cuX = 0; +} + +int Screen::getCursorX() const +{ + return cuX; +} + +int Screen::getCursorY() const +{ + return cuY; +} + +void Screen::clearImage(int loca, int loce, char c) +{ + int scr_TL=loc(0,history->getLines()); + //FIXME: check positions + + //Clear entire selection if it overlaps region to be moved... + if ( (selBottomRight > (loca+scr_TL) )&&(selTopLeft < (loce+scr_TL)) ) + { + clearSelection(); + } + + int topLine = loca/columns; + int bottomLine = loce/columns; + + Character clearCh(c,currentForeground,currentBackground,DEFAULT_RENDITION); + + //if the character being used to clear the area is the same as the + //default character, the affected lines can simply be shrunk. + bool isDefaultCh = (clearCh == Character()); + + for (int y=topLine;y<=bottomLine;y++) + { + lineProperties[y] = 0; + + int endCol = ( y == bottomLine) ? loce%columns : columns-1; + int startCol = ( y == topLine ) ? loca%columns : 0; + + QVector<Character>& line = screenLines[y]; + + if ( isDefaultCh && endCol == columns-1 ) + { + line.resize(startCol); + } + else + { + if (line.size() < endCol + 1) + line.resize(endCol+1); + + Character* data = line.data(); + for (int i=startCol;i<=endCol;i++) + data[i]=clearCh; + } + } +} + +void Screen::moveImage(int dest, int sourceBegin, int sourceEnd) +{ + Q_ASSERT( sourceBegin <= sourceEnd ); + + int lines=(sourceEnd-sourceBegin)/columns; + + //move screen image and line properties: + //the source and destination areas of the image may overlap, + //so it matters that we do the copy in the right order - + //forwards if dest < sourceBegin or backwards otherwise. + //(search the web for 'memmove implementation' for details) + if (dest < sourceBegin) + { + for (int i=0;i<=lines;i++) + { + screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ]; + lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i]; + } + } + else + { + for (int i=lines;i>=0;i--) + { + screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ]; + lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i]; + } + } + + if (lastPos != -1) + { + int diff = dest - sourceBegin; // Scroll by this amount + lastPos += diff; + if ((lastPos < 0) || (lastPos >= (lines*columns))) + lastPos = -1; + } + + // Adjust selection to follow scroll. + if (selBegin != -1) + { + bool beginIsTL = (selBegin == selTopLeft); + int diff = dest - sourceBegin; // Scroll by this amount + int scr_TL=loc(0,history->getLines()); + int srca = sourceBegin+scr_TL; // Translate index from screen to global + int srce = sourceEnd+scr_TL; // Translate index from screen to global + int desta = srca+diff; + int deste = srce+diff; + + if ((selTopLeft >= srca) && (selTopLeft <= srce)) + selTopLeft += diff; + else if ((selTopLeft >= desta) && (selTopLeft <= deste)) + selBottomRight = -1; // Clear selection (see below) + + if ((selBottomRight >= srca) && (selBottomRight <= srce)) + selBottomRight += diff; + else if ((selBottomRight >= desta) && (selBottomRight <= deste)) + selBottomRight = -1; // Clear selection (see below) + + if (selBottomRight < 0) + { + clearSelection(); + } + else + { + if (selTopLeft < 0) + selTopLeft = 0; + } + + if (beginIsTL) + selBegin = selTopLeft; + else + selBegin = selBottomRight; + } +} + +void Screen::clearToEndOfScreen() +{ + clearImage(loc(cuX,cuY),loc(columns-1,lines-1),' '); +} + +void Screen::clearToBeginOfScreen() +{ + clearImage(loc(0,0),loc(cuX,cuY),' '); +} + +void Screen::clearEntireScreen() +{ + // Add entire screen to history + for (int i = 0; i < (lines-1); i++) + { + addHistLine(); scrollUp(0,1); + } + + clearImage(loc(0,0),loc(columns-1,lines-1),' '); +} + +/*! fill screen with 'E' + This is to aid screen alignment + */ + +void Screen::helpAlign() +{ + clearImage(loc(0,0),loc(columns-1,lines-1),'E'); +} + +void Screen::clearToEndOfLine() +{ + clearImage(loc(cuX,cuY),loc(columns-1,cuY),' '); +} + +void Screen::clearToBeginOfLine() +{ + clearImage(loc(0,cuY),loc(cuX,cuY),' '); +} + +void Screen::clearEntireLine() +{ + clearImage(loc(0,cuY),loc(columns-1,cuY),' '); +} + +void Screen::setRendition(int re) +{ + currentRendition |= re; + updateEffectiveRendition(); +} + +void Screen::resetRendition(int re) +{ + currentRendition &= ~re; + updateEffectiveRendition(); +} + +void Screen::setDefaultRendition() +{ + setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR); + setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR); + currentRendition = DEFAULT_RENDITION; + updateEffectiveRendition(); +} + +void Screen::setForeColor(int space, int color) +{ + currentForeground = CharacterColor(space, color); + + if ( currentForeground.isValid() ) + updateEffectiveRendition(); + else + setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR); +} + +void Screen::setBackColor(int space, int color) +{ + currentBackground = CharacterColor(space, color); + + if ( currentBackground.isValid() ) + updateEffectiveRendition(); + else + setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR); +} + +void Screen::clearSelection() +{ + selBottomRight = -1; + selTopLeft = -1; + selBegin = -1; +} + +void Screen::getSelectionStart(int& column , int& line) const +{ + if ( selTopLeft != -1 ) + { + column = selTopLeft % columns; + line = selTopLeft / columns; + } + else + { + column = cuX + getHistLines(); + line = cuY + getHistLines(); + } +} +void Screen::getSelectionEnd(int& column , int& line) const +{ + if ( selBottomRight != -1 ) + { + column = selBottomRight % columns; + line = selBottomRight / columns; + } + else + { + column = cuX + getHistLines(); + line = cuY + getHistLines(); + } +} +void Screen::setSelectionStart(const int x, const int y, const bool mode) +{ + selBegin = loc(x,y); + /* FIXME, HACK to correct for x too far to the right... */ + if (x == columns) selBegin--; + + selBottomRight = selBegin; + selTopLeft = selBegin; + blockSelectionMode = mode; +} + +void Screen::setSelectionEnd( const int x, const int y) +{ + if (selBegin == -1) + return; + + int endPos = loc(x,y); + + if (endPos < selBegin) + { + selTopLeft = endPos; + selBottomRight = selBegin; + } + else + { + /* FIXME, HACK to correct for x too far to the right... */ + if (x == columns) + endPos--; + + selTopLeft = selBegin; + selBottomRight = endPos; + } + + // Normalize the selection in column mode + if (blockSelectionMode) + { + int topRow = selTopLeft / columns; + int topColumn = selTopLeft % columns; + int bottomRow = selBottomRight / columns; + int bottomColumn = selBottomRight % columns; + + selTopLeft = loc(qMin(topColumn,bottomColumn),topRow); + selBottomRight = loc(qMax(topColumn,bottomColumn),bottomRow); + } +} + +bool Screen::isSelected( const int x,const int y) const +{ + bool columnInSelection = true; + if (blockSelectionMode) + { + columnInSelection = x >= (selTopLeft % columns) && + x <= (selBottomRight % columns); + } + + int pos = loc(x,y); + return pos >= selTopLeft && pos <= selBottomRight && columnInSelection; +} + +QString Screen::selectedText(bool preserveLineBreaks) const +{ + QString result; + QTextStream stream(&result, QIODevice::ReadWrite); + + PlainTextDecoder decoder; + decoder.begin(&stream); + writeSelectionToStream(&decoder , preserveLineBreaks); + decoder.end(); + + return result; +} + +bool Screen::isSelectionValid() const +{ + return selTopLeft >= 0 && selBottomRight >= 0; +} + +void Screen::writeSelectionToStream(TerminalCharacterDecoder* decoder , + bool preserveLineBreaks) const +{ + if (!isSelectionValid()) + return; + writeToStream(decoder,selTopLeft,selBottomRight,preserveLineBreaks); +} + +void Screen::writeToStream(TerminalCharacterDecoder* decoder, + int startIndex, int endIndex, + bool preserveLineBreaks) const +{ + int top = startIndex / columns; + int left = startIndex % columns; + + int bottom = endIndex / columns; + int right = endIndex % columns; + + Q_ASSERT( top >= 0 && left >= 0 && bottom >= 0 && right >= 0 ); + + for (int y=top;y<=bottom;y++) + { + int start = 0; + if ( y == top || blockSelectionMode ) start = left; + + int count = -1; + if ( y == bottom || blockSelectionMode ) count = right - start + 1; + + const bool appendNewLine = ( y != bottom ); + int copied = copyLineToStream( y, + start, + count, + decoder, + appendNewLine, + preserveLineBreaks ); + + // if the selection goes beyond the end of the last line then + // append a new line character. + // + // this makes it possible to 'select' a trailing new line character after + // the text on a line. + if ( y == bottom && + copied < count ) + { + Character newLineChar('\n'); + decoder->decodeLine(&newLineChar,1,0); + } + } +} + +int Screen::copyLineToStream(int line , + int start, + int count, + TerminalCharacterDecoder* decoder, + bool appendNewLine, + bool preserveLineBreaks) const +{ + //buffer to hold characters for decoding + //the buffer is static to avoid initialising every + //element on each call to copyLineToStream + //(which is unnecessary since all elements will be overwritten anyway) + static const int MAX_CHARS = 1024; + static Character characterBuffer[MAX_CHARS]; + + assert( count < MAX_CHARS ); + + LineProperty currentLineProperties = 0; + + //determine if the line is in the history buffer or the screen image + if (line < history->getLines()) + { + const int lineLength = history->getLineLen(line); + + // ensure that start position is before end of line + start = qMin(start,qMax(0,lineLength-1)); + + // retrieve line from history buffer. It is assumed + // that the history buffer does not store trailing white space + // at the end of the line, so it does not need to be trimmed here + if (count == -1) + { + count = lineLength-start; + } + else + { + count = qMin(start+count,lineLength)-start; + } + + // safety checks + assert( start >= 0 ); + assert( count >= 0 ); + assert( (start+count) <= history->getLineLen(line) ); + + history->getCells(line,start,count,characterBuffer); + + if ( history->isWrappedLine(line) ) + currentLineProperties |= LINE_WRAPPED; + } + else + { + if ( count == -1 ) + count = columns - start; + + assert( count >= 0 ); + + const int screenLine = line-history->getLines(); + + Character* data = screenLines[screenLine].data(); + int length = screenLines[screenLine].count(); + + //retrieve line from screen image + for (int i=start;i < qMin(start+count,length);i++) + { + characterBuffer[i-start] = data[i]; + } + + // count cannot be any greater than length + count = qBound(0,count,length-start); + + Q_ASSERT( screenLine < lineProperties.count() ); + currentLineProperties |= lineProperties[screenLine]; + } + + // add new line character at end + const bool omitLineBreak = (currentLineProperties & LINE_WRAPPED) || + !preserveLineBreaks; + + if ( !omitLineBreak && appendNewLine && (count+1 < MAX_CHARS) ) + { + characterBuffer[count] = '\n'; + count++; + } + + //decode line and write to text stream + decoder->decodeLine( (Character*) characterBuffer , + count, currentLineProperties ); + + return count; +} + +void Screen::writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const +{ + writeToStream(decoder,loc(0,fromLine),loc(columns-1,toLine)); +} + +void Screen::addHistLine() +{ + // add line to history buffer + // we have to take care about scrolling, too... + + if (hasScroll()) + { + int oldHistLines = history->getLines(); + + history->addCellsVector(screenLines[0]); + history->addLine( lineProperties[0] & LINE_WRAPPED ); + + int newHistLines = history->getLines(); + + bool beginIsTL = (selBegin == selTopLeft); + + // If the history is full, increment the count + // of dropped lines + if ( newHistLines == oldHistLines ) + _droppedLines++; + + // Adjust selection for the new point of reference + if (newHistLines > oldHistLines) + { + if (selBegin != -1) + { + selTopLeft += columns; + selBottomRight += columns; + } + } + + if (selBegin != -1) + { + // Scroll selection in history up + int top_BR = loc(0, 1+newHistLines); + + if (selTopLeft < top_BR) + selTopLeft -= columns; + + if (selBottomRight < top_BR) + selBottomRight -= columns; + + if (selBottomRight < 0) + clearSelection(); + else + { + if (selTopLeft < 0) + selTopLeft = 0; + } + + if (beginIsTL) + selBegin = selTopLeft; + else + selBegin = selBottomRight; + } + } + +} + +int Screen::getHistLines() const +{ + return history->getLines(); +} + +void Screen::setScroll(const HistoryType& t , bool copyPreviousScroll) +{ + clearSelection(); + + if ( copyPreviousScroll ) + history = t.scroll(history); + else + { + HistoryScroll* oldScroll = history; + history = t.scroll(0); + delete oldScroll; + } +} + +bool Screen::hasScroll() const +{ + return history->hasScroll(); +} + +const HistoryType& Screen::getScroll() const +{ + return history->getType(); +} + +void Screen::setLineProperty(LineProperty property , bool enable) +{ + if ( enable ) + lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | property); + else + lineProperties[cuY] = (LineProperty)(lineProperties[cuY] & ~property); +} +void Screen::fillWithDefaultChar(Character* dest, int count) +{ + for (int i=0;i<count;i++) + dest[i] = defaultChar; +}
new file mode 100644 --- /dev/null +++ b/gui//src/Screen.h @@ -0,0 +1,670 @@ +/* + This file is part of Konsole, KDE's terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef SCREEN_H +#define SCREEN_H + +// Qt +#include <QtCore/QRect> +#include <QtCore/QTextStream> +#include <QtCore/QVarLengthArray> + +// Konsole +#include "Character.h" +#include "History.h" + +#define MODE_Origin 0 +#define MODE_Wrap 1 +#define MODE_Insert 2 +#define MODE_Screen 3 +#define MODE_Cursor 4 +#define MODE_NewLine 5 +#define MODES_SCREEN 6 + +class TerminalCharacterDecoder; + +/** + \brief An image of characters with associated attributes. + + The terminal emulation ( Emulation ) receives a serial stream of + characters from the program currently running in the terminal. + From this stream it creates an image of characters which is ultimately + rendered by the display widget ( TerminalDisplay ). Some types of emulation + may have more than one screen image. + + getImage() is used to retrieve the currently visible image + which is then used by the display widget to draw the output from the + terminal. + + The number of lines of output history which are kept in addition to the current + screen image depends on the history scroll being used to store the output. + The scroll is specified using setScroll() + The output history can be retrieved using writeToStream() + + The screen image has a selection associated with it, specified using + setSelectionStart() and setSelectionEnd(). The selected text can be retrieved + using selectedText(). When getImage() is used to retrieve the visible image, + characters which are part of the selection have their colours inverted. +*/ +class Screen +{ +public: + /** Construct a new screen image of size @p lines by @p columns. */ + Screen(int lines, int columns); + ~Screen(); + + // VT100/2 Operations + // Cursor Movement + + /** + * Move the cursor up by @p n lines. The cursor will stop at the + * top margin. + */ + void cursorUp(int n); + /** + * Move the cursor down by @p n lines. The cursor will stop at the + * bottom margin. + */ + void cursorDown(int n); + /** + * Move the cursor to the left by @p n columns. + * The cursor will stop at the first column. + */ + void cursorLeft(int n); + /** + * Move the cursor to the right by @p n columns. + * The cursor will stop at the right-most column. + */ + void cursorRight(int n); + /** Position the cursor on line @p y. */ + void setCursorY(int y); + /** Position the cursor at column @p x. */ + void setCursorX(int x); + /** Position the cursor at line @p y, column @p x. */ + void setCursorYX(int y, int x); + /** + * Sets the margins for scrolling the screen. + * + * @param topLine The top line of the new scrolling margin. + * @param bottomLine The bottom line of the new scrolling margin. + */ + void setMargins(int topLine , int bottomLine); + /** Returns the top line of the scrolling region. */ + int topMargin() const; + /** Returns the bottom line of the scrolling region. */ + int bottomMargin() const; + + /** + * Resets the scrolling margins back to the top and bottom lines + * of the screen. + */ + void setDefaultMargins(); + + /** + * Moves the cursor down one line, if the MODE_NewLine mode + * flag is enabled then the cursor is returned to the leftmost + * column first. + * + * Equivalent to NextLine() if the MODE_NewLine flag is set + * or index() otherwise. + */ + void newLine(); + /** + * Moves the cursor down one line and positions it at the beginning + * of the line. Equivalent to calling Return() followed by index() + */ + void nextLine(); + + /** + * Move the cursor down one line. If the cursor is on the bottom + * line of the scrolling region (as returned by bottomMargin()) the + * scrolling region is scrolled up by one line instead. + */ + void index(); + /** + * Move the cursor up one line. If the cursor is on the top line + * of the scrolling region (as returned by topMargin()) the scrolling + * region is scrolled down by one line instead. + */ + void reverseIndex(); + + /** + * Scroll the scrolling region of the screen up by @p n lines. + * The scrolling region is initially the whole screen, but can be changed + * using setMargins() + */ + void scrollUp(int n); + /** + * Scroll the scrolling region of the screen down by @p n lines. + * The scrolling region is initially the whole screen, but can be changed + * using setMargins() + */ + void scrollDown(int n); + /** + * Moves the cursor to the beginning of the current line. + * Equivalent to setCursorX(0) + */ + void toStartOfLine(); + /** + * Moves the cursor one column to the left and erases the character + * at the new cursor position. + */ + void backspace(); + /** Moves the cursor @p n tab-stops to the right. */ + void tab(int n = 1); + /** Moves the cursor @p n tab-stops to the left. */ + void backtab(int n); + + // Editing + + /** + * Erase @p n characters beginning from the current cursor position. + * This is equivalent to over-writing @p n characters starting with the current + * cursor position with spaces. + * If @p n is 0 then one character is erased. + */ + void eraseChars(int n); + /** + * Delete @p n characters beginning from the current cursor position. + * If @p n is 0 then one character is deleted. + */ + void deleteChars(int n); + /** + * Insert @p n blank characters beginning from the current cursor position. + * The position of the cursor is not altered. + * If @p n is 0 then one character is inserted. + */ + void insertChars(int n); + /** + * Removes @p n lines beginning from the current cursor position. + * The position of the cursor is not altered. + * If @p n is 0 then one line is removed. + */ + void deleteLines(int n); + /** + * Inserts @p lines beginning from the current cursor position. + * The position of the cursor is not altered. + * If @p n is 0 then one line is inserted. + */ + void insertLines(int n); + /** Clears all the tab stops. */ + void clearTabStops(); + /** Sets or removes a tab stop at the cursor's current column. */ + void changeTabStop(bool set); + + /** Resets (clears) the specified screen @p mode. */ + void resetMode(int mode); + /** Sets (enables) the specified screen @p mode. */ + void setMode(int mode); + /** + * Saves the state of the specified screen @p mode. It can be restored + * using restoreMode() + */ + void saveMode(int mode); + /** Restores the state of a screen @p mode saved by calling saveMode() */ + void restoreMode(int mode); + /** Returns whether the specified screen @p mode is enabled or not .*/ + bool getMode(int mode) const; + + /** + * Saves the current position and appearance (text color and style) of the cursor. + * It can be restored by calling restoreCursor() + */ + void saveCursor(); + /** Restores the position and appearance of the cursor. See saveCursor() */ + void restoreCursor(); + + /** Clear the whole screen, moving the current screen contents into the history first. */ + void clearEntireScreen(); + /** + * Clear the area of the screen from the current cursor position to the end of + * the screen. + */ + void clearToEndOfScreen(); + /** + * Clear the area of the screen from the current cursor position to the start + * of the screen. + */ + void clearToBeginOfScreen(); + /** Clears the whole of the line on which the cursor is currently positioned. */ + void clearEntireLine(); + /** Clears from the current cursor position to the end of the line. */ + void clearToEndOfLine(); + /** Clears from the current cursor position to the beginning of the line. */ + void clearToBeginOfLine(); + + /** Fills the entire screen with the letter 'E' */ + void helpAlign(); + + /** + * Enables the given @p rendition flag. Rendition flags control the appearance + * of characters on the screen. + * + * @see Character::rendition + */ + void setRendition(int rendition); + /** + * Disables the given @p rendition flag. Rendition flags control the appearance + * of characters on the screen. + * + * @see Character::rendition + */ + void resetRendition(int rendition); + + /** + * Sets the cursor's foreground color. + * @param space The color space used by the @p color argument + * @param color The new foreground color. The meaning of this depends on + * the color @p space used. + * + * @see CharacterColor + */ + void setForeColor(int space, int color); + /** + * Sets the cursor's background color. + * @param space The color space used by the @p color argumnet. + * @param color The new background color. The meaning of this depends on + * the color @p space used. + * + * @see CharacterColor + */ + void setBackColor(int space, int color); + /** + * Resets the cursor's color back to the default and sets the + * character's rendition flags back to the default settings. + */ + void setDefaultRendition(); + + /** Returns the column which the cursor is positioned at. */ + int getCursorX() const; + /** Returns the line which the cursor is positioned on. */ + int getCursorY() const; + + /** Clear the entire screen and move the cursor to the home position. + * Equivalent to calling clearEntireScreen() followed by home(). + */ + void clear(); + /** + * Sets the position of the cursor to the 'home' position at the top-left + * corner of the screen (0,0) + */ + void home(); + /** + * Resets the state of the screen. This resets the various screen modes + * back to their default states. The cursor style and colors are reset + * (as if setDefaultRendition() had been called) + * + * <ul> + * <li>Line wrapping is enabled.</li> + * <li>Origin mode is disabled.</li> + * <li>Insert mode is disabled.</li> + * <li>Cursor mode is enabled. TODO Document me</li> + * <li>Screen mode is disabled. TODO Document me</li> + * <li>New line mode is disabled. TODO Document me</li> + * </ul> + * + * If @p clearScreen is true then the screen contents are erased entirely, + * otherwise they are unaltered. + */ + void reset(bool clearScreen = true); + + /** + * Displays a new character at the current cursor position. + * + * If the cursor is currently positioned at the right-edge of the screen and + * line wrapping is enabled then the character is added at the start of a new + * line below the current one. + * + * If the MODE_Insert screen mode is currently enabled then the character + * is inserted at the current cursor position, otherwise it will replace the + * character already at the current cursor position. + */ + void displayCharacter(unsigned short c); + + // Do composition with last shown character FIXME: Not implemented yet for KDE 4 + void compose(const QString& compose); + + /** + * Resizes the image to a new fixed size of @p new_lines by @p new_columns. + * In the case that @p new_columns is smaller than the current number of columns, + * existing lines are not truncated. This prevents characters from being lost + * if the terminal display is resized smaller and then larger again. + * + * The top and bottom margins are reset to the top and bottom of the new + * screen size. Tab stops are also reset and the current selection is + * cleared. + */ + void resizeImage(int new_lines, int new_columns); + + /** + * Returns the current screen image. + * The result is an array of Characters of size [getLines()][getColumns()] which + * must be freed by the caller after use. + * + * @param dest Buffer to copy the characters into + * @param size Size of @p dest in Characters + * @param startLine Index of first line to copy + * @param endLine Index of last line to copy + */ + void getImage( Character* dest , int size , int startLine , int endLine ) const; + + /** + * Returns the additional attributes associated with lines in the image. + * The most important attribute is LINE_WRAPPED which specifies that the + * line is wrapped, + * other attributes control the size of characters in the line. + */ + QVector<LineProperty> getLineProperties( int startLine , int endLine ) const; + + + /** Return the number of lines. */ + int getLines() const + { return lines; } + /** Return the number of columns. */ + int getColumns() const + { return columns; } + /** Return the number of lines in the history buffer. */ + int getHistLines() const; + /** + * Sets the type of storage used to keep lines in the history. + * If @p copyPreviousScroll is true then the contents of the previous + * history buffer are copied into the new scroll. + */ + void setScroll(const HistoryType& , bool copyPreviousScroll = true); + /** Returns the type of storage used to keep lines in the history. */ + const HistoryType& getScroll() const; + /** + * Returns true if this screen keeps lines that are scrolled off the screen + * in a history buffer. + */ + bool hasScroll() const; + + /** + * Sets the start of the selection. + * + * @param column The column index of the first character in the selection. + * @param line The line index of the first character in the selection. + * @param blockSelectionMode True if the selection is in column mode. + */ + void setSelectionStart(const int column, const int line, const bool blockSelectionMode); + + /** + * Sets the end of the current selection. + * + * @param column The column index of the last character in the selection. + * @param line The line index of the last character in the selection. + */ + void setSelectionEnd(const int column, const int line); + + /** + * Retrieves the start of the selection or the cursor position if there + * is no selection. + */ + void getSelectionStart(int& column , int& line) const; + + /** + * Retrieves the end of the selection or the cursor position if there + * is no selection. + */ + void getSelectionEnd(int& column , int& line) const; + + /** Clears the current selection */ + void clearSelection(); + + /** + * Returns true if the character at (@p column, @p line) is part of the + * current selection. + */ + bool isSelected(const int column,const int line) const; + + /** + * Convenience method. Returns the currently selected text. + * @param preserveLineBreaks Specifies whether new line characters should + * be inserted into the returned text at the end of each terminal line. + */ + QString selectedText(bool preserveLineBreaks) const; + + /** + * Copies part of the output to a stream. + * + * @param decoder A decoder which converts terminal characters into text + * @param fromLine The first line in the history to retrieve + * @param toLine The last line in the history to retrieve + */ + void writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const; + + /** + * Copies the selected characters, set using @see setSelBeginXY and @see setSelExtentXY + * into a stream. + * + * @param decoder A decoder which converts terminal characters into text. + * PlainTextDecoder is the most commonly used decoder which converts characters + * into plain text with no formatting. + * @param preserveLineBreaks Specifies whether new line characters should + * be inserted into the returned text at the end of each terminal line. + */ + void writeSelectionToStream(TerminalCharacterDecoder* decoder , bool + preserveLineBreaks = true) const; + + /** + * Checks if the text between from and to is inside the current + * selection. If this is the case, the selection is cleared. The + * from and to are coordinates in the current viewable window. + * The loc(x,y) macro can be used to generate these values from a + * column,line pair. + * + * @param from The start of the area to check. + * @param to The end of the area to check + */ + void checkSelection(int from, int to); + + /** + * Sets or clears an attribute of the current line. + * + * @param property The attribute to set or clear + * Possible properties are: + * LINE_WRAPPED: Specifies that the line is wrapped. + * LINE_DOUBLEWIDTH: Specifies that the characters in the current line + * should be double the normal width. + * LINE_DOUBLEHEIGHT:Specifies that the characters in the current line + * should be double the normal height. + * Double-height lines are formed of two lines containing the same characters, + * with both having the LINE_DOUBLEHEIGHT attribute. + * This allows other parts of the code to work on the + * assumption that all lines are the same height. + * + * @param enable true to apply the attribute to the current line or false to remove it + */ + void setLineProperty(LineProperty property , bool enable); + + /** + * Returns the number of lines that the image has been scrolled up or down by, + * since the last call to resetScrolledLines(). + * + * a positive return value indicates that the image has been scrolled up, + * a negative return value indicates that the image has been scrolled down. + */ + int scrolledLines() const; + + /** + * Returns the region of the image which was last scrolled. + * + * This is the area of the image from the top margin to the + * bottom margin when the last scroll occurred. + */ + QRect lastScrolledRegion() const; + + /** + * Resets the count of the number of lines that the image has been scrolled up or down by, + * see scrolledLines() + */ + void resetScrolledLines(); + + /** + * Returns the number of lines of output which have been + * dropped from the history since the last call + * to resetDroppedLines() + * + * If the history is not unlimited then it will drop + * the oldest lines of output if new lines are added when + * it is full. + */ + int droppedLines() const; + + /** + * Resets the count of the number of lines dropped from + * the history. + */ + void resetDroppedLines(); + + /** + * Fills the buffer @p dest with @p count instances of the default (ie. blank) + * Character style. + */ + static void fillWithDefaultChar(Character* dest, int count); + +private: + + //copies a line of text from the screen or history into a stream using a + //specified character decoder. Returns the number of lines actually copied, + //which may be less than 'count' if (start+count) is more than the number of characters on + //the line + // + //line - the line number to copy, from 0 (the earliest line in the history) up to + // history->getLines() + lines - 1 + //start - the first column on the line to copy + //count - the number of characters on the line to copy + //decoder - a decoder which converts terminal characters (an Character array) into text + //appendNewLine - if true a new line character (\n) is appended to the end of the line + int copyLineToStream(int line, + int start, + int count, + TerminalCharacterDecoder* decoder, + bool appendNewLine, + bool preserveLineBreaks) const; + + //fills a section of the screen image with the character 'c' + //the parameters are specified as offsets from the start of the screen image. + //the loc(x,y) macro can be used to generate these values from a column,line pair. + void clearImage(int loca, int loce, char c); + + //move screen image between 'sourceBegin' and 'sourceEnd' to 'dest'. + //the parameters are specified as offsets from the start of the screen image. + //the loc(x,y) macro can be used to generate these values from a column,line pair. + // + //NOTE: moveImage() can only move whole lines + void moveImage(int dest, int sourceBegin, int sourceEnd); + // scroll up 'i' lines in current region, clearing the bottom 'i' lines + void scrollUp(int from, int i); + // scroll down 'i' lines in current region, clearing the top 'i' lines + void scrollDown(int from, int i); + + void addHistLine(); + + void initTabStops(); + + void updateEffectiveRendition(); + void reverseRendition(Character& p) const; + + bool isSelectionValid() const; + // copies text from 'startIndex' to 'endIndex' to a stream + // startIndex and endIndex are positions generated using the loc(x,y) macro + void writeToStream(TerminalCharacterDecoder* decoder, int startIndex, + int endIndex, bool preserveLineBreaks = true) const; + // copies 'count' lines from the screen buffer into 'dest', + // starting from 'startLine', where 0 is the first line in the screen buffer + void copyFromScreen(Character* dest, int startLine, int count) const; + // copies 'count' lines from the history buffer into 'dest', + // starting from 'startLine', where 0 is the first line in the history + void copyFromHistory(Character* dest, int startLine, int count) const; + + + // screen image ---------------- + int lines; + int columns; + + typedef QVector<Character> ImageLine; // [0..columns] + ImageLine* screenLines; // [lines] + + int _scrolledLines; + QRect _lastScrolledRegion; + + int _droppedLines; + + QVarLengthArray<LineProperty,64> lineProperties; + + // history buffer --------------- + HistoryScroll* history; + + // cursor location + int cuX; + int cuY; + + // cursor color and rendition info + CharacterColor currentForeground; + CharacterColor currentBackground; + quint8 currentRendition; + + // margins ---------------- + int _topMargin; + int _bottomMargin; + + // states ---------------- + int currentModes[MODES_SCREEN]; + int savedModes[MODES_SCREEN]; + + // ---------------------------- + + QBitArray tabStops; + + // selection ------------------- + int selBegin; // The first location selected. + int selTopLeft; // TopLeft Location. + int selBottomRight; // Bottom Right Location. + bool blockSelectionMode; // Column selection mode + + // effective colors and rendition ------------ + CharacterColor effectiveForeground; // These are derived from + CharacterColor effectiveBackground; // the cu_* variables above + quint8 effectiveRendition; // to speed up operation + + class SavedState + { + public: + SavedState() + : cursorColumn(0),cursorLine(0),rendition(0) {} + + int cursorColumn; + int cursorLine; + quint8 rendition; + CharacterColor foreground; + CharacterColor background; + }; + SavedState savedState; + + // last position where we added a character + int lastPos; + + static Character defaultChar; +}; + +#endif // SCREEN_H
new file mode 100644 --- /dev/null +++ b/gui//src/ScreenWindow.cpp @@ -0,0 +1,292 @@ +/* + Copyright (C) 2007 by Robert Knight <robertknight@gmail.com> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "ScreenWindow.h" + +// Qt +#include <QtCore> + +// Konsole +#include "Screen.h" + +ScreenWindow::ScreenWindow(QObject* parent) + : QObject(parent) + , _windowBuffer(0) + , _windowBufferSize(0) + , _bufferNeedsUpdate(true) + , _windowLines(1) + , _currentLine(0) + , _trackOutput(true) + , _scrollCount(0) +{ +} +ScreenWindow::~ScreenWindow() +{ + delete[] _windowBuffer; +} +void ScreenWindow::setScreen(Screen* screen) +{ + Q_ASSERT( screen ); + + _screen = screen; +} + +Screen* ScreenWindow::screen() const +{ + return _screen; +} + +Character* ScreenWindow::getImage() +{ + // reallocate internal buffer if the window size has changed + int size = windowLines() * windowColumns(); + if (_windowBuffer == 0 || _windowBufferSize != size) + { + delete[] _windowBuffer; + _windowBufferSize = size; + _windowBuffer = new Character[size]; + _bufferNeedsUpdate = true; + } + + if (!_bufferNeedsUpdate) + return _windowBuffer; + + _screen->getImage(_windowBuffer,size, + currentLine(),endWindowLine()); + + // this window may look beyond the end of the screen, in which + // case there will be an unused area which needs to be filled + // with blank characters + fillUnusedArea(); + + _bufferNeedsUpdate = false; + return _windowBuffer; +} + +void ScreenWindow::fillUnusedArea() +{ + int screenEndLine = _screen->getHistLines() + _screen->getLines() - 1; + int windowEndLine = currentLine() + windowLines() - 1; + + int unusedLines = windowEndLine - screenEndLine; + int charsToFill = unusedLines * windowColumns(); + + Screen::fillWithDefaultChar(_windowBuffer + _windowBufferSize - charsToFill,charsToFill); +} + +// return the index of the line at the end of this window, or if this window +// goes beyond the end of the screen, the index of the line at the end +// of the screen. +// +// when passing a line number to a Screen method, the line number should +// never be more than endWindowLine() +// +int ScreenWindow::endWindowLine() const +{ + return qMin(currentLine() + windowLines() - 1, + lineCount() - 1); +} +QVector<LineProperty> ScreenWindow::getLineProperties() +{ + QVector<LineProperty> result = _screen->getLineProperties(currentLine(),endWindowLine()); + + if (result.count() != windowLines()) + result.resize(windowLines()); + + return result; +} + +QString ScreenWindow::selectedText( bool preserveLineBreaks ) const +{ + return _screen->selectedText( preserveLineBreaks ); +} + +void ScreenWindow::getSelectionStart( int& column , int& line ) +{ + _screen->getSelectionStart(column,line); + line -= currentLine(); +} +void ScreenWindow::getSelectionEnd( int& column , int& line ) +{ + _screen->getSelectionEnd(column,line); + line -= currentLine(); +} +void ScreenWindow::setSelectionStart( int column , int line , bool columnMode ) +{ + _screen->setSelectionStart( column , qMin(line + currentLine(),endWindowLine()) , columnMode); + + _bufferNeedsUpdate = true; + emit selectionChanged(); +} + +void ScreenWindow::setSelectionEnd( int column , int line ) +{ + _screen->setSelectionEnd( column , qMin(line + currentLine(),endWindowLine()) ); + + _bufferNeedsUpdate = true; + emit selectionChanged(); +} + +bool ScreenWindow::isSelected( int column , int line ) +{ + return _screen->isSelected( column , qMin(line + currentLine(),endWindowLine()) ); +} + +void ScreenWindow::clearSelection() +{ + _screen->clearSelection(); + + emit selectionChanged(); +} + +void ScreenWindow::setWindowLines(int lines) +{ + Q_ASSERT(lines > 0); + _windowLines = lines; +} +int ScreenWindow::windowLines() const +{ + return _windowLines; +} + +int ScreenWindow::windowColumns() const +{ + return _screen->getColumns(); +} + +int ScreenWindow::lineCount() const +{ + return _screen->getHistLines() + _screen->getLines(); +} + +int ScreenWindow::columnCount() const +{ + return _screen->getColumns(); +} + +QPoint ScreenWindow::cursorPosition() const +{ + QPoint position; + + position.setX( _screen->getCursorX() ); + position.setY( _screen->getCursorY() ); + + return position; +} + +int ScreenWindow::currentLine() const +{ + return qBound(0,_currentLine,lineCount()-windowLines()); +} + +void ScreenWindow::scrollBy( RelativeScrollMode mode , int amount ) +{ + if ( mode == ScrollLines ) + { + scrollTo( currentLine() + amount ); + } + else if ( mode == ScrollPages ) + { + scrollTo( currentLine() + amount * ( windowLines() / 2 ) ); + } +} + +bool ScreenWindow::atEndOfOutput() const +{ + return currentLine() == (lineCount()-windowLines()); +} + +void ScreenWindow::scrollTo( int line ) +{ + int maxCurrentLineNumber = lineCount() - windowLines(); + line = qBound(0,line,maxCurrentLineNumber); + + const int delta = line - _currentLine; + _currentLine = line; + + // keep track of number of lines scrolled by, + // this can be reset by calling resetScrollCount() + _scrollCount += delta; + + _bufferNeedsUpdate = true; + + emit scrolled(_currentLine); +} + +void ScreenWindow::setTrackOutput(bool trackOutput) +{ + _trackOutput = trackOutput; +} + +bool ScreenWindow::trackOutput() const +{ + return _trackOutput; +} + +int ScreenWindow::scrollCount() const +{ + return _scrollCount; +} + +void ScreenWindow::resetScrollCount() +{ + _scrollCount = 0; +} + +QRect ScreenWindow::scrollRegion() const +{ + bool equalToScreenSize = windowLines() == _screen->getLines(); + + if ( atEndOfOutput() && equalToScreenSize ) + return _screen->lastScrolledRegion(); + else + return QRect(0,0,windowColumns(),windowLines()); +} + +void ScreenWindow::notifyOutputChanged() +{ + // move window to the bottom of the screen and update scroll count + // if this window is currently tracking the bottom of the screen + if ( _trackOutput ) + { + _scrollCount -= _screen->scrolledLines(); + _currentLine = qMax(0,_screen->getHistLines() - (windowLines()-_screen->getLines())); + } + else + { + // if the history is not unlimited then it may + // have run out of space and dropped the oldest + // lines of output - in this case the screen + // window's current line number will need to + // be adjusted - otherwise the output will scroll + _currentLine = qMax(0,_currentLine - + _screen->droppedLines()); + + // ensure that the screen window's current position does + // not go beyond the bottom of the screen + _currentLine = qMin( _currentLine , _screen->getHistLines() ); + } + + _bufferNeedsUpdate = true; + + emit outputChanged(); +}
new file mode 100644 --- /dev/null +++ b/gui//src/ScreenWindow.h @@ -0,0 +1,251 @@ +/* + Copyright (C) 2007 by Robert Knight <robertknight@gmail.com> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef SCREENWINDOW_H +#define SCREENWINDOW_H + +// Qt +#include <QtCore/QObject> +#include <QtCore/QPoint> +#include <QtCore/QRect> + +// Konsole +#include "Character.h" + +class Screen; + +/** + * Provides a window onto a section of a terminal screen. + * This window can then be rendered by a terminal display widget ( TerminalDisplay ). + * + * To use the screen window, create a new ScreenWindow() instance and associated it with + * a terminal screen using setScreen(). + * Use the scrollTo() method to scroll the window up and down on the screen. + * Call the getImage() method to retrieve the character image which is currently visible in the window. + * + * setTrackOutput() controls whether the window moves to the bottom of the associated screen when new + * lines are added to it. + * + * Whenever the output from the underlying screen is changed, the notifyOutputChanged() slot should + * be called. This in turn will update the window's position and emit the outputChanged() signal + * if necessary. + */ +class ScreenWindow : public QObject +{ +Q_OBJECT + +public: + /** + * Constructs a new screen window with the given parent. + * A screen must be specified by calling setScreen() before calling getImage() or getLineProperties(). + * + * You should not call this constructor directly, instead use the Emulation::createWindow() method + * to create a window on the emulation which you wish to view. This allows the emulation + * to notify the window when the associated screen has changed and synchronize selection updates + * between all views on a session. + */ + ScreenWindow(QObject* parent = 0); + virtual ~ScreenWindow(); + + /** Sets the screen which this window looks onto */ + void setScreen(Screen* screen); + /** Returns the screen which this window looks onto */ + Screen* screen() const; + + /** + * Returns the image of characters which are currently visible through this window + * onto the screen. + * + * The buffer is managed by the ScreenWindow instance and does not need to be + * deleted by the caller. + */ + Character* getImage(); + + /** + * Returns the line attributes associated with the lines of characters which + * are currently visible through this window + */ + QVector<LineProperty> getLineProperties(); + + /** + * Returns the number of lines which the region of the window + * specified by scrollRegion() has been scrolled by since the last call + * to resetScrollCount(). scrollRegion() is in most cases the + * whole window, but will be a smaller area in, for example, applications + * which provide split-screen facilities. + * + * This is not guaranteed to be accurate, but allows views to optimise + * rendering by reducing the amount of costly text rendering that + * needs to be done when the output is scrolled. + */ + int scrollCount() const; + + /** + * Resets the count of scrolled lines returned by scrollCount() + */ + void resetScrollCount(); + + /** + * Returns the area of the window which was last scrolled, this is + * usually the whole window area. + * + * Like scrollCount(), this is not guaranteed to be accurate, + * but allows views to optimise rendering. + */ + QRect scrollRegion() const; + + /** + * Sets the start of the selection to the given @p line and @p column within + * the window. + */ + void setSelectionStart( int column , int line , bool columnMode ); + /** + * Sets the end of the selection to the given @p line and @p column within + * the window. + */ + void setSelectionEnd( int column , int line ); + /** + * Retrieves the start of the selection within the window. + */ + void getSelectionStart( int& column , int& line ); + /** + * Retrieves the end of the selection within the window. + */ + void getSelectionEnd( int& column , int& line ); + /** + * Returns true if the character at @p line , @p column is part of the selection. + */ + bool isSelected( int column , int line ); + /** + * Clears the current selection + */ + void clearSelection(); + + /** Sets the number of lines in the window */ + void setWindowLines(int lines); + /** Returns the number of lines in the window */ + int windowLines() const; + /** Returns the number of columns in the window */ + int windowColumns() const; + + /** Returns the total number of lines in the screen */ + int lineCount() const; + /** Returns the total number of columns in the screen */ + int columnCount() const; + + /** Returns the index of the line which is currently at the top of this window */ + int currentLine() const; + + /** + * Returns the position of the cursor + * within the window. + */ + QPoint cursorPosition() const; + + /** + * Convenience method. Returns true if the window is currently at the bottom + * of the screen. + */ + bool atEndOfOutput() const; + + /** Scrolls the window so that @p line is at the top of the window */ + void scrollTo( int line ); + + enum RelativeScrollMode + { + ScrollLines, + ScrollPages + }; + + /** + * Scrolls the window relative to its current position on the screen. + * + * @param mode Specifies whether @p amount refers to the number of lines or the number + * of pages to scroll. + * @param amount The number of lines or pages ( depending on @p mode ) to scroll by. If + * this number is positive, the view is scrolled down. If this number is negative, the view + * is scrolled up. + */ + void scrollBy( RelativeScrollMode mode , int amount ); + + /** + * Specifies whether the window should automatically move to the bottom + * of the screen when new output is added. + * + * If this is set to true, the window will be moved to the bottom of the associated screen ( see + * screen() ) when the notifyOutputChanged() method is called. + */ + void setTrackOutput(bool trackOutput); + /** + * Returns whether the window automatically moves to the bottom of the screen as + * new output is added. See setTrackOutput() + */ + bool trackOutput() const; + + /** + * Returns the text which is currently selected. + * + * @param preserveLineBreaks See Screen::selectedText() + */ + QString selectedText( bool preserveLineBreaks ) const; + +public slots: + /** + * Notifies the window that the contents of the associated terminal screen have changed. + * This moves the window to the bottom of the screen if trackOutput() is true and causes + * the outputChanged() signal to be emitted. + */ + void notifyOutputChanged(); + +signals: + /** + * Emitted when the contents of the associated terminal screen ( see screen() ) changes. + */ + void outputChanged(); + + /** + * Emitted when the screen window is scrolled to a different position. + * + * @param line The line which is now at the top of the window. + */ + void scrolled(int line); + + /** + * Emitted when the selection is changed. + */ + void selectionChanged(); + +private: + int endWindowLine() const; + void fillUnusedArea(); + + Screen* _screen; // see setScreen() , screen() + Character* _windowBuffer; + int _windowBufferSize; + bool _bufferNeedsUpdate; + + int _windowLines; + int _currentLine; // see scrollTo() , currentLine() + bool _trackOutput; // see setTrackOutput() , trackOutput() + int _scrollCount; // count of lines which the window has been scrolled by since + // the last call to resetScrollCount() +}; +#endif // SCREENWINDOW_H
new file mode 100644 --- /dev/null +++ b/gui//src/Session.cpp @@ -0,0 +1,1209 @@ +/* + This file is part of Konsole + + Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "Session.h" + +// Standard +#include <assert.h> +#include <stdlib.h> +#include <signal.h> + +// Qt +#include <QtGui/QApplication> +#include <QtCore/QByteRef> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QRegExp> +#include <QtCore/QStringList> +#include <QtCore/QDate> + +#include "kprocess.h" +#include "kptydevice.h" + +#include "ProcessInfo.h" +#include "Pty.h" +#include "TerminalDisplay.h" +#include "ShellCommand.h" +#include "Vt102Emulation.h" + +int Session::lastSessionId = 0; + +// HACK This is copied out of QUuid::createUuid with reseeding forced. +// Required because color schemes repeatedly seed the RNG... +// ...with a constant. +QUuid createUuid() +{ + static const int intbits = sizeof(int)*8; + static int randbits = 0; + if (!randbits) + { + int max = RAND_MAX; + do { ++randbits; } while ((max=max>>1)); + } + + qsrand(uint(QDateTime::currentDateTime().toTime_t())); + qrand(); // Skip first + + QUuid result; + uint *data = &(result.data1); + int chunks = 16 / sizeof(uint); + while (chunks--) { + uint randNumber = 0; + for (int filled = 0; filled < intbits; filled += randbits) + randNumber |= qrand()<<filled; + *(data+chunks) = randNumber; + } + + result.data4[0] = (result.data4[0] & 0x3F) | 0x80; // UV_DCE + result.data3 = (result.data3 & 0x0FFF) | 0x4000; // UV_Random + + return result; +} + +Session::Session(QObject* parent) : + QObject(parent) + , _shellProcess(0) + , _emulation(0) + , _monitorActivity(false) + , _monitorSilence(false) + , _notifiedActivity(false) + , _autoClose(true) + , _wantedClose(false) + , _silenceSeconds(10) + , _addToUtmp(true) + , _flowControl(true) + , _fullScripting(false) + , _sessionId(0) + , _sessionProcessInfo(0) + , _foregroundProcessInfo(0) + , _foregroundPid(0) + //, _zmodemBusy(false) + //, _zmodemProc(0) + //, _zmodemProgress(0) + , _hasDarkBackground(false) +{ + _uniqueIdentifier = createUuid(); + + //prepare DBus communication + //new SessionAdaptor(this); + _sessionId = ++lastSessionId; + + // JPS: commented out for lack of DBUS support by default on OSX + //QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/")+QString::number(_sessionId), this); + + //create emulation backend + _emulation = new Vt102Emulation(); + + connect( _emulation, SIGNAL( titleChanged( int, const QString & ) ), + this, SLOT( setUserTitle( int, const QString & ) ) ); + connect( _emulation, SIGNAL( stateSet(int) ), + this, SLOT( activityStateSet(int) ) ); + //connect( _emulation, SIGNAL( zmodemDetected() ), this , + // SLOT( fireZModemDetected() ) ); + connect( _emulation, SIGNAL( changeTabTextColorRequest( int ) ), + this, SIGNAL( changeTabTextColorRequest( int ) ) ); + connect( _emulation, SIGNAL(profileChangeCommandReceived(const QString&)), + this, SIGNAL( profileChangeCommandReceived(const QString&)) ); + connect( _emulation, SIGNAL(flowControlKeyPressed(bool)) , this, + SLOT(updateFlowControlState(bool)) ); + + //create new teletype for I/O with shell process + openTeletype(-1); + + //setup timer for monitoring session activity + _monitorTimer = new QTimer(this); + _monitorTimer->setSingleShot(true); + connect(_monitorTimer, SIGNAL(timeout()), this, SLOT(monitorTimerDone())); +} + +void Session::openTeletype(int fd) +{ + if (_shellProcess && isRunning()) + { + //kWarning() << "Attempted to open teletype in a running session."; + return; + } + + delete _shellProcess; + + if (fd < 0) + _shellProcess = new Pty(); + else + _shellProcess = new Pty(fd); + + _shellProcess->setUtf8Mode(_emulation->utf8()); + + //connect teletype to emulation backend + connect( _shellProcess,SIGNAL(receivedData(const char*,int)),this, + SLOT(onReceiveBlock(const char*,int)) ); + connect( _emulation,SIGNAL(sendData(const char*,int)),_shellProcess, + SLOT(sendData(const char*,int)) ); + connect( _emulation,SIGNAL(lockPtyRequest(bool)),_shellProcess,SLOT(lockPty(bool)) ); + connect( _emulation,SIGNAL(useUtf8Request(bool)),_shellProcess,SLOT(setUtf8Mode(bool)) ); + connect( _shellProcess,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(done(int)) ); + connect( _emulation,SIGNAL(imageSizeChanged(int,int)),this,SLOT(updateWindowSize(int,int)) ); +} + +WId Session::windowId() const +{ + // Returns a window ID for this session which is used + // to set the WINDOWID environment variable in the shell + // process. + // + // Sessions can have multiple views or no views, which means + // that a single ID is not always going to be accurate. + // + // If there are no views, the window ID is just 0. If + // there are multiple views, then the window ID for the + // top-level window which contains the first view is + // returned + + if ( _views.count() == 0 ) + return 0; + else + { + QWidget* window = _views.first(); + + Q_ASSERT( window ); + + while ( window->parentWidget() != 0 ) + window = window->parentWidget(); + + return window->winId(); + } +} + +void Session::setDarkBackground(bool darkBackground) +{ + _hasDarkBackground = darkBackground; +} +bool Session::hasDarkBackground() const +{ + return _hasDarkBackground; +} +bool Session::isRunning() const +{ + return _shellProcess->state() == QProcess::Running; +} + +void Session::setCodec(QTextCodec* codec) +{ + emulation()->setCodec(codec); +} + +bool Session::setCodec(QByteArray name) +{ + QTextCodec *codec = QTextCodec::codecForName(name); + if (codec) { + setCodec(codec); + return true; + } + return false; +} + +QByteArray Session::codec() +{ + return _emulation->codec()->name(); +} + +void Session::setProgram(const QString& program) +{ + _program = ShellCommand::expand(program); +} +void Session::setInitialWorkingDirectory(const QString& dir) +{ + //_initialWorkingDir = KShell::tildeExpand(ShellCommand::expand(dir)); + _initialWorkingDir = ShellCommand::expand(dir); +} +void Session::setArguments(const QStringList& arguments) +{ + _arguments = ShellCommand::expand(arguments); +} + +QString Session::currentWorkingDirectory() +{ + // only returned cached value + if (_currentWorkingDir.isEmpty()) updateWorkingDirectory(); + return _currentWorkingDir; +} +ProcessInfo* Session::updateWorkingDirectory() +{ + ProcessInfo *process = getProcessInfo(); + _currentWorkingDir = process->validCurrentDir(); + return process; +} + +QList<TerminalDisplay*> Session::views() const +{ + return _views; +} + +void Session::addView(TerminalDisplay* widget) +{ + Q_ASSERT( !_views.contains(widget) ); + + _views.append(widget); + + if ( _emulation != 0 ) + { + // connect emulation - view signals and slots + connect( widget , SIGNAL(keyPressedSignal(QKeyEvent*)) , _emulation , + SLOT(sendKeyEvent(QKeyEvent*)) ); + connect( widget , SIGNAL(mouseSignal(int,int,int,int)) , _emulation , + SLOT(sendMouseEvent(int,int,int,int)) ); + connect( widget , SIGNAL(sendStringToEmu(const char*)) , _emulation , + SLOT(sendString(const char*)) ); + + // allow emulation to notify view when the foreground process + // indicates whether or not it is interested in mouse signals + connect( _emulation , SIGNAL(programUsesMouseChanged(bool)) , widget , + SLOT(setUsesMouse(bool)) ); + + widget->setUsesMouse( _emulation->programUsesMouse() ); + + widget->setScreenWindow(_emulation->createWindow()); + } + + //connect view signals and slots + QObject::connect( widget ,SIGNAL(changedContentSizeSignal(int,int)),this, + SLOT(onViewSizeChange(int,int))); + + QObject::connect( widget ,SIGNAL(destroyed(QObject*)) , this , + SLOT(viewDestroyed(QObject*)) ); +} + +void Session::viewDestroyed(QObject* view) +{ + TerminalDisplay* display = (TerminalDisplay*)view; + + Q_ASSERT( _views.contains(display) ); + + removeView(display); +} + +void Session::removeView(TerminalDisplay* widget) +{ + _views.removeAll(widget); + + disconnect(widget,0,this,0); + + if ( _emulation != 0 ) + { + // disconnect + // - key presses signals from widget + // - mouse activity signals from widget + // - string sending signals from widget + // + // ... and any other signals connected in addView() + disconnect( widget, 0, _emulation, 0); + + // disconnect state change signals emitted by emulation + disconnect( _emulation , 0 , widget , 0); + } + + // close the session automatically when the last view is removed + if ( _views.count() == 0 ) + { + close(); + } +} + +QString Session::checkProgram(const QString& program) const +{ + // Upon a KPty error, there is no description on what that error was... + // Check to see if the given program is executable. + QString exec = QFile::encodeName(program); + + if (exec.isEmpty()) + return QString(); + + // if 'exec' is not specified, fall back to default shell. if that + // is not set then fall back to /bin/sh + if ( exec.isEmpty() ) + exec = qgetenv("SHELL"); + if ( exec.isEmpty() ) + exec = "/bin/sh"; + return program; +} + +void Session::terminalWarning(const QString& message) +{ + static const QByteArray warningText = QByteArray("@info:shell Alert the user with red color text"); + QByteArray messageText = message.toLocal8Bit(); + + static const char redPenOn[] = "\033[1m\033[31m"; + static const char redPenOff[] = "\033[0m"; + + _emulation->receiveData(redPenOn,strlen(redPenOn)); + _emulation->receiveData("\n\r\n\r",4); + _emulation->receiveData(warningText.constData(),strlen(warningText.constData())); + _emulation->receiveData(messageText.constData(),strlen(messageText.constData())); + _emulation->receiveData("\n\r\n\r",4); + _emulation->receiveData(redPenOff,strlen(redPenOff)); +} + +QString Session::shellSessionId() const +{ + QString friendlyUuid(_uniqueIdentifier.toString()); + friendlyUuid.remove('-').remove('{').remove('}'); + + return friendlyUuid; +} + +void Session::run() +{ + //check that everything is in place to run the session + if (_program.isEmpty()) + { + //kWarning() << "Session::run() - program to run not set."; + } + if (_arguments.isEmpty()) + { + //kWarning() << "Session::run() - no command line arguments specified."; + } + if (_uniqueIdentifier.isNull()) + { + _uniqueIdentifier = createUuid(); + } + + const int CHOICE_COUNT = 3; + QString programs[CHOICE_COUNT] = {_program,qgetenv("SHELL"),"/bin/sh"}; + QString exec; + int choice = 0; + while (choice < CHOICE_COUNT) + { + exec = checkProgram(programs[choice]); + if (exec.isEmpty()) + choice++; + else + break; + } + + // if a program was specified via setProgram(), but it couldn't be found, print a warning + if (choice != 0 && choice < CHOICE_COUNT && !_program.isEmpty()) + { + QString msg; + QTextStream msgStream(&msg); + msgStream << "Could not find '" << _program << "', starting '" << exec << "' instead. Please check your profile settings."; + terminalWarning(msg); + //terminalWarning(i18n("Could not find '%1', starting '%2' instead. Please check your profile settings.",_program.toLatin1().data(),exec.toLatin1().data())); + } + // if none of the choices are available, print a warning + else if (choice == CHOICE_COUNT) + { + terminalWarning(QString("Could not find an interactive shell to start.")); + return; + } + + // if no arguments are specified, fall back to program name + QStringList arguments = _arguments.join(QChar(' ')).isEmpty() ? + QStringList() << exec : _arguments; + + // JPS: commented out for lack of DBUS support by default on OSX + QString dbusService = ""; //QDBusConnection::sessionBus().baseService(); + if (!_initialWorkingDir.isEmpty()) + _shellProcess->setWorkingDirectory(_initialWorkingDir); + else + _shellProcess->setWorkingDirectory(QDir::homePath()); + + _shellProcess->setFlowControlEnabled(_flowControl); + _shellProcess->setErase(_emulation->eraseChar()); + + // this is not strictly accurate use of the COLORFGBG variable. This does not + // tell the terminal exactly which colors are being used, but instead approximates + // the color scheme as "black on white" or "white on black" depending on whether + // the background color is deemed dark or not + QString backgroundColorHint = _hasDarkBackground ? "COLORFGBG=15;0" : "COLORFGBG=0;15"; + _environment << backgroundColorHint; + _environment << QString("SHELL_SESSION_ID=%1").arg(shellSessionId()); + + int result = _shellProcess->start(exec, + arguments, + _environment, + windowId(), + _addToUtmp, + dbusService, + (QLatin1String("/Sessions/") + + QString::number(_sessionId))); + + if (result < 0) + { + QString msg; + QTextStream msgStream(&msg); + msgStream << QString("Could not start program '") << exec << QString("' with arguments '") << arguments.join(" ") << QString("'."); + terminalWarning (msg); + // terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec.toLatin1().data(), arguments.join(" ").toLatin1().data())); + return; + } + + _shellProcess->setWriteable(false); // We are reachable via kwrited. + + emit started(); +} + +void Session::setUserTitle( int what, const QString &caption ) +{ + //set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle ) + bool modified = false; + + if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) + { + if ( _userTitle != caption ) { + _userTitle = caption; + modified = true; + } + } + + if ((what == IconNameAndWindowTitle) || (what == IconName)) + { + if ( _iconText != caption ) { + _iconText = caption; + modified = true; + } + } + + if (what == TextColor || what == BackgroundColor) + { + QString colorString = caption.section(';',0,0); + QColor color = QColor(colorString); + if (color.isValid()) + { + if (what == TextColor) + emit changeForegroundColorRequest(color); + else + emit changeBackgroundColorRequest(color); + } + } + + if (what == SessionName) + { + if ( _nameTitle != caption ) { + setTitle(Session::NameRole,caption); + return; + } + } + + if (what == 31) + { + QString cwd=caption; + cwd=cwd.replace( QRegExp("^~"), QDir::homePath() ); + emit openUrlRequest(cwd); + } + + // change icon via \033]32;Icon\007 + if (what == 32) + { + if ( _iconName != caption ) { + _iconName = caption; + + modified = true; + } + } + + if (what == ProfileChange) + { + emit profileChangeCommandReceived(caption); + return; + } + + if ( modified ) + emit titleChanged(); +} + +QString Session::userTitle() const +{ + return _userTitle; +} +void Session::setTabTitleFormat(TabTitleContext context , const QString& format) +{ + if ( context == LocalTabTitle ) + _localTabTitleFormat = format; + else if ( context == RemoteTabTitle ) + _remoteTabTitleFormat = format; +} +QString Session::tabTitleFormat(TabTitleContext context) const +{ + if ( context == LocalTabTitle ) + return _localTabTitleFormat; + else if ( context == RemoteTabTitle ) + return _remoteTabTitleFormat; + + return QString(); +} + +void Session::monitorTimerDone() +{ + //FIXME: The idea here is that the notification popup will appear to tell the user than output from + //the terminal has stopped and the popup will disappear when the user activates the session. + // + //This breaks with the addition of multiple views of a session. The popup should disappear + //when any of the views of the session becomes active + + + //FIXME: Make message text for this notification and the activity notification more descriptive. + if (_monitorSilence) { + //KNotification::event("Silence", i18n("Silence in session '%1'", _nameTitle)propagateSize, QPixmap(), + // QApplication::activeWindow(), + // KNotification::CloseWhenWidgetActivated); + emit stateChanged(NOTIFYSILENCE); + } + else + { + emit stateChanged(NOTIFYNORMAL); + } + + _notifiedActivity=false; +} +void Session::updateFlowControlState(bool suspended) +{ + if (suspended) + { + if (flowControlEnabled()) + { + foreach(TerminalDisplay* display,_views) + { + if (display->flowControlWarningEnabled()) + display->outputSuspended(true); + } + } + } + else + { + foreach(TerminalDisplay* display,_views) + display->outputSuspended(false); + } +} +void Session::activityStateSet(int state) +{ + if (state==NOTIFYBELL) + { + emit bellRequest(QString("Bell in session '%1'").arg(_nameTitle.toLatin1().data())); + } + else if (state==NOTIFYACTIVITY) + { + if (_monitorSilence) { + _monitorTimer->start(_silenceSeconds*1000); + } + + if ( _monitorActivity ) { + //FIXME: See comments in Session::monitorTimerDone() + if (!_notifiedActivity) { + //KNotification::event("Activity", i18n("Activity in session '%1'", _nameTitle), QPixmap(), + // QApplication::activeWindow(), + //KNotification::CloseWhenWidgetActivated); + _notifiedActivity=true; + } + } + } + + if ( state==NOTIFYACTIVITY && !_monitorActivity ) + state = NOTIFYNORMAL; + if ( state==NOTIFYSILENCE && !_monitorSilence ) + state = NOTIFYNORMAL; + + emit stateChanged(state); +} + +void Session::onViewSizeChange(int /*height*/, int /*width*/) +{ + updateTerminalSize(); +} + +void Session::updateTerminalSize() +{ + QListIterator<TerminalDisplay*> viewIter(_views); + + int minLines = -1; + int minColumns = -1; + + // minimum number of lines and columns that views require for + // their size to be taken into consideration ( to avoid problems + // with new view widgets which haven't yet been set to their correct size ) + const int VIEW_LINES_THRESHOLD = 2; + const int VIEW_COLUMNS_THRESHOLD = 2; + + //select largest number of lines and columns that will fit in all visible views + while ( viewIter.hasNext() ) + { + TerminalDisplay* view = viewIter.next(); + if ( view->isHidden() == false && + view->lines() >= VIEW_LINES_THRESHOLD && + view->columns() >= VIEW_COLUMNS_THRESHOLD ) + { + minLines = (minLines == -1) ? view->lines() : qMin( minLines , view->lines() ); + minColumns = (minColumns == -1) ? view->columns() : qMin( minColumns , view->columns() ); + view->processFilters(); + } + } + + // backend emulation must have a _terminal of at least 1 column x 1 line in size + if ( minLines > 0 && minColumns > 0 ) + { + _emulation->setImageSize( minLines , minColumns ); + } +} +void Session::updateWindowSize(int lines, int columns) +{ + Q_ASSERT(lines > 0 && columns > 0); + _shellProcess->setWindowSize(lines,columns); +} +void Session::refresh() +{ + // attempt to get the shell process to redraw the display + // + // this requires the program running in the shell + // to cooperate by sending an update in response to + // a window size change + // + // the window size is changed twice, first made slightly larger and then + // resized back to its normal size so that there is actually a change + // in the window size (some shells do nothing if the + // new and old sizes are the same) + // + // if there is a more 'correct' way to do this, please + // send an email with method or patches to konsole-devel@kde.org + + const QSize existingSize = _shellProcess->windowSize(); + _shellProcess->setWindowSize(existingSize.height(),existingSize.width()+1); + _shellProcess->setWindowSize(existingSize.height(),existingSize.width()); +} + +bool Session::kill(int signal) +{ + int result = ::kill(_shellProcess->pid(),signal); + + if ( result == 0 ) + { + _shellProcess->waitForFinished(); + return true; + } + else + return false; +} + +void Session::close() +{ + _autoClose = true; + _wantedClose = true; + + if (!isRunning() || !kill(SIGHUP)) + { + if (isRunning()) + { + //kWarning() << "Process" << _shellProcess->pid() << "did not respond to SIGHUP"; + + // close the pty and wait to see if the process finishes. If it does, + // the done() slot will have been called so we can return. Otherwise, + // emit the finished() signal regardless + _shellProcess->pty()->close(); + if (_shellProcess->waitForFinished(3000)) + return; + + //kWarning() << "Unable to kill process" << _shellProcess->pid(); + } + + // Forced close. + QTimer::singleShot(1, this, SIGNAL(finished())); + } +} + +void Session::sendText(const QString &text) const +{ + _emulation->sendText(text); +} + +void Session::sendMouseEvent(int buttons, int column, int line, int eventType) +{ + _emulation->sendMouseEvent(buttons, column, line, eventType); +} + +Session::~Session() +{ + if (_foregroundProcessInfo) + delete _foregroundProcessInfo; + if (_sessionProcessInfo) + delete _sessionProcessInfo; + delete _emulation; + delete _shellProcess; + //delete _zmodemProc; +} + +void Session::done(int exitStatus) +{ + if (!_autoClose) + { + _userTitle = QString("@info:shell This session is done"); + emit titleChanged(); + return; + } + + QString message; + QTextStream msgStream(&message); + if (!_wantedClose || exitStatus != 0) + { + if (_shellProcess->exitStatus() == QProcess::NormalExit) + { + msgStream << "Program '" << _program << "' exited with statis " << exitStatus << "."; + //message = i18n("Program '%1' exited with status %2.", _program.toLatin1().data(), exitStatus); + + } + else + { + msgStream << "Program '" << _program << "' crashed."; + //message = i18n("Program '%1' crashed.", _program.toLatin1().data()); + + } + + //FIXME: See comments in Session::monitorTimerDone() + //KNotification::event("Finished", message , QPixmap(), + // QApplication::activeWindow(), + // KNotification::CloseWhenWidgetActivated); + } + + if ( !_wantedClose && _shellProcess->exitStatus() != QProcess::NormalExit ) + terminalWarning(message); + else + emit finished(); +} + +Emulation* Session::emulation() const +{ + return _emulation; +} + +QString Session::keyBindings() const +{ + return _emulation->keyBindings(); +} + +QStringList Session::environment() const +{ + return _environment; +} + +void Session::setEnvironment(const QStringList& environment) +{ + _environment = environment; +} + +int Session::sessionId() const +{ + return _sessionId; +} + +void Session::setKeyBindings(const QString &id) +{ + _emulation->setKeyBindings(id); +} + +void Session::setTitle(TitleRole role , const QString& newTitle) +{ + if ( title(role) != newTitle ) + { + if ( role == NameRole ) + _nameTitle = newTitle; + else if ( role == DisplayedTitleRole ) + _displayTitle = newTitle; + + emit titleChanged(); + } +} + +QString Session::title(TitleRole role) const +{ + if ( role == NameRole ) + return _nameTitle; + else if ( role == DisplayedTitleRole ) + return _displayTitle; + else + return QString(); +} + +ProcessInfo* Session::getProcessInfo() +{ + ProcessInfo* process; + + if (isForegroundProcessActive()) + process = _foregroundProcessInfo; + else + { + updateSessionProcessInfo(); + process = _sessionProcessInfo; + } + + return process; +} + +void Session::updateSessionProcessInfo() +{ + Q_ASSERT(_shellProcess); + if (!_sessionProcessInfo) + { + _sessionProcessInfo = ProcessInfo::newInstance(processId()); + _sessionProcessInfo->setUserHomeDir(); + } + _sessionProcessInfo->update(); +} + +bool Session::updateForegroundProcessInfo() +{ + bool valid = (_foregroundProcessInfo != 0); + + // has foreground process changed? + Q_ASSERT(_shellProcess); + int pid = _shellProcess->foregroundProcessGroup(); + if (pid != _foregroundPid) + { + if (valid) + delete _foregroundProcessInfo; + _foregroundProcessInfo = ProcessInfo::newInstance(pid); + _foregroundPid = pid; + valid = true; + } + + if (valid) + { + _foregroundProcessInfo->update(); + valid = _foregroundProcessInfo->isValid(); + } + + return valid; +} + +bool Session::isRemote() +{ + ProcessInfo* process = getProcessInfo(); + + bool ok = false; + return ( process->name(&ok) == "ssh" && ok ); +} + +QString Session::getDynamicTitle() +{ + // update current directory from process + ProcessInfo* process = updateWorkingDirectory(); + + // format tab titles using process info + bool ok = false; + QString title; + if ( process->name(&ok) == "ssh" && ok ) + { + SSHProcessInfo sshInfo(*process); + title = sshInfo.format(tabTitleFormat(Session::RemoteTabTitle)); + } + else + title = process->format(tabTitleFormat(Session::LocalTabTitle)); + + return title; +} + +void Session::setIconName(const QString& iconName) +{ + if ( iconName != _iconName ) + { + _iconName = iconName; + emit titleChanged(); + } +} + +void Session::setIconText(const QString& iconText) +{ + _iconText = iconText; +} + +QString Session::iconName() const +{ + return _iconName; +} + +QString Session::iconText() const +{ + return _iconText; +} + +void Session::setHistoryType(const HistoryType &hType) +{ + _emulation->setHistory(hType); +} + +const HistoryType& Session::historyType() const +{ + return _emulation->history(); +} + +void Session::clearHistory() +{ + _emulation->clearHistory(); +} + +QStringList Session::arguments() const +{ + return _arguments; +} + +QString Session::program() const +{ + return _program; +} + +// unused currently +bool Session::isMonitorActivity() const { return _monitorActivity; } +// unused currently +bool Session::isMonitorSilence() const { return _monitorSilence; } + +void Session::setMonitorActivity(bool _monitor) +{ + _monitorActivity=_monitor; + _notifiedActivity=false; + + activityStateSet(NOTIFYNORMAL); +} + +void Session::setMonitorSilence(bool _monitor) +{ + if (_monitorSilence==_monitor) + return; + + _monitorSilence=_monitor; + if (_monitorSilence) + { + _monitorTimer->start(_silenceSeconds*1000); + } + else + _monitorTimer->stop(); + + activityStateSet(NOTIFYNORMAL); +} + +void Session::setMonitorSilenceSeconds(int seconds) +{ + _silenceSeconds=seconds; + if (_monitorSilence) { + _monitorTimer->start(_silenceSeconds*1000); + } +} + +void Session::setAddToUtmp(bool set) +{ + _addToUtmp = set; +} + +void Session::setFlowControlEnabled(bool enabled) +{ + _flowControl = enabled; + + if (_shellProcess) + _shellProcess->setFlowControlEnabled(_flowControl); + emit flowControlEnabledChanged(enabled); +} +bool Session::flowControlEnabled() const +{ + if (_shellProcess) + return _shellProcess->flowControlEnabled(); + else + return _flowControl; +} + +void Session::onReceiveBlock( const char* buf, int len ) +{ + _emulation->receiveData( buf, len ); + emit receivedData( QString::fromLatin1( buf, len ) ); +} + +QSize Session::size() +{ + return _emulation->imageSize(); +} + +void Session::setSize(const QSize& size) +{ + if ((size.width() <= 1) || (size.height() <= 1)) + return; + + emit resizeRequest(size); +} +int Session::processId() const +{ + return _shellProcess->pid(); +} + +void Session::setTitle(int role , const QString& title) +{ + switch (role) { + case (0): + this->setTitle(Session::NameRole, title); + break; + case (1): + this->setTitle(Session::DisplayedTitleRole, title); + break; + } +} + +QString Session::title(int role) const +{ + switch (role) { + case (0): + return this->title(Session::NameRole); + case (1): + return this->title(Session::DisplayedTitleRole); + default: + return QString(); + } +} + +void Session::setTabTitleFormat(int context , const QString& format) +{ + switch (context) { + case (0): + this->setTabTitleFormat(Session::LocalTabTitle, format); + break; + case (1): + this->setTabTitleFormat(Session::RemoteTabTitle, format); + break; + } +} + +QString Session::tabTitleFormat(int context) const +{ + switch (context) { + case (0): + return this->tabTitleFormat(Session::LocalTabTitle); + case (1): + return this->tabTitleFormat(Session::RemoteTabTitle); + default: + return QString(); + } +} + +int Session::foregroundProcessId() +{ + int pid; + + bool ok = false; + pid = getProcessInfo()->pid(&ok); + if (!ok) + pid = -1; + + return pid; +} + +bool Session::isForegroundProcessActive() +{ + // foreground process info is always updated after this + return updateForegroundProcessInfo() && (processId() != _foregroundPid); +} + +QString Session::foregroundProcessName() +{ + QString name; + + if (updateForegroundProcessInfo()) + { + bool ok = false; + name = _foregroundProcessInfo->name(&ok); + if (!ok) + name.clear(); + } + + return name; +} + +SessionGroup::SessionGroup(QObject* parent) + : QObject(parent), _masterMode(0) +{ +} +SessionGroup::~SessionGroup() +{ +} +int SessionGroup::masterMode() const { return _masterMode; } +QList<Session*> SessionGroup::sessions() const { return _sessions.keys(); } +bool SessionGroup::masterStatus(Session* session) const { return _sessions[session]; } + +void SessionGroup::addSession(Session* session) +{ + connect(session,SIGNAL(finished()),this,SLOT(sessionFinished())); + _sessions.insert(session,false); +} +void SessionGroup::removeSession(Session* session) +{ + disconnect(session,SIGNAL(finished()),this,SLOT(sessionFinished())); + setMasterStatus(session,false); + _sessions.remove(session); +} +void SessionGroup::sessionFinished() +{ + Session* session = qobject_cast<Session*>(sender()); + Q_ASSERT(session); + removeSession(session); +} +void SessionGroup::setMasterMode(int mode) +{ + _masterMode = mode; +} +QList<Session*> SessionGroup::masters() const +{ + return _sessions.keys(true); +} +void SessionGroup::setMasterStatus(Session* session , bool master) +{ + const bool wasMaster = _sessions[session]; + + if (wasMaster == master) { + // No status change -> nothing to do. + return; + } + _sessions[session] = master; + + if(master) { + connect( session->emulation() , SIGNAL(sendData(const char*,int)) , this, + SLOT(forwardData(const char*,int)) ); + } + else { + disconnect( session->emulation() , SIGNAL(sendData(const char*,int)) , this, + SLOT(forwardData(const char*,int)) ); + } +} +void SessionGroup::forwardData(const char* data, int size) +{ + static bool _inForwardData = false; + if(_inForwardData) { // Avoid recursive calls among session groups! + // A recursive call happens when a master in group A calls forwardData() + // in group B. If one of the destination sessions in group B is also a + // master of a group including the master session of group A, this would + // again call forwardData() in group A, and so on. + return; + } + + _inForwardData = true; + QListIterator<Session*> iter(_sessions.keys()); + while(iter.hasNext()) { + Session* other = iter.next(); + if(!_sessions[other]) { + other->emulation()->sendString(data, size); + } + } + _inForwardData = false; +} + +
new file mode 100644 --- /dev/null +++ b/gui//src/Session.h @@ -0,0 +1,752 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef SESSION_H +#define SESSION_H + +// Qt +#include <QtCore/QStringList> +#include <QtCore/QByteRef> +#include <QtCore/QSize> +#include <QUuid> +#include <QWidget> + +// Konsole +#include "History.h" + +class KProcess; +class KUrl; +class Emulation; +class Pty; +class ProcessInfo; +class TerminalDisplay; + //class ZModemDialog; +/** + * Represents a terminal session consisting of a pseudo-teletype and a terminal emulation. + * The pseudo-teletype (or PTY) handles I/O between the terminal process and Konsole. + * The terminal emulation ( Emulation and subclasses ) processes the output stream from the + * PTY and produces a character image which is then shown on views connected to the session. + * + * Each Session can be connected to one or more views by using the addView() method. + * The attached views can then display output from the program running in the terminal + * or send input to the program in the terminal in the form of keypresses and mouse + * activity. + */ +class Session : public QObject +{ +Q_OBJECT +Q_CLASSINFO("D-Bus Interface", "org.kde.konsole.Session") + +public: + Q_PROPERTY(QString name READ nameTitle) + Q_PROPERTY(int processId READ processId) + Q_PROPERTY(QString keyBindings READ keyBindings WRITE setKeyBindings) + Q_PROPERTY(QSize size READ size WRITE setSize) + + /** + * Constructs a new session. + * + * To start the terminal process, call the run() method, + * after specifying the program and arguments + * using setProgram() and setArguments() + * + * If no program or arguments are specified explicitly, the Session + * falls back to using the program specified in the SHELL environment + * variable. + */ + explicit Session(QObject* parent = 0); + ~Session(); + + /** + * Connect to an existing terminal. When a new Session() is constructed it + * automatically searches for and opens a new teletype. If you want to + * use an existing teletype (given its file descriptor) call this after + * constructing the session. + * + * Calling openTeletype() while a session is running has no effect. + * + * @param masterFd The file descriptor of the pseudo-teletype master (See KPtyProcess::KPtyProcess()) + */ + void openTeletype(int masterFd); + + /** + * Returns true if the session is currently running. This will be true + * after run() has been called successfully. + */ + bool isRunning() const; + + /** + * Adds a new view for this session. + * + * The viewing widget will display the output from the terminal and + * input from the viewing widget (key presses, mouse activity etc.) + * will be sent to the terminal. + * + * Views can be removed using removeView(). The session is automatically + * closed when the last view is removed. + */ + void addView(TerminalDisplay* widget); + /** + * Removes a view from this session. When the last view is removed, + * the session will be closed automatically. + * + * @p widget will no longer display output from or send input + * to the terminal + */ + void removeView(TerminalDisplay* widget); + + /** + * Returns the views connected to this session + */ + QList<TerminalDisplay*> views() const; + + /** + * Returns the terminal emulation instance being used to encode / decode + * characters to / from the process. + */ + Emulation* emulation() const; + + /** Returns the unique ID for this session. */ + int sessionId() const; + + /** + * This enum describes the contexts for which separate + * tab title formats may be specified. + */ + enum TabTitleContext + { + /** Default tab title format */ + LocalTabTitle, + /** + * Tab title format used session currently contains + * a connection to a remote computer (via SSH) + */ + RemoteTabTitle + }; + + /** + * Returns true if the session currently contains a connection to a + * remote computer. It currently supports ssh. + */ + bool isRemote(); + + /** + * Sets the format used by this session for tab titles. + * + * @param context The context whoose format should be set. + * @param format The tab title format. This may be a mixture + * of plain text and dynamic elements denoted by a '%' character + * followed by a letter. (eg. %d for directory). The dynamic + * elements available depend on the @p context + */ + void setTabTitleFormat(TabTitleContext context , const QString& format); + /** Returns the format used by this session for tab titles. */ + QString tabTitleFormat(TabTitleContext context) const; + + + /** Returns the arguments passed to the shell process when run() is called. */ + QStringList arguments() const; + /** Returns the program name of the shell process started when run() is called. */ + QString program() const; + + /** + * Sets the command line arguments which the session's program will be passed when + * run() is called. + */ + void setArguments(const QStringList& arguments); + /** Sets the program to be executed when run() is called. */ + void setProgram(const QString& program); + + /** Returns the session's current working directory. */ + QString initialWorkingDirectory() { return _initialWorkingDir; } + + /** + * Sets the initial working directory for the session when it is run + * This has no effect once the session has been started. + */ + void setInitialWorkingDirectory( const QString& dir ); + + /** + * Returns the current directory of the foreground process in the session + */ + QString currentWorkingDirectory(); + + /** + * Sets the type of history store used by this session. + * Lines of output produced by the terminal are added + * to the history store. The type of history store + * used affects the number of lines which can be + * remembered before they are lost and the storage + * (in memory, on-disk etc.) used. + */ + void setHistoryType(const HistoryType& type); + /** + * Returns the type of history store used by this session. + */ + const HistoryType& historyType() const; + /** + * Clears the history store used by this session. + */ + void clearHistory(); + + /** + * Sets the key bindings used by this session. The bindings + * specify how input key sequences are translated into + * the character stream which is sent to the terminal. + * + * @param id The name of the key bindings to use. The + * names of available key bindings can be determined using the + * KeyboardTranslatorManager class. + */ + void setKeyBindings(const QString& id); + /** Returns the name of the key bindings used by this session. */ + QString keyBindings() const; + + /** + * This enum describes the available title roles. + */ + enum TitleRole + { + /** The name of the session. */ + NameRole, + /** The title of the session which is displayed in tabs etc. */ + DisplayedTitleRole + }; + + /** + * Return the session title set by the user (ie. the program running + * in the terminal), or an empty string if the user has not set a custom title + */ + QString userTitle() const; + + /** Convenience method used to read the name property. Returns title(Session::NameRole). */ + QString nameTitle() const { return title(Session::NameRole); } + /** Returns a title generated from tab format and process information. */ + QString getDynamicTitle(); + + /** Sets the name of the icon associated with this session. */ + void setIconName(const QString& iconName); + /** Returns the name of the icon associated with this session. */ + QString iconName() const; + + /** Return URL for the session. */ + //KUrl getUrl(); + + /** Sets the text of the icon associated with this session. */ + void setIconText(const QString& iconText); + /** Returns the text of the icon associated with this session. */ + QString iconText() const; + + /** Sets the session's title for the specified @p role to @p title. */ + void setTitle(TitleRole role , const QString& title); + + /** Returns the session's title for the specified @p role. */ + QString title(TitleRole role) const; + + /** + * Specifies whether a utmp entry should be created for the pty used by this session. + * If true, KPty::login() is called when the session is started. + */ + void setAddToUtmp(bool); + + /** + * Specifies whether to close the session automatically when the terminal + * process terminates. + */ + void setAutoClose(bool b) { _autoClose = b; } + + /** Returns true if the user has started a program in the session. */ + bool isForegroundProcessActive(); + + /** Returns the name of the current foreground process. */ + QString foregroundProcessName(); + + /** Returns the terminal session's window size in lines and columns. */ + QSize size(); + /** + * Emits a request to resize the session to accommodate + * the specified window size. + * + * @param size The size in lines and columns to request. + */ + void setSize(const QSize& size); + + /** + * Sets whether the session has a dark background or not. The session + * uses this information to set the COLORFGBG variable in the process's + * environment, which allows the programs running in the terminal to determine + * whether the background is light or dark and use appropriate colors by default. + * + * This has no effect once the session is running. + */ + void setDarkBackground(bool darkBackground); + /** + * Returns true if the session has a dark background. + * See setDarkBackground() + */ + bool hasDarkBackground() const; + + /** + * Attempts to get the shell program to redraw the current display area. + * This can be used after clearing the screen, for example, to get the + * shell to redraw the prompt line. + */ + void refresh(); + + // void startZModem(const QString &rz, const QString &dir, const QStringList &list); + // void cancelZModem(); + // bool isZModemBusy() { return _zmodemBusy; } + + /** + * Possible values of the @p what parameter for setUserTitle() + * See "Operating System Controls" section on http://rtfm.etla.org/xterm/ctlseq.html + */ + enum UserTitleChange + { + IconNameAndWindowTitle = 0, + IconName = 1, + WindowTitle = 2, + TextColor = 10, + BackgroundColor = 11, + SessionName = 30, + ProfileChange = 50 // this clashes with Xterm's font change command + }; + + // Sets the text codec used by this sessions terminal emulation. + void setCodec(QTextCodec* codec); + + // session management + //void saveSession(KConfigGroup& group); + //void restoreSession(KConfigGroup& group); + +public slots: + + /** + * Starts the terminal session. + * + * This creates the terminal process and connects the teletype to it. + */ + void run(); + + /** + * Returns the environment of this session as a list of strings like + * VARIABLE=VALUE + */ + Q_SCRIPTABLE QStringList environment() const; + + /** + * Sets the environment for this session. + * @p environment should be a list of strings like + * VARIABLE=VALUE + */ + Q_SCRIPTABLE void setEnvironment(const QStringList& environment); + + /** + * Closes the terminal session. This sends a hangup signal + * (SIGHUP) to the terminal process and causes the finished() + * signal to be emitted. If the process does not respond to the SIGHUP signal + * then the terminal connection (the pty) is closed and Konsole waits for the + * process to exit. + */ + Q_SCRIPTABLE void close(); + + /** + * Changes the session title or other customizable aspects of the terminal + * emulation display. For a list of what may be changed see the + * Emulation::titleChanged() signal. + * + * @param what The feature being changed. Value is one of UserTitleChange + * @param caption The text part of the terminal command + */ + void setUserTitle( int what , const QString &caption ); + + /** + * Enables monitoring for activity in the session. + * This will cause notifySessionState() to be emitted + * with the NOTIFYACTIVITY state flag when output is + * received from the terminal. + */ + Q_SCRIPTABLE void setMonitorActivity(bool); + + /** Returns true if monitoring for activity is enabled. */ + Q_SCRIPTABLE bool isMonitorActivity() const; + + /** + * Enables monitoring for silence in the session. + * This will cause notifySessionState() to be emitted + * with the NOTIFYSILENCE state flag when output is not + * received from the terminal for a certain period of + * time, specified with setMonitorSilenceSeconds() + */ + Q_SCRIPTABLE void setMonitorSilence(bool); + + /** + * Returns true if monitoring for inactivity (silence) + * in the session is enabled. + */ + Q_SCRIPTABLE bool isMonitorSilence() const; + + /** See setMonitorSilence() */ + Q_SCRIPTABLE void setMonitorSilenceSeconds(int seconds); + + /** + * Sets whether flow control is enabled for this terminal + * session. + */ + Q_SCRIPTABLE void setFlowControlEnabled(bool enabled); + + /** Returns whether flow control is enabled for this terminal session. */ + Q_SCRIPTABLE bool flowControlEnabled() const; + + /** + * Sends @p text to the current foreground terminal program. + */ + Q_SCRIPTABLE void sendText(const QString& text) const; + + /** + * Sends a mouse event of type @p eventType emitted by button + * @p buttons on @p column/@p line to the current foreground + * terminal program + */ + Q_SCRIPTABLE void sendMouseEvent(int buttons, int column, int line, int eventType); + + /** + * Returns the process id of the terminal process. + * This is the id used by the system API to refer to the process. + */ + Q_SCRIPTABLE int processId() const; + + /** + * Returns the process id of the terminal's foreground process. + * This is initially the same as processId() but can change + * as the user starts other programs inside the terminal. + */ + Q_SCRIPTABLE int foregroundProcessId(); + + /** Sets the text codec used by this sessions terminal emulation. + * Overloaded to accept a QByteArray for convenience since DBus + * does not accept QTextCodec directky. + */ + Q_SCRIPTABLE bool setCodec(QByteArray codec); + + /** Returns the codec used to decode incoming characters in this + * terminal emulation + */ + Q_SCRIPTABLE QByteArray codec(); + + /** Sets the session's title for the specified @p role to @p title. + * This is an overloaded member function for setTitle(TitleRole, QString) + * provided for convenience since enum data types may not be + * exported directly through DBus + */ + Q_SCRIPTABLE void setTitle(int role, const QString& title); + + /** Returns the session's title for the specified @p role. + * This is an overloaded member function for setTitle(TitleRole) + * provided for convenience since enum data types may not be + * exported directly through DBus + */ + Q_SCRIPTABLE QString title(int role) const; + + /** Returns the "friendly" version of the QUuid of this session. + * This is a QUuid with the braces and dashes removed, so it cannot be + * used to construct a new QUuid. The same text appears in the + * SHELL_SESSION_ID environment variable. + */ + Q_SCRIPTABLE QString shellSessionId() const; + + /** Sets the session's tab title format for the specified @p context to @p format. + * This is an overloaded member function for setTabTitleFormat(TabTitleContext, QString) + * provided for convenience since enum data types may not be + * exported directly through DBus + */ + Q_SCRIPTABLE void setTabTitleFormat(int context, const QString& format); + + /** Returns the session's tab title format for the specified @p context. + * This is an overloaded member function for tabTitleFormat(TitleRole) + * provided for convenience since enum data types may not be + * exported directly through DBus + */ + Q_SCRIPTABLE QString tabTitleFormat(int context) const; + +signals: + + /** Emitted when the terminal process starts. */ + void started(); + + /** + * Emitted when the terminal process exits. + */ + void finished(); + + /** + * Emitted when output is received from the terminal process. + */ + void receivedData( const QString& text ); + + /** Emitted when the session's title has changed. */ + void titleChanged(); + + /** + * Emitted when the activity state of this session changes. + * + * @param state The new state of the session. This may be one + * of NOTIFYNORMAL, NOTIFYSILENCE or NOTIFYACTIVITY + */ + void stateChanged(int state); + + /** Emitted when a bell event occurs in the session. */ + void bellRequest( const QString& message ); + + /** + * Requests that the color the text for any tabs associated with + * this session should be changed; + * + * TODO: Document what the parameter does + */ + void changeTabTextColorRequest(int); + + /** + * Requests that the background color of views on this session + * should be changed. + */ + void changeBackgroundColorRequest(const QColor&); + /** + * Requests that the text color of views on this session should + * be changed to @p color. + */ + void changeForegroundColorRequest(const QColor&); + + /** TODO: Document me. */ + void openUrlRequest(const QString& url); + + /** TODO: Document me. */ + //void zmodemDetected(); + + /** + * Emitted when the terminal process requests a change + * in the size of the terminal window. + * + * @param size The requested window size in terms of lines and columns. + */ + void resizeRequest(const QSize& size); + + /** + * Emitted when a profile change command is received from the terminal. + * + * @param text The text of the command. This is a string of the form + * "PropertyName=Value;PropertyName=Value ..." + */ + void profileChangeCommandReceived(const QString& text); + + /** + * Emitted when the flow control state changes. + * + * @param enabled True if flow control is enabled or false otherwise. + */ + void flowControlEnabledChanged(bool enabled); + +private slots: + void done(int); + + // void fireZModemDetected(); + + void onReceiveBlock( const char* buffer, int len ); + void monitorTimerDone(); + + void onViewSizeChange(int height, int width); + + void activityStateSet(int); + + //automatically detach views from sessions when view is destroyed + void viewDestroyed(QObject* view); + + //void zmodemReadStatus(); + //void zmodemReadAndSendBlock(); + //void zmodemRcvBlock(const char *data, int len); + //void zmodemFinished(); + + void updateFlowControlState(bool suspended); + void updateWindowSize(int lines, int columns); +private: + + void updateTerminalSize(); + WId windowId() const; + bool kill(int signal); + // print a warning message in the terminal. This is used + // if the program fails to start, or if the shell exits in + // an unsuccessful manner + void terminalWarning(const QString& message); + // checks that the binary 'program' is available and can be executed + // returns the binary name if available or an empty string otherwise + QString checkProgram(const QString& program) const; + ProcessInfo* getProcessInfo(); + void updateSessionProcessInfo(); + bool updateForegroundProcessInfo(); + ProcessInfo* updateWorkingDirectory(); + + QUuid _uniqueIdentifier; // SHELL_SESSION_ID + + Pty* _shellProcess; + Emulation* _emulation; + + QList<TerminalDisplay*> _views; + + bool _monitorActivity; + bool _monitorSilence; + bool _notifiedActivity; + bool _masterMode; + bool _autoClose; + bool _wantedClose; + QTimer* _monitorTimer; + + int _silenceSeconds; + + QString _nameTitle; + QString _displayTitle; + QString _userTitle; + + QString _localTabTitleFormat; + QString _remoteTabTitleFormat; + + QString _iconName; + QString _iconText; // as set by: echo -en '\033]1;IconText\007 + bool _addToUtmp; + bool _flowControl; + bool _fullScripting; + + QString _program; + QStringList _arguments; + + QStringList _environment; + int _sessionId; + + QString _initialWorkingDir; + QString _currentWorkingDir; + + ProcessInfo* _sessionProcessInfo; + ProcessInfo* _foregroundProcessInfo; + int _foregroundPid; + + // ZModem + // bool _zmodemBusy; + // KProcess* _zmodemProc; + // ZModemDialog* _zmodemProgress; + + // Color/Font Changes by ESC Sequences + + QColor _modifiedBackground; // as set by: echo -en '\033]11;Color\007 + + QString _profileKey; + + bool _hasDarkBackground; + + static int lastSessionId; + +}; + +/** + * Provides a group of sessions which is divided into master and slave sessions. + * Activity in master sessions can be propagated to all sessions within the group. + * The type of activity which is propagated and method of propagation is controlled + * by the masterMode() flags. + */ +class SessionGroup : public QObject +{ +Q_OBJECT + +public: + /** Constructs an empty session group. */ + SessionGroup(QObject* parent); + /** Destroys the session group and removes all connections between master and slave sessions. */ + ~SessionGroup(); + + /** Adds a session to the group. */ + void addSession( Session* session ); + /** Removes a session from the group. */ + void removeSession( Session* session ); + + /** Returns the list of sessions currently in the group. */ + QList<Session*> sessions() const; + + /** + * Sets whether a particular session is a master within the group. + * Changes or activity in the group's master sessions may be propagated + * to all the sessions in the group, depending on the current masterMode() + * + * @param session The session whoose master status should be changed. + * @param master True to make this session a master or false otherwise + */ + void setMasterStatus( Session* session , bool master ); + /** Returns the master status of a session. See setMasterStatus() */ + bool masterStatus( Session* session ) const; + + /** + * This enum describes the options for propagating certain activity or + * changes in the group's master sessions to all sessions in the group. + */ + enum MasterMode + { + /** + * Any input key presses in the master sessions are sent to all + * sessions in the group. + */ + CopyInputToAll = 1 + }; + + /** + * Specifies which activity in the group's master sessions is propagated + * to all sessions in the group. + * + * @param mode A bitwise OR of MasterMode flags. + */ + void setMasterMode( int mode ); + /** + * Returns a bitwise OR of the active MasterMode flags for this group. + * See setMasterMode() + */ + int masterMode() const; + +private slots: + void sessionFinished(); + void forwardData(const char* data, int size); + +private: + QList<Session*> masters() const; + + // maps sessions to their master status + QHash<Session*,bool> _sessions; + + int _masterMode; +}; + +#endif + +/* + Local Variables: + mode: c++ + c-file-style: "stroustrup" + indent-tabs-mode: nil + tab-width: 4 + End: +*/
new file mode 100644 --- /dev/null +++ b/gui//src/ShellCommand.cpp @@ -0,0 +1,165 @@ +/* + Copyright (C) 2007 by Robert Knight <robertknight@gmail.com> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "ShellCommand.h" + +//some versions of gcc(4.3) require explicit include +#include <cstdlib> + +// expands environment variables in 'text' +// function copied from kdelibs/kio/kio/kurlcompletion.cpp +static bool expandEnv(QString& text); + +ShellCommand::ShellCommand(const QString& fullCommand) +{ + bool inQuotes = false; + + QString builder; + + for ( int i = 0 ; i < fullCommand.count() ; i++ ) + { + QChar ch = fullCommand[i]; + + const bool isLastChar = ( i == fullCommand.count() - 1 ); + const bool isQuote = ( ch == '\'' || ch == '\"' ); + + if ( !isLastChar && isQuote ) + inQuotes = !inQuotes; + else + { + if ( (!ch.isSpace() || inQuotes) && !isQuote ) + builder.append(ch); + + if ( (ch.isSpace() && !inQuotes) || ( i == fullCommand.count()-1 ) ) + { + _arguments << builder; + builder.clear(); + } + } + } +} +ShellCommand::ShellCommand(const QString& command , const QStringList& arguments) +{ + _arguments = arguments; + + if ( !_arguments.isEmpty() ) + _arguments[0] == command; +} +QString ShellCommand::fullCommand() const +{ + return _arguments.join(QChar(' ')); +} +QString ShellCommand::command() const +{ + if ( !_arguments.isEmpty() ) + return _arguments[0]; + else + return QString(); +} +QStringList ShellCommand::arguments() const +{ + return _arguments; +} +bool ShellCommand::isRootCommand() const +{ + Q_ASSERT(0); // not implemented yet + return false; +} +bool ShellCommand::isAvailable() const +{ + Q_ASSERT(0); // not implemented yet + return false; +} +QStringList ShellCommand::expand(const QStringList& items) +{ + QStringList result; + + foreach( QString item , items ) + result << expand(item); + + return result; +} +QString ShellCommand::expand(const QString& text) +{ + QString result = text; + expandEnv(result); + return result; +} + +/* + * expandEnv + * + * Expand environment variables in text. Escaped '$' characters are ignored. + * Return true if any variables were expanded + */ +static bool expandEnv( QString &text ) +{ + // Find all environment variables beginning with '$' + // + int pos = 0; + + bool expanded = false; + + while ( (pos = text.indexOf(QLatin1Char('$'), pos)) != -1 ) { + + // Skip escaped '$' + // + if ( pos > 0 && text.at(pos-1) == QLatin1Char('\\') ) { + pos++; + } + // Variable found => expand + // + else { + // Find the end of the variable = next '/' or ' ' + // + int pos2 = text.indexOf( QLatin1Char(' '), pos+1 ); + int pos_tmp = text.indexOf( QLatin1Char('/'), pos+1 ); + + if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) ) + pos2 = pos_tmp; + + if ( pos2 == -1 ) + pos2 = text.length(); + + // Replace if the variable is terminated by '/' or ' ' + // and defined + // + if ( pos2 >= 0 ) { + int len = pos2 - pos; + QString key = text.mid( pos+1, len-1); + QString value = + QString::fromLocal8Bit( ::getenv(key.toLocal8Bit()) ); + + if ( !value.isEmpty() ) { + expanded = true; + text.replace( pos, len, value ); + pos = pos + value.length(); + } + else { + pos = pos2; + } + } + } + } + + return expanded; +}
new file mode 100644 --- /dev/null +++ b/gui//src/ShellCommand.h @@ -0,0 +1,88 @@ +/* + Copyright (C) 2007 by Robert Knight <robertknight@gmail.com> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef SHELLCOMMAND_H +#define SHELLCOMMAND_H + +// Qt +#include <QtCore/QStringList> + +/** + * A class to parse and extract information about shell commands. + * + * ShellCommand can be used to: + * + * <ul> + * <li>Take a command-line (eg "/bin/sh -c /path/to/my/script") and split it + * into its component parts (eg. the command "/bin/sh" and the arguments + * "-c","/path/to/my/script") + * </li> + * <li>Take a command and a list of arguments and combine them to + * form a complete command line. + * </li> + * <li>Determine whether the binary specified by a command exists in the + * user's PATH. + * </li> + * <li>Determine whether a command-line specifies the execution of + * another command as the root user using su/sudo etc. + * </li> + * </ul> + */ +class ShellCommand +{ +public: + /** + * Constructs a ShellCommand from a command line. + * + * @param fullCommand The command line to parse. + */ + ShellCommand(const QString& fullCommand); + /** + * Constructs a ShellCommand with the specified @p command and @p arguments. + */ + ShellCommand(const QString& command , const QStringList& arguments); + + /** Returns the command. */ + QString command() const; + /** Returns the arguments. */ + QStringList arguments() const; + + /** + * Returns the full command line. + */ + QString fullCommand() const; + + /** Returns true if this is a root command. */ + bool isRootCommand() const; + /** Returns true if the program specified by @p command() exists. */ + bool isAvailable() const; + + /** Expands environment variables in @p text .*/ + static QString expand(const QString& text); + + /** Expands environment variables in each string in @p list. */ + static QStringList expand(const QStringList& items); + +private: + QStringList _arguments; +}; +#endif // SHELLCOMMAND_H +
new file mode 100644 --- /dev/null +++ b/gui//src/SimpleEditor.cpp @@ -0,0 +1,288 @@ +/* Copyright (C) 2010 P.L. Lucas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "SimpleEditor.h" +#include <QFile> +#include <QTextStream> +#include <QTextBlock> +#include <QFileInfo> +#include <QDir> + +SimpleEditor::SimpleEditor(QWidget *parent) + : QPlainTextEdit(parent), + m_syntaxHighlighter(0), + m_firstTimeUse(true) { + + m_completerModel = new QStringListModel (); + m_completer = new QCompleter(m_completerModel, this); + m_completer->setCompletionMode(QCompleter::PopupCompletion); + m_completer->setWidget(this); + m_autoIndentation = true; + m_automaticIndentationStatement = true; + + QFont font; + font.setFamily("Courier"); + font.setPointSize(10); + setFont(font); + + connect(m_completer, SIGNAL(activated(const QString &)), this, SLOT(activated(const QString &))); + connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChangedCallBack())); + connect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(autoComplete(int, int, int))); +} + +void SimpleEditor::loadSyntaxXMLDescription() { + QString installPath = QString("../syntax_files") + + QDir::separator(); + + QFileInfo file(m_currentFileName); + QString suffix = file.suffix(); + + if(m_commandsCompletionList.isEmpty()) { + QString home = QDir::home().path() + + QDir::separator() + + ".qtoctave" + + QDir::separator() + + "commands.txt"; + + QFile file(home); + + if(file.open(QFile::ReadOnly)) { + char buf[1024]; + while(file.readLine(buf, sizeof(buf)) >= 0) { + m_commandsCompletionList.append(QString(buf).trimmed()); + } + file.close(); + } + } + + QFileInfo xml(installPath + suffix + ".xml"); + if(xml.exists()) { + m_syntaxHighlighter = new SyntaxHighlighter(document()); + m_syntaxHighlighter->load(xml.absoluteFilePath()); + m_syntaxHighlighter->setDocument(document()); + } +} + +bool SimpleEditor::load(QString file) { + if(file.isEmpty()) { + setPlainText(""); + m_currentFileName = file; + return true; + } + + FILE *input = fopen(file.toLocal8Bit().data(),"r"); + if(!input) + return false; + fclose(input); + QFile in(file); + if(!in.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + QByteArray data = in.readAll(); + setPlainText(QString::fromLocal8Bit(data)); + m_currentFileName = file; + m_firstTimeUse = false; + + loadSyntaxXMLDescription(); + + return true; +} + +bool SimpleEditor::save() { + QFile::remove(m_currentFileName + "~"); + QFile::copy(m_currentFileName, m_currentFileName + "~"); + FILE *out=fopen(m_currentFileName.toLocal8Bit().data(),"w"); + if(!out) + return false; + fprintf(out, "%s", toPlainText().toLocal8Bit().data()); + fclose(out); + document()->setModified(false); + return true; +} + +void SimpleEditor::keyPressEvent(QKeyEvent * keyEvent) { + //In all cases completer popup must been hided. + if(keyEvent->key() != Qt::Key_Return && keyEvent->key() != Qt::Key_Enter) { + QAbstractItemView *view = m_completer->popup(); + if(view->isVisible()) view->hide(); + } + + if(keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { + QAbstractItemView *view = m_completer->popup(); + if(view->isVisible()) { + QString word = view->currentIndex().data().toString(); + if(word.isEmpty()) { + word = m_completer->currentCompletion(); + } + activated(word); + return; + } else if(m_autoIndentation) { + QTextCursor cursor = textCursor(); + QString line = cursor.block().text(); + QString line2 = line; + for(int i=0;i<line.length();i++) { + if(line[i] != ' ' && line[i] != '\t') { + line.resize(i); + break; + } + } + + cursor.insertText("\n" + line); + if(m_automaticIndentationStatement) { + QRegExp re("^while .*|^if .*|^for .*|^switch .*|^do$|^try|^function .*|^else$|^elseif .*"); + if(re.exactMatch(line2.trimmed())) { + cursor.insertText("\t"); + } + } + setTextCursor(cursor); + } else { + QPlainTextEdit::keyPressEvent(keyEvent); + } + } else if(keyEvent->key() == Qt::Key_Tab) { + QTextCursor cursor=textCursor(); + int start=cursor.selectionStart(); + int end=cursor.selectionEnd(); + if(start == end) { + QPlainTextEdit::keyPressEvent(keyEvent); + return; + } + cursor.beginEditBlock(); + cursor.setPosition(end); + end=cursor.blockNumber(); + cursor.setPosition(start); + cursor.movePosition(QTextCursor::StartOfBlock); + while(true) { + cursor.insertText("\t"); + if(cursor.blockNumber()>=end) { + break; + } + cursor.movePosition(QTextCursor::NextBlock); + } + cursor.endEditBlock(); + } else if(keyEvent->key()==Qt::Key_Backtab) { + QTextCursor cursor=textCursor(); + int start=cursor.selectionStart(); + int end=cursor.selectionEnd(); + if(start==end) { + QPlainTextEdit::keyPressEvent(keyEvent); + return; + } + cursor.beginEditBlock(); + cursor.setPosition(end); + end=cursor.blockNumber(); + cursor.setPosition(start); + cursor.movePosition(QTextCursor::StartOfBlock); + while(true) { + QString line=cursor.block().text(); + if(line.length()>0 && (line[0]==' ' || line[0] =='\t')) { + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + if(cursor.blockNumber()>=end) break; + cursor.movePosition(QTextCursor::NextBlock); + cursor.movePosition(QTextCursor::StartOfBlock); + } + cursor.endEditBlock(); + } else { + if(keyEvent->key()==(Qt::Key_B) && Qt::ControlModifier==keyEvent->modifiers()) { + autoComplete(0); + return; + } + QPlainTextEdit::keyPressEvent(keyEvent); + } +} + +void SimpleEditor::setCharFormat(QTextCharFormat charFormat) { + this->m_charFormat=charFormat; + QTextCursor cursor=textCursor(); + cursor.movePosition(QTextCursor::Start); + cursor.setCharFormat(charFormat); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + setFont(charFormat.font()); + + QFontMetrics fm(charFormat.font()); + int textWidthInPixels = fm.width(" "); + setTabStopWidth(textWidthInPixels); +} + +void SimpleEditor::activated(const QString& text) { + QAbstractItemView *view=m_completer->popup(); + QTextCursor cursor=textCursor(); + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); + cursor.insertText(text); + view->hide(); +} + +void SimpleEditor::autoComplete(int position, int charsRemoved, int charsAdded) { + if(charsAdded==1) + autoComplete(); +} + +void SimpleEditor::autoComplete(int size) { + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); + if(cursor.selectedText().endsWith(" ") + || cursor.selectedText().trimmed().length() < size) { + return; + } + + QStringList list=toPlainText().split(QRegExp("\\W+")); + list.removeDuplicates(); + list.removeOne(cursor.selectedText()); + list.sort(); + list.append(m_commandsCompletionList); + + m_completerModel->setStringList(list); + m_completer->setCompletionPrefix(cursor.selectedText()); + + if(m_completer->completionCount() > 0) { + QRect r=cursorRect(cursor); + r.setWidth(200); + m_completer->complete(r); + } +} + +QString SimpleEditor::getFileName() { + return m_currentFileName; +} + +void SimpleEditor::setFile(QString file) { + m_currentFileName = file; + loadSyntaxXMLDescription(); +} + +void SimpleEditor::cursorPositionChangedCallBack() { + if(m_syntaxHighlighter) + m_syntaxHighlighter->setFormatPairBrackets(this); +} + +void SimpleEditor::publicBlockBoundingRectList(QVector<qreal> &list, int &firstLine) { + qreal pageBottom = height(); + QPointF offset = contentOffset(); + QTextBlock block = firstVisibleBlock(); + firstLine = block.blockNumber() + 1; + qreal first_position = blockBoundingGeometry(block).topLeft().y(); + for(; block.isValid(); block = block.next()) { + QRectF position = blockBoundingGeometry(block); + qreal y = position.topLeft().y() + offset.y() - first_position; + if(y > pageBottom) + break; + list.append(y); + } +} +
new file mode 100644 --- /dev/null +++ b/gui//src/SimpleEditor.h @@ -0,0 +1,61 @@ +/* Copyright (C) 2010 P.L. Lucas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SIMPLEEDITOR_H +#define SIMPLEEDITOR_H + +#include <QPlainTextEdit> +#include <QCompleter> +#include <QStringListModel> +#include "SyntaxHighlighter.h" + +class SimpleEditor : public QPlainTextEdit { + Q_OBJECT +public: + SimpleEditor(QWidget * parent = 0); + bool load(QString file); + bool save(); + QString getFileName(); + void setFile(QString file); + void setCharFormat(QTextCharFormat m_charFormat); + void publicBlockBoundingRectList(QVector<qreal> &list, int &firstLine); + void loadSyntaxXMLDescription(); + +public slots: + void activated(const QString& text); + void cursorPositionChangedCallBack(); + void autoComplete(int size = 3); + void autoComplete(int position, int charsRemoved, int charsAdded); + +protected: + virtual void keyPressEvent(QKeyEvent * e); + +private: + bool m_firstTimeUse; + QString m_currentFileName; + QTextCharFormat m_charFormat; + QCompleter *m_completer; + QStringListModel *m_completerModel; + SyntaxHighlighter *m_syntaxHighlighter; + QStringList m_commandsCompletionList; + bool m_autoIndentation; + bool m_automaticIndentationStatement; +}; + +#endif // SIMPLEEDITOR_H +
new file mode 100644 --- /dev/null +++ b/gui//src/SyntaxHighlighter.cpp @@ -0,0 +1,486 @@ +/* Copyright (C) 2010 P.L. Lucas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#include "SyntaxHighlighter.h" +#include <QXmlStreamReader> +#include <QStack> +#include <QFile> +#include <stdio.h> + +SyntaxHighlighter::SyntaxHighlighter(QObject * parent):QSyntaxHighlighter(parent) +{ +} + + +bool SyntaxHighlighter::load(QString file) +{ + QXmlStreamReader xml; + QStack <QString> stack; + QFile fileDevice(file); + if (!fileDevice.open(QFile::ReadOnly | QFile::Text)) { + return false; + } + + xml.setDevice(&fileDevice); + + QMap <QString,QString> values; + + QVector<QString> xmlMainItems; + xmlMainItems << "item" << "block" << "bracket"; + + int ruleOrder=0; + + while (!xml.atEnd()) + { + QXmlStreamReader::TokenType tokenType=xml.readNext(); + switch(tokenType) + { + case QXmlStreamReader::StartElement: + if(xml.name()!="syntax") + { + if( xmlMainItems.contains(xml.name().toString()) ) + stack.push(xml.name().toString()); + else + values[xml.name().toString()]=xml.readElementText().trimmed(); + } + break; + case QXmlStreamReader::EndElement: + if(stack.isEmpty()) break; + QString name=stack.top(); + if(name==xml.name()) stack.pop(); + if(stack.isEmpty()) + { + QTextCharFormat format; + if(values.contains("bold") && values["bold"]=="true") format.setFontWeight(QFont::Bold); + if(values.contains("underline") && values["underline"]=="true") format.setFontUnderline(true); + if(values.contains("italic") && values["italic"]=="true") format.setFontItalic(true); + if(values.contains("foreground")) format.setForeground(QBrush(QColor(values["foreground"]))); + if(values.contains("background")) format.setBackground(QBrush(QColor(values["background"]))); + if(name=="item") + { + HighlightingRule rule; + rule.format=format; + rule.pattern=QRegExp(values["pattern"]); + rule.ruleOrder=ruleOrder++; + highlightingRules.append(rule); + values.clear(); + } + else if(name=="block" || name=="bracket") + { + HighlightingBlockRule rule; + rule.format=format; + rule.startPattern=QRegExp(values["startPattern"]); + rule.endPattern=QRegExp(values["endPattern"]); + rule.ruleOrder=ruleOrder++; + if(name=="block") highlightingBlockRules.append(rule); + else highlightingBracketsRules.append(rule); //Bracket rule + values.clear(); + } + } + break; + } + } + if (xml.hasError()) + { + // do error handling + printf("Error %s: %ld:%ld %s\n", file.toLocal8Bit().data(), xml.lineNumber(), xml.columnNumber(), xml.errorString().toLocal8Bit().data() ); + return false; + } + + return true; +} + +SyntaxHighlighter::Rule1st SyntaxHighlighter::highlight1stRule(const QString & text, int startIndex) +{ + Rule1st rule1st; + rule1st.startIndex=text.length(); + rule1st.rule=-1; + + for(int i=0; i<highlightingRules.size(); i++) + { + HighlightingRule *rule=&(highlightingRules[i]); + + QRegExp *expression = &(rule->pattern); + int index = rule->lastFound; + if(index>-1 && index<startIndex) + { + rule->lastFound = index = expression->indexIn(text, startIndex); + } + if ( index>-1 && index<rule1st.startIndex ) + { + rule1st.startIndex=index; + rule1st.length=expression->matchedLength(); + rule1st.rule=i; + rule1st.ruleOrder=rule->ruleOrder; + } + + if(index==startIndex) break; + } + + if(rule1st.rule==-1) rule1st.startIndex=-1; + + return rule1st; +} + +SyntaxHighlighter::Rule1st SyntaxHighlighter::highlight1stBlockRule(const QString & text, int startIndex) +{ + Rule1st rule1st; + rule1st.startIndex=text.length(); + rule1st.rule=-1; + + for(int i=0; i<highlightingBlockRules.size(); i++) + { + HighlightingBlockRule rule=highlightingBlockRules[i]; + + int index = rule.startPattern.indexIn(text, startIndex); + + if ( index>-1 && index<rule1st.startIndex ) + { + rule1st.startIndex=index; + rule1st.rule=i; + rule1st.ruleOrder=rule.ruleOrder; + } + + if(index==startIndex) break; + } + + if(rule1st.rule==-1) rule1st.startIndex=-1; + + return rule1st; +} + +/**Inserts brackets in position order in blockData->brackets + */ +static void insertInOrder(BlockData *blockData, BlockData::Bracket &bracket) +{ + if(blockData->brackets.isEmpty()) blockData->brackets.append(bracket); + else + { + int j=0; + + for(;j<blockData->brackets.size();j++) + { + if(blockData->brackets[j].pos>bracket.pos) + { + blockData->brackets.insert(j,bracket); + break; + } + } + if(j>=blockData->brackets.size()) + { + blockData->brackets.append(bracket); + } + } +} + + +void SyntaxHighlighter::findBrackets(const QString & text, int start, int end, BlockData *blockData) +{ + //blockData->brackets.clear(); + + if( end<0 || end>text.length() ) end=text.length(); + + if(start>end) return; + + for(int i=0; i<highlightingBracketsRules.size(); i++) + { + HighlightingBlockRule rule=highlightingBracketsRules[i]; + + int startIndex=start; + + int index = rule.startPattern.indexIn(text, startIndex); + + while( index>-1 && index<end ) + { + BlockData::Bracket bracket; + bracket.pos=index; + bracket.type=i; + bracket.length=rule.startPattern.matchedLength(); + bracket.startBracketOk=true; + + startIndex=index+bracket.length; + + insertInOrder(blockData, bracket); + index = rule.startPattern.indexIn(text, startIndex); + } + + startIndex=start; + + index = rule.endPattern.indexIn(text, startIndex); + + + + while( index>-1 && index<end ) + { + BlockData::Bracket bracket; + bracket.pos=index; + bracket.type=i; + bracket.length=rule.endPattern.matchedLength(); + bracket.startBracketOk=false; + insertInOrder(blockData, bracket); + startIndex=index+bracket.length; + index = rule.endPattern.indexIn(text, startIndex); + } + } +} + + +int SyntaxHighlighter::ruleSetFormat(Rule1st rule1st) +{ + HighlightingRule rule=highlightingRules[rule1st.rule]; + + setFormat(rule1st.startIndex, rule1st.length, rule.format); + + return rule1st.startIndex + rule1st.length; +} + + +int SyntaxHighlighter::blockRuleSetFormat(const QString & text, Rule1st rule1st) +{ + HighlightingBlockRule rule=highlightingBlockRules[rule1st.rule]; + + int endIndex = rule.endPattern.indexIn(text, rule1st.startIndex); + int commentLength; + if (endIndex == -1) + { + setCurrentBlockState(rule1st.rule); + commentLength = text.length() - rule1st.startIndex; + setFormat(rule1st.startIndex, commentLength, rule.format); + return text.length(); + } + else + { + commentLength = endIndex - rule1st.startIndex + + rule.endPattern.matchedLength(); + setFormat(rule1st.startIndex, commentLength, rule.format); + + return endIndex+1; + } +} + + +void SyntaxHighlighter::highlightBlock ( const QString & text ) +{ + + setCurrentBlockState(-1); + + int startIndex = 0; + + //Checks previous block state + if (previousBlockState() >= 0) + { + Rule1st rule1st; + rule1st.rule=previousBlockState(); + rule1st.startIndex=0; + + startIndex=blockRuleSetFormat(text,rule1st); + + //TODO: Posible fallo al establecer el estado del bloque + + if(startIndex==text.length()) return; + } + + //Gets BlockData + BlockData *blockData=new BlockData(); + + //Finds first rule to apply. + + Rule1st rule1st, blockRule1st; + + //Find initial matches + for(int i=0; i<highlightingRules.size(); i++) + { + HighlightingRule *rule= &(highlightingRules[i]); + QRegExp *expression = &(rule->pattern); + int index = expression->indexIn(text, startIndex); + rule->lastFound = index; + } + + rule1st=highlight1stRule( text, startIndex); + blockRule1st=highlight1stBlockRule( text, startIndex); + + while(rule1st.rule>=0 || blockRule1st.rule>=0) + { + if(rule1st.rule>=0 && blockRule1st.rule>=0) + { + if + ( + rule1st.startIndex<blockRule1st.startIndex + || + ( + rule1st.startIndex==blockRule1st.startIndex + && + rule1st.ruleOrder<blockRule1st.ruleOrder + ) + ) + { + findBrackets(text, startIndex, rule1st.startIndex, blockData); + startIndex=ruleSetFormat(rule1st); + rule1st=highlight1stRule( text, startIndex); + } + else + { + findBrackets(text, startIndex, blockRule1st.startIndex, blockData); + startIndex=blockRuleSetFormat(text,blockRule1st); + blockRule1st=highlight1stBlockRule( text, startIndex); + } + } + else if(rule1st.rule>=0) + { + findBrackets(text, startIndex, rule1st.startIndex, blockData); + startIndex=ruleSetFormat(rule1st); + rule1st=highlight1stRule( text, startIndex); + } + else + { + findBrackets(text, startIndex, blockRule1st.startIndex, blockData); + startIndex=blockRuleSetFormat(text,blockRule1st); + blockRule1st=highlight1stBlockRule( text, startIndex); + } + } + + findBrackets(text,startIndex, -1, blockData); + + setCurrentBlockUserData(blockData); +} + +/**Search brackets in one QTextBlock.*/ +static BlockData::Bracket *searchBracket(int i, int increment, int &bracketsCount, BlockData *blockData, BlockData::Bracket *bracket1) +{ + if(blockData==NULL) return NULL; + + if(i==-1) i=blockData->brackets.size()-1; + + for(; i>=0 && i<blockData->brackets.size(); i+=increment) + { + BlockData::Bracket *bracket=&(blockData->brackets[i]); + if(bracket->type==bracket1->type) + { + if(bracket->startBracketOk!=bracket1->startBracketOk) + bracketsCount--; + else + bracketsCount++; + + if(bracketsCount==0) + return bracket; + } + } + + //printf("[searchBracket] bracketsCount=%d\n", bracketsCount); + + return NULL; +} + +void SyntaxHighlighter::setFormatPairBrackets(QPlainTextEdit *textEdit) +{ + QList<QTextEdit::ExtraSelection> selections; + + textEdit->setExtraSelections(selections); + + QTextCursor cursor=textEdit->textCursor(); + QTextBlock block=cursor.block(); + BlockData *blockData=(BlockData *)block.userData(); + if(blockData==NULL) return; + + int pos=cursor.position()-block.position(); + + BlockData::Bracket *bracket1; + QTextBlock block_bracket1=block; + + int i=blockData->brackets.size()-1; + for(; i>=0; i--) + { + BlockData::Bracket *bracket=&(blockData->brackets[i]); + if(bracket->pos==pos) + { + bracket1=bracket; + break; + } + } + + if(i<0) return; + + int increment=(bracket1->startBracketOk) ? +1:-1; + int bracketsCount=0; + //Looks in this block the other bracket + BlockData::Bracket *bracket2=NULL; + QTextBlock block_bracket2=block; + + bracket2=searchBracket( i, increment, bracketsCount, blockData, bracket1); + + { //Search brackets in other blocks + while( bracket2==NULL ) + { + if(increment>0) + { + block_bracket2=block_bracket2.next(); + i=0; + } + else + { + block_bracket2=block_bracket2.previous(); + i=-1; + } + + if(!block_bracket2.isValid()) break; + + blockData=(BlockData *)block_bracket2.userData(); + /* + printf("[Syntax::setFormatPairBrackets] Interno brackets.size=%d\n", blockData->brackets.size()); + for(int x=0;x<blockData->brackets.size();x++) + { + BlockData::Bracket *bracket=&(blockData->brackets[x]); + printf("[Syntax::setFormatPairBrackets] bracket.pos=%d bracket.type=%d bracket.len=%d bracket.start=%d\n", bracket->pos, bracket->type, bracket->length, (bracket->startBracketOk) ); + } + */ + + bracket2=searchBracket( i, increment, bracketsCount, blockData, bracket1); + } + + if(bracket2==NULL) return; + } + + pos=cursor.position(); + + QTextEdit::ExtraSelection selection1; + + cursor.setPosition(pos+bracket1->length, QTextCursor::KeepAnchor); + selection1.cursor=cursor; + selection1.format=highlightingBracketsRules[bracket1->type].format; + + pos=bracket2->pos+block_bracket2.position(); + QTextEdit::ExtraSelection selection2; + cursor.setPosition(pos); + cursor.setPosition(pos+bracket2->length, QTextCursor::KeepAnchor); + selection2.cursor=cursor; + selection2.format=highlightingBracketsRules[bracket2->type].format; + + selections.append(selection1); selections.append(selection2); + + textEdit->setExtraSelections(selections); + +} + + + +BlockData::BlockData():QTextBlockUserData() +{ +} + +
new file mode 100644 --- /dev/null +++ b/gui//src/SyntaxHighlighter.h @@ -0,0 +1,107 @@ +/* Copyright (C) 2010 P.L. Lucas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __SYNTAX_H__ +#define __SYNTAX_H__ + +#include <QSyntaxHighlighter> +#include <QTextBlockUserData> +#include <QVector> +#include <QPlainTextEdit> + +class BlockData:public QTextBlockUserData +{ + public: + BlockData(); + + struct Bracket + { + int type; //Type of bracket + int pos; //Position of bracket + int length; //Number of chars of bracket + bool startBracketOk; //Is it a start or end bracket? + }; + + QVector <Bracket> brackets; +}; + +class SyntaxHighlighter:public QSyntaxHighlighter +{ + Q_OBJECT + + struct HighlightingRule + { + QRegExp pattern; + QTextCharFormat format; + int ruleOrder; + int lastFound; + }; + + QVector<HighlightingRule> highlightingRules; + + struct HighlightingBlockRule + { + QRegExp startPattern, endPattern; + QTextCharFormat format; + int ruleOrder; + }; + + QVector<HighlightingBlockRule> highlightingBlockRules; + QVector<HighlightingBlockRule> highlightingBracketsRules; + + struct Rule1st + { + int rule; + int startIndex; + int length; + int ruleOrder; + }; + + /**1st rule to apply from startIndex. + */ + Rule1st highlight1stRule(const QString & text, int startIndex); + + /**1st block rule to apply from startIndex. + */ + Rule1st highlight1stBlockRule(const QString & text, int startIndex); + + /** Set format using rule. + */ + int ruleSetFormat(Rule1st rule); + + /** Set format using block rule. + */ + int blockRuleSetFormat(const QString & text, Rule1st rule1st); + + /** Finds brackets and put them in BlockData. + */ + void findBrackets(const QString & text, int start, int end, BlockData *blockData); + + public: + + SyntaxHighlighter(QObject * parent = 0); + bool load(QString file); + + /**Formats pair of brackets + */ + void setFormatPairBrackets(QPlainTextEdit *textEdit); + + protected: + void highlightBlock ( const QString & text ); +}; +#endif
new file mode 100644 --- /dev/null +++ b/gui//src/TerminalCharacterDecoder.cpp @@ -0,0 +1,247 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 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 Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "TerminalCharacterDecoder.h" + +// Qt +#include <QtCore/QTextStream> + +// Konsole +#include "konsole_wcwidth.h" + +PlainTextDecoder::PlainTextDecoder() + : _output(0) + , _includeTrailingWhitespace(true) + , _recordLinePositions(false) +{ + +} +void PlainTextDecoder::setTrailingWhitespace(bool enable) +{ + _includeTrailingWhitespace = enable; +} +bool PlainTextDecoder::trailingWhitespace() const +{ + return _includeTrailingWhitespace; +} +void PlainTextDecoder::begin(QTextStream* output) +{ + _output = output; + if (!_linePositions.isEmpty()) + _linePositions.clear(); +} +void PlainTextDecoder::end() +{ + _output = 0; +} + +void PlainTextDecoder::setRecordLinePositions(bool record) +{ + _recordLinePositions = record; +} +QList<int> PlainTextDecoder::linePositions() const +{ + return _linePositions; +} +void PlainTextDecoder::decodeLine(const Character* const characters, int count, LineProperty /*properties*/ + ) +{ + Q_ASSERT( _output ); + + if (_recordLinePositions && _output->string()) + { + int pos = _output->string()->count(); + _linePositions << pos; + } + + //TODO should we ignore or respect the LINE_WRAPPED line property? + + //note: we build up a QString and send it to the text stream rather writing into the text + //stream a character at a time because it is more efficient. + //(since QTextStream always deals with QStrings internally anyway) + QString plainText; + plainText.reserve(count); + + int outputCount = count; + + // if inclusion of trailing whitespace is disabled then find the end of the + // line + if ( !_includeTrailingWhitespace ) + { + for (int i = count-1 ; i >= 0 ; i--) + { + if ( characters[i].character != ' ' ) + break; + else + outputCount--; + } + } + + for (int i=0;i<outputCount;) + { + plainText.append( QChar(characters[i].character) ); + i += qMax(1,konsole_wcwidth(characters[i].character)); + } + *_output << plainText; +} + +HTMLDecoder::HTMLDecoder() : + _output(0) + ,_colorTable(base_color_table) + ,_innerSpanOpen(false) + ,_lastRendition(DEFAULT_RENDITION) +{ + +} + +void HTMLDecoder::begin(QTextStream* output) +{ + _output = output; + + QString text; + + //open monospace span + openSpan(text,"font-family:monospace"); + + *output << text; +} + +void HTMLDecoder::end() +{ + Q_ASSERT( _output ); + + QString text; + + closeSpan(text); + + *_output << text; + + _output = 0; + +} + +//TODO: Support for LineProperty (mainly double width , double height) +void HTMLDecoder::decodeLine(const Character* const characters, int count, LineProperty /*properties*/ + ) +{ + Q_ASSERT( _output ); + + QString text; + + int spaceCount = 0; + + for (int i=0;i<count;i++) + { + QChar ch(characters[i].character); + + //check if appearance of character is different from previous char + if ( characters[i].rendition != _lastRendition || + characters[i].foregroundColor != _lastForeColor || + characters[i].backgroundColor != _lastBackColor ) + { + if ( _innerSpanOpen ) + closeSpan(text); + + _lastRendition = characters[i].rendition; + _lastForeColor = characters[i].foregroundColor; + _lastBackColor = characters[i].backgroundColor; + + //build up style string + QString style; + + bool useBold; + ColorEntry::FontWeight weight = characters[i].fontWeight(_colorTable); + if (weight == ColorEntry::UseCurrentFormat) + useBold = _lastRendition & RE_BOLD; + else + useBold = weight == ColorEntry::Bold; + + if (useBold) + style.append("font-weight:bold;"); + + if ( _lastRendition & RE_UNDERLINE ) + style.append("font-decoration:underline;"); + + //colours - a colour table must have been defined first + if ( _colorTable ) + { + style.append( QString("color:%1;").arg(_lastForeColor.color(_colorTable).name() ) ); + + if (!characters[i].isTransparent(_colorTable)) + { + style.append( QString("background-color:%1;").arg(_lastBackColor.color(_colorTable).name() ) ); + } + } + + //open the span with the current style + openSpan(text,style); + _innerSpanOpen = true; + } + + //handle whitespace + if (ch.isSpace()) + spaceCount++; + else + spaceCount = 0; + + + //output current character + if (spaceCount < 2) + { + //escape HTML tag characters and just display others as they are + if ( ch == '<' ) + text.append("<"); + else if (ch == '>') + text.append(">"); + else + text.append(ch); + } + else + { + text.append(" "); //HTML truncates multiple spaces, so use a space marker instead + } + + } + + //close any remaining open inner spans + if ( _innerSpanOpen ) + closeSpan(text); + + //start new line + text.append("<br>"); + + *_output << text; +} +void HTMLDecoder::openSpan(QString& text , const QString& style) +{ + text.append( QString("<span style=\"%1\">").arg(style) ); +} + +void HTMLDecoder::closeSpan(QString& text) +{ + text.append("</span>"); +} + +void HTMLDecoder::setColorTable(const ColorEntry* table) +{ + _colorTable = table; +}
new file mode 100644 --- /dev/null +++ b/gui//src/TerminalCharacterDecoder.h @@ -0,0 +1,145 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 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 Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef TERMINAL_CHARACTER_DECODER_H +#define TERMINAL_CHARACTER_DECODER_H + +#include "Character.h" + +#include <QList> + +class QTextStream; + +/** + * Base class for terminal character decoders + * + * The decoder converts lines of terminal characters which consist of a unicode character, foreground + * and background colours and other appearance-related properties into text strings. + * + * Derived classes may produce either plain text with no other colour or appearance information, or + * they may produce text which incorporates these additional properties. + */ +class TerminalCharacterDecoder +{ +public: + virtual ~TerminalCharacterDecoder() {} + + /** Begin decoding characters. The resulting text is appended to @p output. */ + virtual void begin(QTextStream* output) = 0; + /** End decoding. */ + virtual void end() = 0; + + /** + * Converts a line of terminal characters with associated properties into a text string + * and writes the string into an output QTextStream. + * + * @param characters An array of characters of length @p count. + * @param count The number of characters + * @param properties Additional properties which affect all characters in the line + */ + virtual void decodeLine(const Character* const characters, + int count, + LineProperty properties) = 0; +}; + +/** + * A terminal character decoder which produces plain text, ignoring colours and other appearance-related + * properties of the original characters. + */ +class PlainTextDecoder : public TerminalCharacterDecoder +{ +public: + PlainTextDecoder(); + + /** + * Set whether trailing whitespace at the end of lines should be included + * in the output. + * Defaults to true. + */ + void setTrailingWhitespace(bool enable); + /** + * Returns whether trailing whitespace at the end of lines is included + * in the output. + */ + bool trailingWhitespace() const; + /** + * Returns of character positions in the output stream + * at which new lines where added. Returns an empty if setTrackLinePositions() is false or if + * the output device is not a string. + */ + QList<int> linePositions() const; + /** Enables recording of character positions at which new lines are added. See linePositions() */ + void setRecordLinePositions(bool record); + + virtual void begin(QTextStream* output); + virtual void end(); + + virtual void decodeLine(const Character* const characters, + int count, + LineProperty properties); + + +private: + QTextStream* _output; + bool _includeTrailingWhitespace; + + bool _recordLinePositions; + QList<int> _linePositions; +}; + +/** + * A terminal character decoder which produces pretty HTML markup + */ +class HTMLDecoder : public TerminalCharacterDecoder +{ +public: + /** + * Constructs an HTML decoder using a default black-on-white color scheme. + */ + HTMLDecoder(); + + /** + * Sets the colour table which the decoder uses to produce the HTML colour codes in its + * output + */ + void setColorTable( const ColorEntry* table ); + + virtual void decodeLine(const Character* const characters, + int count, + LineProperty properties); + + virtual void begin(QTextStream* output); + virtual void end(); + +private: + void openSpan(QString& text , const QString& style); + void closeSpan(QString& text); + + QTextStream* _output; + const ColorEntry* _colorTable; + bool _innerSpanOpen; + quint8 _lastRendition; + CharacterColor _lastForeColor; + CharacterColor _lastBackColor; + +}; + +#endif
new file mode 100644 --- /dev/null +++ b/gui//src/TerminalDisplay.cpp @@ -0,0 +1,2975 @@ +/* + This file is part of Konsole, a terminal emulator for KDE. + + Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "TerminalDisplay.h" + +// Qt +#include <QtGui/QApplication> +#include <QtGui/QBoxLayout> +#include <QtGui/QClipboard> +#include <QtGui/QKeyEvent> +#include <QtCore/QEvent> +#include <QtCore/QTime> +#include <QtCore/QFile> +#include <QtGui/QGridLayout> +#include <QtGui/QLabel> +#include <QtGui/QLayout> +#include <QtGui/QPainter> +#include <QtGui/QPixmap> +#include <QtGui/QScrollBar> +#include <QtGui/QStyle> +#include <QtCore/QTimer> +#include <QtGui/QToolTip> +#include <QtCore/QTextStream> + +#include "Filter.h" +#include "konsole_wcwidth.h" +#include "ScreenWindow.h" +#include "TerminalCharacterDecoder.h" + +#ifndef loc +#define loc(X,Y) ((Y)*_columns+(X)) +#endif + +#define yMouseScroll 1 + +#define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "abcdefgjijklmnopqrstuvwxyz" \ + "0123456789./+@" + +const ColorEntry base_color_table[TABLE_COLORS] = +// The following are almost IBM standard color codes, with some slight +// gamma correction for the dim colors to compensate for bright X screens. +// It contains the 8 ansiterm/xterm colors in 2 intensities. +{ + // Fixme: could add faint colors here, also. + // normal + ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 1), // Dfore, Dback + ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0x18,0x18), 0), // Black, Red + ColorEntry(QColor(0x18,0xB2,0x18), 0), ColorEntry( QColor(0xB2,0x68,0x18), 0), // Green, Yellow + ColorEntry(QColor(0x18,0x18,0xB2), 0), ColorEntry( QColor(0xB2,0x18,0xB2), 0), // Blue, Magenta + ColorEntry(QColor(0x18,0xB2,0xB2), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 0), // Cyan, White + // intensiv + ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 1), + ColorEntry(QColor(0x68,0x68,0x68), 0), ColorEntry( QColor(0xFF,0x54,0x54), 0), + ColorEntry(QColor(0x54,0xFF,0x54), 0), ColorEntry( QColor(0xFF,0xFF,0x54), 0), + ColorEntry(QColor(0x54,0x54,0xFF), 0), ColorEntry( QColor(0xFF,0x54,0xFF), 0), + ColorEntry(QColor(0x54,0xFF,0xFF), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 0) +}; + +// scroll increment used when dragging selection at top/bottom of window. + +// static +bool TerminalDisplay::_antialiasText = true; +bool TerminalDisplay::HAVE_TRANSPARENCY = false; + +// we use this to force QPainter to display text in LTR mode +// more information can be found in: http://unicode.org/reports/tr9/ +const QChar LTR_OVERRIDE_CHAR( 0x202D ); + +/* ------------------------------------------------------------------------- */ +/* */ +/* Colors */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb) + + Code 0 1 2 3 4 5 6 7 + ----------- ------- ------- ------- ------- ------- ------- ------- ------- + ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White + IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White +*/ + +ScreenWindow* TerminalDisplay::screenWindow() const +{ + return _screenWindow; +} +void TerminalDisplay::setScreenWindow(ScreenWindow* window) +{ + // disconnect existing screen window if any + if ( _screenWindow ) + { + disconnect( _screenWindow , 0 , this , 0 ); + } + + _screenWindow = window; + + if ( window ) + { + +// TODO: Determine if this is an issue. +//#warning "The order here is not specified - does it matter whether updateImage or updateLineProperties comes first?" + connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateLineProperties()) ); + connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateImage()) ); + window->setWindowLines(_lines); + } +} + +const ColorEntry* TerminalDisplay::colorTable() const +{ + return _colorTable; +} +void TerminalDisplay::setBackgroundColor(const QColor& color) +{ + _colorTable[DEFAULT_BACK_COLOR].color = color; + QPalette p = palette(); + p.setColor( backgroundRole(), color ); + setPalette( p ); + + // Avoid propagating the palette change to the scroll bar + _scrollBar->setPalette( QApplication::palette() ); + + update(); +} +void TerminalDisplay::setForegroundColor(const QColor& color) +{ + _colorTable[DEFAULT_FORE_COLOR].color = color; + + update(); +} +void TerminalDisplay::setColorTable(const ColorEntry table[]) +{ + for (int i = 0; i < TABLE_COLORS; i++) + _colorTable[i] = table[i]; + + setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Font */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* + The VT100 has 32 special graphical characters. The usual vt100 extended + xterm fonts have these at 0x00..0x1f. + + QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals + come in here as proper unicode characters. + + We treat non-iso10646 fonts as VT100 extended and do the requiered mapping + from unicode to 0x00..0x1f. The remaining translation is then left to the + QCodec. +*/ + +static inline bool isLineChar(quint16 c) { return ((c & 0xFF80) == 0x2500);} +static inline bool isLineCharString(const QString& string) +{ + return (string.length() > 0) && (isLineChar(string.at(0).unicode())); +} + + +// assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i. + +unsigned short vt100_graphics[32] = +{ // 0/8 1/9 2/10 3/11 4/12 5/13 6/14 7/15 + 0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, + 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, + 0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534, + 0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7 +}; + +void TerminalDisplay::fontChange(const QFont&) +{ + QFontMetrics fm(font()); + _fontHeight = fm.height() + _lineSpacing; + + // waba TerminalDisplay 1.123: + // "Base character width on widest ASCII character. This prevents too wide + // characters in the presence of double wide (e.g. Japanese) characters." + // Get the width from representative normal width characters + _fontWidth = qRound((double)fm.width(REPCHAR)/(double)strlen(REPCHAR)); + + _fixedFont = true; + + int fw = fm.width(REPCHAR[0]); + for(unsigned int i=1; i< strlen(REPCHAR); i++) + { + if (fw != fm.width(REPCHAR[i])) + { + _fixedFont = false; + break; + } + } + + if (_fontWidth < 1) + _fontWidth=1; + + _fontAscent = fm.ascent(); + + emit changedFontMetricSignal( _fontHeight, _fontWidth ); + propagateSize(); + update(); +} + +void TerminalDisplay::setVTFont(const QFont& f) +{ + QFont font = f; + + QFontMetrics metrics(font); + + if ( !QFontInfo(font).fixedPitch() ) + { + //kWarning() << "Using an unsupported variable-width font in the terminal. This may produce display errors."; + } + + if ( metrics.height() < height() && metrics.maxWidth() < width() ) + { + // hint that text should be drawn without anti-aliasing. + // depending on the user's font configuration, this may not be respected + if (!_antialiasText) + font.setStyleStrategy( QFont::NoAntialias ); + + // experimental optimization. Konsole assumes that the terminal is using a + // mono-spaced font, in which case kerning information should have an effect. + // Disabling kerning saves some computation when rendering text. + font.setKerning(false); + + QWidget::setFont(font); + fontChange(font); + } +} + +void TerminalDisplay::setFont(const QFont &) +{ + // ignore font change request if not coming from konsole itself +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Constructor / Destructor */ +/* */ +/* ------------------------------------------------------------------------- */ + +TerminalDisplay::TerminalDisplay(QWidget *parent) +:QWidget(parent) +,_screenWindow(0) +,_allowBell(true) +,_gridLayout(0) +,_fontHeight(1) +,_fontWidth(1) +,_fontAscent(1) +,_boldIntense(true) +,_lines(1) +,_columns(1) +,_usedLines(1) +,_usedColumns(1) +,_contentHeight(1) +,_contentWidth(1) +,_image(0) +,_randomSeed(0) +,_resizing(false) +,_terminalSizeHint(false) +,_terminalSizeStartup(true) +,_bidiEnabled(false) +,_actSel(0) +,_wordSelectionMode(false) +,_lineSelectionMode(false) +,_preserveLineBreaks(false) +,_columnSelectionMode(false) +,_scrollbarLocation(NoScrollBar) +,_wordCharacters(":@-./_~") +,_bellMode(SystemBeepBell) +,_blinking(false) +,_hasBlinker(false) +,_cursorBlinking(false) +,_hasBlinkingCursor(false) +,_allowBlinkingText(true) +,_ctrlDrag(true) +,_tripleClickMode(SelectWholeLine) +,_isFixedSize(false) +,_possibleTripleClick(false) +,_resizeWidget(0) +,_resizeTimer(0) +,_flowControlWarningEnabled(false) +,_outputSuspendedLabel(0) +,_lineSpacing(0) +,_colorsInverted(false) +,_blendColor(qRgba(0,0,0,0xff)) +,_filterChain(new TerminalImageFilterChain()) +,_cursorShape(BlockCursor) +{ + // terminal applications are not designed with Right-To-Left in mind, + // so the layout is forced to Left-To-Right + setLayoutDirection(Qt::LeftToRight); + + // The offsets are not yet calculated. + // Do not calculate these too often to be more smoothly when resizing + // konsole in opaque mode. + _topMargin = DEFAULT_TOP_MARGIN; + _leftMargin = DEFAULT_LEFT_MARGIN; + + // create scroll bar for scrolling output up and down + // set the scroll bar's slider to occupy the whole area of the scroll bar initially + _scrollBar = new QScrollBar(this); + setScroll(0,0); + _scrollBar->setCursor( Qt::ArrowCursor ); + connect(_scrollBar, SIGNAL(valueChanged(int)), this, + SLOT(scrollBarPositionChanged(int))); + + // setup timers for blinking cursor and text + _blinkTimer = new QTimer(this); + connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent())); + _blinkCursorTimer = new QTimer(this); + connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent())); + + //KCursor::setAutoHideCursor( this, true ); + + setUsesMouse(true); + setColorTable(base_color_table); + setMouseTracking(true); + + // Enable drag and drop + setAcceptDrops(true); // attempt + dragInfo.state = diNone; + + setFocusPolicy( Qt::WheelFocus ); + + // enable input method support + setAttribute(Qt::WA_InputMethodEnabled, true); + + // this is an important optimization, it tells Qt + // that TerminalDisplay will handle repainting its entire area. + setAttribute(Qt::WA_OpaquePaintEvent); + + _gridLayout = new QGridLayout(this); + _gridLayout->setContentsMargins(0, 0, 0, 0); + + setLayout( _gridLayout ); + + new AutoScrollHandler(this); +} + +TerminalDisplay::~TerminalDisplay() +{ + disconnect(_blinkTimer); + disconnect(_blinkCursorTimer); + qApp->removeEventFilter( this ); + + delete[] _image; + + delete _gridLayout; + delete _outputSuspendedLabel; + delete _filterChain; +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Display Operations */ +/* */ +/* ------------------------------------------------------------------------- */ + +/** + A table for emulating the simple (single width) unicode drawing chars. + It represents the 250x - 257x glyphs. If it's zero, we can't use it. + if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered + 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit. + + Then, the pixels basically have the following interpretation: + _|||_ + -...- + -...- + -...- + _|||_ + +where _ = none + | = vertical line. + - = horizontal line. + */ + + +enum LineEncode +{ + TopL = (1<<1), + TopC = (1<<2), + TopR = (1<<3), + + LeftT = (1<<5), + Int11 = (1<<6), + Int12 = (1<<7), + Int13 = (1<<8), + RightT = (1<<9), + + LeftC = (1<<10), + Int21 = (1<<11), + Int22 = (1<<12), + Int23 = (1<<13), + RightC = (1<<14), + + LeftB = (1<<15), + Int31 = (1<<16), + Int32 = (1<<17), + Int33 = (1<<18), + RightB = (1<<19), + + BotL = (1<<21), + BotC = (1<<22), + BotR = (1<<23) +}; + +#include "LineFont.h" + +static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code) +{ + //Calculate cell midpoints, end points. + int cx = x + w/2; + int cy = y + h/2; + int ex = x + w - 1; + int ey = y + h - 1; + + quint32 toDraw = LineChars[code]; + + //Top _lines: + if (toDraw & TopL) + paint.drawLine(cx-1, y, cx-1, cy-2); + if (toDraw & TopC) + paint.drawLine(cx, y, cx, cy-2); + if (toDraw & TopR) + paint.drawLine(cx+1, y, cx+1, cy-2); + + //Bot _lines: + if (toDraw & BotL) + paint.drawLine(cx-1, cy+2, cx-1, ey); + if (toDraw & BotC) + paint.drawLine(cx, cy+2, cx, ey); + if (toDraw & BotR) + paint.drawLine(cx+1, cy+2, cx+1, ey); + + //Left _lines: + if (toDraw & LeftT) + paint.drawLine(x, cy-1, cx-2, cy-1); + if (toDraw & LeftC) + paint.drawLine(x, cy, cx-2, cy); + if (toDraw & LeftB) + paint.drawLine(x, cy+1, cx-2, cy+1); + + //Right _lines: + if (toDraw & RightT) + paint.drawLine(cx+2, cy-1, ex, cy-1); + if (toDraw & RightC) + paint.drawLine(cx+2, cy, ex, cy); + if (toDraw & RightB) + paint.drawLine(cx+2, cy+1, ex, cy+1); + + //Intersection points. + if (toDraw & Int11) + paint.drawPoint(cx-1, cy-1); + if (toDraw & Int12) + paint.drawPoint(cx, cy-1); + if (toDraw & Int13) + paint.drawPoint(cx+1, cy-1); + + if (toDraw & Int21) + paint.drawPoint(cx-1, cy); + if (toDraw & Int22) + paint.drawPoint(cx, cy); + if (toDraw & Int23) + paint.drawPoint(cx+1, cy); + + if (toDraw & Int31) + paint.drawPoint(cx-1, cy+1); + if (toDraw & Int32) + paint.drawPoint(cx, cy+1); + if (toDraw & Int33) + paint.drawPoint(cx+1, cy+1); + +} + +void TerminalDisplay::drawLineCharString( QPainter& painter, int x, int y, const QString& str, + const Character* attributes) +{ + const QPen& currentPen = painter.pen(); + + if ( (attributes->rendition & RE_BOLD) && _boldIntense ) + { + QPen boldPen(currentPen); + boldPen.setWidth(3); + painter.setPen( boldPen ); + } + + for (int i=0 ; i < str.length(); i++) + { + uchar code = str[i].cell(); + if (LineChars[code]) + drawLineChar(painter, x + (_fontWidth*i), y, _fontWidth, _fontHeight, code); + } + + painter.setPen( currentPen ); +} + +void TerminalDisplay::setKeyboardCursorShape(KeyboardCursorShape shape) +{ + _cursorShape = shape; +} +TerminalDisplay::KeyboardCursorShape TerminalDisplay::keyboardCursorShape() const +{ + return _cursorShape; +} +void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor& color) +{ + if (useForegroundColor) + _cursorColor = QColor(); // an invalid color means that + // the foreground color of the + // current character should + // be used + + else + _cursorColor = color; +} +QColor TerminalDisplay::keyboardCursorColor() const +{ + return _cursorColor; +} + +void TerminalDisplay::setOpacity(qreal opacity) +{ + QColor color(_blendColor); + color.setAlphaF(opacity); + + // enable automatic background filling to prevent the display + // flickering if there is no transparency + /*if ( color.alpha() == 255 ) + { + setAutoFillBackground(true); + } + else + { + setAutoFillBackground(false); + }*/ + + _blendColor = color.rgba(); +} + +void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting ) +{ + // the area of the widget showing the contents of the terminal display is drawn + // using the background color from the color scheme set with setColorTable() + // + // the area of the widget behind the scroll-bar is drawn using the background + // brush from the scroll-bar's palette, to give the effect of the scroll-bar + // being outside of the terminal display and visual consistency with other KDE + // applications. + // + QRect scrollBarArea = _scrollBar->isVisible() ? + rect.intersected(_scrollBar->geometry()) : + QRect(); + QRegion contentsRegion = QRegion(rect).subtracted(scrollBarArea); + QRect contentsRect = contentsRegion.boundingRect(); + + if ( HAVE_TRANSPARENCY && qAlpha(_blendColor) < 0xff && useOpacitySetting ) + { + QColor color(backgroundColor); + color.setAlpha(qAlpha(_blendColor)); + + painter.save(); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.fillRect(contentsRect, color); + painter.restore(); + } + else + painter.fillRect(contentsRect, backgroundColor); + + painter.fillRect(scrollBarArea,_scrollBar->palette().background()); +} + +void TerminalDisplay::drawCursor(QPainter& painter, + const QRect& rect, + const QColor& foregroundColor, + const QColor& /*backgroundColor*/, + bool& invertCharacterColor) +{ + QRect cursorRect = rect; + cursorRect.setHeight(_fontHeight - _lineSpacing - 1); + + if (!_cursorBlinking) + { + if ( _cursorColor.isValid() ) + painter.setPen(_cursorColor); + else + painter.setPen(foregroundColor); + + if ( _cursorShape == BlockCursor ) + { + // draw the cursor outline, adjusting the area so that + // it is draw entirely inside 'rect' + int penWidth = qMax(1,painter.pen().width()); + + painter.drawRect(cursorRect.adjusted(penWidth/2, + penWidth/2, + - penWidth/2 - penWidth%2, + - penWidth/2 - penWidth%2)); + if ( hasFocus() ) + { + painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor); + + if ( !_cursorColor.isValid() ) + { + // invert the colour used to draw the text to ensure that the character at + // the cursor position is readable + invertCharacterColor = true; + } + } + } + else if ( _cursorShape == UnderlineCursor ) + painter.drawLine(cursorRect.left(), + cursorRect.bottom(), + cursorRect.right(), + cursorRect.bottom()); + else if ( _cursorShape == IBeamCursor ) + painter.drawLine(cursorRect.left(), + cursorRect.top(), + cursorRect.left(), + cursorRect.bottom()); + + } +} + +void TerminalDisplay::drawCharacters(QPainter& painter, + const QRect& rect, + const QString& text, + const Character* style, + bool invertCharacterColor) +{ + // don't draw text which is currently blinking + if ( _blinking && (style->rendition & RE_BLINK) ) + return; + + // setup bold and underline + bool useBold; + ColorEntry::FontWeight weight = style->fontWeight(_colorTable); + if (weight == ColorEntry::UseCurrentFormat) + useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold(); + else + useBold = (weight == ColorEntry::Bold) ? true : false; + bool useUnderline = style->rendition & RE_UNDERLINE || font().underline(); + + QFont font = painter.font(); + if ( font.bold() != useBold + || font.underline() != useUnderline ) + { + font.setBold(useBold); + font.setUnderline(useUnderline); + painter.setFont(font); + } + + // setup pen + const CharacterColor& textColor = ( invertCharacterColor ? style->backgroundColor : style->foregroundColor ); + const QColor color = textColor.color(_colorTable); + QPen pen = painter.pen(); + if ( pen.color() != color ) + { + pen.setColor(color); + painter.setPen(color); + } + + // draw text + if ( isLineCharString(text) ) + drawLineCharString(painter,rect.x(),rect.y(),text,style); + else + { + // the drawText(rect,flags,string) overload is used here with null flags + // instead of drawText(rect,string) because the (rect,string) overload causes + // the application's default layout direction to be used instead of + // the widget-specific layout direction, which should always be + // Qt::LeftToRight for this widget + // This was discussed in: http://lists.kde.org/?t=120552223600002&r=1&w=2 + if (_bidiEnabled) + painter.drawText(rect,0,text); + else + painter.drawText(rect,0,LTR_OVERRIDE_CHAR+text); + } +} + +void TerminalDisplay::drawTextFragment(QPainter& painter , + const QRect& rect, + const QString& text, + const Character* style) +{ + painter.save(); + + // setup painter + const QColor foregroundColor = style->foregroundColor.color(_colorTable); + const QColor backgroundColor = style->backgroundColor.color(_colorTable); + + // draw background if different from the display's background color + if ( backgroundColor != palette().background().color() ) + drawBackground(painter,rect,backgroundColor, + false /* do not use transparency */); + + // draw cursor shape if the current character is the cursor + // this may alter the foreground and background colors + bool invertCharacterColor = false; + if ( style->rendition & RE_CURSOR ) + drawCursor(painter,rect,foregroundColor,backgroundColor,invertCharacterColor); + + // draw text + drawCharacters(painter,rect,text,style,invertCharacterColor); + + painter.restore(); +} + +void TerminalDisplay::setRandomSeed(uint randomSeed) { _randomSeed = randomSeed; } +uint TerminalDisplay::randomSeed() const { return _randomSeed; } + +#if 0 +/*! + Set XIM Position +*/ +void TerminalDisplay::setCursorPos(const int curx, const int cury) +{ + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + + int xpos, ypos; + ypos = _topMargin + tLy + _fontHeight*(cury-1) + _fontAscent; + xpos = _leftMargin + tLx + _fontWidth*curx; + _cursorLine = cury; + _cursorCol = curx; +} +#endif + +// scrolls the image by 'lines', down if lines > 0 or up otherwise. +// +// the terminal emulation keeps track of the scrolling of the character +// image as it receives input, and when the view is updated, it calls scrollImage() +// with the final scroll amount. this improves performance because scrolling the +// display is much cheaper than re-rendering all the text for the +// part of the image which has moved up or down. +// Instead only new lines have to be drawn +void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion) +{ + // if the flow control warning is enabled this will interfere with the + // scrolling optimizations and cause artifacts. the simple solution here + // is to just disable the optimization whilst it is visible + if ( _outputSuspendedLabel && _outputSuspendedLabel->isVisible() ) + return; + + // constrain the region to the display + // the bottom of the region is capped to the number of lines in the display's + // internal image - 2, so that the height of 'region' is strictly less + // than the height of the internal image. + QRect region = screenWindowRegion; + region.setBottom( qMin(region.bottom(),this->_lines-2) ); + + // return if there is nothing to do + if ( lines == 0 + || _image == 0 + || !region.isValid() + || (region.top() + abs(lines)) >= region.bottom() + || this->_lines <= region.height() ) return; + + // hide terminal size label to prevent it being scrolled + if (_resizeWidget && _resizeWidget->isVisible()) + _resizeWidget->hide(); + + // Note: With Qt 4.4 the left edge of the scrolled area must be at 0 + // to get the correct (newly exposed) part of the widget repainted. + // + // The right edge must be before the left edge of the scroll bar to + // avoid triggering a repaint of the entire widget, the distance is + // given by SCROLLBAR_CONTENT_GAP + // + // Set the QT_FLUSH_PAINT environment variable to '1' before starting the + // application to monitor repainting. + // + int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->width(); + const int SCROLLBAR_CONTENT_GAP = 1; + QRect scrollRect; + if ( _scrollbarLocation == ScrollBarLeft ) + { + scrollRect.setLeft(scrollBarWidth+SCROLLBAR_CONTENT_GAP); + scrollRect.setRight(width()); + } + else + { + scrollRect.setLeft(0); + scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP); + } + void* firstCharPos = &_image[ region.top() * this->_columns ]; + void* lastCharPos = &_image[ (region.top() + abs(lines)) * this->_columns ]; + + int top = _topMargin + (region.top() * _fontHeight); + int linesToMove = region.height() - abs(lines); + int bytesToMove = linesToMove * + this->_columns * + sizeof(Character); + + Q_ASSERT( linesToMove > 0 ); + Q_ASSERT( bytesToMove > 0 ); + + //scroll internal image + if ( lines > 0 ) + { + // check that the memory areas that we are going to move are valid + Q_ASSERT( (char*)lastCharPos + bytesToMove < + (char*)(_image + (this->_lines * this->_columns)) ); + + Q_ASSERT( (lines*this->_columns) < _imageSize ); + + //scroll internal image down + memmove( firstCharPos , lastCharPos , bytesToMove ); + + //set region of display to scroll + scrollRect.setTop(top); + } + else + { + // check that the memory areas that we are going to move are valid + Q_ASSERT( (char*)firstCharPos + bytesToMove < + (char*)(_image + (this->_lines * this->_columns)) ); + + //scroll internal image up + memmove( lastCharPos , firstCharPos , bytesToMove ); + + //set region of the display to scroll + scrollRect.setTop(top + abs(lines) * _fontHeight); + } + scrollRect.setHeight(linesToMove * _fontHeight ); + + Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty()); + + //scroll the display vertically to match internal _image + scroll( 0 , _fontHeight * (-lines) , scrollRect ); +} + +QRegion TerminalDisplay::hotSpotRegion() const +{ + QRegion region; + foreach( Filter::HotSpot* hotSpot , _filterChain->hotSpots() ) + { + QRect r; + if (hotSpot->startLine()==hotSpot->endLine()) { + r.setLeft(hotSpot->startColumn()); + r.setTop(hotSpot->startLine()); + r.setRight(hotSpot->endColumn()); + r.setBottom(hotSpot->endLine()); + region |= imageToWidget(r);; + } else { + r.setLeft(hotSpot->startColumn()); + r.setTop(hotSpot->startLine()); + r.setRight(_columns); + r.setBottom(hotSpot->startLine()); + region |= imageToWidget(r);; + for ( int line = hotSpot->startLine()+1 ; line < hotSpot->endLine() ; line++ ) { + r.setLeft(0); + r.setTop(line); + r.setRight(_columns); + r.setBottom(line); + region |= imageToWidget(r);; + } + r.setLeft(0); + r.setTop(hotSpot->endLine()); + r.setRight(hotSpot->endColumn()); + r.setBottom(hotSpot->endLine()); + region |= imageToWidget(r);; + } + } + return region; +} + +void TerminalDisplay::processFilters() +{ + if (!_screenWindow) + return; + + QRegion preUpdateHotSpots = hotSpotRegion(); + + // use _screenWindow->getImage() here rather than _image because + // other classes may call processFilters() when this display's + // ScreenWindow emits a scrolled() signal - which will happen before + // updateImage() is called on the display and therefore _image is + // out of date at this point + _filterChain->setImage( _screenWindow->getImage(), + _screenWindow->windowLines(), + _screenWindow->windowColumns(), + _screenWindow->getLineProperties() ); + _filterChain->process(); + + QRegion postUpdateHotSpots = hotSpotRegion(); + + update( preUpdateHotSpots | postUpdateHotSpots ); +} + +void TerminalDisplay::updateImage() +{ + if ( !_screenWindow ) + return; + + // optimization - scroll the existing image where possible and + // avoid expensive text drawing for parts of the image that + // can simply be moved up or down + scrollImage( _screenWindow->scrollCount() , + _screenWindow->scrollRegion() ); + _screenWindow->resetScrollCount(); + + if (!_image) { + // Create _image. + // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first. + updateImageSize(); + } + + Character* const newimg = _screenWindow->getImage(); + int lines = _screenWindow->windowLines(); + int columns = _screenWindow->windowColumns(); + + setScroll( _screenWindow->currentLine() , _screenWindow->lineCount() ); + + Q_ASSERT( this->_usedLines <= this->_lines ); + Q_ASSERT( this->_usedColumns <= this->_columns ); + + int y,x,len; + + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + _hasBlinker = false; + + CharacterColor cf; // undefined + CharacterColor _clipboard; // undefined + int cr = -1; // undefined + + const int linesToUpdate = qMin(this->_lines, qMax(0,lines )); + const int columnsToUpdate = qMin(this->_columns,qMax(0,columns)); + + QChar *disstrU = new QChar[columnsToUpdate]; + char *dirtyMask = new char[columnsToUpdate+2]; + QRegion dirtyRegion; + + // debugging variable, this records the number of lines that are found to + // be 'dirty' ( ie. have changed from the old _image to the new _image ) and + // which therefore need to be repainted + int dirtyLineCount = 0; + + for (y = 0; y < linesToUpdate; ++y) + { + const Character* currentLine = &_image[y*this->_columns]; + const Character* const newLine = &newimg[y*columns]; + + bool updateLine = false; + + // The dirty mask indicates which characters need repainting. We also + // mark surrounding neighbours dirty, in case the character exceeds + // its cell boundaries + memset(dirtyMask, 0, columnsToUpdate+2); + + for( x = 0 ; x < columnsToUpdate ; ++x) + { + if ( newLine[x] != currentLine[x] ) + { + dirtyMask[x] = true; + } + } + + if (!_resizing) // not while _resizing, we're expecting a paintEvent + for (x = 0; x < columnsToUpdate; ++x) + { + _hasBlinker |= (newLine[x].rendition & RE_BLINK); + + // Start drawing if this character or the next one differs. + // We also take the next one into account to handle the situation + // where characters exceed their cell width. + if (dirtyMask[x]) + { + quint16 c = newLine[x+0].character; + if ( !c ) + continue; + int p = 0; + disstrU[p++] = c; //fontMap(c); + bool lineDraw = isLineChar(c); + bool doubleWidth = (x+1 == columnsToUpdate) ? false : (newLine[x+1].character == 0); + cr = newLine[x].rendition; + _clipboard = newLine[x].backgroundColor; + if (newLine[x].foregroundColor != cf) cf = newLine[x].foregroundColor; + int lln = columnsToUpdate - x; + for (len = 1; len < lln; ++len) + { + const Character& ch = newLine[x+len]; + + if (!ch.character) + continue; // Skip trailing part of multi-col chars. + + bool nextIsDoubleWidth = (x+len+1 == columnsToUpdate) ? false : (newLine[x+len+1].character == 0); + + if ( ch.foregroundColor != cf || + ch.backgroundColor != _clipboard || + ch.rendition != cr || + !dirtyMask[x+len] || + isLineChar(c) != lineDraw || + nextIsDoubleWidth != doubleWidth ) + break; + + disstrU[p++] = c; //fontMap(c); + } + + QString unistr(disstrU, p); + + bool saveFixedFont = _fixedFont; + if (lineDraw) + _fixedFont = false; + if (doubleWidth) + _fixedFont = false; + + updateLine = true; + + _fixedFont = saveFixedFont; + x += len - 1; + } + + } + + //both the top and bottom halves of double height _lines must always be redrawn + //although both top and bottom halves contain the same characters, only + //the top one is actually + //drawn. + if (_lineProperties.count() > y) + updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT); + + // if the characters on the line are different in the old and the new _image + // then this line must be repainted. + if (updateLine) + { + dirtyLineCount++; + + // add the area occupied by this line to the region which needs to be + // repainted + QRect dirtyRect = QRect( _leftMargin+tLx , + _topMargin+tLy+_fontHeight*y , + _fontWidth * columnsToUpdate , + _fontHeight ); + + dirtyRegion |= dirtyRect; + } + + // replace the line of characters in the old _image with the + // current line of the new _image + memcpy((void*)currentLine,(const void*)newLine,columnsToUpdate*sizeof(Character)); + } + + // if the new _image is smaller than the previous _image, then ensure that the area + // outside the new _image is cleared + if ( linesToUpdate < _usedLines ) + { + dirtyRegion |= QRect( _leftMargin+tLx , + _topMargin+tLy+_fontHeight*linesToUpdate , + _fontWidth * this->_columns , + _fontHeight * (_usedLines-linesToUpdate) ); + } + _usedLines = linesToUpdate; + + if ( columnsToUpdate < _usedColumns ) + { + dirtyRegion |= QRect( _leftMargin+tLx+columnsToUpdate*_fontWidth , + _topMargin+tLy , + _fontWidth * (_usedColumns-columnsToUpdate) , + _fontHeight * this->_lines ); + } + _usedColumns = columnsToUpdate; + + dirtyRegion |= _inputMethodData.previousPreeditRect; + + // update the parts of the display which have changed + update(dirtyRegion); + + if ( _hasBlinker && !_blinkTimer->isActive()) _blinkTimer->start( TEXT_BLINK_DELAY ); + if (!_hasBlinker && _blinkTimer->isActive()) { _blinkTimer->stop(); _blinking = false; } + delete[] dirtyMask; + delete[] disstrU; + +} + +void TerminalDisplay::showResizeNotification() +{ + if (_terminalSizeHint && isVisible()) + { + if (_terminalSizeStartup) { + _terminalSizeStartup=false; + return; + } + if (!_resizeWidget) + { + _resizeWidget = new QLabel(QString("Size: XXX x XXX"), this); + _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width(QString("Size: XXX x XXX"))); + _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height()); + _resizeWidget->setAlignment(Qt::AlignCenter); + + _resizeWidget->setStyleSheet("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)"); + + _resizeTimer = new QTimer(this); + _resizeTimer->setSingleShot(true); + connect(_resizeTimer, SIGNAL(timeout()), _resizeWidget, SLOT(hide())); + } + QString sizeStr = QString("Size: %1 x %2").arg(_columns).arg(_lines); + _resizeWidget->setText(sizeStr); + _resizeWidget->move((width()-_resizeWidget->width())/2, + (height()-_resizeWidget->height())/2+20); + _resizeWidget->show(); + _resizeTimer->start(1000); + } +} + +void TerminalDisplay::setBlinkingCursor(bool blink) +{ + _hasBlinkingCursor=blink; + + if (blink && !_blinkCursorTimer->isActive()) + _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2); + + if (!blink && _blinkCursorTimer->isActive()) + { + _blinkCursorTimer->stop(); + if (_cursorBlinking) + blinkCursorEvent(); + else + _cursorBlinking = false; + } +} + +void TerminalDisplay::setBlinkingTextEnabled(bool blink) +{ + _allowBlinkingText = blink; + + if (blink && !_blinkTimer->isActive()) + _blinkTimer->start(TEXT_BLINK_DELAY); + + if (!blink && _blinkTimer->isActive()) + { + _blinkTimer->stop(); + _blinking = false; + } +} + +void TerminalDisplay::focusOutEvent(QFocusEvent*) +{ + // trigger a repaint of the cursor so that it is both visible (in case + // it was hidden during blinking) + // and drawn in a focused out state + _cursorBlinking = false; + updateCursor(); + + _blinkCursorTimer->stop(); + if (_blinking) + blinkEvent(); + + _blinkTimer->stop(); +} +void TerminalDisplay::focusInEvent(QFocusEvent*) +{ + if (_hasBlinkingCursor) + { + _blinkCursorTimer->start(); + } + updateCursor(); + + if (_hasBlinker) + _blinkTimer->start(); +} + +void TerminalDisplay::paintEvent( QPaintEvent* pe ) +{ + QPainter paint(this); + + foreach (const QRect &rect, (pe->region() & contentsRect()).rects()) + { + drawBackground(paint,rect,palette().background().color(), + true /* use opacity setting */); + drawContents(paint, rect); + } + drawInputMethodPreeditString(paint,preeditRect()); + paintFilters(paint); +} + +QPoint TerminalDisplay::cursorPosition() const +{ + if (_screenWindow) + return _screenWindow->cursorPosition(); + else + return QPoint(0,0); +} + +QRect TerminalDisplay::preeditRect() const +{ + const int preeditLength = string_width(_inputMethodData.preeditString); + + if ( preeditLength == 0 ) + return QRect(); + + return QRect(_leftMargin + _fontWidth*cursorPosition().x(), + _topMargin + _fontHeight*cursorPosition().y(), + _fontWidth*preeditLength, + _fontHeight); +} + +void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect) +{ + if ( _inputMethodData.preeditString.isEmpty() ) + return; + + const QPoint cursorPos = cursorPosition(); + + bool invertColors = false; + const QColor background = _colorTable[DEFAULT_BACK_COLOR].color; + const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color; + const Character* style = &_image[loc(cursorPos.x(),cursorPos.y())]; + + drawBackground(painter,rect,background,true); + drawCursor(painter,rect,foreground,background,invertColors); + drawCharacters(painter,rect,_inputMethodData.preeditString,style,invertColors); + + _inputMethodData.previousPreeditRect = rect; +} + +FilterChain* TerminalDisplay::filterChain() const +{ + return _filterChain; +} + +void TerminalDisplay::paintFilters(QPainter& painter) +{ + // get color of character under mouse and use it to draw + // lines for filters + QPoint cursorPos = mapFromGlobal(QCursor::pos()); + int cursorLine; + int cursorColumn; + int scrollBarWidth = (_scrollbarLocation == ScrollBarLeft) ? _scrollBar->width() : 0; + + getCharacterPosition( cursorPos , cursorLine , cursorColumn ); + Character cursorCharacter = _image[loc(cursorColumn,cursorLine)]; + + painter.setPen( QPen(cursorCharacter.foregroundColor.color(colorTable())) ); + + // iterate over hotspots identified by the display's currently active filters + // and draw appropriate visuals to indicate the presence of the hotspot + + QList<Filter::HotSpot*> spots = _filterChain->hotSpots(); + QListIterator<Filter::HotSpot*> iter(spots); + while (iter.hasNext()) + { + Filter::HotSpot* spot = iter.next(); + + QRegion region; + if ( spot->type() == Filter::HotSpot::Link ) { + QRect r; + if (spot->startLine()==spot->endLine()) { + r.setCoords( spot->startColumn()*_fontWidth + 1 + scrollBarWidth, + spot->startLine()*_fontHeight + 1, + (spot->endColumn()-1)*_fontWidth - 1 + scrollBarWidth, + (spot->endLine()+1)*_fontHeight - 1 ); + region |= r; + } else { + r.setCoords( spot->startColumn()*_fontWidth + 1 + scrollBarWidth, + spot->startLine()*_fontHeight + 1, + (_columns-1)*_fontWidth - 1 + scrollBarWidth, + (spot->startLine()+1)*_fontHeight - 1 ); + region |= r; + for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) { + r.setCoords( 0*_fontWidth + 1 + scrollBarWidth, + line*_fontHeight + 1, + (_columns-1)*_fontWidth - 1 + scrollBarWidth, + (line+1)*_fontHeight - 1 ); + region |= r; + } + r.setCoords( 0*_fontWidth + 1 + scrollBarWidth, + spot->endLine()*_fontHeight + 1, + (spot->endColumn()-1)*_fontWidth - 1 + scrollBarWidth, + (spot->endLine()+1)*_fontHeight - 1 ); + region |= r; + } + } + + for ( int line = spot->startLine() ; line <= spot->endLine() ; line++ ) + { + int startColumn = 0; + int endColumn = _columns-1; // TODO use number of _columns which are actually + // occupied on this line rather than the width of the + // display in _columns + + // ignore whitespace at the end of the lines + while ( QChar(_image[loc(endColumn,line)].character).isSpace() && endColumn > 0 ) + endColumn--; + + // increment here because the column which we want to set 'endColumn' to + // is the first whitespace character at the end of the line + endColumn++; + + if ( line == spot->startLine() ) + startColumn = spot->startColumn(); + if ( line == spot->endLine() ) + endColumn = spot->endColumn(); + + // subtract one pixel from + // the right and bottom so that + // we do not overdraw adjacent + // hotspots + // + // subtracting one pixel from all sides also prevents an edge case where + // moving the mouse outside a link could still leave it underlined + // because the check below for the position of the cursor + // finds it on the border of the target area + QRect r; + r.setCoords( startColumn*_fontWidth + 1 + scrollBarWidth, + line*_fontHeight + 1, + endColumn*_fontWidth - 1 + scrollBarWidth, + (line+1)*_fontHeight - 1 ); + // Underline link hotspots + if ( spot->type() == Filter::HotSpot::Link ) + { + QFontMetrics metrics(font()); + + // find the baseline (which is the invisible line that the characters in the font sit on, + // with some having tails dangling below) + int baseline = r.bottom() - metrics.descent(); + // find the position of the underline below that + int underlinePos = baseline + metrics.underlinePos(); + if ( region.contains( mapFromGlobal(QCursor::pos()) ) ){ + painter.drawLine( r.left() , underlinePos , + r.right() , underlinePos ); + } + } + // Marker hotspots simply have a transparent rectanglular shape + // drawn on top of them + else if ( spot->type() == Filter::HotSpot::Marker ) + { + //TODO - Do not use a hardcoded colour for this + painter.fillRect(r,QBrush(QColor(255,0,0,120))); + } + } + } +} +void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect) +{ + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + + int lux = qMin(_usedColumns-1, qMax(0,(rect.left() - tLx - _leftMargin ) / _fontWidth)); + int luy = qMin(_usedLines-1, qMax(0,(rect.top() - tLy - _topMargin ) / _fontHeight)); + int rlx = qMin(_usedColumns-1, qMax(0,(rect.right() - tLx - _leftMargin ) / _fontWidth)); + int rly = qMin(_usedLines-1, qMax(0,(rect.bottom() - tLy - _topMargin ) / _fontHeight)); + + const int bufferSize = _usedColumns; + QString unistr; + unistr.reserve(bufferSize); + for (int y = luy; y <= rly; y++) + { + quint16 c = _image[loc(lux,y)].character; + int x = lux; + if(!c && x) + x--; // Search for start of multi-column character + for (; x <= rlx; x++) + { + int len = 1; + int p = 0; + + // reset our buffer to the maximal size + unistr.resize(bufferSize); + QChar *disstrU = unistr.data(); + + // is this a single character or a sequence of characters ? + if ( _image[loc(x,y)].rendition & RE_EXTENDED_CHAR ) + { + // sequence of characters + ushort extendedCharLength = 0; + ushort* chars = ExtendedCharTable::instance + .lookupExtendedChar(_image[loc(x,y)].charSequence,extendedCharLength); + for ( int index = 0 ; index < extendedCharLength ; index++ ) + { + Q_ASSERT( p < bufferSize ); + disstrU[p++] = chars[index]; + } + } + else + { + // single character + c = _image[loc(x,y)].character; + if (c) + { + Q_ASSERT( p < bufferSize ); + disstrU[p++] = c; //fontMap(c); + } + } + + bool lineDraw = isLineChar(c); + bool doubleWidth = (_image[ qMin(loc(x,y)+1,_imageSize) ].character == 0); + CharacterColor currentForeground = _image[loc(x,y)].foregroundColor; + CharacterColor currentBackground = _image[loc(x,y)].backgroundColor; + quint8 currentRendition = _image[loc(x,y)].rendition; + + while (x+len <= rlx && + _image[loc(x+len,y)].foregroundColor == currentForeground && + _image[loc(x+len,y)].backgroundColor == currentBackground && + _image[loc(x+len,y)].rendition == currentRendition && + (_image[ qMin(loc(x+len,y)+1,_imageSize) ].character == 0) == doubleWidth && + isLineChar( c = _image[loc(x+len,y)].character) == lineDraw) // Assignment! + { + if (c) + disstrU[p++] = c; //fontMap(c); + if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition + len++; // Skip trailing part of multi-column character + len++; + } + if ((x+len < _usedColumns) && (!_image[loc(x+len,y)].character)) + len++; // Adjust for trailing part of multi-column character + + bool save__fixedFont = _fixedFont; + if (lineDraw) + _fixedFont = false; + if (doubleWidth) + _fixedFont = false; + unistr.resize(p); + + // Create a text scaling matrix for double width and double height lines. + QMatrix textScale; + + if (y < _lineProperties.size()) + { + if (_lineProperties[y] & LINE_DOUBLEWIDTH) + textScale.scale(2,1); + + if (_lineProperties[y] & LINE_DOUBLEHEIGHT) + textScale.scale(1,2); + } + + //Apply text scaling matrix. + paint.setWorldMatrix(textScale, true); + + //calculate the area in which the text will be drawn + QRect textArea = QRect( _leftMargin+tLx+_fontWidth*x , _topMargin+tLy+_fontHeight*y , _fontWidth*len , _fontHeight); + + //move the calculated area to take account of scaling applied to the painter. + //the position of the area from the origin (0,0) is scaled + //by the opposite of whatever + //transformation has been applied to the painter. this ensures that + //painting does actually start from textArea.topLeft() + //(instead of textArea.topLeft() * painter-scale) + textArea.moveTopLeft( textScale.inverted().map(textArea.topLeft()) ); + + //paint text fragment + drawTextFragment( paint, + textArea, + unistr, + &_image[loc(x,y)] ); //, + //0, + //!_isPrinting ); + + _fixedFont = save__fixedFont; + + //reset back to single-width, single-height _lines + paint.setWorldMatrix(textScale.inverted(), true); + + if (y < _lineProperties.size()-1) + { + //double-height _lines are represented by two adjacent _lines + //containing the same characters + //both _lines will have the LINE_DOUBLEHEIGHT attribute. + //If the current line has the LINE_DOUBLEHEIGHT attribute, + //we can therefore skip the next line + if (_lineProperties[y] & LINE_DOUBLEHEIGHT) + y++; + } + + x += len - 1; + } + } +} + +void TerminalDisplay::blinkEvent() +{ + if (!_allowBlinkingText) return; + + _blinking = !_blinking; + + //TODO: Optimize to only repaint the areas of the widget + // where there is blinking text + // rather than repainting the whole widget. + update(); +} + +QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const +{ + QRect result; + result.setLeft( _leftMargin + _fontWidth * imageArea.left() ); + result.setTop( _topMargin + _fontHeight * imageArea.top() ); + result.setWidth( _fontWidth * imageArea.width() ); + result.setHeight( _fontHeight * imageArea.height() ); + + return result; +} + +void TerminalDisplay::updateCursor() +{ + QRect cursorRect = imageToWidget( QRect(cursorPosition(),QSize(1,1)) ); + update(cursorRect); +} + +void TerminalDisplay::blinkCursorEvent() +{ + _cursorBlinking = !_cursorBlinking; + updateCursor(); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Resizing */ +/* */ +/* ------------------------------------------------------------------------- */ + +void TerminalDisplay::resizeEvent(QResizeEvent*) +{ + updateImageSize(); +} + +void TerminalDisplay::propagateSize() +{ + if (_isFixedSize) + { + setSize(_columns, _lines); + QWidget::setFixedSize(sizeHint()); + parentWidget()->adjustSize(); + parentWidget()->setFixedSize(parentWidget()->sizeHint()); + return; + } + if (_image) + updateImageSize(); +} + +void TerminalDisplay::updateImageSize() +{ + Character* oldimg = _image; + int oldlin = _lines; + int oldcol = _columns; + + makeImage(); + + // copy the old image to reduce flicker + int lines = qMin(oldlin,_lines); + int columns = qMin(oldcol,_columns); + + if (oldimg) + { + for (int line = 0; line < lines; line++) + { + memcpy((void*)&_image[_columns*line], + (void*)&oldimg[oldcol*line],columns*sizeof(Character)); + } + delete[] oldimg; + } + + if (_screenWindow) + _screenWindow->setWindowLines(_lines); + + _resizing = (oldlin!=_lines) || (oldcol!=_columns); + + if ( _resizing ) + { + showResizeNotification(); + emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent + } + + _resizing = false; +} + +//showEvent and hideEvent are reimplemented here so that it appears to other classes that the +//display has been resized when the display is hidden or shown. +// +//TODO: Perhaps it would be better to have separate signals for show and hide instead of using +//the same signal as the one for a content size change +void TerminalDisplay::showEvent(QShowEvent*) +{ + emit changedContentSizeSignal(_contentHeight,_contentWidth); +} +void TerminalDisplay::hideEvent(QHideEvent*) +{ + emit changedContentSizeSignal(_contentHeight,_contentWidth); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Scrollbar */ +/* */ +/* ------------------------------------------------------------------------- */ + +void TerminalDisplay::scrollBarPositionChanged(int) +{ + if ( !_screenWindow ) + return; + + _screenWindow->scrollTo( _scrollBar->value() ); + + // if the thumb has been moved to the bottom of the _scrollBar then set + // the display to automatically track new output, + // that is, scroll down automatically + // to how new _lines as they are added + const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum()); + _screenWindow->setTrackOutput( atEndOfOutput ); + + updateImage(); +} + +void TerminalDisplay::setScroll(int cursor, int slines) +{ + // update _scrollBar if the range or value has changed, + // otherwise return + // + // setting the range or value of a _scrollBar will always trigger + // a repaint, so it should be avoided if it is not necessary + if ( _scrollBar->minimum() == 0 && + _scrollBar->maximum() == (slines - _lines) && + _scrollBar->value() == cursor ) + { + return; + } + + disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int))); + _scrollBar->setRange(0,slines - _lines); + _scrollBar->setSingleStep(1); + _scrollBar->setPageStep(_lines); + _scrollBar->setValue(cursor); + connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int))); +} + +void TerminalDisplay::setScrollBarPosition(ScrollBarPosition position) +{ + if (_scrollbarLocation == position) + return; + + if ( position == NoScrollBar ) + _scrollBar->hide(); + else + _scrollBar->show(); + + _topMargin = _leftMargin = 1; + _scrollbarLocation = position; + + propagateSize(); + update(); +} + +void TerminalDisplay::mousePressEvent(QMouseEvent* ev) +{ + if ( _possibleTripleClick && (ev->button()==Qt::LeftButton) ) { + mouseTripleClickEvent(ev); + return; + } + + if ( !contentsRect().contains(ev->pos()) ) return; + + if ( !_screenWindow ) return; + + int charLine; + int charColumn; + getCharacterPosition(ev->pos(),charLine,charColumn); + QPoint pos = QPoint(charColumn,charLine); + + if ( ev->button() == Qt::LeftButton) + { + _lineSelectionMode = false; + _wordSelectionMode = false; + + emit isBusySelecting(true); // Keep it steady... + // Drag only when the Control key is hold + bool selected = false; + + // The receiver of the testIsSelected() signal will adjust + // 'selected' accordingly. + //emit testIsSelected(pos.x(), pos.y(), selected); + + selected = _screenWindow->isSelected(pos.x(),pos.y()); + + if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected ) { + // The user clicked inside selected text + dragInfo.state = diPending; + dragInfo.start = ev->pos(); + } + else { + // No reason to ever start a drag event + dragInfo.state = diNone; + + _preserveLineBreaks = !( ( ev->modifiers() & Qt::ControlModifier ) && !(ev->modifiers() & Qt::AltModifier) ); + _columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier); + + if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) + { + _screenWindow->clearSelection(); + + //emit clearSelectionSignal(); + pos.ry() += _scrollBar->value(); + _iPntSel = _pntSel = pos; + _actSel = 1; // left mouse button pressed but nothing selected yet. + + } + else + { + emit mouseSignal( 0, charColumn + 1, charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } + } + } + else if ( ev->button() == Qt::MidButton ) + { + if ( _mouseMarks || (!_mouseMarks && (ev->modifiers() & Qt::ShiftModifier)) ) + emitSelection(true,ev->modifiers() & Qt::ControlModifier); + else + emit mouseSignal( 1, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } + else if ( ev->button() == Qt::RightButton ) + { + if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) + emit configureRequest(ev->pos()); + else + emit mouseSignal( 2, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } +} + +QList<QAction*> TerminalDisplay::filterActions(const QPoint& position) +{ + int charLine, charColumn; + getCharacterPosition(position,charLine,charColumn); + + Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn); + + return spot ? spot->actions() : QList<QAction*>(); +} + +void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev) +{ + int charLine = 0; + int charColumn = 0; + int scrollBarWidth = (_scrollbarLocation == ScrollBarLeft) ? _scrollBar->width() : 0; + + getCharacterPosition(ev->pos(),charLine,charColumn); + + // handle filters + // change link hot-spot appearance on mouse-over + Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn); + if ( spot && spot->type() == Filter::HotSpot::Link) + { + QRegion previousHotspotArea = _mouseOverHotspotArea; + _mouseOverHotspotArea = QRegion(); + QRect r; + if (spot->startLine()==spot->endLine()) { + r.setCoords( spot->startColumn()*_fontWidth + scrollBarWidth, + spot->startLine()*_fontHeight, + spot->endColumn()*_fontWidth + scrollBarWidth, + (spot->endLine()+1)*_fontHeight - 1 ); + _mouseOverHotspotArea |= r; + } else { + r.setCoords( spot->startColumn()*_fontWidth + scrollBarWidth, + spot->startLine()*_fontHeight, + _columns*_fontWidth - 1 + scrollBarWidth, + (spot->startLine()+1)*_fontHeight ); + _mouseOverHotspotArea |= r; + for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) { + r.setCoords( 0*_fontWidth + scrollBarWidth, + line*_fontHeight, + _columns*_fontWidth + scrollBarWidth, + (line+1)*_fontHeight ); + _mouseOverHotspotArea |= r; + } + r.setCoords( 0*_fontWidth + scrollBarWidth, + spot->endLine()*_fontHeight, + spot->endColumn()*_fontWidth + scrollBarWidth, + (spot->endLine()+1)*_fontHeight ); + _mouseOverHotspotArea |= r; + } + // display tooltips when mousing over links + // TODO: Extend this to work with filter types other than links + const QString& tooltip = spot->tooltip(); + if ( !tooltip.isEmpty() ) + { + QToolTip::showText( mapToGlobal(ev->pos()) , tooltip , this , _mouseOverHotspotArea.boundingRect() ); + } + + update( _mouseOverHotspotArea | previousHotspotArea ); + } + else if ( !_mouseOverHotspotArea.isEmpty() ) + { + update( _mouseOverHotspotArea ); + // set hotspot area to an invalid rectangle + _mouseOverHotspotArea = QRegion(); + } + + // for auto-hiding the cursor, we need mouseTracking + if (ev->buttons() == Qt::NoButton ) return; + + // if the terminal is interested in mouse movements + // then emit a mouse movement signal, unless the shift + // key is being held down, which overrides this. + if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) + { + int button = 3; + if (ev->buttons() & Qt::LeftButton) + button = 0; + if (ev->buttons() & Qt::MidButton) + button = 1; + if (ev->buttons() & Qt::RightButton) + button = 2; + + + emit mouseSignal( button, + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum(), + 1 ); + + return; + } + + if (dragInfo.state == diPending) + { + // we had a mouse down, but haven't confirmed a drag yet + // if the mouse has moved sufficiently, we will confirm + + int distance = 10; //KGlobalSettings::dndEventDelay(); + if ( ev->x() > dragInfo.start.x() + distance || ev->x() < dragInfo.start.x() - distance || + ev->y() > dragInfo.start.y() + distance || ev->y() < dragInfo.start.y() - distance) + { + // we've left the drag square, we can start a real drag operation now + emit isBusySelecting(false); // Ok.. we can breath again. + + _screenWindow->clearSelection(); + doDrag(); + } + return; + } + else if (dragInfo.state == diDragging) + { + // this isn't technically needed because mouseMoveEvent is suppressed during + // Qt drag operations, replaced by dragMoveEvent + return; + } + + if (_actSel == 0) return; + + // don't extend selection while pasting + if (ev->buttons() & Qt::MidButton) return; + + extendSelection( ev->pos() ); +} + +void TerminalDisplay::extendSelection( const QPoint& position ) +{ + QPoint pos = position; + + if ( !_screenWindow ) + return; + + //if ( !contentsRect().contains(ev->pos()) ) return; + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + int scroll = _scrollBar->value(); + + // we're in the process of moving the mouse with the left button pressed + // the mouse cursor will kept caught within the bounds of the text in + // this widget. + + int linesBeyondWidget = 0; + + QRect textBounds(tLx + _leftMargin, + tLy + _topMargin, + _usedColumns*_fontWidth-1, + _usedLines*_fontHeight-1); + + // Adjust position within text area bounds. + QPoint oldpos = pos; + + pos.setX( qBound(textBounds.left(),pos.x(),textBounds.right()) ); + pos.setY( qBound(textBounds.top(),pos.y(),textBounds.bottom()) ); + + if ( oldpos.y() > textBounds.bottom() ) + { + linesBeyondWidget = (oldpos.y()-textBounds.bottom()) / _fontHeight; + _scrollBar->setValue(_scrollBar->value()+linesBeyondWidget+1); // scrollforward + } + if ( oldpos.y() < textBounds.top() ) + { + linesBeyondWidget = (textBounds.top()-oldpos.y()) / _fontHeight; + _scrollBar->setValue(_scrollBar->value()-linesBeyondWidget-1); // history + } + + int charColumn = 0; + int charLine = 0; + getCharacterPosition(pos,charLine,charColumn); + + QPoint here = QPoint(charColumn,charLine); //QPoint((pos.x()-tLx-_leftMargin+(_fontWidth/2))/_fontWidth,(pos.y()-tLy-_topMargin)/_fontHeight); + QPoint ohere; + QPoint _iPntSelCorr = _iPntSel; + _iPntSelCorr.ry() -= _scrollBar->value(); + QPoint _pntSelCorr = _pntSel; + _pntSelCorr.ry() -= _scrollBar->value(); + bool swapping = false; + + if ( _wordSelectionMode ) + { + // Extend to word boundaries + int i; + QChar selClass; + + bool left_not_right = ( here.y() < _iPntSelCorr.y() || + ( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) ); + bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() || + ( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) ); + swapping = left_not_right != old_left_not_right; + + // Find left (left_not_right ? from here : from start) + QPoint left = left_not_right ? here : _iPntSelCorr; + i = loc(left.x(),left.y()); + if (i>=0 && i<=_imageSize) { + selClass = charClass(_image[i].character); + while ( ((left.x()>0) || (left.y()>0 && (_lineProperties[left.y()-1] & LINE_WRAPPED) )) + && charClass(_image[i-1].character) == selClass ) + { i--; if (left.x()>0) left.rx()--; else {left.rx()=_usedColumns-1; left.ry()--;} } + } + + // Find left (left_not_right ? from start : from here) + QPoint right = left_not_right ? _iPntSelCorr : here; + i = loc(right.x(),right.y()); + if (i>=0 && i<=_imageSize) { + selClass = charClass(_image[i].character); + while( ((right.x()<_usedColumns-1) || (right.y()<_usedLines-1 && (_lineProperties[right.y()] & LINE_WRAPPED) )) + && charClass(_image[i+1].character) == selClass ) + { i++; if (right.x()<_usedColumns-1) right.rx()++; else {right.rx()=0; right.ry()++; } } + } + + // Pick which is start (ohere) and which is extension (here) + if ( left_not_right ) + { + here = left; ohere = right; + } + else + { + here = right; ohere = left; + } + ohere.rx()++; + } + + if ( _lineSelectionMode ) + { + // Extend to complete line + bool above_not_below = ( here.y() < _iPntSelCorr.y() ); + + QPoint above = above_not_below ? here : _iPntSelCorr; + QPoint below = above_not_below ? _iPntSelCorr : here; + + while (above.y()>0 && (_lineProperties[above.y()-1] & LINE_WRAPPED) ) + above.ry()--; + while (below.y()<_usedLines-1 && (_lineProperties[below.y()] & LINE_WRAPPED) ) + below.ry()++; + + above.setX(0); + below.setX(_usedColumns-1); + + // Pick which is start (ohere) and which is extension (here) + if ( above_not_below ) + { + here = above; ohere = below; + } + else + { + here = below; ohere = above; + } + + QPoint newSelBegin = QPoint( ohere.x(), ohere.y() ); + swapping = !(_tripleSelBegin==newSelBegin); + _tripleSelBegin = newSelBegin; + + ohere.rx()++; + } + + int offset = 0; + if ( !_wordSelectionMode && !_lineSelectionMode ) + { + int i; + QChar selClass; + + bool left_not_right = ( here.y() < _iPntSelCorr.y() || + ( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) ); + bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() || + ( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) ); + swapping = left_not_right != old_left_not_right; + + // Find left (left_not_right ? from here : from start) + QPoint left = left_not_right ? here : _iPntSelCorr; + + // Find left (left_not_right ? from start : from here) + QPoint right = left_not_right ? _iPntSelCorr : here; + if ( right.x() > 0 && !_columnSelectionMode ) + { + i = loc(right.x(),right.y()); + if (i>=0 && i<=_imageSize) { + selClass = charClass(_image[i-1].character); + /* if (selClass == ' ') + { + while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) && + !(_lineProperties[right.y()] & LINE_WRAPPED)) + { i++; right.rx()++; } + if (right.x() < _usedColumns-1) + right = left_not_right ? _iPntSelCorr : here; + else + right.rx()++; // will be balanced later because of offset=-1; + }*/ + } + } + + // Pick which is start (ohere) and which is extension (here) + if ( left_not_right ) + { + here = left; ohere = right; offset = 0; + } + else + { + here = right; ohere = left; offset = -1; + } + } + + if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) return; // not moved + + if (here == ohere) return; // It's not left, it's not right. + + if ( _actSel < 2 || swapping ) + { + if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode ) + { + _screenWindow->setSelectionStart( ohere.x() , ohere.y() , true ); + } + else + { + _screenWindow->setSelectionStart( ohere.x()-1-offset , ohere.y() , false ); + } + + } + + _actSel = 2; // within selection + _pntSel = here; + _pntSel.ry() += _scrollBar->value(); + + if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode ) + { + _screenWindow->setSelectionEnd( here.x() , here.y() ); + } + else + { + _screenWindow->setSelectionEnd( here.x()+offset , here.y() ); + } + +} + +void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev) +{ + if ( !_screenWindow ) + return; + + int charLine; + int charColumn; + getCharacterPosition(ev->pos(),charLine,charColumn); + + if ( ev->button() == Qt::LeftButton) + { + emit isBusySelecting(false); + if(dragInfo.state == diPending) + { + // We had a drag event pending but never confirmed. Kill selection + _screenWindow->clearSelection(); + //emit clearSelectionSignal(); + } + else + { + if ( _actSel > 1 ) + { + setSelection( _screenWindow->selectedText(_preserveLineBreaks) ); + } + + _actSel = 0; + + //FIXME: emits a release event even if the mouse is + // outside the range. The procedure used in `mouseMoveEvent' + // applies here, too. + + if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) + emit mouseSignal( 3, // release + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } + dragInfo.state = diNone; + } + + + if ( !_mouseMarks && + ((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier)) + || ev->button() == Qt::MidButton) ) + { + emit mouseSignal( 3, + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , + 0); + } +} + +void TerminalDisplay::getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const +{ + column = (widgetPoint.x() + _fontWidth/2 -contentsRect().left()-_leftMargin) / _fontWidth; + line = (widgetPoint.y()-contentsRect().top()-_topMargin) / _fontHeight; + + if ( line < 0 ) + line = 0; + if ( column < 0 ) + column = 0; + + if ( line >= _usedLines ) + line = _usedLines-1; + + // the column value returned can be equal to _usedColumns, which + // is the position just after the last character displayed in a line. + // + // this is required so that the user can select characters in the right-most + // column (or left-most for right-to-left input) + if ( column > _usedColumns ) + column = _usedColumns; +} + +void TerminalDisplay::updateLineProperties() +{ + if ( !_screenWindow ) + return; + + _lineProperties = _screenWindow->getLineProperties(); +} + +void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev) +{ + if ( ev->button() != Qt::LeftButton) return; + if ( !_screenWindow ) return; + + int charLine = 0; + int charColumn = 0; + + getCharacterPosition(ev->pos(),charLine,charColumn); + + QPoint pos(charColumn,charLine); + + // pass on double click as two clicks. + if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) + { + // Send just _ONE_ click event, since the first click of the double click + // was already sent by the click handler + emit mouseSignal( 0, + pos.x()+1, + pos.y()+1 +_scrollBar->value() -_scrollBar->maximum(), + 0 ); // left button + return; + } + + _screenWindow->clearSelection(); + QPoint bgnSel = pos; + QPoint endSel = pos; + int i = loc(bgnSel.x(),bgnSel.y()); + _iPntSel = bgnSel; + _iPntSel.ry() += _scrollBar->value(); + + _wordSelectionMode = true; + + // find word boundaries... + QChar selClass = charClass(_image[i].character); + { + // find the start of the word + int x = bgnSel.x(); + while ( ((x>0) || (bgnSel.y()>0 && (_lineProperties[bgnSel.y()-1] & LINE_WRAPPED) )) + && charClass(_image[i-1].character) == selClass ) + { + i--; + if (x>0) + x--; + else + { + x=_usedColumns-1; + bgnSel.ry()--; + } + } + + bgnSel.setX(x); + _screenWindow->setSelectionStart( bgnSel.x() , bgnSel.y() , false ); + + // find the end of the word + i = loc( endSel.x(), endSel.y() ); + x = endSel.x(); + while( ((x<_usedColumns-1) || (endSel.y()<_usedLines-1 && (_lineProperties[endSel.y()] & LINE_WRAPPED) )) + && charClass(_image[i+1].character) == selClass ) + { + i++; + if (x<_usedColumns-1) + x++; + else + { + x=0; + endSel.ry()++; + } + } + + endSel.setX(x); + + // In word selection mode don't select @ (64) if at end of word. + if ( ( QChar( _image[i].character ) == '@' ) && ( ( endSel.x() - bgnSel.x() ) > 0 ) ) + endSel.setX( x - 1 ); + + + _actSel = 2; // within selection + + _screenWindow->setSelectionEnd( endSel.x() , endSel.y() ); + + setSelection( _screenWindow->selectedText(_preserveLineBreaks) ); + } + + _possibleTripleClick=true; + + QTimer::singleShot(QApplication::doubleClickInterval(),this, + SLOT(tripleClickTimeout())); +} + +void TerminalDisplay::wheelEvent( QWheelEvent* ev ) +{ + if (ev->orientation() != Qt::Vertical) + return; + + // if the terminal program is not interested mouse events + // then send the event to the scrollbar if the slider has room to move + // or otherwise send simulated up / down key presses to the terminal program + // for the benefit of programs such as 'less' + if ( _mouseMarks ) + { + bool canScroll = _scrollBar->maximum() > 0; + if (canScroll) + _scrollBar->event(ev); + else + { + // assume that each Up / Down key event will cause the terminal application + // to scroll by one line. + // + // to get a reasonable scrolling speed, scroll by one line for every 5 degrees + // of mouse wheel rotation. Mouse wheels typically move in steps of 15 degrees, + // giving a scroll of 3 lines + int key = ev->delta() > 0 ? Qt::Key_Up : Qt::Key_Down; + + // QWheelEvent::delta() gives rotation in eighths of a degree + int wheelDegrees = ev->delta() / 8; + int linesToScroll = abs(wheelDegrees) / 5; + + QKeyEvent keyScrollEvent(QEvent::KeyPress,key,Qt::NoModifier); + + for (int i=0;i<linesToScroll;i++) + emit keyPressedSignal(&keyScrollEvent); + } + } + else + { + // terminal program wants notification of mouse activity + + int charLine; + int charColumn; + getCharacterPosition( ev->pos() , charLine , charColumn ); + + emit mouseSignal( ev->delta() > 0 ? 4 : 5, + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , + 0); + } +} + +void TerminalDisplay::tripleClickTimeout() +{ + _possibleTripleClick=false; +} + +void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev) +{ + if ( !_screenWindow ) return; + + int charLine; + int charColumn; + getCharacterPosition(ev->pos(),charLine,charColumn); + _iPntSel = QPoint(charColumn,charLine); + + _screenWindow->clearSelection(); + + _lineSelectionMode = true; + _wordSelectionMode = false; + + _actSel = 2; // within selection + emit isBusySelecting(true); // Keep it steady... + + while (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) ) + _iPntSel.ry()--; + + if (_tripleClickMode == SelectForwardsFromCursor) { + // find word boundary start + int i = loc(_iPntSel.x(),_iPntSel.y()); + QChar selClass = charClass(_image[i].character); + int x = _iPntSel.x(); + + while ( ((x>0) || + (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) ) + ) + && charClass(_image[i-1].character) == selClass ) + { + i--; + if (x>0) + x--; + else + { + x=_columns-1; + _iPntSel.ry()--; + } + } + + _screenWindow->setSelectionStart( x , _iPntSel.y() , false ); + _tripleSelBegin = QPoint( x, _iPntSel.y() ); + } + else if (_tripleClickMode == SelectWholeLine) { + _screenWindow->setSelectionStart( 0 , _iPntSel.y() , false ); + _tripleSelBegin = QPoint( 0, _iPntSel.y() ); + } + + while (_iPntSel.y()<_lines-1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED) ) + _iPntSel.ry()++; + + _screenWindow->setSelectionEnd( _columns - 1 , _iPntSel.y() ); + + setSelection(_screenWindow->selectedText(_preserveLineBreaks)); + + _iPntSel.ry() += _scrollBar->value(); +} + + +bool TerminalDisplay::focusNextPrevChild( bool next ) +{ + if (next) + return false; // This disables changing the active part in konqueror + // when pressing Tab + return QWidget::focusNextPrevChild( next ); +} + + +QChar TerminalDisplay::charClass(QChar qch) const +{ + if ( qch.isSpace() ) return ' '; + + if ( qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive ) ) + return 'a'; + + return qch; +} + +void TerminalDisplay::setWordCharacters(const QString& wc) +{ + _wordCharacters = wc; +} + +void TerminalDisplay::setUsesMouse(bool on) +{ + _mouseMarks = on; + setCursor( _mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor ); +} +bool TerminalDisplay::usesMouse() const +{ + return _mouseMarks; +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Clipboard */ +/* */ +/* ------------------------------------------------------------------------- */ + +#undef KeyPress + +void TerminalDisplay::emitSelection(bool useXselection,bool appendReturn) +{ + if ( !_screenWindow ) + return; + + // Paste Clipboard by simulating keypress events + QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection : + QClipboard::Clipboard); + if(appendReturn) + text.append("\r"); + if ( ! text.isEmpty() ) + { + text.replace('\n', '\r'); + QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text); + emit keyPressedSignal(&e); // expose as a big fat keypress event + + _screenWindow->clearSelection(); + } +} + +void TerminalDisplay::setSelection(const QString& t) +{ + QApplication::clipboard()->setText(t, QClipboard::Selection); +} + +void TerminalDisplay::copyClipboard() +{ + if ( !_screenWindow ) + return; + + QString text = _screenWindow->selectedText(_preserveLineBreaks); + if (!text.isEmpty()) + QApplication::clipboard()->setText(text); +} + +void TerminalDisplay::pasteClipboard() +{ + emitSelection(false,false); +} + +void TerminalDisplay::pasteSelection() +{ + emitSelection(true,false); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Keyboard */ +/* */ +/* ------------------------------------------------------------------------- */ + +void TerminalDisplay::setFlowControlWarningEnabled( bool enable ) +{ + _flowControlWarningEnabled = enable; + + // if the dialog is currently visible and the flow control warning has + // been disabled then hide the dialog + if (!enable) + outputSuspended(false); +} + +void TerminalDisplay::keyPressEvent( QKeyEvent* event ) +{ + bool emitKeyPressSignal = true; + + if(event->modifiers() == Qt::ControlModifier) + { + switch(event->key()) { + case Qt::Key_C: + copyClipboard(); + break; + case Qt::Key_V: + pasteClipboard(); + break; + }; + } else if ( event->modifiers() == Qt::ShiftModifier ) { + bool update = true; + + if ( event->key() == Qt::Key_PageUp ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollPages , -1 ); + } + else if ( event->key() == Qt::Key_PageDown ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollPages , 1 ); + } + else if ( event->key() == Qt::Key_Up ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollLines , -1 ); + } + else if ( event->key() == Qt::Key_Down ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollLines , 1 ); + } + else + update = false; + + if ( update ) + { + _screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() ); + + updateLineProperties(); + updateImage(); + + // do not send key press to terminal + emitKeyPressSignal = false; + } + } + + _actSel=0; // Key stroke implies a screen update, so TerminalDisplay won't + // know where the current selection is. + + if (_hasBlinkingCursor) + { + _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2); + if (_cursorBlinking) + blinkCursorEvent(); + else + _cursorBlinking = false; + } + + if ( emitKeyPressSignal ) + emit keyPressedSignal(event); + + event->accept(); +} + +void TerminalDisplay::inputMethodEvent( QInputMethodEvent* event ) +{ + QKeyEvent keyEvent(QEvent::KeyPress,0,Qt::NoModifier,event->commitString()); + emit keyPressedSignal(&keyEvent); + + _inputMethodData.preeditString = event->preeditString(); + update(preeditRect() | _inputMethodData.previousPreeditRect); + + event->accept(); +} +QVariant TerminalDisplay::inputMethodQuery( Qt::InputMethodQuery query ) const +{ + const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0,0); + switch ( query ) + { + case Qt::ImMicroFocus: + return imageToWidget(QRect(cursorPos.x(),cursorPos.y(),1,1)); + break; + case Qt::ImFont: + return font(); + break; + case Qt::ImCursorPosition: + // return the cursor position within the current line + return cursorPos.x(); + break; + case Qt::ImSurroundingText: + { + // return the text from the current line + QString lineText; + QTextStream stream(&lineText); + PlainTextDecoder decoder; + decoder.begin(&stream); + decoder.decodeLine(&_image[loc(0,cursorPos.y())],_usedColumns,_lineProperties[cursorPos.y()]); + decoder.end(); + return lineText; + } + break; + case Qt::ImCurrentSelection: + return QString(); + break; + default: + break; + } + + return QVariant(); +} + +bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent* keyEvent) +{ + int modifiers = keyEvent->modifiers(); + + // When a possible shortcut combination is pressed, + // emit the overrideShortcutCheck() signal to allow the host + // to decide whether the terminal should override it or not. + if (modifiers != Qt::NoModifier) + { + int modifierCount = 0; + unsigned int currentModifier = Qt::ShiftModifier; + + while (currentModifier <= Qt::KeypadModifier) + { + if (modifiers & currentModifier) + modifierCount++; + currentModifier <<= 1; + } + if (modifierCount < 2) + { + bool override = false; + emit overrideShortcutCheck(keyEvent,override); + if (override) + { + keyEvent->accept(); + return true; + } + } + } + + // Override any of the following shortcuts because + // they are needed by the terminal + int keyCode = keyEvent->key() | modifiers; + switch ( keyCode ) + { + // list is taken from the QLineEdit::event() code + case Qt::Key_Tab: + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + keyEvent->accept(); + return true; + } + return false; +} + +bool TerminalDisplay::event(QEvent* event) +{ + bool eventHandled = false; + switch (event->type()) + { + case QEvent::ShortcutOverride: + eventHandled = handleShortcutOverrideEvent((QKeyEvent*)event); + break; + case QEvent::PaletteChange: + case QEvent::ApplicationPaletteChange: + _scrollBar->setPalette( QApplication::palette() ); + break; + default: + break; + } + return eventHandled ? true : QWidget::event(event); +} + +void TerminalDisplay::setBellMode(int mode) +{ + _bellMode=mode; +} + +void TerminalDisplay::enableBell() +{ + _allowBell = true; +} + +void TerminalDisplay::bell(const QString& message) +{ + if (_bellMode==NoBell) return; + + //limit the rate at which bells can occur + //...mainly for sound effects where rapid bells in sequence + //produce a horrible noise + if ( _allowBell ) + { + _allowBell = false; + QTimer::singleShot(500,this,SLOT(enableBell())); + + if (_bellMode==SystemBeepBell) + { + // TODO: This will need added back in at some point + //KNotification::beep(); + } + else if (_bellMode==NotifyBell) + { + // TODO: This will need added back in at some point + //KNotification::event("BellVisible", message,QPixmap(),this); + } + else if (_bellMode==VisualBell) + { + swapColorTable(); + QTimer::singleShot(200,this,SLOT(swapColorTable())); + } + } +} + +void TerminalDisplay::swapColorTable() +{ + ColorEntry color = _colorTable[1]; + _colorTable[1]=_colorTable[0]; + _colorTable[0]= color; + _colorsInverted = !_colorsInverted; + update(); +} + +void TerminalDisplay::clearImage() +{ + // We initialize _image[_imageSize] too. See makeImage() + for (int i = 0; i <= _imageSize; i++) + { + _image[i].character = ' '; + _image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT, + DEFAULT_FORE_COLOR); + _image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT, + DEFAULT_BACK_COLOR); + _image[i].rendition = DEFAULT_RENDITION; + } +} + +void TerminalDisplay::calcGeometry() +{ + _scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height()); + switch(_scrollbarLocation) + { + case NoScrollBar : + _leftMargin = DEFAULT_LEFT_MARGIN; + _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN; + break; + case ScrollBarLeft : + _leftMargin = DEFAULT_LEFT_MARGIN + _scrollBar->width(); + _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width(); + _scrollBar->move(contentsRect().topLeft()); + break; + case ScrollBarRight: + _leftMargin = DEFAULT_LEFT_MARGIN; + _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width(); + _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width()-1,0)); + break; + } + + _topMargin = DEFAULT_TOP_MARGIN; + _contentHeight = contentsRect().height() - 2 * DEFAULT_TOP_MARGIN + /* mysterious */ 1; + + if (!_isFixedSize) + { + // ensure that display is always at least one column wide + _columns = qMax(1,_contentWidth / _fontWidth); + _usedColumns = qMin(_usedColumns,_columns); + + // ensure that display is always at least one line high + _lines = qMax(1,_contentHeight / _fontHeight); + _usedLines = qMin(_usedLines,_lines); + } +} + +void TerminalDisplay::makeImage() +{ + calcGeometry(); + + // confirm that array will be of non-zero size, since the painting code + // assumes a non-zero array length + Q_ASSERT( _lines > 0 && _columns > 0 ); + Q_ASSERT( _usedLines <= _lines && _usedColumns <= _columns ); + + _imageSize=_lines*_columns; + + // We over-commit one character so that we can be more relaxed in dealing with + // certain boundary conditions: _image[_imageSize] is a valid but unused position + _image = new Character[_imageSize+1]; + + clearImage(); +} + +// calculate the needed size, this must be synced with calcGeometry() +void TerminalDisplay::setSize(int columns, int lines) +{ + int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->sizeHint().width(); + int horizontalMargin = 2 * DEFAULT_LEFT_MARGIN; + int verticalMargin = 2 * DEFAULT_TOP_MARGIN; + + QSize newSize = QSize( horizontalMargin + scrollBarWidth + (columns * _fontWidth) , + verticalMargin + (lines * _fontHeight) ); + + if ( newSize != size() ) + { + _size = newSize; + updateGeometry(); + } +} + +void TerminalDisplay::setFixedSize(int cols, int lins) +{ + _isFixedSize = true; + + //ensure that display is at least one line by one column in size + _columns = qMax(1,cols); + _lines = qMax(1,lins); + _usedColumns = qMin(_usedColumns,_columns); + _usedLines = qMin(_usedLines,_lines); + + if (_image) + { + delete[] _image; + makeImage(); + } + setSize(cols, lins); + QWidget::setFixedSize(_size); +} + +QSize TerminalDisplay::sizeHint() const +{ + return _size; +} + + +/* --------------------------------------------------------------------- */ +/* */ +/* Drag & Drop */ +/* */ +/* --------------------------------------------------------------------- */ + +void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event) +{ + if (event->mimeData()->hasFormat("text/plain")) + event->acceptProposedAction(); +} + +void TerminalDisplay::dropEvent(QDropEvent* event) +{ + //KUrl::List urls = KUrl::List::fromMimeData(event->mimeData()); + + QString dropText; + /* + if (!urls.isEmpty()) + { + for ( int i = 0 ; i < urls.count() ; i++ ) + { + KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 ); + QString urlText; + + if (url.isLocalFile()) + urlText = url.path(); + else + urlText = url.url(); + + // in future it may be useful to be able to insert file names with drag-and-drop + // without quoting them (this only affects paths with spaces in) + urlText = KShell::quoteArg(urlText); + + dropText += urlText; + + if ( i != urls.count()-1 ) + dropText += ' '; + } + } + else + { + dropText = event->mimeData()->text(); + } + */ + + if(event->mimeData()->hasFormat("text/plain")) + { + emit sendStringToEmu(dropText.toLocal8Bit()); + } +} + +void TerminalDisplay::doDrag() +{ + dragInfo.state = diDragging; + dragInfo.dragObject = new QDrag(this); + QMimeData *mimeData = new QMimeData; + mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection)); + dragInfo.dragObject->setMimeData(mimeData); + dragInfo.dragObject->start(Qt::CopyAction); + // Don't delete the QTextDrag object. Qt will delete it when it's done with it. +} + +void TerminalDisplay::outputSuspended(bool suspended) +{ + //create the label when this function is first called + if (!_outputSuspendedLabel) + { + //This label includes a link to an English language website + //describing the 'flow control' (Xon/Xoff) feature found in almost + //all terminal emulators. + //If there isn't a suitable article available in the target language the link + //can simply be removed. + _outputSuspendedLabel = new QLabel( QString("<qt>Output has been " + "<a href=\"http://en.wikipedia.org/wiki/Flow_control\">suspended</a>" + " by pressing Ctrl+S." + " Press <b>Ctrl+Q</b> to resume.</qt>"), + this ); + + QPalette palette(_outputSuspendedLabel->palette()); + //KColorScheme::adjustBackground(palette,KColorScheme::NeutralBackground); + _outputSuspendedLabel->setPalette(palette); + _outputSuspendedLabel->setAutoFillBackground(true); + _outputSuspendedLabel->setBackgroundRole(QPalette::Base); + _outputSuspendedLabel->setFont(QApplication::font()); + _outputSuspendedLabel->setContentsMargins(5, 5, 5, 5); + + //enable activation of "Xon/Xoff" link in label + _outputSuspendedLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse | + Qt::LinksAccessibleByKeyboard); + _outputSuspendedLabel->setOpenExternalLinks(true); + _outputSuspendedLabel->setVisible(false); + + _gridLayout->addWidget(_outputSuspendedLabel); + _gridLayout->addItem( new QSpacerItem(0,0,QSizePolicy::Expanding, + QSizePolicy::Expanding), + 1,0); + + } + + _outputSuspendedLabel->setVisible(suspended); +} + +uint TerminalDisplay::lineSpacing() const +{ + return _lineSpacing; +} + +void TerminalDisplay::setLineSpacing(uint i) +{ + _lineSpacing = i; + setVTFont(font()); // Trigger an update. +} + +AutoScrollHandler::AutoScrollHandler(QWidget* parent) +: QObject(parent) +, _timerId(0) +{ + //parent->installEventFilter(this); +} +void AutoScrollHandler::timerEvent(QTimerEvent* event) +{ + if (event->timerId() != _timerId) + return; + + QMouseEvent mouseEvent( QEvent::MouseMove, + widget()->mapFromGlobal(QCursor::pos()), + Qt::NoButton, + Qt::LeftButton, + Qt::NoModifier); + + QApplication::sendEvent(widget(),&mouseEvent); +} +bool AutoScrollHandler::eventFilter(QObject* watched,QEvent* event) +{ + Q_ASSERT( watched == parent() ); + Q_UNUSED( watched ); + + QMouseEvent* mouseEvent = (QMouseEvent*)event; + switch (event->type()) + { + case QEvent::MouseMove: + { + bool mouseInWidget = widget()->rect().contains(mouseEvent->pos()); + + if (mouseInWidget) + { + if (_timerId) + killTimer(_timerId); + _timerId = 0; + } + else + { + if (!_timerId && (mouseEvent->buttons() & Qt::LeftButton)) + _timerId = startTimer(100); + } + break; + } + case QEvent::MouseButtonRelease: + if (_timerId && (mouseEvent->buttons() & ~Qt::LeftButton)) + { + killTimer(_timerId); + _timerId = 0; + } + break; + default: + break; + }; + + return false; +}
new file mode 100644 --- /dev/null +++ b/gui//src/TerminalDisplay.h @@ -0,0 +1,816 @@ +/* + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef TERMINALDISPLAY_H +#define TERMINALDISPLAY_H + +// Qt +#include <QtGui/QColor> +#include <QtCore/QPointer> +#include <QtGui/QWidget> + +// Konsole +#include "Filter.h" +#include "Character.h" + +class QDrag; +class QDragEnterEvent; +class QDropEvent; +class QLabel; +class QTimer; +class QEvent; +class QGridLayout; +class QKeyEvent; +class QScrollBar; +class QShowEvent; +class QHideEvent; +class QTimerEvent; +class QWidget; + +class KMenu; + +extern unsigned short vt100_graphics[32]; + +class ScreenWindow; + +/** + * A widget which displays output from a terminal emulation and sends input keypresses and mouse activity + * to the terminal. + * + * When the terminal emulation receives new output from the program running in the terminal, + * it will update the display by calling updateImage(). + * + * TODO More documentation + */ +class TerminalDisplay : public QWidget +{ + Q_OBJECT + +public: + /** Constructs a new terminal display widget with the specified parent. */ + TerminalDisplay(QWidget *parent=0); + virtual ~TerminalDisplay(); + + /** Returns the terminal color palette used by the display. */ + const ColorEntry* colorTable() const; + /** Sets the terminal color palette used by the display. */ + void setColorTable(const ColorEntry table[]); + /** + * Sets the seed used to generate random colors for the display + * (in color schemes that support them). + */ + void setRandomSeed(uint seed); + /** + * Returns the seed used to generate random colors for the display + * (in color schemes that support them). + */ + uint randomSeed() const; + + /** Sets the opacity of the terminal display. */ + void setOpacity(qreal opacity); + + /** + * This enum describes the location where the scroll bar is positioned in the display widget. + */ + enum ScrollBarPosition + { + /** Do not show the scroll bar. */ + NoScrollBar=0, + /** Show the scroll bar on the left side of the display. */ + ScrollBarLeft=1, + /** Show the scroll bar on the right side of the display. */ + ScrollBarRight=2 + }; + /** + * Specifies whether the terminal display has a vertical scroll bar, and if so whether it + * is shown on the left or right side of the display. + */ + void setScrollBarPosition(ScrollBarPosition position); + + /** + * Sets the current position and range of the display's scroll bar. + * + * @param cursor The position of the scroll bar's thumb. + * @param lines The maximum value of the scroll bar. + */ + void setScroll(int cursor, int lines); + + /** + * Returns the display's filter chain. When the image for the display is updated, + * the text is passed through each filter in the chain. Each filter can define + * hotspots which correspond to certain strings (such as URLs or particular words). + * Depending on the type of the hotspots created by the filter ( returned by Filter::Hotspot::type() ) + * the view will draw visual cues such as underlines on mouse-over for links or translucent + * rectangles for markers. + * + * To add a new filter to the view, call: + * viewWidget->filterChain()->addFilter( filterObject ); + */ + FilterChain* filterChain() const; + + /** + * Updates the filters in the display's filter chain. This will cause + * the hotspots to be updated to match the current image. + * + * WARNING: This function can be expensive depending on the + * image size and number of filters in the filterChain() + * + * TODO - This API does not really allow efficient usage. Revise it so + * that the processing can be done in a better way. + * + * eg: + * - Area of interest may be known ( eg. mouse cursor hovering + * over an area ) + */ + void processFilters(); + + /** + * Returns a list of menu actions created by the filters for the content + * at the given @p position. + */ + QList<QAction*> filterActions(const QPoint& position); + + /** Returns true if the cursor is set to blink or false otherwise. */ + bool blinkingCursor() { return _hasBlinkingCursor; } + /** Specifies whether or not the cursor blinks. */ + void setBlinkingCursor(bool blink); + + /** Specifies whether or not text can blink. */ + void setBlinkingTextEnabled(bool blink); + + void setCtrlDrag(bool enable) { _ctrlDrag=enable; } + bool ctrlDrag() { return _ctrlDrag; } + + /** + * This enum describes the methods for selecting text when + * the user triple-clicks within the display. + */ + enum TripleClickMode + { + /** Select the whole line underneath the cursor. */ + SelectWholeLine, + /** Select from the current cursor position to the end of the line. */ + SelectForwardsFromCursor + }; + /** Sets how the text is selected when the user triple clicks within the display. */ + void setTripleClickMode(TripleClickMode mode) { _tripleClickMode = mode; } + /** See setTripleClickSelectionMode() */ + TripleClickMode tripleClickMode() { return _tripleClickMode; } + + void setLineSpacing(uint); + uint lineSpacing() const; + + void emitSelection(bool useXselection,bool appendReturn); + + /** + * This enum describes the available shapes for the keyboard cursor. + * See setKeyboardCursorShape() + */ + enum KeyboardCursorShape + { + /** A rectangular block which covers the entire area of the cursor character. */ + BlockCursor, + /** + * A single flat line which occupies the space at the bottom of the cursor + * character's area. + */ + UnderlineCursor, + /** + * An cursor shaped like the capital letter 'I', similar to the IBeam + * cursor used in Qt/KDE text editors. + */ + IBeamCursor + }; + /** + * Sets the shape of the keyboard cursor. This is the cursor drawn + * at the position in the terminal where keyboard input will appear. + * + * In addition the terminal display widget also has a cursor for + * the mouse pointer, which can be set using the QWidget::setCursor() + * method. + * + * Defaults to BlockCursor + */ + void setKeyboardCursorShape(KeyboardCursorShape shape); + /** + * Returns the shape of the keyboard cursor. See setKeyboardCursorShape() + */ + KeyboardCursorShape keyboardCursorShape() const; + + /** + * Sets the color used to draw the keyboard cursor. + * + * The keyboard cursor defaults to using the foreground color of the character + * underneath it. + * + * @param useForegroundColor If true, the cursor color will change to match + * the foreground color of the character underneath it as it is moved, in this + * case, the @p color parameter is ignored and the color of the character + * under the cursor is inverted to ensure that it is still readable. + * @param color The color to use to draw the cursor. This is only taken into + * account if @p useForegroundColor is false. + */ + void setKeyboardCursorColor(bool useForegroundColor , const QColor& color); + + /** + * Returns the color of the keyboard cursor, or an invalid color if the keyboard + * cursor color is set to change according to the foreground color of the character + * underneath it. + */ + QColor keyboardCursorColor() const; + + /** + * Returns the number of lines of text which can be displayed in the widget. + * + * This will depend upon the height of the widget and the current font. + * See fontHeight() + */ + int lines() { return _lines; } + /** + * Returns the number of characters of text which can be displayed on + * each line in the widget. + * + * This will depend upon the width of the widget and the current font. + * See fontWidth() + */ + int columns() { return _columns; } + + /** + * Returns the height of the characters in the font used to draw the text in the display. + */ + int fontHeight() { return _fontHeight; } + /** + * Returns the width of the characters in the display. + * This assumes the use of a fixed-width font. + */ + int fontWidth() { return _fontWidth; } + + void setSize(int cols, int lins); + void setFixedSize(int cols, int lins); + + // reimplemented + QSize sizeHint() const; + + /** + * Sets which characters, in addition to letters and numbers, + * are regarded as being part of a word for the purposes + * of selecting words in the display by double clicking on them. + * + * The word boundaries occur at the first and last characters which + * are either a letter, number, or a character in @p wc + * + * @param wc An array of characters which are to be considered parts + * of a word ( in addition to letters and numbers ). + */ + void setWordCharacters(const QString& wc); + /** + * Returns the characters which are considered part of a word for the + * purpose of selecting words in the display with the mouse. + * + * @see setWordCharacters() + */ + QString wordCharacters() { return _wordCharacters; } + + /** + * Sets the type of effect used to alert the user when a 'bell' occurs in the + * terminal session. + * + * The terminal session can trigger the bell effect by calling bell() with + * the alert message. + */ + void setBellMode(int mode); + /** + * Returns the type of effect used to alert the user when a 'bell' occurs in + * the terminal session. + * + * See setBellMode() + */ + int bellMode() { return _bellMode; } + + /** + * This enum describes the different types of sounds and visual effects which + * can be used to alert the user when a 'bell' occurs in the terminal + * session. + */ + enum BellMode + { + /** A system beep. */ + SystemBeepBell=0, + /** + * KDE notification. This may play a sound, show a passive popup + * or perform some other action depending on the user's settings. + */ + NotifyBell=1, + /** A silent, visual bell (eg. inverting the display's colors briefly) */ + VisualBell=2, + /** No bell effects */ + NoBell=3 + }; + + void setSelection(const QString &t); + + /** + * Reimplemented. Has no effect. Use setVTFont() to change the font + * used to draw characters in the display. + */ + virtual void setFont(const QFont &); + + /** Returns the font used to draw characters in the display */ + QFont getVTFont() { return font(); } + + /** + * Sets the font used to draw the display. Has no effect if @p font + * is larger than the size of the display itself. + */ + void setVTFont(const QFont& font); + + /** + * Specified whether anti-aliasing of text in the terminal display + * is enabled or not. Defaults to enabled. + */ + static void setAntialias( bool antialias ) { _antialiasText = antialias; } + /** + * Returns true if anti-aliasing of text in the terminal is enabled. + */ + static bool antialias() { return _antialiasText; } + + /** + * Specifies whether characters with intense colors should be rendered + * as bold. Defaults to true. + */ + void setBoldIntense(bool value) { _boldIntense = value; } + /** + * Returns true if characters with intense colors are rendered in bold. + */ + bool getBoldIntense() { return _boldIntense; } + + /** + * Sets whether or not the current height and width of the + * terminal in lines and columns is displayed whilst the widget + * is being resized. + */ + void setTerminalSizeHint(bool on) { _terminalSizeHint=on; } + /** + * Returns whether or not the current height and width of + * the terminal in lines and columns is displayed whilst the widget + * is being resized. + */ + bool terminalSizeHint() { return _terminalSizeHint; } + /** + * Sets whether the terminal size display is shown briefly + * after the widget is first shown. + * + * See setTerminalSizeHint() , isTerminalSizeHint() + */ + void setTerminalSizeStartup(bool on) { _terminalSizeStartup=on; } + + /** + * Sets the status of the BiDi rendering inside the terminal display. + * Defaults to disabled. + */ + void setBidiEnabled(bool set) { _bidiEnabled=set; } + /** + * Returns the status of the BiDi rendering in this widget. + */ + bool isBidiEnabled() { return _bidiEnabled; } + + /** + * Sets the terminal screen section which is displayed in this widget. + * When updateImage() is called, the display fetches the latest character image from the + * the associated terminal screen window. + * + * In terms of the model-view paradigm, the ScreenWindow is the model which is rendered + * by the TerminalDisplay. + */ + void setScreenWindow( ScreenWindow* window ); + /** Returns the terminal screen section which is displayed in this widget. See setScreenWindow() */ + ScreenWindow* screenWindow() const; + + static bool HAVE_TRANSPARENCY; + +public slots: + + /** + * Causes the terminal display to fetch the latest character image from the associated + * terminal screen ( see setScreenWindow() ) and redraw the display. + */ + void updateImage(); + /** + * Causes the terminal display to fetch the latest line status flags from the + * associated terminal screen ( see setScreenWindow() ). + */ + void updateLineProperties(); + + /** Copies the selected text to the clipboard. */ + void copyClipboard(); + /** + * Pastes the content of the clipboard into the + * display. + */ + void pasteClipboard(); + /** + * Pastes the content of the selection into the + * display. + */ + void pasteSelection(); + + /** + * Changes whether the flow control warning box should be shown when the flow control + * stop key (Ctrl+S) are pressed. + */ + void setFlowControlWarningEnabled(bool enabled); + /** + * Returns true if the flow control warning box is enabled. + * See outputSuspended() and setFlowControlWarningEnabled() + */ + bool flowControlWarningEnabled() const + { return _flowControlWarningEnabled; } + + /** + * Causes the widget to display or hide a message informing the user that terminal + * output has been suspended (by using the flow control key combination Ctrl+S) + * + * @param suspended True if terminal output has been suspended and the warning message should + * be shown or false to indicate that terminal output has been resumed and that + * the warning message should disappear. + */ + void outputSuspended(bool suspended); + + /** + * Sets whether the program whoose output is being displayed in the view + * is interested in mouse events. + * + * If this is set to true, mouse signals will be emitted by the view when the user clicks, drags + * or otherwise moves the mouse inside the view. + * The user interaction needed to create selections will also change, and the user will be required + * to hold down the shift key to create a selection or perform other mouse activities inside the + * view area - since the program running in the terminal is being allowed to handle normal mouse + * events itself. + * + * @param usesMouse Set to true if the program running in the terminal is interested in mouse events + * or false otherwise. + */ + void setUsesMouse(bool usesMouse); + + /** See setUsesMouse() */ + bool usesMouse() const; + + /** + * Shows a notification that a bell event has occurred in the terminal. + * TODO: More documentation here + */ + void bell(const QString& message); + + /** + * Sets the background of the display to the specified color. + * @see setColorTable(), setForegroundColor() + */ + void setBackgroundColor(const QColor& color); + + /** + * Sets the text of the display to the specified color. + * @see setColorTable(), setBackgroundColor() + */ + void setForegroundColor(const QColor& color); + +signals: + + /** + * Emitted when the user presses a key whilst the terminal widget has focus. + */ + void keyPressedSignal(QKeyEvent *e); + + /** + * A mouse event occurred. + * @param button The mouse button (0 for left button, 1 for middle button, 2 for right button, 3 for release) + * @param column The character column where the event occurred + * @param line The character row where the event occurred + * @param eventType The type of event. 0 for a mouse press / release or 1 for mouse motion + */ + void mouseSignal(int button, int column, int line, int eventType); + void changedFontMetricSignal(int height, int width); + void changedContentSizeSignal(int height, int width); + + /** + * Emitted when the user right clicks on the display, or right-clicks with the Shift + * key held down if usesMouse() is true. + * + * This can be used to display a context menu. + */ + void configureRequest(const QPoint& position); + + /** + * When a shortcut which is also a valid terminal key sequence is pressed while + * the terminal widget has focus, this signal is emitted to allow the host to decide + * whether the shortcut should be overridden. + * When the shortcut is overridden, the key sequence will be sent to the terminal emulation instead + * and the action associated with the shortcut will not be triggered. + * + * @p override is set to false by default and the shortcut will be triggered as normal. + */ + void overrideShortcutCheck(QKeyEvent* keyEvent,bool& override); + + void isBusySelecting(bool); + void sendStringToEmu(const char*); + +protected: + virtual bool event( QEvent * ); + + virtual void paintEvent( QPaintEvent * ); + + virtual void showEvent(QShowEvent*); + virtual void hideEvent(QHideEvent*); + virtual void resizeEvent(QResizeEvent*); + + virtual void fontChange(const QFont &font); + virtual void focusInEvent(QFocusEvent* event); + virtual void focusOutEvent(QFocusEvent* event); + virtual void keyPressEvent(QKeyEvent* event); + virtual void mouseDoubleClickEvent(QMouseEvent* ev); + virtual void mousePressEvent( QMouseEvent* ); + virtual void mouseReleaseEvent( QMouseEvent* ); + virtual void mouseMoveEvent( QMouseEvent* ); + virtual void extendSelection( const QPoint& pos ); + virtual void wheelEvent( QWheelEvent* ); + + virtual bool focusNextPrevChild( bool next ); + + // drag and drop + virtual void dragEnterEvent(QDragEnterEvent* event); + virtual void dropEvent(QDropEvent* event); + void doDrag(); + enum DragState { diNone, diPending, diDragging }; + + struct _dragInfo { + DragState state; + QPoint start; + QDrag *dragObject; + } dragInfo; + + // classifies the 'ch' into one of three categories + // and returns a character to indicate which category it is in + // + // - A space (returns ' ') + // - Part of a word (returns 'a') + // - Other characters (returns the input character) + QChar charClass(QChar ch) const; + + void clearImage(); + + void mouseTripleClickEvent(QMouseEvent* ev); + + // reimplemented + virtual void inputMethodEvent ( QInputMethodEvent* event ); + virtual QVariant inputMethodQuery( Qt::InputMethodQuery query ) const; + +protected slots: + + void scrollBarPositionChanged(int value); + void blinkEvent(); + void blinkCursorEvent(); + + //Renables bell noises and visuals. Used to disable further bells for a short period of time + //after emitting the first in a sequence of bell events. + void enableBell(); + +private slots: + + void swapColorTable(); + void tripleClickTimeout(); // resets possibleTripleClick + +private: + + // -- Drawing helpers -- + + // divides the part of the display specified by 'rect' into + // fragments according to their colors and styles and calls + // drawTextFragment() to draw the fragments + void drawContents(QPainter &paint, const QRect &rect); + // draws a section of text, all the text in this section + // has a common color and style + void drawTextFragment(QPainter& painter, const QRect& rect, + const QString& text, const Character* style); + // draws the background for a text fragment + // if useOpacitySetting is true then the color's alpha value will be set to + // the display's transparency (set with setOpacity()), otherwise the background + // will be drawn fully opaque + void drawBackground(QPainter& painter, const QRect& rect, const QColor& color, + bool useOpacitySetting); + // draws the cursor character + void drawCursor(QPainter& painter, const QRect& rect , const QColor& foregroundColor, + const QColor& backgroundColor , bool& invertColors); + // draws the characters or line graphics in a text fragment + void drawCharacters(QPainter& painter, const QRect& rect, const QString& text, + const Character* style, bool invertCharacterColor); + // draws a string of line graphics + void drawLineCharString(QPainter& painter, int x, int y, + const QString& str, const Character* attributes); + + // draws the preedit string for input methods + void drawInputMethodPreeditString(QPainter& painter , const QRect& rect); + + // -- + + // maps an area in the character image to an area on the widget + QRect imageToWidget(const QRect& imageArea) const; + + // maps a point on the widget to the position ( ie. line and column ) + // of the character at that point. + void getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const; + + // the area where the preedit string for input methods will be draw + QRect preeditRect() const; + + // shows a notification window in the middle of the widget indicating the terminal's + // current size in columns and lines + void showResizeNotification(); + + // scrolls the image by a number of lines. + // 'lines' may be positive ( to scroll the image down ) + // or negative ( to scroll the image up ) + // 'region' is the part of the image to scroll - currently only + // the top, bottom and height of 'region' are taken into account, + // the left and right are ignored. + void scrollImage(int lines , const QRect& region); + + void calcGeometry(); + void propagateSize(); + void updateImageSize(); + void makeImage(); + + void paintFilters(QPainter& painter); + + // returns a region covering all of the areas of the widget which contain + // a hotspot + QRegion hotSpotRegion() const; + + // returns the position of the cursor in columns and lines + QPoint cursorPosition() const; + + // redraws the cursor + void updateCursor(); + + bool handleShortcutOverrideEvent(QKeyEvent* event); + + // the window onto the terminal screen which this display + // is currently showing. + QPointer<ScreenWindow> _screenWindow; + + bool _allowBell; + + QGridLayout* _gridLayout; + + bool _fixedFont; // has fixed pitch + int _fontHeight; // height + int _fontWidth; // width + int _fontAscent; // ascend + bool _boldIntense; // Whether intense colors should be rendered with bold font + + int _leftMargin; // offset + int _topMargin; // offset + + int _lines; // the number of lines that can be displayed in the widget + int _columns; // the number of columns that can be displayed in the widget + + int _usedLines; // the number of lines that are actually being used, this will be less + // than 'lines' if the character image provided with setImage() is smaller + // than the maximum image size which can be displayed + + int _usedColumns; // the number of columns that are actually being used, this will be less + // than 'columns' if the character image provided with setImage() is smaller + // than the maximum image size which can be displayed + + int _contentHeight; + int _contentWidth; + Character* _image; // [lines][columns] + // only the area [usedLines][usedColumns] in the image contains valid data + + int _imageSize; + QVector<LineProperty> _lineProperties; + + ColorEntry _colorTable[TABLE_COLORS]; + uint _randomSeed; + + bool _resizing; + bool _terminalSizeHint; + bool _terminalSizeStartup; + bool _bidiEnabled; + bool _mouseMarks; + + QPoint _iPntSel; // initial selection point + QPoint _pntSel; // current selection point + QPoint _tripleSelBegin; // help avoid flicker + int _actSel; // selection state + bool _wordSelectionMode; + bool _lineSelectionMode; + bool _preserveLineBreaks; + bool _columnSelectionMode; + + QClipboard* _clipboard; + QScrollBar* _scrollBar; + ScrollBarPosition _scrollbarLocation; + QString _wordCharacters; + int _bellMode; + + bool _blinking; // hide text in paintEvent + bool _hasBlinker; // has characters to blink + bool _cursorBlinking; // hide cursor in paintEvent + bool _hasBlinkingCursor; // has blinking cursor enabled + bool _allowBlinkingText; // allow text to blink + bool _ctrlDrag; // require Ctrl key for drag + TripleClickMode _tripleClickMode; + bool _isFixedSize; //Columns / lines are locked. + QTimer* _blinkTimer; // active when hasBlinker + QTimer* _blinkCursorTimer; // active when hasBlinkingCursor + + KMenu* _drop; + QString _dropText; + int _dndFileCount; + + bool _possibleTripleClick; // is set in mouseDoubleClickEvent and deleted + // after QApplication::doubleClickInterval() delay + + + QLabel* _resizeWidget; + QTimer* _resizeTimer; + + bool _flowControlWarningEnabled; + + //widgets related to the warning message that appears when the user presses Ctrl+S to suspend + //terminal output - informing them what has happened and how to resume output + QLabel* _outputSuspendedLabel; + + uint _lineSpacing; + + bool _colorsInverted; // true during visual bell + + QSize _size; + + QRgb _blendColor; + + // list of filters currently applied to the display. used for links and + // search highlight + TerminalImageFilterChain* _filterChain; + QRegion _mouseOverHotspotArea; + + KeyboardCursorShape _cursorShape; + + // custom cursor color. if this is invalid then the foreground + // color of the character under the cursor is used + QColor _cursorColor; + + + struct InputMethodData + { + QString preeditString; + QRect previousPreeditRect; + }; + InputMethodData _inputMethodData; + + static bool _antialiasText; // do we antialias or not + + //the delay in milliseconds between redrawing blinking text + static const int TEXT_BLINK_DELAY = 500; + static const int DEFAULT_LEFT_MARGIN = 1; + static const int DEFAULT_TOP_MARGIN = 1; + +public: + static void setTransparencyEnabled(bool enable) + { + HAVE_TRANSPARENCY = enable; + } +}; + +class AutoScrollHandler : public QObject +{ +Q_OBJECT + +public: + AutoScrollHandler(QWidget* parent); +protected: + virtual void timerEvent(QTimerEvent* event); + virtual bool eventFilter(QObject* watched,QEvent* event); +private: + QWidget* widget() const { return static_cast<QWidget*>(parent()); } + int _timerId; +}; + + +#endif // TERMINALDISPLAY_H
new file mode 100644 --- /dev/null +++ b/gui//src/VariablesDockWidget.cpp @@ -0,0 +1,168 @@ +#include "VariablesDockWidget.h" +#include <QHBoxLayout> +#include <QVBoxLayout> +#include <QPushButton> + +VariablesDockWidget::VariablesDockWidget(QWidget *parent) + : QDockWidget(parent) { + setObjectName("VariablesDockWidget"); + construct(); +} + +void VariablesDockWidget::construct() { + m_updateSemaphore = new QSemaphore(1); + QStringList headerLabels; + headerLabels << tr("Name") << tr("Type") << tr("Value"); + m_variablesTreeWidget = new QTreeWidget(this); + m_variablesTreeWidget->setHeaderHidden(false); + m_variablesTreeWidget->setHeaderLabels(headerLabels); + QVBoxLayout *layout = new QVBoxLayout(); + + setWindowTitle(tr("Workspace")); + setWidget(new QWidget()); + + layout->addWidget(m_variablesTreeWidget); + QWidget *buttonBar = new QWidget(this); + layout->addWidget(buttonBar); + + QHBoxLayout *buttonBarLayout = new QHBoxLayout(); + QPushButton *saveWorkspaceButton = new QPushButton(tr("Save"), buttonBar); + QPushButton *loadWorkspaceButton = new QPushButton(tr("Load"), buttonBar); + QPushButton *clearWorkspaceButton = new QPushButton(tr("Clear"), buttonBar); + buttonBarLayout->addWidget(saveWorkspaceButton); + buttonBarLayout->addWidget(loadWorkspaceButton); + buttonBarLayout->addWidget(clearWorkspaceButton); + buttonBarLayout->setMargin(2); + buttonBar->setLayout(buttonBarLayout); + + layout->setMargin(2); + widget()->setLayout(layout); + + connect(saveWorkspaceButton, SIGNAL(clicked()), this, SLOT(emitSaveWorkspace())); + connect(loadWorkspaceButton, SIGNAL(clicked()), this, SLOT(emitLoadWorkspace())); + connect(clearWorkspaceButton, SIGNAL(clicked()), this, SLOT(emitClearWorkspace())); + + QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem(); + treeWidgetItem->setData(0, 0, QString(tr("Local"))); + m_variablesTreeWidget->insertTopLevelItem(0, treeWidgetItem); + + treeWidgetItem = new QTreeWidgetItem(); + treeWidgetItem->setData(0, 0, QString(tr("Global"))); + m_variablesTreeWidget->insertTopLevelItem(1, treeWidgetItem); + + treeWidgetItem = new QTreeWidgetItem(); + treeWidgetItem->setData(0, 0, QString(tr("Persistent"))); + m_variablesTreeWidget->insertTopLevelItem(2, treeWidgetItem); + + treeWidgetItem = new QTreeWidgetItem(); + treeWidgetItem->setData(0, 0, QString(tr("Hidden"))); + m_variablesTreeWidget->insertTopLevelItem(3, treeWidgetItem); + + m_variablesTreeWidget->expandAll(); + m_variablesTreeWidget->setAlternatingRowColors(true); + m_variablesTreeWidget->setAnimated(true); +} + +void VariablesDockWidget::updateTreeEntry(QTreeWidgetItem *treeItem, SymbolRecord symbolRecord) { + treeItem->setData(0, 0, QString(symbolRecord.name().c_str())); + treeItem->setData(1, 0, QString(symbolRecord.varval().type_name().c_str())); + treeItem->setData(2, 0, OctaveLink::octaveValueAsQString(symbolRecord.varval())); +} + +void VariablesDockWidget::setVariablesList(QList<SymbolRecord> symbolTable) { + m_updateSemaphore->acquire(); + // Split the symbol table into its different scopes. + QList<SymbolRecord> localSymbolTable; + QList<SymbolRecord> globalSymbolTable; + QList<SymbolRecord> persistentSymbolTable; + QList<SymbolRecord> hiddenSymbolTable; + + foreach(SymbolRecord symbolRecord, symbolTable) { + // It's true that being global or hidden includes it's can mean it's also locally visible, + // but we want to distinguish that here. + if(symbolRecord.is_local() && !symbolRecord.is_global() && !symbolRecord.is_hidden()) { + localSymbolTable.append(symbolRecord); + } + + if(symbolRecord.is_global()) { + globalSymbolTable.append(symbolRecord); + } + + if(symbolRecord.is_persistent()) { + persistentSymbolTable.append(symbolRecord); + } + + if(symbolRecord.is_hidden()) { + hiddenSymbolTable.append(symbolRecord); + } + } + + updateScope(0, localSymbolTable); + updateScope(1, globalSymbolTable); + updateScope(2, persistentSymbolTable); + updateScope(3, hiddenSymbolTable); + m_updateSemaphore->release(); +} + +void VariablesDockWidget::updateScope(int topLevelItemIndex, QList<SymbolRecord> symbolTable) { + // This method may be a little bit confusing; variablesList is a complete list of all + // variables that are in the workspace currently. + QTreeWidgetItem *topLevelItem = m_variablesTreeWidget->topLevelItem(topLevelItemIndex); + + // First we check, if any variables that exist in the model tree have to be updated + // or created. So we walk the variablesList check against the tree. + foreach(SymbolRecord symbolRecord, symbolTable) { + int childCount = topLevelItem->childCount(); + bool alreadyExists = false; + QTreeWidgetItem *child; + + // Search for the corresponding item in the tree. If it has been found, child + // will contain the appropriate QTreeWidgetItem* pointing at it. + for(int i = 0; i < childCount; i++) { + child = topLevelItem->child(i); + if(child->data(0, 0).toString() == QString(symbolRecord.name().c_str())) { + alreadyExists = true; + break; + } + } + + // If it already exists, just update it. + if(alreadyExists) { + updateTreeEntry(child, symbolRecord); + } else { + // It does not exist, so create a new one and set the right values. + child = new QTreeWidgetItem(); + updateTreeEntry(child, symbolRecord); + topLevelItem->addChild(child); + } + } + + // Check the tree against the list for deleted variables. + for(int i = 0; i < topLevelItem->childCount(); i++) { + bool existsInVariableList = false; + QTreeWidgetItem *child = topLevelItem->child(i); + foreach(SymbolRecord symbolRecord, symbolTable) { + if(QString(symbolRecord.name().c_str()) == child->data(0, 0).toString()) { + existsInVariableList = true; + } + } + + if(!existsInVariableList) { + topLevelItem->removeChild(child); + delete child; + i--; + } + } +} + +void VariablesDockWidget::emitSaveWorkspace() { + emit saveWorkspace(); +} + +void VariablesDockWidget::emitLoadWorkspace() { + emit loadWorkspace(); +} + +void VariablesDockWidget::emitClearWorkspace() { + emit clearWorkspace(); +}
new file mode 100644 --- /dev/null +++ b/gui//src/VariablesDockWidget.h @@ -0,0 +1,34 @@ +#ifndef VARIABLESDOCKWIDGET_H +#define VARIABLESDOCKWIDGET_H + +#include <QDockWidget> +#include <QTreeWidget> +#include <QSemaphore> +#include "OctaveLink.h" + +class VariablesDockWidget : public QDockWidget +{ + Q_OBJECT +public: + VariablesDockWidget(QWidget *parent = 0); + void setVariablesList(QList<SymbolRecord> symbolTable); + +signals: + void saveWorkspace(); + void loadWorkspace(); + void clearWorkspace(); + +private slots: + void emitSaveWorkspace(); + void emitLoadWorkspace(); + void emitClearWorkspace(); + +private: + void construct(); + void updateTreeEntry(QTreeWidgetItem *treeItem, SymbolRecord symbolRecord); + void updateScope(int topLevelItemIndex, QList<SymbolRecord> symbolTable); + QTreeWidget *m_variablesTreeWidget; + QSemaphore *m_updateSemaphore; +}; + +#endif // VARIABLESDOCKWIDGET_H
new file mode 100644 --- /dev/null +++ b/gui//src/Vt102Emulation.cpp @@ -0,0 +1,1214 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight <robert.knight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "Vt102Emulation.h" + + +// this allows konsole to be compiled without XKB and XTEST extensions +// even though it might be available on a particular system. +#if defined(AVOID_XKB) + #undef HAVE_XKB +#endif + +#if defined(HAVE_XKB) + void scrolllock_set_off(); + void scrolllock_set_on(); +#endif + +// Standard +#include <stdio.h> +#include <unistd.h> +#include <assert.h> + +// Qt +#include <QtCore/QEvent> +#include <QtGui/QKeyEvent> +#include <QtCore/QByteRef> + +// Konsole +#include "KeyboardTranslator.h" +#include "Screen.h" + +Vt102Emulation::Vt102Emulation() + : Emulation(), + _titleUpdateTimer(new QTimer(this)) +{ + _titleUpdateTimer->setSingleShot(true); + QObject::connect(_titleUpdateTimer , SIGNAL(timeout()) , this , SLOT(updateTitle())); + + initTokenizer(); + reset(); +} + +Vt102Emulation::~Vt102Emulation() +{} + +void Vt102Emulation::clearEntireScreen() +{ + _currentScreen->clearEntireScreen(); + bufferedUpdate(); +} + +void Vt102Emulation::reset() +{ + resetTokenizer(); + resetModes(); + resetCharset(0); + _screen[0]->reset(); + resetCharset(1); + _screen[1]->reset(); + setCodec(LocaleCodec); + + bufferedUpdate(); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Processing the incoming byte stream */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* Incoming Bytes Event pipeline + + This section deals with decoding the incoming character stream. + Decoding means here, that the stream is first separated into `tokens' + which are then mapped to a `meaning' provided as operations by the + `Screen' class or by the emulation class itself. + + The pipeline proceeds as follows: + + - Tokenizing the ESC codes (onReceiveChar) + - VT100 code page translation of plain characters (applyCharset) + - Interpretation of ESC codes (processToken) + + The escape codes and their meaning are described in the + technical reference of this program. +*/ + +// Tokens ------------------------------------------------------------------ -- + +/* + Since the tokens are the central notion if this section, we've put them + in front. They provide the syntactical elements used to represent the + terminals operations as byte sequences. + + They are encodes here into a single machine word, so that we can later + switch over them easily. Depending on the token itself, additional + argument variables are filled with parameter values. + + The tokens are defined below: + + - CHR - Printable characters (32..255 but DEL (=127)) + - CTL - Control characters (0..31 but ESC (= 27), DEL) + - ESC - Escape codes of the form <ESC><CHR but `[]()+*#'> + - ESC_DE - Escape codes of the form <ESC><any of `()+*#%'> C + - CSI_PN - Escape codes of the form <ESC>'[' {Pn} ';' {Pn} C + - CSI_PS - Escape codes of the form <ESC>'[' {Pn} ';' ... C + - CSI_PR - Escape codes of the form <ESC>'[' '?' {Pn} ';' ... C + - CSI_PE - Escape codes of the form <ESC>'[' '!' {Pn} ';' ... C + - VT52 - VT52 escape codes + - <ESC><Chr> + - <ESC>'Y'{Pc}{Pc} + - XTE_HA - Xterm window/terminal attribute commands + of the form <ESC>`]' {Pn} `;' {Text} <BEL> + (Note that these are handled differently to the other formats) + + The last two forms allow list of arguments. Since the elements of + the lists are treated individually the same way, they are passed + as individual tokens to the interpretation. Further, because the + meaning of the parameters are names (althought represented as numbers), + they are includes within the token ('N'). + +*/ + +#define TY_CONSTRUCT(T,A,N) ( ((((int)N) & 0xffff) << 16) | ((((int)A) & 0xff) << 8) | (((int)T) & 0xff) ) + +#define TY_CHR( ) TY_CONSTRUCT(0,0,0) +#define TY_CTL(A ) TY_CONSTRUCT(1,A,0) +#define TY_ESC(A ) TY_CONSTRUCT(2,A,0) +#define TY_ESC_CS(A,B) TY_CONSTRUCT(3,A,B) +#define TY_ESC_DE(A ) TY_CONSTRUCT(4,A,0) +#define TY_CSI_PS(A,N) TY_CONSTRUCT(5,A,N) +#define TY_CSI_PN(A ) TY_CONSTRUCT(6,A,0) +#define TY_CSI_PR(A,N) TY_CONSTRUCT(7,A,N) + +#define TY_VT52(A) TY_CONSTRUCT(8,A,0) +#define TY_CSI_PG(A) TY_CONSTRUCT(9,A,0) +#define TY_CSI_PE(A) TY_CONSTRUCT(10,A,0) + +#define MAX_ARGUMENT 4096 + +// Tokenizer --------------------------------------------------------------- -- + +/* The tokenizer's state + + The state is represented by the buffer (tokenBuffer, tokenBufferPos), + and accompanied by decoded arguments kept in (argv,argc). + Note that they are kept internal in the tokenizer. +*/ + +void Vt102Emulation::resetTokenizer() +{ + tokenBufferPos = 0; + argc = 0; + argv[0] = 0; + argv[1] = 0; +} + +void Vt102Emulation::addDigit(int digit) +{ + if (argv[argc] < MAX_ARGUMENT) + argv[argc] = 10*argv[argc] + digit; +} + +void Vt102Emulation::addArgument() +{ + argc = qMin(argc+1,MAXARGS-1); + argv[argc] = 0; +} + +void Vt102Emulation::addToCurrentToken(int cc) +{ + tokenBuffer[tokenBufferPos] = cc; + tokenBufferPos = qMin(tokenBufferPos+1,MAX_TOKEN_LENGTH-1); +} + +// Character Class flags used while decoding + +#define CTL 1 // Control character +#define CHR 2 // Printable character +#define CPN 4 // TODO: Document me +#define DIG 8 // Digit +#define SCS 16 // TODO: Document me +#define GRP 32 // TODO: Document me +#define CPS 64 // Character which indicates end of window resize + // escape sequence '\e[8;<row>;<col>t' + +void Vt102Emulation::initTokenizer() +{ + int i; + quint8* s; + for(i = 0;i < 256; ++i) + charClass[i] = 0; + for(i = 0;i < 32; ++i) + charClass[i] |= CTL; + for(i = 32;i < 256; ++i) + charClass[i] |= CHR; + for(s = (quint8*)"@ABCDGHILMPSTXZcdfry"; *s; ++s) + charClass[*s] |= CPN; + // resize = \e[8;<row>;<col>t + for(s = (quint8*)"t"; *s; ++s) + charClass[*s] |= CPS; + for(s = (quint8*)"0123456789"; *s; ++s) + charClass[*s] |= DIG; + for(s = (quint8*)"()+*%"; *s; ++s) + charClass[*s] |= SCS; + for(s = (quint8*)"()+*#[]%"; *s; ++s) + charClass[*s] |= GRP; + + resetTokenizer(); +} + +/* Ok, here comes the nasty part of the decoder. + + Instead of keeping an explicit state, we deduce it from the + token scanned so far. It is then immediately combined with + the current character to form a scanning decision. + + This is done by the following defines. + + - P is the length of the token scanned so far. + - L (often P-1) is the position on which contents we base a decision. + - C is a character or a group of characters (taken from 'charClass'). + + - 'cc' is the current character + - 's' is a pointer to the start of the token buffer + - 'p' is the current position within the token buffer + + Note that they need to applied in proper order. +*/ + +#define lec(P,L,C) (p == (P) && s[(L)] == (C)) +#define lun( ) (p == 1 && cc >= 32 ) +#define les(P,L,C) (p == (P) && s[L] < 256 && (charClass[s[(L)]] & (C)) == (C)) +#define eec(C) (p >= 3 && cc == (C)) +#define ees(C) (p >= 3 && cc < 256 && (charClass[cc] & (C)) == (C)) +#define eps(C) (p >= 3 && s[2] != '?' && s[2] != '!' && s[2] != '>' && cc < 256 && (charClass[cc] & (C)) == (C)) +#define epp( ) (p >= 3 && s[2] == '?') +#define epe( ) (p >= 3 && s[2] == '!') +#define egt( ) (p >= 3 && s[2] == '>') +#define Xpe (tokenBufferPos >= 2 && tokenBuffer[1] == ']') +#define Xte (Xpe && cc == 7 ) +#define ces(C) (cc < 256 && (charClass[cc] & (C)) == (C) && !Xte) + +#define ESC 27 +#define CNTL(c) ((c)-'@') + +// process an incoming unicode character +void Vt102Emulation::receiveChar(int cc) +{ + if (cc == 127) + return; //VT100: ignore. + + if (ces(CTL)) + { + // DEC HACK ALERT! Control Characters are allowed *within* esc sequences in VT100 + // This means, they do neither a resetTokenizer() nor a pushToToken(). Some of them, do + // of course. Guess this originates from a weakly layered handling of the X-on + // X-off protocol, which comes really below this level. + if (cc == CNTL('X') || cc == CNTL('Z') || cc == ESC) + resetTokenizer(); //VT100: CAN or SUB + if (cc != ESC) + { + processToken(TY_CTL(cc+'@' ),0,0); + return; + } + } + // advance the state + addToCurrentToken(cc); + + int* s = tokenBuffer; + int p = tokenBufferPos; + + if (getMode(MODE_Ansi)) + { + if (lec(1,0,ESC)) { return; } + if (lec(1,0,ESC+128)) { s[0] = ESC; receiveChar('['); return; } + if (les(2,1,GRP)) { return; } + if (Xte ) { processWindowAttributeChange(); resetTokenizer(); return; } + if (Xpe ) { return; } + if (lec(3,2,'?')) { return; } + if (lec(3,2,'>')) { return; } + if (lec(3,2,'!')) { return; } + if (lun( )) { processToken( TY_CHR(), applyCharset(cc), 0); resetTokenizer(); return; } + if (lec(2,0,ESC)) { processToken( TY_ESC(s[1]), 0, 0); resetTokenizer(); return; } + if (les(3,1,SCS)) { processToken( TY_ESC_CS(s[1],s[2]), 0, 0); resetTokenizer(); return; } + if (lec(3,1,'#')) { processToken( TY_ESC_DE(s[2]), 0, 0); resetTokenizer(); return; } + if (eps( CPN)) { processToken( TY_CSI_PN(cc), argv[0],argv[1]); resetTokenizer(); return; } + + // resize = \e[8;<row>;<col>t + if (eps(CPS)) + { + processToken( TY_CSI_PS(cc, argv[0]), argv[1], argv[2]); + resetTokenizer(); + return; + } + + if (epe( )) { processToken( TY_CSI_PE(cc), 0, 0); resetTokenizer(); return; } + if (ees(DIG)) { addDigit(cc-'0'); return; } + if (eec(';')) { addArgument(); return; } + for (int i=0;i<=argc;i++) + { + if (epp()) + processToken( TY_CSI_PR(cc,argv[i]), 0, 0); + else if (egt()) + processToken( TY_CSI_PG(cc), 0, 0); // spec. case for ESC]>0c or ESC]>c + else if (cc == 'm' && argc - i >= 4 && (argv[i] == 38 || argv[i] == 48) && argv[i+1] == 2) + { + // ESC[ ... 48;2;<red>;<green>;<blue> ... m -or- ESC[ ... 38;2;<red>;<green>;<blue> ... m + i += 2; + processToken( TY_CSI_PS(cc, argv[i-2]), COLOR_SPACE_RGB, (argv[i] << 16) | (argv[i+1] << 8) | argv[i+2]); + i += 2; + } + else if (cc == 'm' && argc - i >= 2 && (argv[i] == 38 || argv[i] == 48) && argv[i+1] == 5) + { + // ESC[ ... 48;5;<index> ... m -or- ESC[ ... 38;5;<index> ... m + i += 2; + processToken( TY_CSI_PS(cc, argv[i-2]), COLOR_SPACE_256, argv[i]); + } + else + processToken( TY_CSI_PS(cc,argv[i]), 0, 0); + } + resetTokenizer(); + } + else + { + // VT52 Mode + if (lec(1,0,ESC)) + return; + if (les(1,0,CHR)) + { + processToken( TY_CHR(), s[0], 0); + resetTokenizer(); + return; + } + if (lec(2,1,'Y')) + return; + if (lec(3,1,'Y')) + return; + if (p < 4) + { + processToken( TY_VT52(s[1] ), 0, 0); + resetTokenizer(); + return; + } + processToken( TY_VT52(s[1]), s[2], s[3]); + resetTokenizer(); + return; + } +} +void Vt102Emulation::processWindowAttributeChange() +{ + // Describes the window or terminal session attribute to change + // See Session::UserTitleChange for possible values + int attributeToChange = 0; + int i; + for (i = 2; i < tokenBufferPos && + tokenBuffer[i] >= '0' && + tokenBuffer[i] <= '9'; i++) + { + attributeToChange = 10 * attributeToChange + (tokenBuffer[i]-'0'); + } + + if (tokenBuffer[i] != ';') + { + reportDecodingError(); + return; + } + + QString newValue; + newValue.reserve(tokenBufferPos-i-2); + for (int j = 0; j < tokenBufferPos-i-2; j++) + newValue[j] = tokenBuffer[i+1+j]; + + _pendingTitleUpdates[attributeToChange] = newValue; + _titleUpdateTimer->start(20); +} + +void Vt102Emulation::updateTitle() +{ + QListIterator<int> iter( _pendingTitleUpdates.keys() ); + while (iter.hasNext()) { + int arg = iter.next(); + emit titleChanged( arg , _pendingTitleUpdates[arg] ); + } + _pendingTitleUpdates.clear(); +} + +// Interpreting Codes --------------------------------------------------------- + +/* + Now that the incoming character stream is properly tokenized, + meaning is assigned to them. These are either operations of + the current _screen, or of the emulation class itself. + + The token to be interpreteted comes in as a machine word + possibly accompanied by two parameters. + + Likewise, the operations assigned to, come with up to two + arguments. One could consider to make up a proper table + from the function below. + + The technical reference manual provides more information + about this mapping. +*/ + +void Vt102Emulation::processToken(int token, int p, int q) +{ + switch (token) + { + + case TY_CHR( ) : _currentScreen->displayCharacter (p ); break; //UTF16 + + // 127 DEL : ignored on input + + case TY_CTL('@' ) : /* NUL: ignored */ break; + case TY_CTL('A' ) : /* SOH: ignored */ break; + case TY_CTL('B' ) : /* STX: ignored */ break; + case TY_CTL('C' ) : /* ETX: ignored */ break; + case TY_CTL('D' ) : /* EOT: ignored */ break; + case TY_CTL('E' ) : reportAnswerBack ( ); break; //VT100 + case TY_CTL('F' ) : /* ACK: ignored */ break; + case TY_CTL('G' ) : emit stateSet(NOTIFYBELL); + break; //VT100 + case TY_CTL('H' ) : _currentScreen->backspace ( ); break; //VT100 + case TY_CTL('I' ) : _currentScreen->tab ( ); break; //VT100 + case TY_CTL('J' ) : _currentScreen->newLine ( ); break; //VT100 + case TY_CTL('K' ) : _currentScreen->newLine ( ); break; //VT100 + case TY_CTL('L' ) : _currentScreen->newLine ( ); break; //VT100 + case TY_CTL('M' ) : _currentScreen->toStartOfLine ( ); break; //VT100 + + case TY_CTL('N' ) : useCharset ( 1); break; //VT100 + case TY_CTL('O' ) : useCharset ( 0); break; //VT100 + + case TY_CTL('P' ) : /* DLE: ignored */ break; + case TY_CTL('Q' ) : /* DC1: XON continue */ break; //VT100 + case TY_CTL('R' ) : /* DC2: ignored */ break; + case TY_CTL('S' ) : /* DC3: XOFF halt */ break; //VT100 + case TY_CTL('T' ) : /* DC4: ignored */ break; + case TY_CTL('U' ) : /* NAK: ignored */ break; + case TY_CTL('V' ) : /* SYN: ignored */ break; + case TY_CTL('W' ) : /* ETB: ignored */ break; + case TY_CTL('X' ) : _currentScreen->displayCharacter ( 0x2592); break; //VT100 + case TY_CTL('Y' ) : /* EM : ignored */ break; + case TY_CTL('Z' ) : _currentScreen->displayCharacter ( 0x2592); break; //VT100 + case TY_CTL('[' ) : /* ESC: cannot be seen here. */ break; + case TY_CTL('\\' ) : /* FS : ignored */ break; + case TY_CTL(']' ) : /* GS : ignored */ break; + case TY_CTL('^' ) : /* RS : ignored */ break; + case TY_CTL('_' ) : /* US : ignored */ break; + + case TY_ESC('D' ) : _currentScreen->index ( ); break; //VT100 + case TY_ESC('E' ) : _currentScreen->nextLine ( ); break; //VT100 + case TY_ESC('H' ) : _currentScreen->changeTabStop (true ); break; //VT100 + case TY_ESC('M' ) : _currentScreen->reverseIndex ( ); break; //VT100 + case TY_ESC('Z' ) : reportTerminalType ( ); break; + case TY_ESC('c' ) : reset ( ); break; + + case TY_ESC('n' ) : useCharset ( 2); break; + case TY_ESC('o' ) : useCharset ( 3); break; + case TY_ESC('7' ) : saveCursor ( ); break; + case TY_ESC('8' ) : restoreCursor ( ); break; + + case TY_ESC('=' ) : setMode (MODE_AppKeyPad); break; + case TY_ESC('>' ) : resetMode (MODE_AppKeyPad); break; + case TY_ESC('<' ) : setMode (MODE_Ansi ); break; //VT100 + + case TY_ESC_CS('(', '0') : setCharset (0, '0'); break; //VT100 + case TY_ESC_CS('(', 'A') : setCharset (0, 'A'); break; //VT100 + case TY_ESC_CS('(', 'B') : setCharset (0, 'B'); break; //VT100 + + case TY_ESC_CS(')', '0') : setCharset (1, '0'); break; //VT100 + case TY_ESC_CS(')', 'A') : setCharset (1, 'A'); break; //VT100 + case TY_ESC_CS(')', 'B') : setCharset (1, 'B'); break; //VT100 + + case TY_ESC_CS('*', '0') : setCharset (2, '0'); break; //VT100 + case TY_ESC_CS('*', 'A') : setCharset (2, 'A'); break; //VT100 + case TY_ESC_CS('*', 'B') : setCharset (2, 'B'); break; //VT100 + + case TY_ESC_CS('+', '0') : setCharset (3, '0'); break; //VT100 + case TY_ESC_CS('+', 'A') : setCharset (3, 'A'); break; //VT100 + case TY_ESC_CS('+', 'B') : setCharset (3, 'B'); break; //VT100 + + case TY_ESC_CS('%', 'G') : setCodec (Utf8Codec ); break; //LINUX + case TY_ESC_CS('%', '@') : setCodec (LocaleCodec ); break; //LINUX + + case TY_ESC_DE('3' ) : /* Double height line, top half */ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true ); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , true ); + break; + case TY_ESC_DE('4' ) : /* Double height line, bottom half */ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true ); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , true ); + break; + case TY_ESC_DE('5' ) : /* Single width, single height line*/ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , false); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , false); + break; + case TY_ESC_DE('6' ) : /* Double width, single height line*/ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , false); + break; + case TY_ESC_DE('8' ) : _currentScreen->helpAlign ( ); break; + +// resize = \e[8;<row>;<col>t + case TY_CSI_PS('t', 8) : setImageSize( q /* columns */, p /* lines */ ); break; + +// change tab text color : \e[28;<color>t color: 0-16,777,215 + case TY_CSI_PS('t', 28) : emit changeTabTextColorRequest ( p ); break; + + case TY_CSI_PS('K', 0) : _currentScreen->clearToEndOfLine ( ); break; + case TY_CSI_PS('K', 1) : _currentScreen->clearToBeginOfLine ( ); break; + case TY_CSI_PS('K', 2) : _currentScreen->clearEntireLine ( ); break; + case TY_CSI_PS('J', 0) : _currentScreen->clearToEndOfScreen ( ); break; + case TY_CSI_PS('J', 1) : _currentScreen->clearToBeginOfScreen ( ); break; + case TY_CSI_PS('J', 2) : _currentScreen->clearEntireScreen ( ); break; + case TY_CSI_PS('J', 3) : clearHistory(); break; + case TY_CSI_PS('g', 0) : _currentScreen->changeTabStop (false ); break; //VT100 + case TY_CSI_PS('g', 3) : _currentScreen->clearTabStops ( ); break; //VT100 + case TY_CSI_PS('h', 4) : _currentScreen-> setMode (MODE_Insert ); break; + case TY_CSI_PS('h', 20) : setMode (MODE_NewLine ); break; + case TY_CSI_PS('i', 0) : /* IGNORE: attached printer */ break; //VT100 + case TY_CSI_PS('l', 4) : _currentScreen-> resetMode (MODE_Insert ); break; + case TY_CSI_PS('l', 20) : resetMode (MODE_NewLine ); break; + case TY_CSI_PS('s', 0) : saveCursor ( ); break; + case TY_CSI_PS('u', 0) : restoreCursor ( ); break; + + case TY_CSI_PS('m', 0) : _currentScreen->setDefaultRendition ( ); break; + case TY_CSI_PS('m', 1) : _currentScreen-> setRendition (RE_BOLD ); break; //VT100 + case TY_CSI_PS('m', 4) : _currentScreen-> setRendition (RE_UNDERLINE); break; //VT100 + case TY_CSI_PS('m', 5) : _currentScreen-> setRendition (RE_BLINK ); break; //VT100 + case TY_CSI_PS('m', 7) : _currentScreen-> setRendition (RE_REVERSE ); break; + case TY_CSI_PS('m', 10) : /* IGNORED: mapping related */ break; //LINUX + case TY_CSI_PS('m', 11) : /* IGNORED: mapping related */ break; //LINUX + case TY_CSI_PS('m', 12) : /* IGNORED: mapping related */ break; //LINUX + case TY_CSI_PS('m', 22) : _currentScreen->resetRendition (RE_BOLD ); break; + case TY_CSI_PS('m', 24) : _currentScreen->resetRendition (RE_UNDERLINE); break; + case TY_CSI_PS('m', 25) : _currentScreen->resetRendition (RE_BLINK ); break; + case TY_CSI_PS('m', 27) : _currentScreen->resetRendition (RE_REVERSE ); break; + + case TY_CSI_PS('m', 30) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 0); break; + case TY_CSI_PS('m', 31) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 1); break; + case TY_CSI_PS('m', 32) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 2); break; + case TY_CSI_PS('m', 33) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 3); break; + case TY_CSI_PS('m', 34) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 4); break; + case TY_CSI_PS('m', 35) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 5); break; + case TY_CSI_PS('m', 36) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 6); break; + case TY_CSI_PS('m', 37) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 7); break; + + case TY_CSI_PS('m', 38) : _currentScreen->setForeColor (p, q); break; + + case TY_CSI_PS('m', 39) : _currentScreen->setForeColor (COLOR_SPACE_DEFAULT, 0); break; + + case TY_CSI_PS('m', 40) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 0); break; + case TY_CSI_PS('m', 41) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 1); break; + case TY_CSI_PS('m', 42) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 2); break; + case TY_CSI_PS('m', 43) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 3); break; + case TY_CSI_PS('m', 44) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 4); break; + case TY_CSI_PS('m', 45) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 5); break; + case TY_CSI_PS('m', 46) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 6); break; + case TY_CSI_PS('m', 47) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 7); break; + + case TY_CSI_PS('m', 48) : _currentScreen->setBackColor (p, q); break; + + case TY_CSI_PS('m', 49) : _currentScreen->setBackColor (COLOR_SPACE_DEFAULT, 1); break; + + case TY_CSI_PS('m', 90) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 8); break; + case TY_CSI_PS('m', 91) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 9); break; + case TY_CSI_PS('m', 92) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 10); break; + case TY_CSI_PS('m', 93) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 11); break; + case TY_CSI_PS('m', 94) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 12); break; + case TY_CSI_PS('m', 95) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 13); break; + case TY_CSI_PS('m', 96) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 14); break; + case TY_CSI_PS('m', 97) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 15); break; + + case TY_CSI_PS('m', 100) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 8); break; + case TY_CSI_PS('m', 101) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 9); break; + case TY_CSI_PS('m', 102) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 10); break; + case TY_CSI_PS('m', 103) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 11); break; + case TY_CSI_PS('m', 104) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 12); break; + case TY_CSI_PS('m', 105) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 13); break; + case TY_CSI_PS('m', 106) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 14); break; + case TY_CSI_PS('m', 107) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 15); break; + + case TY_CSI_PS('n', 5) : reportStatus ( ); break; + case TY_CSI_PS('n', 6) : reportCursorPosition ( ); break; + case TY_CSI_PS('q', 0) : /* IGNORED: LEDs off */ break; //VT100 + case TY_CSI_PS('q', 1) : /* IGNORED: LED1 on */ break; //VT100 + case TY_CSI_PS('q', 2) : /* IGNORED: LED2 on */ break; //VT100 + case TY_CSI_PS('q', 3) : /* IGNORED: LED3 on */ break; //VT100 + case TY_CSI_PS('q', 4) : /* IGNORED: LED4 on */ break; //VT100 + case TY_CSI_PS('x', 0) : reportTerminalParms ( 2); break; //VT100 + case TY_CSI_PS('x', 1) : reportTerminalParms ( 3); break; //VT100 + + case TY_CSI_PN('@' ) : _currentScreen->insertChars (p ); break; + case TY_CSI_PN('A' ) : _currentScreen->cursorUp (p ); break; //VT100 + case TY_CSI_PN('B' ) : _currentScreen->cursorDown (p ); break; //VT100 + case TY_CSI_PN('C' ) : _currentScreen->cursorRight (p ); break; //VT100 + case TY_CSI_PN('D' ) : _currentScreen->cursorLeft (p ); break; //VT100 + case TY_CSI_PN('G' ) : _currentScreen->setCursorX (p ); break; //LINUX + case TY_CSI_PN('H' ) : _currentScreen->setCursorYX (p, q); break; //VT100 + case TY_CSI_PN('I' ) : _currentScreen->tab (p ); break; + case TY_CSI_PN('L' ) : _currentScreen->insertLines (p ); break; + case TY_CSI_PN('M' ) : _currentScreen->deleteLines (p ); break; + case TY_CSI_PN('P' ) : _currentScreen->deleteChars (p ); break; + case TY_CSI_PN('S' ) : _currentScreen->scrollUp (p ); break; + case TY_CSI_PN('T' ) : _currentScreen->scrollDown (p ); break; + case TY_CSI_PN('X' ) : _currentScreen->eraseChars (p ); break; + case TY_CSI_PN('Z' ) : _currentScreen->backtab (p ); break; + case TY_CSI_PN('c' ) : reportTerminalType ( ); break; //VT100 + case TY_CSI_PN('d' ) : _currentScreen->setCursorY (p ); break; //LINUX + case TY_CSI_PN('f' ) : _currentScreen->setCursorYX (p, q); break; //VT100 + case TY_CSI_PN('r' ) : setMargins (p, q); break; //VT100 + case TY_CSI_PN('y' ) : /* IGNORED: Confidence test */ break; //VT100 + + case TY_CSI_PR('h', 1) : setMode (MODE_AppCuKeys); break; //VT100 + case TY_CSI_PR('l', 1) : resetMode (MODE_AppCuKeys); break; //VT100 + case TY_CSI_PR('s', 1) : saveMode (MODE_AppCuKeys); break; //FIXME + case TY_CSI_PR('r', 1) : restoreMode (MODE_AppCuKeys); break; //FIXME + + case TY_CSI_PR('l', 2) : resetMode (MODE_Ansi ); break; //VT100 + + case TY_CSI_PR('h', 3) : setMode (MODE_132Columns);break; //VT100 + case TY_CSI_PR('l', 3) : resetMode (MODE_132Columns);break; //VT100 + + case TY_CSI_PR('h', 4) : /* IGNORED: soft scrolling */ break; //VT100 + case TY_CSI_PR('l', 4) : /* IGNORED: soft scrolling */ break; //VT100 + + case TY_CSI_PR('h', 5) : _currentScreen-> setMode (MODE_Screen ); break; //VT100 + case TY_CSI_PR('l', 5) : _currentScreen-> resetMode (MODE_Screen ); break; //VT100 + + case TY_CSI_PR('h', 6) : _currentScreen-> setMode (MODE_Origin ); break; //VT100 + case TY_CSI_PR('l', 6) : _currentScreen-> resetMode (MODE_Origin ); break; //VT100 + case TY_CSI_PR('s', 6) : _currentScreen-> saveMode (MODE_Origin ); break; //FIXME + case TY_CSI_PR('r', 6) : _currentScreen->restoreMode (MODE_Origin ); break; //FIXME + + case TY_CSI_PR('h', 7) : _currentScreen-> setMode (MODE_Wrap ); break; //VT100 + case TY_CSI_PR('l', 7) : _currentScreen-> resetMode (MODE_Wrap ); break; //VT100 + case TY_CSI_PR('s', 7) : _currentScreen-> saveMode (MODE_Wrap ); break; //FIXME + case TY_CSI_PR('r', 7) : _currentScreen->restoreMode (MODE_Wrap ); break; //FIXME + + case TY_CSI_PR('h', 8) : /* IGNORED: autorepeat on */ break; //VT100 + case TY_CSI_PR('l', 8) : /* IGNORED: autorepeat off */ break; //VT100 + case TY_CSI_PR('s', 8) : /* IGNORED: autorepeat on */ break; //VT100 + case TY_CSI_PR('r', 8) : /* IGNORED: autorepeat off */ break; //VT100 + + case TY_CSI_PR('h', 9) : /* IGNORED: interlace */ break; //VT100 + case TY_CSI_PR('l', 9) : /* IGNORED: interlace */ break; //VT100 + case TY_CSI_PR('s', 9) : /* IGNORED: interlace */ break; //VT100 + case TY_CSI_PR('r', 9) : /* IGNORED: interlace */ break; //VT100 + + case TY_CSI_PR('h', 12) : /* IGNORED: Cursor blink */ break; //att610 + case TY_CSI_PR('l', 12) : /* IGNORED: Cursor blink */ break; //att610 + case TY_CSI_PR('s', 12) : /* IGNORED: Cursor blink */ break; //att610 + case TY_CSI_PR('r', 12) : /* IGNORED: Cursor blink */ break; //att610 + + case TY_CSI_PR('h', 25) : setMode (MODE_Cursor ); break; //VT100 + case TY_CSI_PR('l', 25) : resetMode (MODE_Cursor ); break; //VT100 + case TY_CSI_PR('s', 25) : saveMode (MODE_Cursor ); break; //VT100 + case TY_CSI_PR('r', 25) : restoreMode (MODE_Cursor ); break; //VT100 + + case TY_CSI_PR('h', 40) : setMode(MODE_Allow132Columns ); break; // XTERM + case TY_CSI_PR('l', 40) : resetMode(MODE_Allow132Columns ); break; // XTERM + + case TY_CSI_PR('h', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + case TY_CSI_PR('l', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + case TY_CSI_PR('s', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + case TY_CSI_PR('r', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + + case TY_CSI_PR('h', 47) : setMode (MODE_AppScreen); break; //VT100 + case TY_CSI_PR('l', 47) : resetMode (MODE_AppScreen); break; //VT100 + case TY_CSI_PR('s', 47) : saveMode (MODE_AppScreen); break; //XTERM + case TY_CSI_PR('r', 47) : restoreMode (MODE_AppScreen); break; //XTERM + + case TY_CSI_PR('h', 67) : /* IGNORED: DECBKM */ break; //XTERM + case TY_CSI_PR('l', 67) : /* IGNORED: DECBKM */ break; //XTERM + case TY_CSI_PR('s', 67) : /* IGNORED: DECBKM */ break; //XTERM + case TY_CSI_PR('r', 67) : /* IGNORED: DECBKM */ break; //XTERM + + // XTerm defines the following modes: + // SET_VT200_MOUSE 1000 + // SET_VT200_HIGHLIGHT_MOUSE 1001 + // SET_BTN_EVENT_MOUSE 1002 + // SET_ANY_EVENT_MOUSE 1003 + // + + //Note about mouse modes: + //There are four mouse modes which xterm-compatible terminals can support - 1000,1001,1002,1003 + //Konsole currently supports mode 1000 (basic mouse press and release) and mode 1002 (dragging the mouse). + //TODO: Implementation of mouse modes 1001 (something called hilight tracking) and + //1003 (a slight variation on dragging the mouse) + // + + case TY_CSI_PR('h', 1000) : setMode (MODE_Mouse1000); break; //XTERM + case TY_CSI_PR('l', 1000) : resetMode (MODE_Mouse1000); break; //XTERM + case TY_CSI_PR('s', 1000) : saveMode (MODE_Mouse1000); break; //XTERM + case TY_CSI_PR('r', 1000) : restoreMode (MODE_Mouse1000); break; //XTERM + + case TY_CSI_PR('h', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM + case TY_CSI_PR('l', 1001) : resetMode (MODE_Mouse1001); break; //XTERM + case TY_CSI_PR('s', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM + case TY_CSI_PR('r', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM + + case TY_CSI_PR('h', 1002) : setMode (MODE_Mouse1002); break; //XTERM + case TY_CSI_PR('l', 1002) : resetMode (MODE_Mouse1002); break; //XTERM + case TY_CSI_PR('s', 1002) : saveMode (MODE_Mouse1002); break; //XTERM + case TY_CSI_PR('r', 1002) : restoreMode (MODE_Mouse1002); break; //XTERM + + case TY_CSI_PR('h', 1003) : setMode (MODE_Mouse1003); break; //XTERM + case TY_CSI_PR('l', 1003) : resetMode (MODE_Mouse1003); break; //XTERM + case TY_CSI_PR('s', 1003) : saveMode (MODE_Mouse1003); break; //XTERM + case TY_CSI_PR('r', 1003) : restoreMode (MODE_Mouse1003); break; //XTERM + + case TY_CSI_PR('h', 1034) : /* IGNORED: 8bitinput activation */ break; //XTERM + + case TY_CSI_PR('h', 1047) : setMode (MODE_AppScreen); break; //XTERM + case TY_CSI_PR('l', 1047) : _screen[1]->clearEntireScreen(); resetMode(MODE_AppScreen); break; //XTERM + case TY_CSI_PR('s', 1047) : saveMode (MODE_AppScreen); break; //XTERM + case TY_CSI_PR('r', 1047) : restoreMode (MODE_AppScreen); break; //XTERM + + //FIXME: Unitoken: save translations + case TY_CSI_PR('h', 1048) : saveCursor ( ); break; //XTERM + case TY_CSI_PR('l', 1048) : restoreCursor ( ); break; //XTERM + case TY_CSI_PR('s', 1048) : saveCursor ( ); break; //XTERM + case TY_CSI_PR('r', 1048) : restoreCursor ( ); break; //XTERM + + //FIXME: every once new sequences like this pop up in xterm. + // Here's a guess of what they could mean. + case TY_CSI_PR('h', 1049) : saveCursor(); _screen[1]->clearEntireScreen(); setMode(MODE_AppScreen); break; //XTERM + case TY_CSI_PR('l', 1049) : resetMode(MODE_AppScreen); restoreCursor(); break; //XTERM + + //FIXME: weird DEC reset sequence + case TY_CSI_PE('p' ) : /* IGNORED: reset ( ) */ break; + + //FIXME: when changing between vt52 and ansi mode evtl do some resetting. + case TY_VT52('A' ) : _currentScreen->cursorUp ( 1); break; //VT52 + case TY_VT52('B' ) : _currentScreen->cursorDown ( 1); break; //VT52 + case TY_VT52('C' ) : _currentScreen->cursorRight ( 1); break; //VT52 + case TY_VT52('D' ) : _currentScreen->cursorLeft ( 1); break; //VT52 + + case TY_VT52('F' ) : setAndUseCharset (0, '0'); break; //VT52 + case TY_VT52('G' ) : setAndUseCharset (0, 'B'); break; //VT52 + + case TY_VT52('H' ) : _currentScreen->setCursorYX (1,1 ); break; //VT52 + case TY_VT52('I' ) : _currentScreen->reverseIndex ( ); break; //VT52 + case TY_VT52('J' ) : _currentScreen->clearToEndOfScreen ( ); break; //VT52 + case TY_VT52('K' ) : _currentScreen->clearToEndOfLine ( ); break; //VT52 + case TY_VT52('Y' ) : _currentScreen->setCursorYX (p-31,q-31 ); break; //VT52 + case TY_VT52('Z' ) : reportTerminalType ( ); break; //VT52 + case TY_VT52('<' ) : setMode (MODE_Ansi ); break; //VT52 + case TY_VT52('=' ) : setMode (MODE_AppKeyPad); break; //VT52 + case TY_VT52('>' ) : resetMode (MODE_AppKeyPad); break; //VT52 + + case TY_CSI_PG('c' ) : reportSecondaryAttributes( ); break; //VT100 + + default: + reportDecodingError(); + break; + }; +} + +void Vt102Emulation::clearScreenAndSetColumns(int columnCount) +{ + setImageSize(_currentScreen->getLines(),columnCount); + clearEntireScreen(); + setDefaultMargins(); + _currentScreen->setCursorYX(0,0); +} + +void Vt102Emulation::sendString(const char* s , int length) +{ + if ( length >= 0 ) + emit sendData(s,length); + else + emit sendData(s,strlen(s)); +} + +void Vt102Emulation::reportCursorPosition() +{ + char tmp[20]; + sprintf(tmp,"\033[%d;%dR",_currentScreen->getCursorY()+1,_currentScreen->getCursorX()+1); + sendString(tmp); +} + +void Vt102Emulation::reportTerminalType() +{ + // Primary device attribute response (Request was: ^[[0c or ^[[c (from TT321 Users Guide)) + // VT220: ^[[?63;1;2;3;6;7;8c (list deps on emul. capabilities) + // VT100: ^[[?1;2c + // VT101: ^[[?1;0c + // VT102: ^[[?6v + if (getMode(MODE_Ansi)) + sendString("\033[?1;2c"); // I'm a VT100 + else + sendString("\033/Z"); // I'm a VT52 +} + +void Vt102Emulation::reportSecondaryAttributes() +{ + // Seconday device attribute response (Request was: ^[[>0c or ^[[>c) + if (getMode(MODE_Ansi)) + sendString("\033[>0;115;0c"); // Why 115? ;) + else + sendString("\033/Z"); // FIXME I don't think VT52 knows about it but kept for + // konsoles backward compatibility. +} + +void Vt102Emulation::reportTerminalParms(int p) +// DECREPTPARM +{ + char tmp[100]; + sprintf(tmp,"\033[%d;1;1;112;112;1;0x",p); // not really true. + sendString(tmp); +} + +void Vt102Emulation::reportStatus() +{ + sendString("\033[0n"); //VT100. Device status report. 0 = Ready. +} + +void Vt102Emulation::reportAnswerBack() +{ + // FIXME - Test this with VTTEST + // This is really obsolete VT100 stuff. + const char* ANSWER_BACK = ""; + sendString(ANSWER_BACK); +} + +/*! + `cx',`cy' are 1-based. + `eventType' indicates the button pressed (0-2) + or a general mouse release (3). + + eventType represents the kind of mouse action that occurred: + 0 = Mouse button press or release + 1 = Mouse drag +*/ + +void Vt102Emulation::sendMouseEvent( int cb, int cx, int cy , int eventType ) +{ + if (cx < 1 || cy < 1) + return; + + // normal buttons are passed as 0x20 + button, + // mouse wheel (buttons 4,5) as 0x5c + button + if (cb >= 4) + cb += 0x3c; + + //Mouse motion handling + if ((getMode(MODE_Mouse1002) || getMode(MODE_Mouse1003)) && eventType == 1) + cb += 0x20; //add 32 to signify motion event + + char command[20]; + sprintf(command,"\033[M%c%c%c",cb+0x20,cx+0x20,cy+0x20); + sendString(command); +} + +void Vt102Emulation::sendText( const QString& text ) +{ + if (!text.isEmpty()) + { + QKeyEvent event(QEvent::KeyPress, + 0, + Qt::NoModifier, + text); + + sendKeyEvent(&event); // expose as a big fat keypress event + } +} +void Vt102Emulation::sendKeyEvent( QKeyEvent* event ) +{ + Qt::KeyboardModifiers modifiers = event->modifiers(); + KeyboardTranslator::States states = KeyboardTranslator::NoState; + + // get current states + if (getMode(MODE_NewLine) ) states |= KeyboardTranslator::NewLineState; + if (getMode(MODE_Ansi) ) states |= KeyboardTranslator::AnsiState; + if (getMode(MODE_AppCuKeys)) states |= KeyboardTranslator::CursorKeysState; + if (getMode(MODE_AppScreen)) states |= KeyboardTranslator::AlternateScreenState; + if (getMode(MODE_AppKeyPad) && (modifiers & Qt::KeypadModifier)) + states |= KeyboardTranslator::ApplicationKeypadState; + + // check flow control state + if (modifiers & Qt::ControlModifier) + { + if (event->key() == Qt::Key_S) + emit flowControlKeyPressed(true); + else if (event->key() == Qt::Key_Q) + emit flowControlKeyPressed(false); + } + + // lookup key binding + if ( _keyTranslator ) + { + KeyboardTranslator::Entry entry = _keyTranslator->findEntry( + event->key() , + modifiers, + states ); + + // send result to terminal + QByteArray textToSend; + + // special handling for the Alt (aka. Meta) modifier. pressing + // Alt+[Character] results in Esc+[Character] being sent + // (unless there is an entry defined for this particular combination + // in the keyboard modifier) + bool wantsAltModifier = entry.modifiers() & entry.modifierMask() & Qt::AltModifier; + bool wantsAnyModifier = entry.state() & + entry.stateMask() & KeyboardTranslator::AnyModifierState; + + if ( modifiers & Qt::AltModifier && !(wantsAltModifier || wantsAnyModifier) + && !event->text().isEmpty() ) + { + textToSend.prepend("\033"); + } + + if ( entry.command() != KeyboardTranslator::NoCommand ) + { + if (entry.command() & KeyboardTranslator::EraseCommand) + textToSend += eraseChar(); + + // TODO command handling + } + else if ( !entry.text().isEmpty() ) + { + textToSend += _codec->fromUnicode(entry.text(true,modifiers)); + } + else + textToSend += _codec->fromUnicode(event->text()); + + sendData( textToSend.constData() , textToSend.length() ); + } + else + { + // print an error message to the terminal if no key translator has been + // set + QString translatorError = QString("No keyboard translator available. " + "The information needed to convert key presses " + "into characters to send to the terminal " + "is missing."); + reset(); + receiveData( translatorError.toAscii().constData() , translatorError.count() ); + } +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* VT100 Charsets */ +/* */ +/* ------------------------------------------------------------------------- */ + +// Character Set Conversion ------------------------------------------------ -- + +/* + The processing contains a VT100 specific code translation layer. + It's still in use and mainly responsible for the line drawing graphics. + + These and some other glyphs are assigned to codes (0x5f-0xfe) + normally occupied by the latin letters. Since this codes also + appear within control sequences, the extra code conversion + does not permute with the tokenizer and is placed behind it + in the pipeline. It only applies to tokens, which represent + plain characters. + + This conversion it eventually continued in TerminalDisplay.C, since + it might involve VT100 enhanced fonts, which have these + particular glyphs allocated in (0x00-0x1f) in their code page. +*/ + +#define CHARSET _charset[_currentScreen==_screen[1]] + +// Apply current character map. + +unsigned short Vt102Emulation::applyCharset(unsigned short c) +{ + if (CHARSET.graphic && 0x5f <= c && c <= 0x7e) return vt100_graphics[c-0x5f]; + if (CHARSET.pound && c == '#' ) return 0xa3; //This mode is obsolete + return c; +} + +/* + "Charset" related part of the emulation state. + This configures the VT100 charset filter. + + While most operation work on the current _screen, + the following two are different. +*/ + +void Vt102Emulation::resetCharset(int scrno) +{ + _charset[scrno].cu_cs = 0; + strncpy(_charset[scrno].charset,"BBBB",4); + _charset[scrno].sa_graphic = false; + _charset[scrno].sa_pound = false; + _charset[scrno].graphic = false; + _charset[scrno].pound = false; +} + +void Vt102Emulation::setCharset(int n, int cs) // on both screens. +{ + _charset[0].charset[n&3] = cs; useCharset(_charset[0].cu_cs); + _charset[1].charset[n&3] = cs; useCharset(_charset[1].cu_cs); +} + +void Vt102Emulation::setAndUseCharset(int n, int cs) +{ + CHARSET.charset[n&3] = cs; + useCharset(n&3); +} + +void Vt102Emulation::useCharset(int n) +{ + CHARSET.cu_cs = n&3; + CHARSET.graphic = (CHARSET.charset[n&3] == '0'); + CHARSET.pound = (CHARSET.charset[n&3] == 'A'); //This mode is obsolete +} + +void Vt102Emulation::setDefaultMargins() +{ + _screen[0]->setDefaultMargins(); + _screen[1]->setDefaultMargins(); +} + +void Vt102Emulation::setMargins(int t, int b) +{ + _screen[0]->setMargins(t, b); + _screen[1]->setMargins(t, b); +} + +void Vt102Emulation::saveCursor() +{ + CHARSET.sa_graphic = CHARSET.graphic; + CHARSET.sa_pound = CHARSET.pound; //This mode is obsolete + // we are not clear about these + //sa_charset = charsets[cScreen->_charset]; + //sa_charset_num = cScreen->_charset; + _currentScreen->saveCursor(); +} + +void Vt102Emulation::restoreCursor() +{ + CHARSET.graphic = CHARSET.sa_graphic; + CHARSET.pound = CHARSET.sa_pound; //This mode is obsolete + _currentScreen->restoreCursor(); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Mode Operations */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* + Some of the emulations state is either added to the state of the screens. + + This causes some scoping problems, since different emulations choose to + located the mode either to the current _screen or to both. + + For strange reasons, the extend of the rendition attributes ranges over + all screens and not over the actual _screen. + + We decided on the precise precise extend, somehow. +*/ + +// "Mode" related part of the state. These are all booleans. + +void Vt102Emulation::resetModes() +{ + // MODE_Allow132Columns is not reset here + // to match Xterm's behaviour (see Xterm's VTReset() function) + + resetMode(MODE_132Columns); saveMode(MODE_132Columns); + resetMode(MODE_Mouse1000); saveMode(MODE_Mouse1000); + resetMode(MODE_Mouse1001); saveMode(MODE_Mouse1001); + resetMode(MODE_Mouse1002); saveMode(MODE_Mouse1002); + resetMode(MODE_Mouse1003); saveMode(MODE_Mouse1003); + + resetMode(MODE_AppScreen); saveMode(MODE_AppScreen); + resetMode(MODE_AppCuKeys); saveMode(MODE_AppCuKeys); + resetMode(MODE_AppKeyPad); saveMode(MODE_AppKeyPad); + resetMode(MODE_NewLine); + setMode(MODE_Ansi); +} + +void Vt102Emulation::setMode(int m) +{ + _currentModes.mode[m] = true; + switch (m) + { + case MODE_132Columns: + if (getMode(MODE_Allow132Columns)) + clearScreenAndSetColumns(132); + else + _currentModes.mode[m] = false; + break; + case MODE_Mouse1000: + case MODE_Mouse1001: + case MODE_Mouse1002: + case MODE_Mouse1003: + emit programUsesMouseChanged(false); + break; + + case MODE_AppScreen : _screen[1]->clearSelection(); + setScreen(1); + break; + } + if (m < MODES_SCREEN || m == MODE_NewLine) + { + _screen[0]->setMode(m); + _screen[1]->setMode(m); + } +} + +void Vt102Emulation::resetMode(int m) +{ + _currentModes.mode[m] = false; + switch (m) + { + case MODE_132Columns: + if (getMode(MODE_Allow132Columns)) + clearScreenAndSetColumns(80); + break; + case MODE_Mouse1000 : + case MODE_Mouse1001 : + case MODE_Mouse1002 : + case MODE_Mouse1003 : + emit programUsesMouseChanged(true); + break; + + case MODE_AppScreen : + _screen[0]->clearSelection(); + setScreen(0); + break; + } + if (m < MODES_SCREEN || m == MODE_NewLine) + { + _screen[0]->resetMode(m); + _screen[1]->resetMode(m); + } +} + +void Vt102Emulation::saveMode(int m) +{ + _savedModes.mode[m] = _currentModes.mode[m]; +} + +void Vt102Emulation::restoreMode(int m) +{ + if (_savedModes.mode[m]) + setMode(m); + else + resetMode(m); +} + +bool Vt102Emulation::getMode(int m) +{ + return _currentModes.mode[m]; +} + +char Vt102Emulation::eraseChar() const +{ + KeyboardTranslator::Entry entry = _keyTranslator->findEntry( + Qt::Key_Backspace, + 0, + 0); + if ( entry.text().count() > 0 ) + return entry.text()[0]; + else + return '\b'; +} + +// print contents of the scan buffer +static void hexdump(int* s, int len) +{ int i; + for (i = 0; i < len; i++) + { + if (s[i] == '\\') + printf("\\\\"); + else + if ((s[i]) > 32 && s[i] < 127) + printf("%c",s[i]); + else + printf("\\%04x(hex)",s[i]); + } +} + +void Vt102Emulation::reportDecodingError() +{ + if (tokenBufferPos == 0 || ( tokenBufferPos == 1 && (tokenBuffer[0] & 0xff) >= 32) ) + return; + printf("Undecodable sequence: "); + hexdump(tokenBuffer,tokenBufferPos); + printf("\n"); +} +
new file mode 100644 --- /dev/null +++ b/gui//src/Vt102Emulation.h @@ -0,0 +1,186 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef VT102EMULATION_H +#define VT102EMULATION_H + +// Standard Library +#include <stdio.h> + +// Qt +#include <QtGui/QKeyEvent> +#include <QtCore/QHash> +#include <QtCore/QTimer> + +// Konsole +#include "Emulation.h" +#include "Screen.h" + +#define MODE_AppScreen (MODES_SCREEN+0) // Mode #1 +#define MODE_AppCuKeys (MODES_SCREEN+1) // Application cursor keys (DECCKM) +#define MODE_AppKeyPad (MODES_SCREEN+2) // +#define MODE_Mouse1000 (MODES_SCREEN+3) // Send mouse X,Y position on press and release +#define MODE_Mouse1001 (MODES_SCREEN+4) // Use Hilight mouse tracking +#define MODE_Mouse1002 (MODES_SCREEN+5) // Use cell motion mouse tracking +#define MODE_Mouse1003 (MODES_SCREEN+6) // Use all motion mouse tracking +#define MODE_Ansi (MODES_SCREEN+7) // Use US Ascii for character sets G0-G3 (DECANM) +#define MODE_132Columns (MODES_SCREEN+8) // 80 <-> 132 column mode switch (DECCOLM) +#define MODE_Allow132Columns (MODES_SCREEN+9) // Allow DECCOLM mode +#define MODE_total (MODES_SCREEN+10) + +struct CharCodes +{ + // coding info + char charset[4]; // + int cu_cs; // actual charset. + bool graphic; // Some VT100 tricks + bool pound ; // Some VT100 tricks + bool sa_graphic; // saved graphic + bool sa_pound; // saved pound +}; + +/** + * Provides an xterm compatible terminal emulation based on the DEC VT102 terminal. + * A full description of this terminal can be found at http://vt100.net/docs/vt102-ug/ + * + * In addition, various additional xterm escape sequences are supported to provide + * features such as mouse input handling. + * See http://rtfm.etla.org/xterm/ctlseq.html for a description of xterm's escape + * sequences. + * + */ +class Vt102Emulation : public Emulation +{ +Q_OBJECT + +public: + /** Constructs a new emulation */ + Vt102Emulation(); + ~Vt102Emulation(); + + // reimplemented from Emulation + virtual void clearEntireScreen(); + virtual void reset(); + virtual char eraseChar() const; + +public slots: + // reimplemented from Emulation + virtual void sendString(const char*,int length = -1); + virtual void sendText(const QString& text); + virtual void sendKeyEvent(QKeyEvent*); + virtual void sendMouseEvent(int buttons, int column, int line, int eventType); + +protected: + // reimplemented from Emulation + virtual void setMode(int mode); + virtual void resetMode(int mode); + virtual void receiveChar(int cc); + +private slots: + //causes changeTitle() to be emitted for each (int,QString) pair in pendingTitleUpdates + //used to buffer multiple title updates + void updateTitle(); + +private: + unsigned short applyCharset(unsigned short c); + void setCharset(int n, int cs); + void useCharset(int n); + void setAndUseCharset(int n, int cs); + void saveCursor(); + void restoreCursor(); + void resetCharset(int scrno); + + void setMargins(int top, int bottom); + //set margins for all screens back to their defaults + void setDefaultMargins(); + + // returns true if 'mode' is set or false otherwise + bool getMode (int mode); + // saves the current boolean value of 'mode' + void saveMode (int mode); + // restores the boolean value of 'mode' + void restoreMode(int mode); + // resets all modes + // (except MODE_Allow132Columns) + void resetModes(); + + void resetTokenizer(); + #define MAX_TOKEN_LENGTH 80 + void addToCurrentToken(int cc); + int tokenBuffer[MAX_TOKEN_LENGTH]; //FIXME: overflow? + int tokenBufferPos; +#define MAXARGS 15 + void addDigit(int dig); + void addArgument(); + int argv[MAXARGS]; + int argc; + void initTokenizer(); + + // Set of flags for each of the ASCII characters which indicates + // what category they fall into (printable character, control, digit etc.) + // for the purposes of decoding terminal output + int charClass[256]; + + void reportDecodingError(); + + void processToken(int code, int p, int q); + void processWindowAttributeChange(); + + void reportTerminalType(); + void reportSecondaryAttributes(); + void reportStatus(); + void reportAnswerBack(); + void reportCursorPosition(); + void reportTerminalParms(int p); + + void onScrollLock(); + void scrollLock(const bool lock); + + // clears the screen and resizes it to the specified + // number of columns + void clearScreenAndSetColumns(int columnCount); + + CharCodes _charset[2]; + + class TerminalState + { + public: + // Initializes all modes to false + TerminalState() + { memset(&mode,false,MODE_total * sizeof(bool)); } + + bool mode[MODE_total]; + }; + + TerminalState _currentModes; + TerminalState _savedModes; + + //hash table and timer for buffering calls to the session instance + //to update the name of the session + //or window title. + //these calls occur when certain escape sequences are seen in the + //output from the terminal + QHash<int,QString> _pendingTitleUpdates; + QTimer* _titleUpdateTimer; +}; + +#endif // VT102EMULATION_H
new file mode 100644 --- /dev/null +++ b/gui//src/konsole_export.h @@ -0,0 +1,67 @@ +/* + This file is part of the KDE project + Copyright (C) 2009 Patrick Spendrin <ps_ml@gmx.de> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef KONSOLE_EXPORT_H +#define KONSOLE_EXPORT_H + +/* needed for KDE_EXPORT macros */ +//#include <kdemacros.h> +#include <QtCore/qglobal.h> +#define KDE_EXPORT +#define KDE_IMPORT + +#ifndef KONSOLEPRIVATE_EXPORT +# if defined(MAKE_KONSOLEPRIVATE_LIB) + /* We are building this library */ +# define KONSOLEPRIVATE_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define KONSOLEPRIVATE_EXPORT KDE_IMPORT +# endif +#endif + +#include <iostream> +//#define kWarning(x) std::cout + +#include <stdio.h> + +//#define i18n +inline QString i18n(char *buff,...) +{ + char msg[2048]; + va_list arglist; + + va_start(arglist,buff); + + snprintf(msg,2048,buff, arglist); + + va_end(arglist); + + return QString(msg); +} + +#define i18nc + + +//#define KDE_fseek ::fseek +//#define KDE_lseek ::lseek + + +#endif
new file mode 100644 --- /dev/null +++ b/gui//src/konsole_wcwidth.cpp @@ -0,0 +1,216 @@ +/* $XFree86: xc/programs/xterm/wcwidth.character,v 1.3 2001/07/29 22:08:16 tsi Exp $ */ +/* + * This is an implementation of wcwidth() and wcswidth() as defined in + * "The Single UNIX Specification, Version 2, The Open Group, 1997" + * <http://www.UNIX-systems.org/online.html> + * + * Markus Kuhn -- 2001-01-12 -- public domain + */ + +#include "konsole_wcwidth.h" + +struct interval { + unsigned short first; + unsigned short last; +}; + +/* auxiliary function for binary search in interval table */ +static int bisearch(quint16 ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * FullWidth (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that quint16 characters are encoded + * in ISO 10646. + */ + +int konsole_wcwidth(quint16 ucs) +{ + /* sorted list of non-overlapping intervals of non-spacing characters */ + static const struct interval combining[] = { + { 0x0300, 0x034E }, { 0x0360, 0x0362 }, { 0x0483, 0x0486 }, + { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 }, + { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C4 }, { 0x064B, 0x0655 }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x0901, 0x0902 }, { 0x093C, 0x093C }, + { 0x0941, 0x0948 }, { 0x094D, 0x094D }, { 0x0951, 0x0954 }, + { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, + { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, + { 0x0A02, 0x0A02 }, { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, + { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, + { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, + { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0B01, 0x0B01 }, + { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, + { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, + { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, + { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, { 0x0DCA, 0x0DCA }, + { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 }, + { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, + { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD }, + { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, + { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, + { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, { 0x0F99, 0x0FBC }, + { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1032 }, + { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, { 0x1058, 0x1059 }, + { 0x1160, 0x11FF }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 }, + { 0x17C9, 0x17D3 }, { 0x180B, 0x180E }, { 0x18A9, 0x18A9 }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x206A, 0x206F }, + { 0x20D0, 0x20E3 }, { 0x302A, 0x302F }, { 0x3099, 0x309A }, + { 0xFB1E, 0xFB1E }, { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, + { 0xFFF9, 0xFFFB } + }; + + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + (ucs >= 0x2e80 && ucs <= 0xa4cf && (ucs & ~0x0011) != 0x300a && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff5f) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) /* do not compare UINT16 with 0x20000 || + (ucs >= 0x20000 && ucs <= 0x2ffff) */)); +} + +#if 0 +/* + * The following function is the same as konsole_wcwidth(), except that + * spacing characters in the East Asian Ambiguous (A) category as + * defined in Unicode Technical Report #11 have a column width of 2. + * This experimental variant might be useful for users of CJK legacy + * encodings who want to migrate to UCS. It is not otherwise + * recommended for general use. + */ +int konsole_wcwidth_cjk(quint16 ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AD, 0x00AD }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014A }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, { 0x02CD, 0x02CD }, + { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, { 0x02DD, 0x02DD }, + { 0x0391, 0x03A1 }, { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, + { 0x03C3, 0x03C9 }, { 0x0401, 0x0401 }, { 0x0410, 0x044F }, + { 0x0451, 0x0451 }, { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, + { 0x2018, 0x2019 }, { 0x201C, 0x201D }, { 0x2020, 0x2021 }, + { 0x2025, 0x2027 }, { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, + { 0x2035, 0x2035 }, { 0x203B, 0x203B }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2121, 0x2122 }, { 0x2126, 0x2126 }, + { 0x212B, 0x212B }, { 0x2154, 0x2155 }, { 0x215B, 0x215B }, + { 0x215E, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21D2, 0x21D2 }, { 0x21D4, 0x21D4 }, + { 0x2200, 0x2200 }, { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, + { 0x220B, 0x220B }, { 0x220F, 0x220F }, { 0x2211, 0x2211 }, + { 0x2215, 0x2215 }, { 0x221A, 0x221A }, { 0x221D, 0x2220 }, + { 0x2223, 0x2223 }, { 0x2225, 0x2225 }, { 0x2227, 0x222C }, + { 0x222E, 0x222E }, { 0x2234, 0x2237 }, { 0x223C, 0x223D }, + { 0x2248, 0x2248 }, { 0x224C, 0x224C }, { 0x2252, 0x2252 }, + { 0x2260, 0x2261 }, { 0x2264, 0x2267 }, { 0x226A, 0x226B }, + { 0x226E, 0x226F }, { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, + { 0x2295, 0x2295 }, { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, + { 0x22BF, 0x22BF }, { 0x2312, 0x2312 }, { 0x2460, 0x24BF }, + { 0x24D0, 0x24E9 }, { 0x2500, 0x254B }, { 0x2550, 0x2574 }, + { 0x2580, 0x258F }, { 0x2592, 0x2595 }, { 0x25A0, 0x25A1 }, + { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, { 0x25B6, 0x25B7 }, + { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, { 0x25C6, 0x25C8 }, + { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, { 0x25E2, 0x25E5 }, + { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, { 0x2609, 0x2609 }, + { 0x260E, 0x260F }, { 0x261C, 0x261C }, { 0x261E, 0x261E }, + { 0x2640, 0x2640 }, { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, + { 0x2663, 0x2665 }, { 0x2667, 0x266A }, { 0x266C, 0x266D }, + { 0x266F, 0x266F }, { 0x300A, 0x300B }, { 0x301A, 0x301B }, + { 0xE000, 0xF8FF }, { 0xFFFD, 0xFFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return konsole_wcwidth(ucs); +} +#endif + +// single byte char: +1, multi byte char: +2 +int string_width( const QString &txt ) +{ + int w = 0; + for ( int i = 0; i < txt.length(); ++i ) + w += konsole_wcwidth( txt[ i ].unicode() ); + return w; +}
new file mode 100644 --- /dev/null +++ b/gui//src/konsole_wcwidth.h @@ -0,0 +1,24 @@ +/* $XFree86: xc/programs/xterm/wcwidth.h,v 1.2 2001/06/18 19:09:27 dickey Exp $ */ + +/* Markus Kuhn -- 2001-01-12 -- public domain */ +/* Adaptions for KDE by Waldo Bastian <bastian@kde.org> */ +/* + Rewritten for QT4 by e_k <e_k at users.sourceforge.net> +*/ + + +#ifndef _KONSOLE_WCWIDTH_H_ +#define _KONSOLE_WCWIDTH_H_ + +// Qt +#include <QtCore/QBool> +#include <QtCore/QString> + +int konsole_wcwidth(quint16 ucs); +#if 0 +int konsole_wcwidth_cjk(Q_UINT16 ucs); +#endif + +int string_width( const QString &txt ); + +#endif
new file mode 100644 --- /dev/null +++ b/gui//src/kprocess.cpp @@ -0,0 +1,345 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kprocess_p.h" + +#include <qfile.h> + +#ifdef Q_OS_WIN +# include <windows.h> +#else +# include <unistd.h> +# include <errno.h> +#endif + +#ifndef Q_OS_WIN +# define STD_OUTPUT_HANDLE 1 +# define STD_ERROR_HANDLE 2 +#endif + +#ifdef _WIN32_WCE +#include <stdio.h> +#endif + +void KProcessPrivate::writeAll(const QByteArray &buf, int fd) +{ +#ifdef Q_OS_WIN +#ifndef _WIN32_WCE + HANDLE h = GetStdHandle(fd); + if (h) { + DWORD wr; + WriteFile(h, buf.data(), buf.size(), &wr, 0); + } +#else + fwrite(buf.data(), 1, buf.size(), (FILE*)fd); +#endif +#else + int off = 0; + do { + int ret = ::write(fd, buf.data() + off, buf.size() - off); + if (ret < 0) { + if (errno != EINTR) + return; + } else { + off += ret; + } + } while (off < buf.size()); +#endif +} + +void KProcessPrivate::forwardStd(KProcess::ProcessChannel good, int fd) +{ + Q_Q(KProcess); + + QProcess::ProcessChannel oc = q->readChannel(); + q->setReadChannel(good); + writeAll(q->readAll(), fd); + q->setReadChannel(oc); +} + +void KProcessPrivate::_k_forwardStdout() +{ +#ifndef _WIN32_WCE + forwardStd(KProcess::StandardOutput, STD_OUTPUT_HANDLE); +#else + forwardStd(KProcess::StandardOutput, (int)stdout); +#endif +} + +void KProcessPrivate::_k_forwardStderr() +{ +#ifndef _WIN32_WCE + forwardStd(KProcess::StandardError, STD_ERROR_HANDLE); +#else + forwardStd(KProcess::StandardError, (int)stderr); +#endif +} + +///////////////////////////// +// public member functions // +///////////////////////////// + +KProcess::KProcess(QObject *parent) : + QProcess(parent), + d_ptr(new KProcessPrivate) +{ + d_ptr->q_ptr = this; + setOutputChannelMode(ForwardedChannels); +} + +KProcess::KProcess(KProcessPrivate *d, QObject *parent) : + QProcess(parent), + d_ptr(d) +{ + d_ptr->q_ptr = this; + setOutputChannelMode(ForwardedChannels); +} + +KProcess::~KProcess() +{ + delete d_ptr; +} + +void KProcess::setOutputChannelMode(OutputChannelMode mode) +{ + Q_D(KProcess); + + d->outputChannelMode = mode; + disconnect(this, SIGNAL(readyReadStandardOutput())); + disconnect(this, SIGNAL(readyReadStandardError())); + switch (mode) { + case OnlyStdoutChannel: + connect(this, SIGNAL(readyReadStandardError()), SLOT(_k_forwardStderr())); + break; + case OnlyStderrChannel: + connect(this, SIGNAL(readyReadStandardOutput()), SLOT(_k_forwardStdout())); + break; + default: + QProcess::setProcessChannelMode((ProcessChannelMode)mode); + return; + } + QProcess::setProcessChannelMode(QProcess::SeparateChannels); +} + +KProcess::OutputChannelMode KProcess::outputChannelMode() const +{ + Q_D(const KProcess); + + return d->outputChannelMode; +} + +void KProcess::setNextOpenMode(QIODevice::OpenMode mode) +{ + Q_D(KProcess); + + d->openMode = mode; +} + +#define DUMMYENV "_KPROCESS_DUMMY_=" + +void KProcess::clearEnvironment() +{ + setEnvironment(QStringList() << QString::fromLatin1(DUMMYENV)); +} + +void KProcess::setEnv(const QString &name, const QString &value, bool overwrite) +{ + QStringList env = environment(); + if (env.isEmpty()) { + env = systemEnvironment(); + env.removeAll(QString::fromLatin1(DUMMYENV)); + } + QString fname(name); + fname.append(QLatin1Char('=')); + for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) + if ((*it).startsWith(fname)) { + if (overwrite) { + *it = fname.append(value); + setEnvironment(env); + } + return; + } + env.append(fname.append(value)); + setEnvironment(env); +} + +void KProcess::unsetEnv(const QString &name) +{ + QStringList env = environment(); + if (env.isEmpty()) { + env = systemEnvironment(); + env.removeAll(QString::fromLatin1(DUMMYENV)); + } + QString fname(name); + fname.append(QLatin1Char('=')); + for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) + if ((*it).startsWith(fname)) { + env.erase(it); + if (env.isEmpty()) + env.append(QString::fromLatin1(DUMMYENV)); + setEnvironment(env); + return; + } +} + +void KProcess::setProgram(const QString &exe, const QStringList &args) +{ + Q_D(KProcess); + + d->prog = exe; + d->args = args; +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +void KProcess::setProgram(const QStringList &argv) +{ + Q_D(KProcess); + + Q_ASSERT( !argv.isEmpty() ); + d->args = argv; + d->prog = d->args.takeFirst(); +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +KProcess &KProcess::operator<<(const QString &arg) +{ + Q_D(KProcess); + + if (d->prog.isEmpty()) + d->prog = arg; + else + d->args << arg; + return *this; +} + +KProcess &KProcess::operator<<(const QStringList &args) +{ + Q_D(KProcess); + + if (d->prog.isEmpty()) + setProgram(args); + else + d->args << args; + return *this; +} + +void KProcess::clearProgram() +{ + Q_D(KProcess); + + d->prog.clear(); + d->args.clear(); +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +void KProcess::setShellCommand(const QString &cmd) +{ + Q_D(KProcess); + d->args.clear(); + d->prog = QString::fromLatin1("/bin/sh"); + d->args << QString::fromLatin1("-c") << cmd; +} + +QStringList KProcess::program() const +{ + Q_D(const KProcess); + + QStringList argv = d->args; + argv.prepend(d->prog); + return argv; +} + +void KProcess::start() +{ + Q_D(KProcess); + + QProcess::start(d->prog, d->args, d->openMode); +} + +int KProcess::execute(int msecs) +{ + start(); + if (!waitForFinished(msecs)) { + kill(); + waitForFinished(-1); + return -2; + } + return (exitStatus() == QProcess::NormalExit) ? exitCode() : -1; +} + +// static +int KProcess::execute(const QString &exe, const QStringList &args, int msecs) +{ + KProcess p; + p.setProgram(exe, args); + return p.execute(msecs); +} + +// static +int KProcess::execute(const QStringList &argv, int msecs) +{ + KProcess p; + p.setProgram(argv); + return p.execute(msecs); +} + +int KProcess::startDetached() +{ + Q_D(KProcess); + + qint64 pid; + if (!QProcess::startDetached(d->prog, d->args, workingDirectory(), &pid)) + return 0; + return (int) pid; +} + +// static +int KProcess::startDetached(const QString &exe, const QStringList &args) +{ + qint64 pid; + if (!QProcess::startDetached(exe, args, QString(), &pid)) + return 0; + return (int) pid; +} + +// static +int KProcess::startDetached(const QStringList &argv) +{ + QStringList args = argv; + QString prog = args.takeFirst(); + return startDetached(prog, args); +} + +int KProcess::pid() const +{ +#ifdef Q_OS_UNIX + return (int) QProcess::pid(); +#else + return QProcess::pid() ? QProcess::pid()->dwProcessId : 0; +#endif +} +
new file mode 100644 --- /dev/null +++ b/gui//src/kprocess.h @@ -0,0 +1,342 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPROCESS_H +#define KPROCESS_H + +#include <QtCore/QProcess> +class KProcess; +class KProcessPrivate; + + +/** + * \class KProcess kprocess.h <KProcess> + * + * Child process invocation, monitoring and control. + * + * This class extends QProcess by some useful functionality, overrides + * some defaults with saner values and wraps parts of the API into a more + * accessible one. + * This is the preferred way of spawning child processes in KDE; don't + * use QProcess directly. + * + * @author Oswald Buddenhagen <ossi@kde.org> + **/ +class KProcess : public QProcess +{ + Q_OBJECT + Q_DECLARE_PRIVATE(KProcess) + +public: + + /** + * Modes in which the output channels can be opened. + */ + enum OutputChannelMode { + SeparateChannels = QProcess::SeparateChannels, + /**< Standard output and standard error are handled by KProcess + as separate channels */ + MergedChannels = QProcess::MergedChannels, + /**< Standard output and standard error are handled by KProcess + as one channel */ + ForwardedChannels = QProcess::ForwardedChannels, + /**< Both standard output and standard error are forwarded + to the parent process' respective channel */ + OnlyStdoutChannel, + /**< Only standard output is handled; standard error is forwarded */ + OnlyStderrChannel /**< Only standard error is handled; standard output is forwarded */ + }; + + /** + * Constructor + */ + explicit KProcess(QObject *parent = 0); + + /** + * Destructor + */ + virtual ~KProcess(); + + /** + * Set how to handle the output channels of the child process. + * + * The default is ForwardedChannels, which is unlike in QProcess. + * Do not request more than you actually handle, as this output is + * simply lost otherwise. + * + * This function must be called before starting the process. + * + * @param mode the output channel handling mode + */ + void setOutputChannelMode(OutputChannelMode mode); + + /** + * Query how the output channels of the child process are handled. + * + * @return the output channel handling mode + */ + OutputChannelMode outputChannelMode() const; + + /** + * Set the QIODevice open mode the process will be opened in. + * + * This function must be called before starting the process, obviously. + * + * @param mode the open mode. Note that this mode is automatically + * "reduced" according to the channel modes and redirections. + * The default is QIODevice::ReadWrite. + */ + void setNextOpenMode(QIODevice::OpenMode mode); + + /** + * Adds the variable @p name to the process' environment. + * + * This function must be called before starting the process. + * + * @param name the name of the environment variable + * @param value the new value for the environment variable + * @param overwrite if @c false and the environment variable is already + * set, the old value will be preserved + */ + void setEnv(const QString &name, const QString &value, bool overwrite = true); + + /** + * Removes the variable @p name from the process' environment. + * + * This function must be called before starting the process. + * + * @param name the name of the environment variable + */ + void unsetEnv(const QString &name); + + /** + * Empties the process' environment. + * + * Note that LD_LIBRARY_PATH/DYLD_LIBRARY_PATH is automatically added + * on *NIX. + * + * This function must be called before starting the process. + */ + void clearEnvironment(); + + /** + * Set the program and the command line arguments. + * + * This function must be called before starting the process, obviously. + * + * @param exe the program to execute + * @param args the command line arguments for the program, + * one per list element + */ + void setProgram(const QString &exe, const QStringList &args = QStringList()); + + /** + * @overload + * + * @param argv the program to execute and the command line arguments + * for the program, one per list element + */ + void setProgram(const QStringList &argv); + + /** + * Append an element to the command line argument list for this process. + * + * If no executable is set yet, it will be set instead. + * + * For example, doing an "ls -l /usr/local/bin" can be achieved by: + * \code + * KProcess p; + * p << "ls" << "-l" << "/usr/local/bin"; + * ... + * \endcode + * + * This function must be called before starting the process, obviously. + * + * @param arg the argument to add + * @return a reference to this KProcess + */ + KProcess &operator<<(const QString& arg); + + /** + * @overload + * + * @param args the arguments to add + * @return a reference to this KProcess + */ + KProcess &operator<<(const QStringList& args); + + /** + * Clear the program and command line argument list. + */ + void clearProgram(); + + /** + * Set a command to execute through a shell (a POSIX sh on *NIX + * and cmd.exe on Windows). + * + * Using this for anything but user-supplied commands is usually a bad + * idea, as the command's syntax depends on the platform. + * Redirections including pipes, etc. are better handled by the + * respective functions provided by QProcess. + * + * If KProcess determines that the command does not really need a + * shell, it will trasparently execute it without one for performance + * reasons. + * + * This function must be called before starting the process, obviously. + * + * @param cmd the command to execute through a shell. + * The caller must make sure that all filenames etc. are properly + * quoted when passed as argument. Failure to do so often results in + * serious security holes. See KShell::quoteArg(). + */ + void setShellCommand(const QString &cmd); + + /** + * Obtain the currently set program and arguments. + * + * @return a list, the first element being the program, the remaining ones + * being command line arguments to the program. + */ + QStringList program() const; + + /** + * Start the process. + * + * @see QProcess::start(const QString &, const QStringList &, OpenMode) + */ + void start(); + + /** + * Start the process, wait for it to finish, and return the exit code. + * + * This method is roughly equivalent to the sequence: + * <code> + * start(); + * waitForFinished(msecs); + * return exitCode(); + * </code> + * + * Unlike the other execute() variants this method is not static, + * so the process can be parametrized properly and talked to. + * + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + int execute(int msecs = -1); + + /** + * @overload + * + * @param exe the program to execute + * @param args the command line arguments for the program, + * one per list element + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + static int execute(const QString &exe, const QStringList &args = QStringList(), int msecs = -1); + + /** + * @overload + * + * @param argv the program to execute and the command line arguments + * for the program, one per list element + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + static int execute(const QStringList &argv, int msecs = -1); + + /** + * Start the process and detach from it. See QProcess::startDetached() + * for details. + * + * Unlike the other startDetached() variants this method is not static, + * so the process can be parametrized properly. + * @note Currently, only the setProgram()/setShellCommand() and + * setWorkingDirectory() parametrizations are supported. + * + * The KProcess object may be re-used immediately after calling this + * function. + * + * @return the PID of the started process or 0 on error + */ + int startDetached(); + + /** + * @overload + * + * @param exe the program to start + * @param args the command line arguments for the program, + * one per list element + * @return the PID of the started process or 0 on error + */ + static int startDetached(const QString &exe, const QStringList &args = QStringList()); + + /** + * @overload + * + * @param argv the program to start and the command line arguments + * for the program, one per list element + * @return the PID of the started process or 0 on error + */ + static int startDetached(const QStringList &argv); + + /** + * Obtain the process' ID as known to the system. + * + * Unlike with QProcess::pid(), this is a real PID also on Windows. + * + * This function can be called only while the process is running. + * It cannot be applied to detached processes. + * + * @return the process ID + */ + int pid() const; + +protected: + /** + * @internal + */ + KProcess(KProcessPrivate *d, QObject *parent); + + /** + * @internal + */ + KProcessPrivate * const d_ptr; + +private: + // hide those + using QProcess::setReadChannelMode; + using QProcess::readChannelMode; + using QProcess::setProcessChannelMode; + using QProcess::processChannelMode; + + Q_PRIVATE_SLOT(d_func(), void _k_forwardStdout()) + Q_PRIVATE_SLOT(d_func(), void _k_forwardStderr()) +}; + +#include "kprocess_p.h" + +#endif +
new file mode 100644 --- /dev/null +++ b/gui//src/kprocess_p.h @@ -0,0 +1,49 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPROCESS_P_H +#define KPROCESS_P_H +class KProcess; +class KProcessPrivate; + +#include "kprocess.h" +class KProcessPrivate { + Q_DECLARE_PUBLIC(KProcess) +protected: + KProcessPrivate() : + openMode(QIODevice::ReadWrite) + { + } + void writeAll(const QByteArray &buf, int fd); + void forwardStd(KProcess::ProcessChannel good, int fd); + void _k_forwardStdout(); + void _k_forwardStderr(); + + QString prog; + QStringList args; + KProcess::OutputChannelMode outputChannelMode; + QIODevice::OpenMode openMode; + + KProcess *q_ptr; +}; + + +#endif
new file mode 100644 --- /dev/null +++ b/gui//src/kpty.cpp @@ -0,0 +1,710 @@ +/* + + This file is part of the KDE libraries + Copyright (C) 2002 Waldo Bastian <bastian@kde.org> + Copyright (C) 2002-2003,2007-2008 Oswald Buddenhagen <ossi@kde.org> + Copyright (C) 2010 KDE e.V. <kde-ev-board@kde.org> + Author Adriaan de Groot <groot@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kpty_p.h" + +#ifdef __sgi +#define __svr4__ +#endif + +#ifdef __osf__ +#define _OSF_SOURCE +#include <float.h> +#endif + +#ifdef _AIX +#define _ALL_SOURCE +#endif + +// __USE_XOPEN isn't defined by default in ICC +// (needed for ptsname(), grantpt() and unlockpt()) +#ifdef __INTEL_COMPILER +# ifndef __USE_XOPEN +# define __USE_XOPEN +# endif +#endif + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/param.h> + +#include <errno.h> +#include <fcntl.h> +#include <time.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <grp.h> + +#if defined(HAVE_PTY_H) +# include <pty.h> +#endif + +#ifdef HAVE_LIBUTIL_H +# include <libutil.h> +#elif defined(HAVE_UTIL_H) +# include <util.h> +#endif + +#define HAVE_UTMPX +#define _UTMPX_COMPAT + +#ifdef HAVE_UTEMPTER +extern "C" { +# include <utempter.h> +} +#else +# include <utmp.h> +# ifdef HAVE_UTMPX +# include <utmpx.h> +# endif +# if !defined(_PATH_UTMPX) && defined(_UTMPX_FILE) +# define _PATH_UTMPX _UTMPX_FILE +# endif +# if !defined(_PATH_WTMPX) && defined(_WTMPX_FILE) +# define _PATH_WTMPX _WTMPX_FILE +# endif +#endif + +/* for HP-UX (some versions) the extern C is needed, and for other + platforms it doesn't hurt */ +extern "C" { +#include <termios.h> +#if defined(HAVE_TERMIO_H) +# include <termio.h> // struct winsize on some systems +#endif +} + +#if defined (_HPUX_SOURCE) +# define _TERMIOS_INCLUDED +# include <bsdtty.h> +#endif + +#ifdef HAVE_SYS_STROPTS_H +# include <sys/stropts.h> // Defines I_PUSH +# define _NEW_TTY_CTRL +#endif + +#if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) || defined (__bsdi__) || defined(__APPLE__) || defined (__DragonFly__) +# define _tcgetattr(fd, ttmode) ioctl(fd, TIOCGETA, (char *)ttmode) +#else +# if defined(_HPUX_SOURCE) || defined(__Lynx__) || defined (__CYGWIN__) || defined(__sun) +# define _tcgetattr(fd, ttmode) tcgetattr(fd, ttmode) +# else +# define _tcgetattr(fd, ttmode) ioctl(fd, TCGETS, (char *)ttmode) +# endif +#endif + +#if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) || defined (__bsdi__) || defined(__APPLE__) || defined (__DragonFly__) +# define _tcsetattr(fd, ttmode) ioctl(fd, TIOCSETA, (char *)ttmode) +#else +# if defined(_HPUX_SOURCE) || defined(__CYGWIN__) || defined(__sun) +# define _tcsetattr(fd, ttmode) tcsetattr(fd, TCSANOW, ttmode) +# else +# define _tcsetattr(fd, ttmode) ioctl(fd, TCSETS, (char *)ttmode) +# endif +#endif + +#include <QtCore/Q_PID> + +#define TTY_GROUP "tty" + +#ifndef PATH_MAX +# ifdef MAXPATHLEN +# define PATH_MAX MAXPATHLEN +# else +# define PATH_MAX 1024 +# endif +#endif + +/////////////////////// +// private functions // +/////////////////////// + +////////////////// +// private data // +////////////////// + +KPtyPrivate::KPtyPrivate(KPty* parent) : + masterFd(-1), slaveFd(-1), ownMaster(true), q_ptr(parent) +{ +} + +KPtyPrivate::~KPtyPrivate() +{ +} + +#ifndef HAVE_OPENPTY +bool KPtyPrivate::chownpty(bool grant) +{ + return !QProcess::execute(KStandardDirs::findExe("kgrantpty"), + QStringList() << (grant?"--grant":"--revoke") << QString::number(masterFd)); +} +#endif + +///////////////////////////// +// public member functions // +///////////////////////////// + +KPty::KPty() : + d_ptr(new KPtyPrivate(this)) +{ +} + +KPty::KPty(KPtyPrivate *d) : + d_ptr(d) +{ + d_ptr->q_ptr = this; +} + +KPty::~KPty() +{ + close(); + delete d_ptr; +} + +bool KPty::open() +{ + Q_D(KPty); + + if (d->masterFd >= 0) + return true; + + d->ownMaster = true; + + QByteArray ptyName; + + // Find a master pty that we can open //////////////////////////////// + + // Because not all the pty animals are created equal, they want to + // be opened by several different methods. + + // We try, as we know them, one by one. + +#ifdef HAVE_OPENPTY + + char ptsn[PATH_MAX]; + if (::openpty( &d->masterFd, &d->slaveFd, ptsn, 0, 0)) + { + d->masterFd = -1; + d->slaveFd = -1; + //kWarning(175) << "Can't open a pseudo teletype"; + return false; + } + d->ttyName = ptsn; + +#else + +#ifdef HAVE__GETPTY // irix + + char *ptsn = _getpty(&d->masterFd, O_RDWR|O_NOCTTY, S_IRUSR|S_IWUSR, 0); + if (ptsn) { + d->ttyName = ptsn; + goto grantedpt; + } + +#elif defined(HAVE_PTSNAME) || defined(TIOCGPTN) + +#ifdef HAVE_POSIX_OPENPT + d->masterFd = ::posix_openpt(O_RDWR|O_NOCTTY); +#elif defined(HAVE_GETPT) + d->masterFd = ::getpt(); +#elif defined(PTM_DEVICE) + //d->masterFd = KDE_open(PTM_DEVICE, O_RDWR|O_NOCTTY); +d->masterFd = ::open(PTM_DEVICE, O_RDWR|O_NOCTTY); +#else +# error No method to open a PTY master detected. +#endif + if (d->masterFd >= 0) + { +#ifdef HAVE_PTSNAME + char *ptsn = ptsname(d->masterFd); + if (ptsn) { + d->ttyName = ptsn; +#else + int ptyno; + if (!ioctl(d->masterFd, TIOCGPTN, &ptyno)) { + char buf[32]; + sprintf(buf, "/dev/pts/%d", ptyno); + d->ttyName = buf; +#endif +#ifdef HAVE_GRANTPT + if (!grantpt(d->masterFd)) + goto grantedpt; +#else + goto gotpty; +#endif + } + ::close(d->masterFd); + d->masterFd = -1; + } +#endif // HAVE_PTSNAME || TIOCGPTN + + // Linux device names, FIXME: Trouble on other systems? + for (const char* s3 = "pqrstuvwxyzabcde"; *s3; s3++) + { + for (const char* s4 = "0123456789abcdef"; *s4; s4++) + { + ptyName = QString().sprintf("/dev/pty%c%c", *s3, *s4).toAscii(); + d->ttyName = QString().sprintf("/dev/tty%c%c", *s3, *s4).toAscii(); + + d->masterFd = ::open(ptyName.data(), O_RDWR); + if (d->masterFd >= 0) + { +#ifdef Q_OS_SOLARIS + /* Need to check the process group of the pty. + * If it exists, then the slave pty is in use, + * and we need to get another one. + */ + int pgrp_rtn; + if (ioctl(d->masterFd, TIOCGPGRP, &pgrp_rtn) == 0 || errno != EIO) { + ::close(d->masterFd); + d->masterFd = -1; + continue; + } +#endif /* Q_OS_SOLARIS */ + if (!access(d->ttyName.data(),R_OK|W_OK)) // checks availability based on permission bits + { + if (!geteuid()) + { + struct group* p = getgrnam(TTY_GROUP); + if (!p) + p = getgrnam("wheel"); + gid_t gid = p ? p->gr_gid : getgid (); + + chown(d->ttyName.data(), getuid(), gid); + chmod(d->ttyName.data(), S_IRUSR|S_IWUSR|S_IWGRP); + } + goto gotpty; + } + ::close(d->masterFd); + d->masterFd = -1; + } + } + } + + //kWarning(175) << "Can't open a pseudo teletype"; + return false; + + gotpty: + KDE_struct_stat st; + if (KDE_stat(d->ttyName.data(), &st)) + return false; // this just cannot happen ... *cough* Yeah right, I just + // had it happen when pty #349 was allocated. I guess + // there was some sort of leak? I only had a few open. + if (((st.st_uid != getuid()) || + (st.st_mode & (S_IRGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH))) && + !d->chownpty(true)) + { + + /*kWarning(175) + << "chownpty failed for device " << ptyName << "::" << d->ttyName + << "\nThis means the communication can be eavesdropped." << endl; +*/ +} + + grantedpt: + +#ifdef HAVE_REVOKE + revoke(d->ttyName.data()); +#endif + +#ifdef HAVE_UNLOCKPT + unlockpt(d->masterFd); +#elif defined(TIOCSPTLCK) + int flag = 0; + ioctl(d->masterFd, TIOCSPTLCK, &flag); +#endif + + d->slaveFd = ::open(d->ttyName.data(), O_RDWR | O_NOCTTY); + if (d->slaveFd < 0) + { + //kWarning(175) << "Can't open slave pseudo teletype"; + ::close(d->masterFd); + d->masterFd = -1; + return false; + } + +#if (defined(__svr4__) || defined(__sgi__) || defined(Q_OS_SOLARIS)) + // Solaris uses STREAMS for terminal handling. It is possible + // for the pty handling modules to be left off the stream; in that + // case push them on. ioctl(fd, I_FIND, ...) is documented to return + // 1 if the module is on the stream already. + { + static const char *pt = "ptem"; + static const char *ld = "ldterm"; + if (ioctl(d->slaveFd, I_FIND, pt) == 0) + ioctl(d->slaveFd, I_PUSH, pt); + if (ioctl(d->slaveFd, I_FIND, ld) == 0) + ioctl(d->slaveFd, I_PUSH, ld); + } +#endif + +#endif /* HAVE_OPENPTY */ + + fcntl(d->masterFd, F_SETFD, FD_CLOEXEC); + fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC); + + return true; +} + +bool KPty::open(int fd) +{ +#if !defined(HAVE_PTSNAME) && !defined(TIOCGPTN) + //kWarning(175) << "Unsupported attempt to open pty with fd" << fd; + return false; +#else + Q_D(KPty); + + if (d->masterFd >= 0) { + //kWarning(175) << "Attempting to open an already open pty"; + return false; + } + + d->ownMaster = false; + +# ifdef HAVE_PTSNAME + char *ptsn = ptsname(fd); + if (ptsn) { + d->ttyName = ptsn; +# else + int ptyno; + if (!ioctl(fd, TIOCGPTN, &ptyno)) { + char buf[32]; + sprintf(buf, "/dev/pts/%d", ptyno); + d->ttyName = buf; +# endif + } else { + //kWarning(175) << "Failed to determine pty slave device for fd" << fd; + return false; + } + + d->masterFd = fd; + if (!openSlave()) { + d->masterFd = -1; + return false; + } + + return true; +#endif +} + +void KPty::closeSlave() +{ + Q_D(KPty); + + if (d->slaveFd < 0) + return; + ::close(d->slaveFd); + d->slaveFd = -1; +} + +bool KPty::openSlave() +{ + Q_D(KPty); + + if (d->slaveFd >= 0) + return true; + if (d->masterFd < 0) { + //kWarning(175) << "Attempting to open pty slave while master is closed"; + return false; + } + d->slaveFd = ::open(d->ttyName.data(), O_RDWR | O_NOCTTY); + if (d->slaveFd < 0) { + //kWarning(175) << "Can't open slave pseudo teletype"; + return false; + } + fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC); + return true; +} + +void KPty::close() +{ + Q_D(KPty); + + if (d->masterFd < 0) + return; + closeSlave(); + if (d->ownMaster) { +#ifndef HAVE_OPENPTY + // don't bother resetting unix98 pty, it will go away after closing master anyway. + if (memcmp(d->ttyName.data(), "/dev/pts/", 9)) { + if (!geteuid()) { + struct stat st; + if (!stat(d->ttyName.data(), &st)) { + chown(d->ttyName.data(), 0, st.st_gid == getgid() ? 0 : -1); + chmod(d->ttyName.data(), S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); + } + } else { + fcntl(d->masterFd, F_SETFD, 0); + d->chownpty(false); + } + } +#endif + ::close(d->masterFd); + } + d->masterFd = -1; +} + +void KPty::setCTty() +{ + Q_D(KPty); + + // Setup job control ////////////////////////////////// + + // Become session leader, process group leader, + // and get rid of the old controlling terminal. + setsid(); + + // make our slave pty the new controlling terminal. +#ifdef TIOCSCTTY + ioctl(d->slaveFd, TIOCSCTTY, 0); +#else + // __svr4__ hack: the first tty opened after setsid() becomes controlling tty + ::close(open(d->ttyName, O_WRONLY, 0)); +#endif + + // make our new process group the foreground group on the pty + int pgrp = getpid(); +#if defined(_POSIX_VERSION) || defined(__svr4__) + tcsetpgrp(d->slaveFd, pgrp); +#elif defined(TIOCSPGRP) + ioctl(d->slaveFd, TIOCSPGRP, (char *)&pgrp); +#endif +} + +void KPty::login(const char *user, const char *remotehost) +{ +#ifdef HAVE_UTEMPTER + Q_D(KPty); + + addToUtmp(d->ttyName, remotehost, d->masterFd); + Q_UNUSED(user); +#else +# ifdef HAVE_UTMPX + struct utmpx l_struct; +# else + struct utmp l_struct; +# endif + memset(&l_struct, 0, sizeof(l_struct)); + // note: strncpy without terminators _is_ correct here. man 4 utmp + + if (user) + strncpy(l_struct.ut_name, user, sizeof(l_struct.ut_name)); + + if (remotehost) { + strncpy(l_struct.ut_host, remotehost, sizeof(l_struct.ut_host)); +# ifdef HAVE_STRUCT_UTMP_UT_SYSLEN + l_struct.ut_syslen = qMin(strlen(remotehost), sizeof(l_struct.ut_host)); +# endif + } + +# ifndef __GLIBC__ + Q_D(KPty); + const char *str_ptr = d->ttyName.data(); + if (!memcmp(str_ptr, "/dev/", 5)) + str_ptr += 5; + strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line)); +# ifdef HAVE_STRUCT_UTMP_UT_ID + strncpy(l_struct.ut_id, + str_ptr + strlen(str_ptr) - sizeof(l_struct.ut_id), + sizeof(l_struct.ut_id)); +# endif +# endif + +# ifdef HAVE_UTMPX + //gettimeofday(&l_struct.ut_tv, 0); + gettimeofday((struct timeval *)&l_struct.ut_tv, 0); +# else + l_struct.ut_time = time(0); +# endif + +# ifdef HAVE_LOGIN +# ifdef HAVE_LOGINX + ::loginx(&l_struct); +# else + ::login(&l_struct); +# endif +# else +# ifdef HAVE_STRUCT_UTMP_UT_TYPE + l_struct.ut_type = USER_PROCESS; +# endif +# ifdef HAVE_STRUCT_UTMP_UT_PID + l_struct.ut_pid = getpid(); +# ifdef HAVE_STRUCT_UTMP_UT_SESSION + l_struct.ut_session = getsid(0); +# endif +# endif +# ifdef HAVE_UTMPX + utmpxname(_PATH_UTMPX); + setutxent(); + pututxline(&l_struct); + endutxent(); + //updwtmpx(_PATH_WTMPX, &l_struct); +# else + utmpname(_PATH_UTMP); + setutent(); + pututline(&l_struct); + endutent(); + updwtmp(_PATH_WTMP, &l_struct); +# endif +# endif +#endif +} + +void KPty::logout() +{ +#ifdef HAVE_UTEMPTER + Q_D(KPty); + + removeLineFromUtmp(d->ttyName, d->masterFd); +#else + Q_D(KPty); + + const char *str_ptr = d->ttyName.data(); + if (!memcmp(str_ptr, "/dev/", 5)) + str_ptr += 5; +# ifdef __GLIBC__ + else { + const char *sl_ptr = strrchr(str_ptr, '/'); + if (sl_ptr) + str_ptr = sl_ptr + 1; + } +# endif +# ifdef HAVE_LOGIN +# ifdef HAVE_LOGINX + ::logoutx(str_ptr, 0, DEAD_PROCESS); +# else + ::logout(str_ptr); +# endif +# else +# ifdef HAVE_UTMPX + struct utmpx l_struct, *ut; +# else + struct utmp l_struct, *ut; +# endif + memset(&l_struct, 0, sizeof(l_struct)); + + strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line)); + +# ifdef HAVE_UTMPX + utmpxname(_PATH_UTMPX); + setutxent(); + if ((ut = getutxline(&l_struct))) { +# else + utmpname(_PATH_UTMP); + setutent(); + if ((ut = getutline(&l_struct))) { +# endif + memset(ut->ut_name, 0, sizeof(*ut->ut_name)); + memset(ut->ut_host, 0, sizeof(*ut->ut_host)); +# ifdef HAVE_STRUCT_UTMP_UT_SYSLEN + ut->ut_syslen = 0; +# endif +# ifdef HAVE_STRUCT_UTMP_UT_TYPE + ut->ut_type = DEAD_PROCESS; +# endif +# ifdef HAVE_UTMPX + //gettimeofday(&(ut->ut_tv), 0); + gettimeofday((struct timeval *)&(ut->ut_tv), 0); + pututxline(ut); + } + endutxent(); +# else + ut->ut_time = time(0); + pututline(ut); + } + endutent(); +# endif +# endif +#endif +} + +bool KPty::tcGetAttr(struct ::termios *ttmode) const +{ + Q_D(const KPty); + +#ifdef Q_OS_SOLARIS + if (_tcgetattr(d->slaveFd, ttmode) == 0) return true; +#endif + return _tcgetattr(d->masterFd, ttmode) == 0; +} + +bool KPty::tcSetAttr(struct ::termios *ttmode) +{ + Q_D(KPty); + +#ifdef Q_OS_SOLARIS + if (_tcsetattr(d->slaveFd, ttmode) == 0) return true; +#endif + return _tcsetattr(d->masterFd, ttmode) == 0; +} + +bool KPty::setWinSize(int lines, int columns) +{ + Q_D(KPty); + + struct winsize winSize; + memset(&winSize, 0, sizeof(winSize)); + winSize.ws_row = (unsigned short)lines; + winSize.ws_col = (unsigned short)columns; + return ioctl(d->masterFd, TIOCSWINSZ, (char *)&winSize) == 0; +} + +bool KPty::setEcho(bool echo) +{ + struct ::termios ttmode; + if (!tcGetAttr(&ttmode)) + return false; + if (!echo) + ttmode.c_lflag &= ~ECHO; + else + ttmode.c_lflag |= ECHO; + return tcSetAttr(&ttmode); +} + +const char *KPty::ttyName() const +{ + Q_D(const KPty); + + return d->ttyName.data(); +} + +int KPty::masterFd() const +{ + Q_D(const KPty); + + return d->masterFd; +} + +int KPty::slaveFd() const +{ + Q_D(const KPty); + + return d->slaveFd; +}
new file mode 100644 --- /dev/null +++ b/gui//src/kpty.h @@ -0,0 +1,204 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2003,2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kpty_h +#define kpty_h +#include <QtCore/qglobal.h> + +struct KPtyPrivate; +struct termios; + +/** + * Provides primitives for opening & closing a pseudo TTY pair, assigning the + * controlling TTY, utmp registration and setting various terminal attributes. + */ +class KPty { + Q_DECLARE_PRIVATE(KPty) + +public: + + /** + * Constructor + */ + KPty(); + + /** + * Destructor: + * + * If the pty is still open, it will be closed. Note, however, that + * an utmp registration is @em not undone. + */ + ~KPty(); + + /** + * Create a pty master/slave pair. + * + * @return true if a pty pair was successfully opened + */ + bool open(); + + /** + * Open using an existing pty master. + * + * @param fd an open pty master file descriptor. + * The ownership of the fd remains with the caller; + * it will not be automatically closed at any point. + * @return true if a pty pair was successfully opened + */ + bool open(int fd); + + /** + * Close the pty master/slave pair. + */ + void close(); + + /** + * Close the pty slave descriptor. + * + * When creating the pty, KPty also opens the slave and keeps it open. + * Consequently the master will never receive an EOF notification. + * Usually this is the desired behavior, as a closed pty slave can be + * reopened any time - unlike a pipe or socket. However, in some cases + * pipe-alike behavior might be desired. + * + * After this function was called, slaveFd() and setCTty() cannot be + * used. + */ + void closeSlave(); + + /** + * Open the pty slave descriptor. + * + * This undoes the effect of closeSlave(). + * + * @return true if the pty slave was successfully opened + */ + bool openSlave(); + + /** + * Creates a new session and process group and makes this pty the + * controlling tty. + */ + void setCTty(); + + /** + * Creates an utmp entry for the tty. + * This function must be called after calling setCTty and + * making this pty the stdin. + * @param user the user to be logged on + * @param remotehost the host from which the login is coming. This is + * @em not the local host. For remote logins it should be the hostname + * of the client. For local logins from inside an X session it should + * be the name of the X display. Otherwise it should be empty. + */ + void login(const char *user = 0, const char *remotehost = 0); + + /** + * Removes the utmp entry for this tty. + */ + void logout(); + + /** + * Wrapper around tcgetattr(3). + * + * This function can be used only while the PTY is open. + * You will need an #include <termios.h> to do anything useful + * with it. + * + * @param ttmode a pointer to a termios structure. + * Note: when declaring ttmode, @c struct @c ::termios must be used - + * without the '::' some version of HP-UX thinks, this declares + * the struct in your class, in your method. + * @return @c true on success, false otherwise + */ + bool tcGetAttr(struct ::termios *ttmode) const; + + /** + * Wrapper around tcsetattr(3) with mode TCSANOW. + * + * This function can be used only while the PTY is open. + * + * @param ttmode a pointer to a termios structure. + * @return @c true on success, false otherwise. Note that success means + * that @em at @em least @em one attribute could be set. + */ + bool tcSetAttr(struct ::termios *ttmode); + + /** + * Change the logical (screen) size of the pty. + * The default is 24 lines by 80 columns. + * + * This function can be used only while the PTY is open. + * + * @param lines the number of rows + * @param columns the number of columns + * @return @c true on success, false otherwise + */ + bool setWinSize(int lines, int columns); + + /** + * Set whether the pty should echo input. + * + * Echo is on by default. + * If the output of automatically fed (non-interactive) PTY clients + * needs to be parsed, disabling echo often makes it much simpler. + * + * This function can be used only while the PTY is open. + * + * @param echo true if input should be echoed. + * @return @c true on success, false otherwise + */ + bool setEcho(bool echo); + + /** + * @return the name of the slave pty device. + * + * This function should be called only while the pty is open. + */ + const char *ttyName() const; + + /** + * @return the file descriptor of the master pty + * + * This function should be called only while the pty is open. + */ + int masterFd() const; + + /** + * @return the file descriptor of the slave pty + * + * This function should be called only while the pty slave is open. + */ + int slaveFd() const; + +protected: + /** + * @internal + */ + KPty(KPtyPrivate *d); + + /** + * @internal + */ + KPtyPrivate * const d_ptr; +}; + +#endif +
new file mode 100644 --- /dev/null +++ b/gui//src/kpty_export.h @@ -0,0 +1,46 @@ +/* This file is part of the KDE project + Copyright (C) 2007 David Faure <faure@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPTY_EXPORT_H +#define KPTY_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +//#include <kdemacros.h> +#include <QtCore/qglobal.h> +#define KDE_EXPORT +#define KDE_IMPORT + +#ifndef KPTY_EXPORT +# if defined(KDELIBS_STATIC_LIBS) + /* No export/import for static libraries */ +# define KPTY_EXPORT +# elif defined(MAKE_KDECORE_LIB) + /* We are building this library */ +# define KPTY_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define KPTY_EXPORT KDE_IMPORT +# endif +#endif + +# ifndef KPTY_EXPORT_DEPRECATED +# define KPTY_EXPORT_DEPRECATED KDE_DEPRECATED KPTY_EXPORT +# endif + +#endif
new file mode 100644 --- /dev/null +++ b/gui//src/kpty_p.h @@ -0,0 +1,58 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2003,2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kpty_p_h +#define kpty_p_h + +#include "kpty.h" + +#if defined(Q_OS_MAC) +#define HAVE_UTIL_H +#define HAVE_UTMPX +#define _UTMPX_COMPAT +#define HAVE_PTSNAME +#define HAVE_SYS_TIME_H +#else +#define HAVE_PTY_H +#endif + +#define HAVE_OPENPTY + +#include <QtCore/QByteArray> + +struct KPtyPrivate { + Q_DECLARE_PUBLIC(KPty) + + KPtyPrivate(KPty* parent); + virtual ~KPtyPrivate(); +#ifndef HAVE_OPENPTY + bool chownpty(bool grant); +#endif + + int masterFd; + int slaveFd; + bool ownMaster:1; + + QByteArray ttyName; + + KPty *q_ptr; +}; + +#endif
new file mode 100644 --- /dev/null +++ b/gui//src/kptydevice.cpp @@ -0,0 +1,413 @@ +/* + + This file is part of the KDE libraries + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + Copyright (C) 2010 KDE e.V. <kde-ev-board@kde.org> + Author Adriaan de Groot <groot@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kptydevice.h" +#include "kpty_p.h" +#define i18n + +#include <QtCore/QSocketNotifier> + +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <termios.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#if defined(Q_OS_FREEBSD) || defined(Q_OS_MAC) + // "the other end's output queue size" - kinda braindead, huh? +# define PTY_BYTES_AVAILABLE TIOCOUTQ +#elif defined(TIOCINQ) + // "our end's input queue size" +# define PTY_BYTES_AVAILABLE TIOCINQ +#else + // likewise. more generic ioctl (theoretically) +# define PTY_BYTES_AVAILABLE FIONREAD +#endif + +////////////////// +// private data // +////////////////// + +// Lifted from Qt. I don't think they would mind. ;) +// Re-lift again from Qt whenever a proper replacement for pthread_once appears +static void qt_ignore_sigpipe() +{ + static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0); + if (atom.testAndSetRelaxed(0, 1)) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &noaction, 0); + } +} + +#define NO_INTR(ret,func) do { ret = func; } while (ret < 0 && errno == EINTR) + +bool KPtyDevicePrivate::_k_canRead() +{ + Q_Q(KPtyDevice); + qint64 readBytes = 0; + +#ifdef Q_OS_IRIX // this should use a config define, but how to check it? + size_t available; +#else + int available; +#endif + if (!::ioctl(q->masterFd(), PTY_BYTES_AVAILABLE, (char *) &available)) { +#ifdef Q_OS_SOLARIS + // A Pty is a STREAMS module, and those can be activated + // with 0 bytes available. This happens either when ^C is + // pressed, or when an application does an explicit write(a,b,0) + // which happens in experiments fairly often. When 0 bytes are + // available, you must read those 0 bytes to clear the STREAMS + // module, but we don't want to hit the !readBytes case further down. + if (!available) { + char c; + // Read the 0-byte STREAMS message + NO_INTR(readBytes, read(q->masterFd(), &c, 0)); + // Should return 0 bytes read; -1 is error + if (readBytes < 0) { + readNotifier->setEnabled(false); + emit q->readEof(); + return false; + } + return true; + } +#endif + + char *ptr = readBuffer.reserve(available); +#ifdef Q_OS_SOLARIS + // Even if available > 0, it is possible for read() + // to return 0 on Solaris, due to 0-byte writes in the stream. + // Ignore them and keep reading until we hit *some* data. + // In Solaris it is possible to have 15 bytes available + // and to (say) get 0, 0, 6, 0 and 9 bytes in subsequent reads. + // Because the stream is set to O_NONBLOCK in finishOpen(), + // an EOF read will return -1. + readBytes = 0; + while (!readBytes) +#endif + // Useless block braces except in Solaris + { + NO_INTR(readBytes, read(q->masterFd(), ptr, available)); + } + if (readBytes < 0) { + readBuffer.unreserve(available); + q->setErrorString(i18n("Error reading from PTY")); + return false; + } + readBuffer.unreserve(available - readBytes); // *should* be a no-op + } + + if (!readBytes) { + readNotifier->setEnabled(false); + emit q->readEof(); + return false; + } else { + if (!emittedReadyRead) { + emittedReadyRead = true; + emit q->readyRead(); + emittedReadyRead = false; + } + return true; + } +} + +bool KPtyDevicePrivate::_k_canWrite() +{ + Q_Q(KPtyDevice); + + writeNotifier->setEnabled(false); + if (writeBuffer.isEmpty()) + return false; + + qt_ignore_sigpipe(); + int wroteBytes; + NO_INTR(wroteBytes, + write(q->masterFd(), + writeBuffer.readPointer(), writeBuffer.readSize())); + if (wroteBytes < 0) { + q->setErrorString(i18n("Error writing to PTY")); + return false; + } + writeBuffer.free(wroteBytes); + + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(wroteBytes); + emittedBytesWritten = false; + } + + if (!writeBuffer.isEmpty()) + writeNotifier->setEnabled(true); + return true; +} + +#ifndef timeradd +// Lifted from GLIBC +# define timeradd(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ + if ((result)->tv_usec >= 1000000) { \ + ++(result)->tv_sec; \ + (result)->tv_usec -= 1000000; \ + } \ + } while (0) +# define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +bool KPtyDevicePrivate::doWait(int msecs, bool reading) +{ + Q_Q(KPtyDevice); +#ifndef __linux__ + struct timeval etv; +#endif + struct timeval tv, *tvp; + + if (msecs < 0) + tvp = 0; + else { + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs % 1000) * 1000; +#ifndef __linux__ + gettimeofday(&etv, 0); + timeradd(&tv, &etv, &etv); +#endif + tvp = &tv; + } + + while (reading ? readNotifier->isEnabled() : !writeBuffer.isEmpty()) { + fd_set rfds; + fd_set wfds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + if (readNotifier->isEnabled()) + FD_SET(q->masterFd(), &rfds); + if (!writeBuffer.isEmpty()) + FD_SET(q->masterFd(), &wfds); + +#ifndef __linux__ + if (tvp) { + gettimeofday(&tv, 0); + timersub(&etv, &tv, &tv); + if (tv.tv_sec < 0) + tv.tv_sec = tv.tv_usec = 0; + } +#endif + + switch (select(q->masterFd() + 1, &rfds, &wfds, 0, tvp)) { + case -1: + if (errno == EINTR) + break; + return false; + case 0: + q->setErrorString(i18n("PTY operation timed out")); + return false; + default: + if (FD_ISSET(q->masterFd(), &rfds)) { + bool canRead = _k_canRead(); + if (reading && canRead) + return true; + } + if (FD_ISSET(q->masterFd(), &wfds)) { + bool canWrite = _k_canWrite(); + if (!reading) + return canWrite; + } + break; + } + } + return false; +} + +void KPtyDevicePrivate::finishOpen(QIODevice::OpenMode mode) +{ + Q_Q(KPtyDevice); + + q->QIODevice::open(mode); + fcntl(q->masterFd(), F_SETFL, O_NONBLOCK); + readBuffer.clear(); + readNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Read, q); + writeNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Write, q); + QObject::connect(readNotifier, SIGNAL(activated(int)), q, SLOT(_k_canRead())); + QObject::connect(writeNotifier, SIGNAL(activated(int)), q, SLOT(_k_canWrite())); + readNotifier->setEnabled(true); +} + +///////////////////////////// +// public member functions // +///////////////////////////// + +KPtyDevice::KPtyDevice(QObject *parent) : + QIODevice(parent), + KPty(new KPtyDevicePrivate(this)) +{ +} + +KPtyDevice::~KPtyDevice() +{ + close(); +} + +bool KPtyDevice::open(OpenMode mode) +{ + Q_D(KPtyDevice); + + if (masterFd() >= 0) + return true; + + if (!KPty::open()) { + setErrorString(i18n("Error opening PTY")); + return false; + } + + d->finishOpen(mode); + + return true; +} + +bool KPtyDevice::open(int fd, OpenMode mode) +{ + Q_D(KPtyDevice); + + if (!KPty::open(fd)) { + setErrorString(i18n("Error opening PTY")); + return false; + } + + d->finishOpen(mode); + + return true; +} + +void KPtyDevice::close() +{ + Q_D(KPtyDevice); + + if (masterFd() < 0) + return; + + delete d->readNotifier; + delete d->writeNotifier; + + QIODevice::close(); + + KPty::close(); +} + +bool KPtyDevice::isSequential() const +{ + return true; +} + +bool KPtyDevice::canReadLine() const +{ + Q_D(const KPtyDevice); + return QIODevice::canReadLine() || d->readBuffer.canReadLine(); +} + +bool KPtyDevice::atEnd() const +{ + Q_D(const KPtyDevice); + return QIODevice::atEnd() && d->readBuffer.isEmpty(); +} + +qint64 KPtyDevice::bytesAvailable() const +{ + Q_D(const KPtyDevice); + return QIODevice::bytesAvailable() + d->readBuffer.size(); +} + +qint64 KPtyDevice::bytesToWrite() const +{ + Q_D(const KPtyDevice); + return d->writeBuffer.size(); +} + +bool KPtyDevice::waitForReadyRead(int msecs) +{ + Q_D(KPtyDevice); + return d->doWait(msecs, true); +} + +bool KPtyDevice::waitForBytesWritten(int msecs) +{ + Q_D(KPtyDevice); + return d->doWait(msecs, false); +} + +void KPtyDevice::setSuspended(bool suspended) +{ + Q_D(KPtyDevice); + d->readNotifier->setEnabled(!suspended); +} + +bool KPtyDevice::isSuspended() const +{ + Q_D(const KPtyDevice); + return !d->readNotifier->isEnabled(); +} + +// protected +qint64 KPtyDevice::readData(char *data, qint64 maxlen) +{ + Q_D(KPtyDevice); + return d->readBuffer.read(data, (int)qMin<qint64>(maxlen, KMAXINT)); +} + +// protected +qint64 KPtyDevice::readLineData(char *data, qint64 maxlen) +{ + Q_D(KPtyDevice); + return d->readBuffer.readLine(data, (int)qMin<qint64>(maxlen, KMAXINT)); +} + +// protected +qint64 KPtyDevice::writeData(const char *data, qint64 len) +{ + Q_D(KPtyDevice); + Q_ASSERT(len <= KMAXINT); + + d->writeBuffer.write(data, len); + d->writeNotifier->setEnabled(true); + return len; +} +
new file mode 100644 --- /dev/null +++ b/gui//src/kptydevice.h @@ -0,0 +1,353 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kptydev_h +#define kptydev_h + +struct KPtyPrivate; +struct KPtyDevicePrivate; + +#include "kpty.h" +#include "kpty_p.h" +#include <QtCore/QIODevice> +#include <QSocketNotifier> + +#define Q_DECLARE_PRIVATE_MI(Class, SuperClass) \ + inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(SuperClass::d_ptr); } \ + inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(SuperClass::d_ptr); } \ + friend class Class##Private; + +/** + * Encapsulates KPty into a QIODevice, so it can be used with Q*Stream, etc. + */ +class KPtyDevice : public QIODevice, public KPty { //krazy:exclude=dpointer (via macro) + Q_OBJECT + Q_DECLARE_PRIVATE_MI(KPtyDevice, KPty) + +public: + + /** + * Constructor + */ + KPtyDevice(QObject *parent = 0); + + /** + * Destructor: + * + * If the pty is still open, it will be closed. Note, however, that + * an utmp registration is @em not undone. + */ + virtual ~KPtyDevice(); + + /** + * Create a pty master/slave pair. + * + * @return true if a pty pair was successfully opened + */ + virtual bool open(OpenMode mode = ReadWrite | Unbuffered); + + /** + * Open using an existing pty master. The ownership of the fd + * remains with the caller, i.e., close() will not close the fd. + * + * This is useful if you wish to attach a secondary "controller" to an + * existing pty device such as a terminal widget. + * Note that you will need to use setSuspended() on both devices to + * control which one gets the incoming data from the pty. + * + * @param fd an open pty master file descriptor. + * @param mode the device mode to open the pty with. + * @return true if a pty pair was successfully opened + */ + bool open(int fd, OpenMode mode = ReadWrite | Unbuffered); + + /** + * Close the pty master/slave pair. + */ + virtual void close(); + + /** + * Sets whether the KPtyDevice monitors the pty for incoming data. + * + * When the KPtyDevice is suspended, it will no longer attempt to buffer + * data that becomes available from the pty and it will not emit any + * signals. + * + * Do not use on closed ptys. + * After a call to open(), the pty is not suspended. If you need to + * ensure that no data is read, call this function before the main loop + * is entered again (i.e., immediately after opening the pty). + */ + void setSuspended(bool suspended); + + /** + * Returns true if the KPtyDevice is not monitoring the pty for incoming + * data. + * + * Do not use on closed ptys. + * + * See setSuspended() + */ + bool isSuspended() const; + + /** + * @return always true + */ + virtual bool isSequential() const; + + /** + * @reimp + */ + bool canReadLine() const; + + /** + * @reimp + */ + bool atEnd() const; + + /** + * @reimp + */ + qint64 bytesAvailable() const; + + /** + * @reimp + */ + qint64 bytesToWrite() const; + + bool waitForBytesWritten(int msecs = -1); + bool waitForReadyRead(int msecs = -1); + + +Q_SIGNALS: + /** + * Emitted when EOF is read from the PTY. + * + * Data may still remain in the buffers. + */ + void readEof(); + +protected: + virtual qint64 readData(char *data, qint64 maxSize); + virtual qint64 readLineData(char *data, qint64 maxSize); + virtual qint64 writeData(const char *data, qint64 maxSize); + +private: + Q_PRIVATE_SLOT(d_func(), bool _k_canRead()) + Q_PRIVATE_SLOT(d_func(), bool _k_canWrite()) +}; + +#define KMAXINT ((int)(~0U >> 1)) + +///////////////////////////////////////////////////// +// Helper. Remove when QRingBuffer becomes public. // +///////////////////////////////////////////////////// + +#include <QtCore/qbytearray.h> +#include <QtCore/qlinkedlist.h> + +#define CHUNKSIZE 4096 + +class KRingBuffer +{ +public: + KRingBuffer() + { + clear(); + } + + void clear() + { + buffers.clear(); + QByteArray tmp; + tmp.resize(CHUNKSIZE); + buffers << tmp; + head = tail = 0; + totalSize = 0; + } + + inline bool isEmpty() const + { + return buffers.count() == 1 && !tail; + } + + inline int size() const + { + return totalSize; + } + + inline int readSize() const + { + return (buffers.count() == 1 ? tail : buffers.first().size()) - head; + } + + inline const char *readPointer() const + { + Q_ASSERT(totalSize > 0); + return buffers.first().constData() + head; + } + + void free(int bytes) + { + totalSize -= bytes; + Q_ASSERT(totalSize >= 0); + + forever { + int nbs = readSize(); + + if (bytes < nbs) { + head += bytes; + if (head == tail && buffers.count() == 1) { + buffers.first().resize(CHUNKSIZE); + head = tail = 0; + } + break; + } + + bytes -= nbs; + if (buffers.count() == 1) { + buffers.first().resize(CHUNKSIZE); + head = tail = 0; + break; + } + + buffers.removeFirst(); + head = 0; + } + } + + char *reserve(int bytes) + { + totalSize += bytes; + + char *ptr; + if (tail + bytes <= buffers.last().size()) { + ptr = buffers.last().data() + tail; + tail += bytes; + } else { + buffers.last().resize(tail); + QByteArray tmp; + tmp.resize(qMax(CHUNKSIZE, bytes)); + ptr = tmp.data(); + buffers << tmp; + tail = bytes; + } + return ptr; + } + + // release a trailing part of the last reservation + inline void unreserve(int bytes) + { + totalSize -= bytes; + tail -= bytes; + } + + inline void write(const char *data, int len) + { + memcpy(reserve(len), data, len); + } + + // Find the first occurrence of c and return the index after it. + // If c is not found until maxLength, maxLength is returned, provided + // it is smaller than the buffer size. Otherwise -1 is returned. + int indexAfter(char c, int maxLength = KMAXINT) const + { + int index = 0; + int start = head; + QLinkedList<QByteArray>::ConstIterator it = buffers.begin(); + forever { + if (!maxLength) + return index; + if (index == size()) + return -1; + const QByteArray &buf = *it; + ++it; + int len = qMin((it == buffers.end() ? tail : buf.size()) - start, + maxLength); + const char *ptr = buf.data() + start; + if (const char *rptr = (const char *)memchr(ptr, c, len)) + return index + (rptr - ptr) + 1; + index += len; + maxLength -= len; + start = 0; + } + } + + inline int lineSize(int maxLength = KMAXINT) const + { + return indexAfter('\n', maxLength); + } + + inline bool canReadLine() const + { + return lineSize() != -1; + } + + int read(char *data, int maxLength) + { + int bytesToRead = qMin(size(), maxLength); + int readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readPointer(); + int bs = qMin(bytesToRead - readSoFar, readSize()); + memcpy(data + readSoFar, ptr, bs); + readSoFar += bs; + free(bs); + } + return readSoFar; + } + + int readLine(char *data, int maxLength) + { + return read(data, lineSize(qMin(maxLength, size()))); + } + +private: + QLinkedList<QByteArray> buffers; + int head, tail; + int totalSize; +}; + +struct KPtyDevicePrivate : public KPtyPrivate { + Q_DECLARE_PUBLIC(KPtyDevice) + + KPtyDevicePrivate(KPty* parent) : + KPtyPrivate(parent), + emittedReadyRead(false), emittedBytesWritten(false), + readNotifier(0), writeNotifier(0) + { + } + + bool _k_canRead(); + bool _k_canWrite(); + + bool doWait(int msecs, bool reading); + void finishOpen(QIODevice::OpenMode mode); + + bool emittedReadyRead; + bool emittedBytesWritten; + QSocketNotifier *readNotifier; + QSocketNotifier *writeNotifier; + KRingBuffer readBuffer; + KRingBuffer writeBuffer; +}; + +#endif +
new file mode 100644 --- /dev/null +++ b/gui//src/kptyprocess.cpp @@ -0,0 +1,119 @@ +/* + + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "kptyprocess.h" +#include "kprocess.h" + +#include "kptydevice.h" + +#include <stdlib.h> +#include <unistd.h> + +////////////////// +// private data // +////////////////// + +KPtyProcess::KPtyProcess(QObject *parent) : + KProcess(new KPtyProcessPrivate, parent) +{ + Q_D(KPtyProcess); + + d->pty = new KPtyDevice(this); + d->pty->open(); + connect(this, SIGNAL(stateChanged(QProcess::ProcessState)), + SLOT(_k_onStateChanged(QProcess::ProcessState))); +} + +KPtyProcess::KPtyProcess(int ptyMasterFd, QObject *parent) : + KProcess(new KPtyProcessPrivate, parent) +{ + Q_D(KPtyProcess); + + d->pty = new KPtyDevice(this); + d->pty->open(ptyMasterFd); + connect(this, SIGNAL(stateChanged(QProcess::ProcessState)), + SLOT(_k_onStateChanged(QProcess::ProcessState))); +} + +KPtyProcess::~KPtyProcess() +{ + Q_D(KPtyProcess); + + if (state() != QProcess::NotRunning && d->addUtmp) { + d->pty->logout(); + disconnect(SIGNAL(stateChanged(QProcess::ProcessState)), + this, SLOT(_k_onStateChanged(QProcess::ProcessState))); + } + delete d->pty; +} + +void KPtyProcess::setPtyChannels(PtyChannels channels) +{ + Q_D(KPtyProcess); + + d->ptyChannels = channels; +} + +KPtyProcess::PtyChannels KPtyProcess::ptyChannels() const +{ + Q_D(const KPtyProcess); + + return d->ptyChannels; +} + +void KPtyProcess::setUseUtmp(bool value) +{ + Q_D(KPtyProcess); + + d->addUtmp = value; +} + +bool KPtyProcess::isUseUtmp() const +{ + Q_D(const KPtyProcess); + + return d->addUtmp; +} + +KPtyDevice *KPtyProcess::pty() const +{ + Q_D(const KPtyProcess); + + return d->pty; +} + +void KPtyProcess::setupChildProcess() +{ + Q_D(KPtyProcess); + + d->pty->setCTty(); + if (d->addUtmp) + d->pty->login(getenv("USER"), getenv("DISPLAY")); + //d->pty->login(KUser(KUser::UseRealUserID).loginName().toLocal8Bit().data(), qgetenv("DISPLAY")); + if (d->ptyChannels & StdinChannel) + dup2(d->pty->slaveFd(), 0); + if (d->ptyChannels & StdoutChannel) + dup2(d->pty->slaveFd(), 1); + if (d->ptyChannels & StderrChannel) + dup2(d->pty->slaveFd(), 2); + + KProcess::setupChildProcess(); +}
new file mode 100644 --- /dev/null +++ b/gui//src/kptyprocess.h @@ -0,0 +1,157 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPTYPROCESS_H +#define KPTYPROCESS_H + +#include "kprocess.h" +#include "kprocess_p.h" +#include "kptydevice.h" + +class KPtyDevice; +class KPtyProcess; +struct KPtyProcessPrivate; + +/** + * This class extends KProcess by support for PTYs (pseudo TTYs). + * + * The PTY is opened as soon as the class is instantiated. Verify that + * it was opened successfully by checking that pty()->masterFd() is not -1. + * + * The PTY is always made the process' controlling TTY. + * Utmp registration and connecting the stdio handles to the PTY are optional. + * + * No attempt to integrate with QProcess' waitFor*() functions was made, + * for it is impossible. Note that execute() does not work with the PTY, too. + * Use the PTY device's waitFor*() functions or use it asynchronously. + * + * @author Oswald Buddenhagen <ossi@kde.org> + */ +class KPtyProcess : public KProcess +{ + Q_OBJECT + Q_DECLARE_PRIVATE(KPtyProcess) + +public: + enum PtyChannelFlag { + NoChannels = 0, /**< The PTY is not connected to any channel. */ + StdinChannel = 1, /**< Connect PTY to stdin. */ + StdoutChannel = 2, /**< Connect PTY to stdout. */ + StderrChannel = 4, /**< Connect PTY to stderr. */ + AllOutputChannels = 6, /**< Connect PTY to all output channels. */ + AllChannels = 7 /**< Connect PTY to all channels. */ + }; + + Q_DECLARE_FLAGS(PtyChannels, PtyChannelFlag) + + /** + * Constructor + */ + explicit KPtyProcess(QObject *parent = 0); + + /** + * Construct a process using an open pty master. + * + * @param ptyMasterFd an open pty master file descriptor. + * The process does not take ownership of the descriptor; + * it will not be automatically closed at any point. + */ + KPtyProcess(int ptyMasterFd, QObject *parent = 0); + + /** + * Destructor + */ + virtual ~KPtyProcess(); + + /** + * Set to which channels the PTY should be assigned. + * + * This function must be called before starting the process. + * + * @param channels the output channel handling mode + */ + void setPtyChannels(PtyChannels channels); + + /** + * Query to which channels the PTY is assigned. + * + * @return the output channel handling mode + */ + PtyChannels ptyChannels() const; + + /** + * Set whether to register the process as a TTY login in utmp. + * + * Utmp is disabled by default. + * It should enabled for interactively fed processes, like terminal + * emulations. + * + * This function must be called before starting the process. + * + * @param value whether to register in utmp. + */ + void setUseUtmp(bool value); + + /** + * Get whether to register the process as a TTY login in utmp. + * + * @return whether to register in utmp + */ + bool isUseUtmp() const; + + /** + * Get the PTY device of this process. + * + * @return the PTY device + */ + KPtyDevice *pty() const; + +protected: + /** + * @reimp + */ + virtual void setupChildProcess(); + +private: + Q_PRIVATE_SLOT(d_func(), void _k_onStateChanged(QProcess::ProcessState)) +}; + +struct KPtyProcessPrivate : KProcessPrivate { + KPtyProcessPrivate() : + ptyChannels(KPtyProcess::NoChannels), + addUtmp(false) + { + } + + void _k_onStateChanged(QProcess::ProcessState newState) + { + if (newState == QProcess::NotRunning && addUtmp) + pty->logout(); + } + + KPtyDevice *pty; + KPtyProcess::PtyChannels ptyChannels; + bool addUtmp : 1; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(KPtyProcess::PtyChannels) + +#endif
new file mode 100644 --- /dev/null +++ b/gui//syntax_files/cpp.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<syntax> + <item name="keywords"> + <pattern> + \b(auto|const|double|float|int|short|struct|unsigned|break|continue|else|for|long|signed|switch|void|case|default|enum|goto|register|sizeof|typedef|volatile|char|do|extern|if|return|static|union|while|asm|dynamic_cast|namespace|reinterpret_cast|try|bool|explicit|new|static_cast|typeid|catch|false|operator|template|typename|class|friend|private|this|using|const_cast|inline|public|throw|virtual|delete|mutable|protected|true|wchar_t|and|bitand|compl|not_eq|or_eq|xor_eq|and_eq|bitor|not|or|xor)\b + </pattern> + <bold>true</bold> + <foreground>#0000AA</foreground> + </item> + + <item name="defines"> + <pattern>#[^ ]*</pattern> + <foreground>#00AA00</foreground> + </item> + + <item name="quotes"> + <pattern>"([^"]|\\")*"</pattern> + <foreground>#FF0000</foreground> + </item> + + <item name="single-quotes"> + <pattern>'[^']*'</pattern> + <foreground>#FF0000</foreground> + </item> + + <item name="single-line comment"> + <pattern>//.*</pattern> + <foreground>#555555</foreground> + </item> + + <block name="multi-line comment"> + <startPattern>(?!//)/\*</startPattern> + <endPattern>\*/</endPattern> + <foreground>#555555</foreground> + </block> + + <bracket name="{}bracket"> + <startPattern>\{</startPattern> + <endPattern>\}</endPattern> + <background>#ffff00</background> + </bracket> + + <bracket name="()bracket"> + <startPattern>\(</startPattern> + <endPattern>\)</endPattern> + <background>#ffff00</background> + </bracket> + + <bracket name="[]bracket"> + <startPattern>\[</startPattern> + <endPattern>\]</endPattern> + <background>#ffff00</background> + </bracket> + + +</syntax> \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/gui//syntax_files/h.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<syntax> + <item name="keywords"> + <pattern> + \b(auto|const|double|float|int|short|struct|unsigned|break|continue|else|for|long|signed|switch|void|case|default|enum|goto|register|sizeof|typedef|volatile|char|do|extern|if|return|static|union|while|asm|dynamic_cast|namespace|reinterpret_cast|try|bool|explicit|new|static_cast|typeid|catch|false|operator|template|typename|class|friend|private|this|using|const_cast|inline|public|throw|virtual|delete|mutable|protected|true|wchar_t|and|bitand|compl|not_eq|or_eq|xor_eq|and_eq|bitor|not|or|xor)\b + </pattern> + <bold>true</bold> + <foreground>#0000AA</foreground> + </item> + + <item name="defines"> + <pattern>#[^ ]*</pattern> + <foreground>#00AA00</foreground> + </item> + + <item name="quotes"> + <pattern>"([^"]|\\")*"</pattern> + <foreground>#FF0000</foreground> + </item> + + <item name="single-quotes"> + <pattern>'[^']*'</pattern> + <foreground>#FF0000</foreground> + </item> + + <item name="single-line comment"> + <pattern>//.*</pattern> + <foreground>#555555</foreground> + </item> + + <block name="multi-line comment"> + <startPattern>(?!//)/\*</startPattern> + <endPattern>\*/</endPattern> + <foreground>#555555</foreground> + </block> + + <bracket name="{}bracket"> + <startPattern>\{</startPattern> + <endPattern>\}</endPattern> + <background>#ffff00</background> + </bracket> + + <bracket name="()bracket"> + <startPattern>\(</startPattern> + <endPattern>\)</endPattern> + <background>#ffff00</background> + </bracket> + + <bracket name="[]bracket"> + <startPattern>\[</startPattern> + <endPattern>\]</endPattern> + <background>#ffff00</background> + </bracket> + + +</syntax> \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/gui//syntax_files/m.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<syntax> + <item name="keywords"> + <pattern> + \b(all_va_args|break|case|continue|else|elseif|end_unwind_protect|global|gplot|gsplot|otherwise|persistent|replot|return|static|until|unwind_protect|unwind_protect_cleanup|varargin|varargout|casesen|cd|chdir|clear|dbclear|dbstatus|dbstop|dbtype|dbwhere|diary|echo|edit_history|__end__|format|gset|gshow|help|history|hold|iskeyword|isvarname|load|ls|mark_as_command|mislocked|mlock|more|munlock|run_history|save|set|show|type|unmark_command|which|who|whos|for|endfor|if|endif|do|until|while|endwhile|function|endfunction|switch|endswitch|try|end_try_catch|end)\b + </pattern> + <bold>true</bold> + </item> + + <item name="functions"> + <pattern> + \b(argv|e|eps|false|F_DUPFD|F_GETFD|F_GETFL|filesep|F_SETFD|F_SETFL|i|I|inf|Inf|j|J|NA|nan|NaN|O_APPEND|O_ASYNC|O_CREAT|OCTAVE_HOME|OCTAVE_VERSION|O_EXCL|O_NONBLOCK|O_RDONLY|O_RDWR|O_SYNC|O_TRUNC|O_WRONLY|pi|program_invocation_name|program_name|P_tmpdir|realmax|realmin|SEEK_CUR|SEEK_END|SEEK_SET|SIG|stderr|stdin|stdout|true|ans|automatic_replot|beep_on_error|completion_append_char|crash_dumps_octave_core|current_script_file_name|debug_on_error|debug_on_interrupt|debug_on_warning|debug_symtab_lookups|DEFAULT_EXEC_PATH|DEFAULT_LOADPATH|default_save_format|echo_executing_commands|EDITOR|EXEC_PATH|FFTW_WISDOM_PROGRAM|fixed_point_format|gnuplot_binary|gnuplot_command_axes|gnuplot_command_end|gnuplot_command_plot|gnuplot_command_replot|gnuplot_command_splot|gnuplot_command_title|gnuplot_command_using|gnuplot_command_with|gnuplot_has_frames|history_file|history_size|ignore_function_time_stamp|IMAGEPATH|INFO_FILE|INFO_PROGRAM|__kluge_procbuf_delay__|LOADPATH|MAKEINFO_PROGRAM|max_recursion_depth|octave_core_file_format|octave_core_file_limit|octave_core_file_name|output_max_field_width|output_precision|page_output_immediately|PAGER|page_screen_output|print_answer_id_name|print_empty_dimensions|print_rhs_assign_val|PS1|PS2|PS4|save_header_format_string|save_precision|saving_history|sighup_dumps_octave_core|sigterm_dumps_octave_core|silent_functions|split_long_rows|string_fill_char|struct_levels_to_print|suppress_verbose_help_message|variables_can_hide_functions|warn_assign_as_truth_value|warn_divide_by_zero|warn_empty_list_elements|warn_fortran_indexing|warn_function_name_clash|warn_future_time_stamp|warn_imag_to_real|warn_matlab_incompatible|warn_missing_semicolon|warn_neg_dim_as_zero|warn_num_to_str|warn_precedence_change|warn_reload_forces_clear|warn_resize_on_range_error|warn_separator_insert|warn_single_quote_string|warn_str_to_num|warn_undefined_return_values|warn_variable_switch_label|whos_line_format|synchrotron_1|synchrotron_2|syndtable|taylorcoeff|transport_2|transport_3|transport_4|transport_5|trisolve|waitbar|xmlread|zeta|zeta_int|aar|aarmam|ac2poly|ac2rc|acorf|acovf|addpath|ademodce|adim|adsmax|amodce|anderson_darling_cdf|anderson_darling_test|anovan|apkconst|append_save|applylut|ar2poly|ar2rc|arburg|arcext|arfit2|ar_spa|aryule|assert|au|aucapture|auload|auplot|aurecord|ausave|autumn|average_moments|awgn|azimuth|BandToFull|BandToSparse|base64encode|battery|bchpoly|bestblk|best_dir|best_dir_cov|betaln|bfgs|bfgsmin_example|bi2de|biacovf|bilinear|bisdemo|bispec|biterr|blkdiag|blkproc|bmpwrite|bone|bound_convex|boxcar|boxplot|brighten|bs_gradient|butter|buttord|bwborder|bweuler|bwlabel|bwmorph|bwselect|calendar|cceps|cdiff|cellstr|char|cheb|cheb1ord|cheb2ord|chebwin|cheby1|cheby2|chirp|clf|clip|cmpermute|cmunique|cohere|col2im|colfilt|colorgradient|comms|compand|complex|concat|conndef|content|contents|Contents|contourf|convhull|convmtx|cool|copper|corr2|cosets|count|covm|cplxpair|cquadnd|create_lookup_table|crule|crule2d|crule2dgen|csape|csapi|csd|csvread|csvwrite|ctranspose|cumtrapz|czt|d2_min|datenum|datestr|datevec|dct|dct2|dctmtx|de2bi|deal|decimate|decode|deg2rad|del2|delaunay|delaunay3|delta_method|demo|demodmap|deriv|detrend|dfdp|dftmtx|dhbar|dilate|dispatch|distance|dlmread|dlmwrite|dos|double|drawnow|durlev|dxfwrite|edge|edit|ellip|ellipdemo|ellipj|ellipke|ellipord|__ellip_ws|__ellip_ws_min|encode|eomday|erode|example|ExampleEigenValues|ExampleGenEigenValues|expdemo|expfit|eyediagram|factor|factorial|fail|fcnchk|feedback|fem_test|ff2n|fftconv2|fieldnames|fill|fill3|filter2|filtfilt|filtic|findsym|fir1|fir2|fixedpoint|flag|flag_implicit_samplerate|flattopwin|flix|float|fmin|fminbnd|fmins|fminunc|fnder|fnplt|fnval|fplot|freqs|freqs_plot|fsort|fullfact|FullToBand|funm|fzero|gammaln|gapTest|gaussian|gausswin|gconv|gconvmtx|gdeconv|gdftmtx|gen2par|geomean|getfield|getfields|gfft|gftable|gfweight|gget|gifft|ginput|gmm_estimate|gmm_example|gmm_obj|gmm_results|gmm_variance|gmm_variance_inefficient|gquad|gquad2d|gquad2d6|gquad2dgen|gquad6|gquadnd|grace_octave_path|gradient|grayslice|grep|grid|griddata|groots|grpdelay|grule|grule2d|grule2dgen|hadamard|hammgen|hankel|hann|harmmean|hilbert|histeq|histfit|histo|histo2|histo3|histo4|hot|hsv|hup|idct|idct2|idplot|idsim|ifftshift|im2bw|im2col|imadjust|imginfo|imhist|imnoise|impad|impz|imread|imrotate|imshear|imtranslate|imwrite|innerfun|inputname|interp|interp1|interp2|interpft|intersect|invest0|invest1|invfdemo|invfreq|invfreqs|invfreqz|inz|irsa_act|irsa_actcore|irsa_check|irsa_dft|irsa_dftfp|irsa_genreal|irsa_idft|irsa_isregular|irsa_jitsp|irsa_mdsp|irsa_normalize|irsa_plotdft|irsa_resample|irsa_rgenreal|isa|isbw|isdir|isequal|isfield|isgray|isind|ismember|isprime|isrgb|issparse|isunix|jet|kaiser|kaiserord|lambertw|lattice|lauchli|leasqr|leasqrdemo|legend|legendre|levinson|lin2mu|line_min|lloyds|lookup|lookup_table|lpc|lp_test|mad|magic|makelut|MakeShears|map|mat2gray|mat2str|mdsmax|mean2|medfilt2|meshc|minimize|minpol|mkpp|mktheta|mle_estimate|mle_example|mle_obj|mle_results|mle_variance|modmap|mu2lin|mvaar|mvar|mvfilter|mvfreqz|myfeval|nanmax|nanmean|nanmedian|nanmin|nanstd|nansum|ncauer|nchoosek|ncrule|ndims|nelder_mead_min|newmark|nlfilter|nlnewmark|__nlnewmark_fcn__|nmsmax|nonzeros|normplot|now|nrm|nthroot|nze|OCTAVE_FORGE_VERSION|ode23|ode45|ode78|optimset|ordfilt2|orient|pacf|padarray|parameterize|parcor|pareto|pascal|patch|pburg|pcg|pchip|pcolor|pcr|peaks|penddot|pendulum|perms|pie|pink|plot3|__plt3__|poly2ac|poly2ar|poly_2_ex|poly2mask|poly2rc|poly2sym|poly2th|polyarea|polyconf|polyder|polyderiv|polygcd|polystab|__power|ppval|prctile|prettyprint|prettyprint_c|primes|princomp|print|prism|proplan|pulstran|pwelch|pyulear|qaskdeco|qaskenco|qtdecomp|qtgetblk|qtsetblk|quad2dc|quad2dcgen|quad2dg|quad2dggen|quadc|quadg|quadl|quadndg|quantiz|quiver|rad2deg|rainbow|randerr|randint|randsrc|rat|rats|rc2ac|rc2ar|rc2poly|rceps|read_options|read_pdb|rectpuls|resample|rgb2gray|rk2fixed|rk4fixed|rk8fixed|rmfield|rmle|rmpath|roicolor|rosser|rotparams|rotv|rref|rsdecof|rsencof|rsgenpoly|samin_example|save_vrml|sbispec|scale_data|scatter|scatterplot|select_3D_points|selmo|setdiff|setfield|setfields|setxor|sftrans|sgolay|sgolayfilt|sinvest1|slurp_file|sortrows|sound|soundsc|spdiags|specgram|speed|speye|spfun|sphcat|spline|splot|spones|sprand|sprandn|spring|spstats|spsum|sp_test|sptest|spvcat|spy|std2|stem|str2double|strcmpi|stretchlim|strfind|strmatch|strncmp|strncmpi|strsort|strtok|strtoz|struct|strvcat|summer|sumskipnan|surf|surfc|sym2poly|symerr|symfsolve|tabulate|tar|temp_name|test|test_d2_min_1|test_d2_min_2|test_d2_min_3|test_ellipj|test_fminunc_1|testimio|test_inline_1|test_min_1|test_min_2|test_min_3|test_min_4|test_minimize_1|test_nelder_mead_min_1|test_nelder_mead_min_2|test_sncndn|test_struct|test_vmesh|test_vrml_faces|test_wpolyfit|text|textread|tf2zp|tfe|thfm|tics|toeplitz|toggle_grace_use|transpose|trapz|triang|tril|trimmean|tripuls|trisolve|triu|tsademo|tsearchdemo|ucp|uintlut|unique|unix|unmkpp|unscale_parameters|vec2mat|view|vmesh|voronoi|voronoin|vrml_arrow|vrml_Background|vrml_browse|vrml_cyl|vrml_demo_tutorial_1|vrml_demo_tutorial_2|vrml_demo_tutorial_3|vrml_demo_tutorial_4|vrml_ellipsoid|vrml_faces|vrml_flatten|vrml_frame|vrml_group|vrml_kill|vrml_lines|vrml_material|vrml_parallelogram|vrml_PointLight|vrml_points|vrml_select_points|vrml_surf|vrml_text|vrml_thick_surf|vrml_transfo|wavread|wavwrite|weekday|wgn|white|wilkinson|winter|wpolyfit|wpolyfitdemo|write_pdb|wsolve|xcorr|xcorr2|xcov|xlsread|xmlwrite|y2res|zero_count|zoom|zp2tf|zplane|shell_cmd|show|sign|sin|sinh|size|sizeof|sleep|sort|source|splice|sprintf|sqrt|squeeze|sscanf|stat|str2func|streamoff|struct|struct2cell|sum|sumsq|symlink|system|tan|tanh|tilde_expand|tmpfile|tmpnam|toascii|__token_count__|tolower|toupper|type|typeinfo|uint16|uint32|uint64|uint8|umask|undo_string_escapes|unlink|unmark_command|usage|usleep|va_arg|va_start|vectorize|vertcat|vr_val|waitpid|warning|warranty|which|who|whos|zeros|airy|balance|besselh|besseli|besselj|besselk|bessely|betainc|chol|colloc|daspk|daspk_options|dasrt|dasrt_options|dassl|dassl_options|det|eig|endgrent|endpwent|expm|fft|fft2|fftn|fftw_wisdom|filter|find|fsolve|fsolve_options|gammainc|gcd|getgrent|getgrgid|getgrnam|getpwent|getpwnam|getpwuid|getrusage|givens|gmtime|hess|ifft|ifft2|ifftn|inv|inverse|kron|localtime|lpsolve|lpsolve_options|lsode|lsode_options|lu|max|min|minmax|mktime|odessa|odessa_options|pinv|qr|quad|quad_options|qz|rand|randn|schur|setgrent|setpwent|sort|sqrtm|strftime|strptime|svd|syl|time|abcddim|__abcddims__|acot|acoth|acsc|acsch|analdemo|anova|arch_fit|arch_rnd|arch_test|are|arma_rnd|asctime|asec|asech|autocor|autocov|autoreg_matrix|axis|axis2dlim|__axis_label__|bar|bartlett|bartlett_test|base2dec|bddemo|beep|bessel|beta|beta_cdf|betai|beta_inv|beta_pdf|beta_rnd|bin2dec|bincoeff|binomial_cdf|binomial_inv|binomial_pdf|binomial_rnd|bitcmp|bitget|bitset|blackman|blanks|bode|bode_bounds|__bodquist__|bottom_title|bug_report|buildssic|c2d|cart2pol|cart2sph|cauchy_cdf|cauchy_inv|cauchy_pdf|cauchy_rnd|cellidx|center|chisquare_cdf|chisquare_inv|chisquare_pdf|chisquare_rnd|chisquare_test_homogeneity|chisquare_test_independence|circshift|clock|cloglog|close|colormap|columns|com2str|comma|common_size|commutation_matrix|compan|complement|computer|cond|contour|controldemo|conv|cor|corrcoef|cor_test|cot|coth|cov|cputime|create_set|cross|csc|csch|ctime|ctrb|cut|d2c|damp|dare|date|dcgain|deal|deblank|dec2base|dec2bin|dec2hex|deconv|delete|DEMOcontrol|demoquat|detrend|dezero|dgkfdemo|dgram|dhinfdemo|diff|diffpara|dir|discrete_cdf|discrete_inv|discrete_pdf|discrete_rnd|dkalman|dlqe|dlqg|dlqr|dlyap|dmr2d|dmult|dot|dre|dump_prefs|duplication_matrix|durbinlevinson|empirical_cdf|empirical_inv|empirical_pdf|empirical_rnd|erfinv|__errcomm__|errorbar|__errplot__|etime|exponential_cdf|exponential_inv|exponential_pdf|exponential_rnd|f_cdf|fftconv|fftfilt|fftshift|figure|fileparts|findstr|f_inv|fir2sys|flipdim|fliplr|flipud|flops|f_pdf|fractdiff|frdemo|freqchkw|__freqresp__|freqz|freqz_plot|f_rnd|f_test_regression|fullfile|fv|fvl|gamma_cdf|gammai|gamma_inv|gamma_pdf|gamma_rnd|geometric_cdf|geometric_inv|geometric_pdf|geometric_rnd|gls|gram|gray|gray2ind|grid|h2norm|h2syn|hamming|hankel|hanning|hex2dec|hilb|hinf_ctr|hinfdemo|hinfnorm|hinfsyn|hinfsyn_chk|hinfsyn_ric|hist|hotelling_test|hotelling_test_2|housh|hsv2rgb|hurst|hypergeometric_cdf|hypergeometric_inv|hypergeometric_pdf|hypergeometric_rnd|image|imagesc|impulse|imshow|ind2gray|ind2rgb|ind2sub|index|int2str|intersection|invhilb|iqr|irr|isa|is_abcd|is_bool|is_complex|is_controllable|isdefinite|is_detectable|is_dgkf|is_digital|is_duplicate_entry|is_global|is_leap_year|isletter|is_list|is_matrix|is_observable|ispc|is_sample|is_scalar|isscalar|is_signal_list|is_siso|is_square|issquare|is_stabilizable|is_stable|isstr|is_stream|is_struct|is_symmetric|issymmetric|isunix|is_vector|isvector|jet707|kendall|kolmogorov_smirnov_cdf|kolmogorov_smirnov_test|kolmogorov_smirnov_test_2|kruskal_wallis_test|krylov|krylovb|kurtosis|laplace_cdf|laplace_inv|laplace_pdf|laplace_rnd|lcm|lin2mu|listidx|list_primes|loadaudio|loadimage|log2|logical|logistic_cdf|logistic_inv|logistic_pdf|logistic_regression|logistic_regression_derivatives|logistic_regression_likelihood|logistic_rnd|logit|loglog|loglogerr|logm|lognormal_cdf|lognormal_inv|lognormal_pdf|lognormal_rnd|logspace|lower|lqe|lqg|lqr|lsim|ltifr|lyap|mahalanobis|manova|mcnemar_test|mean|meansq|median|menu|mesh|meshdom|meshgrid|minfo|mod|moddemo|moment|mplot|mu2lin|multiplot|nargchk|nextpow2|nichols|norm|normal_cdf|normal_inv|normal_pdf|normal_rnd|not|nper|npv|ntsc2rgb|null|num2str|nyquist|obsv|ocean|ols|oneplot|ord2|orth|__outlist__|pack|packedform|packsys|parallel|paren|pascal_cdf|pascal_inv|pascal_pdf|pascal_rnd|path|periodogram|perror|place|playaudio|plot|plot_border|__plr__|__plr1__|__plr2__|__plt__|__plt1__|__plt2__|__plt2mm__|__plt2mv__|__plt2ss__|__plt2vm__|__plt2vv__|__pltopt__|__pltopt1__|pmt|poisson_cdf|poisson_inv|poisson_pdf|poisson_rnd|pol2cart|polar|poly|polyder|polyderiv|polyfit|polyinteg|polyout|polyreduce|polyval|polyvalm|popen2|postpad|pow2|ppplot|prepad|probit|prompt|prop_test_2|pv|pvl|pzmap|qconj|qcoordinate_plot|qderiv|qderivmat|qinv|qmult|qqplot|qtrans|qtransv|qtransvmat|quaternion|qzhess|qzval|randperm|range|rank|ranks|rate|record|rectangle_lw|rectangle_sw|rem|repmat|residue|rgb2hsv|rgb2ind|rgb2ntsc|rindex|rldemo|rlocus|roots|rot90|rotdim|rotg|rows|run_cmd|run_count|run_test|saveaudio|saveimage|sec|sech|semicolon|semilogx|semilogxerr|semilogy|semilogyerr|series|setaudio|setstr|shg|shift|shiftdim|sign_test|sinc|sinetone|sinewave|skewness|sombrero|sortcom|spearman|spectral_adf|spectral_xdf|spencer|sph2cart|split|ss|ss2sys|ss2tf|ss2zp|stairs|starp|statistics|std|stdnormal_cdf|stdnormal_inv|stdnormal_pdf|stdnormal_rnd|step|__stepimp__|stft|str2mat|str2num|strappend|strcat|strcmp|strerror|strjust|strrep|struct_contains|struct_elements|studentize|sub2ind|subplot|substr|subwindow|swap|swapcols|swaprows|sylvester_matrix|synthesis|sys2fir|sys2ss|sys2tf|sys2zp|sysadd|sysappend|syschnames|__syschnamesl__|syschtsam|__sysconcat__|sysconnect|syscont|__syscont_disc__|__sysdefioname__|__sysdefstname__|sysdimensions|sysdisc|sysdup|sysgetsignals|sysgettsam|sysgettype|sysgroup|__sysgroupn__|sysidx|sysmin|sysmult|sysout|sysprune|sysreorder|sysrepdemo|sysscale|syssetsignals|syssub|sysupdate|table|t_cdf|tempdir|tempname|texas_lotto|tf|tf2ss|tf2sys|__tf2sysl__|tf2zp|__tfl__|tfout|tic|t_inv|title|toc|toeplitz|top_title|t_pdf|trace|triangle_lw|triangle_sw|tril|triu|t_rnd|t_test|t_test_2|t_test_regression|tzero|tzero2|ugain|uniform_cdf|uniform_inv|uniform_pdf|uniform_rnd|union|unix|unpacksys|unwrap|upper|u_test|values|vander|var|var_test|vec|vech|version|vol|weibull_cdf|weibull_inv|weibull_pdf|weibull_rnd|welch_test|wgt1o|wiener_rnd|wilcoxon_test|xlabel|xor|ylabel|yulewalker|zgfmul|zgfslv|zginit|__zgpbal__|zgreduce|zgrownorm|zgscal|zgsgiv|zgshsr|zlabel|zp|zp2ss|__zp2ssg2__|zp2sys|zp2tf|zpout|z_test)\b + </pattern> + <foreground>#0000BB</foreground> + <bold>false</bold> + </item> + + <item name="variables"> + <pattern>[a-zA-Z_][a-zA-Z_0-9]*'?</pattern> + <bold>false</bold> + </item> + + <item name="numbers"> + <pattern>(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?[ij]?</pattern> + <foreground>#BB0000</foreground> + <bold>false</bold> + </item> + + <item name="quotes"> + <pattern>"([^"]|\\")*"</pattern> + <foreground>#FF0000</foreground> + </item> + + <item name="single-quotes"> + <pattern>'([^']|\\')*'</pattern> + <foreground>#FF0000</foreground> + </item> + + <block name="multi-line comment #{"> + <startPattern>#\{</startPattern> + <endPattern>#\}</endPattern> + <foreground>#008800</foreground> + </block> + + <block name="multi-line comment %{"> + <startPattern>%\{</startPattern> + <endPattern>%\}</endPattern> + <foreground>#008800</foreground> + </block> + + <item name="single-line comment"> + <pattern>[#|%].*</pattern> + <foreground>#008800</foreground> + </item> + + <bracket name="{}bracket"> + <startPattern>\{</startPattern> + <endPattern>\}</endPattern> + <background>#ffff00</background> + </bracket> + + <bracket name="()bracket"> + <startPattern>\(</startPattern> + <endPattern>\)</endPattern> + <background>#ffff00</background> + </bracket> + + <bracket name="[]bracket"> + <startPattern>\[</startPattern> + <endPattern>\]</endPattern> + <background>#ffff00</background> + </bracket> + + +</syntax> \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/gui//syntax_files/sh.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<syntax> + <item name="comment"> + <pattern>#[^ ]*</pattern> + <foreground>#00AA00</foreground> + </item> + + <item name="quotes"> + <pattern>"[^"]*"</pattern> + <foreground>#FF0000</foreground> + </item> + + <item name="single-quotes"> + <pattern>'[^']*'</pattern> + <foreground>#FF0000</foreground> + </item> +</syntax> \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/gui/Doxyfile @@ -0,0 +1,1661 @@ +# Doxyfile 1.7.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 = Quint + +# 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 = 0.4 + +# 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 = /home/jacob/Desktop/Quint-Documentation + +# 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 = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# 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 = + +# 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 = 8 + +# 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 = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If 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 = YES + +# 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 = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = 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 <command> <input-file>, where <command> is the value of +# the FILE_VERSION_FILTER tag, and <input-file> 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 = /home/jacob/Desktop/gnu-octave/gui/src + +# 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 = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.vhd \ + *.vhdl + +# 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 <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> 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 = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# 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 = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the stylesheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# 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 + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# 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 = org.doxygen.Project + +# 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 +# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters"> +# Qt Help Project / Custom Filters</a>. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes"> +# Qt Help Project / Filter Attributes</a>. + +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 = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index 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 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvances is that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = 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 = + +# 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 = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will 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.ttf + +# 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 = YES + +# 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 = YES + +# 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 = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES
new file mode 100644 --- /dev/null +++ b/gui/languages/german @@ -0,0 +1,175 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0" language="de_DE"> +<context> + <name>FilesDockWidget</name> + <message> + <location filename="../src/FilesDockWidget.cpp" line="9"/> + <source>Current Folder</source> + <translation>Aktuelles Verzeichnis</translation> + </message> +</context> +<context> + <name>HistoryDockWidget</name> + <message> + <location filename="../src/HistoryDockWidget.cpp" line="41"/> + <source>Command History</source> + <translation>Befehlshistorie</translation> + </message> + <message> + <location filename="../src/HistoryDockWidget.cpp" line="60"/> + <source>History updated.</source> + <translation>Befehlshistorie aktualisiert.</translation> + </message> +</context> +<context> + <name>MainWindow</name> + <message> + <location filename="../src/MainWindow.cpp" line="42"/> + <source>Opening file.</source> + <translation>Öffne Datei.</translation> + </message> + <message> + <location filename="../src/MainWindow.cpp" line="68"/> + <source>Save Workspace</source> + <translation>Speichere Arbeitsumgebung</translation> + </message> + <message> + <location filename="../src/MainWindow.cpp" line="76"/> + <source>Load Workspace</source> + <translation>Lade Arbeitsumgebung</translation> + </message> + <message> + <location filename="../src/MainWindow.cpp" line="94"/> + <source>Saving data and shutting down.</source> + <translation>Speichere Daten und schließe.</translation> + </message> + <message> + <location filename="../src/MainWindow.cpp" line="119"/> + <source>Octave Toolbar</source> + <translation>Octave Werkzeugleiste</translation> + </message> + <message> + <location filename="../src/MainWindow.cpp" line="128"/> + <source>Command Window</source> + <translation>Konsolenfenster</translation> + </message> + <message> + <location filename="../src/MainWindow.cpp" line="129"/> + <source>File Editor</source> + <translation>Dateieditor</translation> + </message> + <message> + <location filename="../src/MainWindow.cpp" line="130"/> + <source>Documentation</source> + <translation>Dokumentation</translation> + </message> + <message> + <location filename="../src/MainWindow.cpp" line="131"/> + <source>Service</source> + <translation>Service</translation> + </message> + <message> + <location filename="../src/MainWindow.cpp" line="179"/> + <source>Established link to Octave.</source> + <translation>Verbindung zu Octave hergestellt.</translation> + </message> +</context> +<context> + <name>NumberBar</name> + <message> + <location filename="../src/NumberedCodeEdit.cpp" line="189"/> + <source>Stop Here</source> + <translation>Stoppe hier</translation> + </message> + <message> + <location filename="../src/NumberedCodeEdit.cpp" line="192"/> + <source>Current Line</source> + <translation>Aktuelle Zeile</translation> + </message> + <message> + <location filename="../src/NumberedCodeEdit.cpp" line="195"/> + <source>Error Line</source> + <translation>Fehlerzeile</translation> + </message> +</context> +<context> + <name>NumberedCodeEdit</name> + <message> + <location filename="../src/NumberedCodeEdit.cpp" line="387"/> + <source>This file name is not valid.</source> + <translation>Dieser Dateiname ist nicht gültig.</translation> + </message> + <message> + <location filename="../src/NumberedCodeEdit.cpp" line="388"/> + <source>Octave doesn't understand this file name: +</source> + <translation>Octave versteht diesen Dateityp nicht:</translation> + </message> + <message> + <location filename="../src/NumberedCodeEdit.cpp" line="388"/> + <source> +Please, change it. + Do you want to save your changes?</source> + <translation>Bitte ändern Sie dies. Möchten Sie Ihre Änderungen sichern?</translation> + </message> +</context> +<context> + <name>VariablesDockWidget</name> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="15"/> + <source>Name</source> + <translation>Bezeichner</translation> + </message> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="15"/> + <source>Type</source> + <translation>Typ</translation> + </message> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="15"/> + <source>Value</source> + <translation>Wert</translation> + </message> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="21"/> + <source>Workspace</source> + <translation>Arbeitsumgebung</translation> + </message> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="29"/> + <source>Save</source> + <translation>Sichern</translation> + </message> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="30"/> + <source>Load</source> + <translation>Laden</translation> + </message> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="31"/> + <source>Clear</source> + <translation>Löschen</translation> + </message> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="46"/> + <source>Local</source> + <translation>Lokal</translation> + </message> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="50"/> + <source>Global</source> + <translation>Global</translation> + </message> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="54"/> + <source>Persistent</source> + <translation>Persistent</translation> + </message> + <message> + <location filename="../src/VariablesDockWidget.cpp" line="58"/> + <source>Hidden</source> + <translation>Versteckt</translation> + </message> +</context> +</TS>
new file mode 100644 index 0000000000000000000000000000000000000000..13cd427ba4fa69ba638a54f79dbb25ff3e02654c GIT binary patch literal 2753 zc$}41U1(fI6h52%Np_oTYAX^n#1Vu_4EqqMXpn~dHfq`gHY6%Ryt{Xjz3$za<=)v1 z2|g);2tJA6gFg5qh-fJ&Rk2oR!79ZE(Kq!$L8wYauoMyWo4GglZqnP0vh1DNxpThr zo$t&!vk#xMhrYP>`m0y&8UFO?H$VAqnuva<>~TfZdxf$K$BFtTDErzaj29?-fn)rO z9=-oLydR?S(o00y8&rPcS*)jN{rUHa@?)vX=UyPnY@~iWf&J<Ap5j&P@A)kK#+Ppq zrS49jPnU>tuVj9F9Cp3s%(b`AV|+PtBYy<@#<FLRevf@;a`%1!|Cy_~sh_{W_)eer zr3d^dKl;$0cv|_(Ux8O<yfFCrMfl%2H2%?tc)i~L;6Lxe@5ciRUr&PHv4Km!7P0<b zas34L_qfGB+^2y5%pjc=*f-<=>`WRvMrn%bq^L!lTzokB#Ap?xM>}{rRHYgTjDDXc zlEb$+)ABqn6q}c>&AnbM4bD3*_h)6bzUJ7QT%|Pj2u%ZF1K%2ueLR5zUlQ03@d$5l zMm8Fju-Ss+E9q@(z}v$4KFEpgkf3gb;aGUD-#I1}MWx8y;Sid&nzp65UFza*gdPL7 z0FE@`2;*BApTO;qBn)BA5w_gYh=o&@BZ8j>>Q0&kNFCG~210?N0zJS-FavPkYCu{` zIZ`B{OozFtz*3M6G(npPdj~T{OURbSZ5t_SX6L5KsS5MkDa2iKzOpK=<JY*&Tu0QI zl5DA}tTrzt`Mv`YSO!UpYU62ykMIuZL%`;22SO&<-$#qEw+*)`69YFGzR^VUtOW7I zvMx#sno6(kH?1m90v+8K^qL8}ZVqn?pssOZ1c+gSAlZtPZpBLEIz-<h)f-@=LF2e% z^#+!jTsUHrZ6NAIy~e3b&8UVnt^W~uSrhCU`9vc8E>(ef%N!8~H&s(FI&DAT8wW80 zH#!Csg2nXt^=mDq%!=F+iBT2dRy%7O$FZorW$v||WU9kAbs0ua8tQHbLjP`GS#*Sf z$)9dDD%_j4yzu<ZhlL+^r%wCAoaagJ*4AZnUYkZVW?DyQ3Fpg7HrWFAl8oCZJe@FQ zOn)-EY7$!Ia@cunGx}7*{1ja`n(t;}ch4hO3-WX^!U#$@*ynd-Q_y5Y?t#Lx%PpUe zv1-i{o18hSRA94`ZOazhvY;7C*Vjh|3tqU%pVW~Y4kF?9jI_BfP7~1LzOrc>mqDM< zwBv9BVsQm1+J=J6+zCO56A{hPu&`{&eQ`{&mcadhCWC{eYL0J8g$b*{CknTwHjB@$ zYj2Nhfmy@Q4Jx{m1cQcun+E4#7N79pl|7Cbeh8^p+j5<t^Cy!iLZRzn;qZ<Qot;4* zOvgC7YIzRY=S|mMKb-^*iIT=06dC4Dy0T)qT`iA<%{@p%RhLX`zu?$57hUZOQG<;M zXLJia&`MuSoous<vU&j7+s#$n`5&RPnlh&$_5t?GVX$F))^}oD%gZ=8xRTKhx}p?K zX+<Ry``7)mt7VQ>^hu5_R<@hnE&9UCC1#mjwcJ*BqmlpE@bi0meIne8c+rze{R^V| BTj2lz
new file mode 100644 index 0000000000000000000000000000000000000000..919ba14e57ef0be670b475a5ce5614ac477a3e04 GIT binary patch literal 12661 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 B{*(X!
new file mode 100644 index 0000000000000000000000000000000000000000..8d014cbea8d1e5745038c7024a7e513067ef3a24 GIT binary patch literal 2474 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
new file mode 100644 index 0000000000000000000000000000000000000000..d05aed4e133cf47bae7ff4ead6405862073e0a33 GIT binary patch literal 27810 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 Y*gBAQf`3K+-`8StQp%Dw5?@392fzQ;-v9sr
new file mode 100644 index 0000000000000000000000000000000000000000..77b128c4d9f919d3e220d31f0ef679e4be12f08d GIT binary patch literal 43423 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 MGAhzlAk(1#0WcE#=Kufz
new file mode 100644 index 0000000000000000000000000000000000000000..582581539bb1f74db61925f2b201ab75b490fea5 GIT binary patch literal 15401 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 T-$=p#egex#DoRv+GYtG6SYdYL
new file mode 100644 --- /dev/null +++ b/gui/src/BlockArray.cpp @@ -0,0 +1,332 @@ +/* + This file is part of Konsole, an X terminal. + Copyright 2000 by Stephan Kulow <coolo@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "BlockArray.h" + +// System +#include <assert.h> +#include <sys/mman.h> +#include <sys/param.h> +#include <unistd.h> +#include <stdio.h> + +#define KDE_fseek ::fseek +#define KDE_lseek ::lseek + +static int blocksize = 0; + +BlockArray::BlockArray() + : size(0), + current(size_t(-1)), + index(size_t(-1)), + lastmap(0), + lastmap_index(size_t(-1)), + lastblock(0), ion(-1), + length(0) +{ + // lastmap_index = index = current = size_t(-1); + if (blocksize == 0) + blocksize = ((sizeof(Block) / getpagesize()) + 1) * getpagesize(); + +} + +BlockArray::~BlockArray() +{ + setHistorySize(0); + assert(!lastblock); +} + +size_t BlockArray::append(Block *block) +{ + if (!size) + return size_t(-1); + + ++current; + if (current >= size) current = 0; + + int rc; + rc = KDE_lseek(ion, current * blocksize, SEEK_SET); if (rc < 0) { perror("HistoryBuffer::add.seek"); setHistorySize(0); return size_t(-1); } + rc = write(ion, block, blocksize); if (rc < 0) { perror("HistoryBuffer::add.write"); setHistorySize(0); return size_t(-1); } + + length++; + if (length > size) length = size; + + ++index; + + delete block; + return current; +} + +size_t BlockArray::newBlock() +{ + if (!size) + return size_t(-1); + append(lastblock); + + lastblock = new Block(); + return index + 1; +} + +Block *BlockArray::lastBlock() const +{ + return lastblock; +} + +bool BlockArray::has(size_t i) const +{ + if (i == index + 1) + return true; + + if (i > index) + return false; + if (index - i >= length) + return false; + return true; +} + +const Block* BlockArray::at(size_t i) +{ + if (i == index + 1) + return lastblock; + + if (i == lastmap_index) + return lastmap; + + if (i > index) { + //kDebug(1211) << "BlockArray::at() i > index\n"; + return 0; + } + +// if (index - i >= length) { +// kDebug(1211) << "BlockArray::at() index - i >= length\n"; +// return 0; +// } + + size_t j = i; // (current - (index - i) + (index/size+1)*size) % size ; + + assert(j < size); + unmap(); + + Block *block = (Block*)mmap(0, blocksize, PROT_READ, MAP_PRIVATE, ion, j * blocksize); + + if (block == (Block*)-1) { perror("mmap"); return 0; } + + lastmap = block; + lastmap_index = i; + + return block; +} + +void BlockArray::unmap() +{ + if (lastmap) { + int res = munmap((char*)lastmap, blocksize); + if (res < 0) perror("munmap"); + } + lastmap = 0; + lastmap_index = size_t(-1); +} + +bool BlockArray::setSize(size_t newsize) +{ + return setHistorySize(newsize * 1024 / blocksize); +} + +bool BlockArray::setHistorySize(size_t newsize) +{ +// kDebug(1211) << "setHistorySize " << size << " " << newsize; + + if (size == newsize) + return false; + + unmap(); + + if (!newsize) { + delete lastblock; + lastblock = 0; + if (ion >= 0) close(ion); + ion = -1; + current = size_t(-1); + return true; + } + + if (!size) { + FILE* tmp = tmpfile(); + if (!tmp) { + perror("konsole: cannot open temp file.\n"); + } else { + ion = dup(fileno(tmp)); + if (ion<0) { + perror("konsole: cannot dup temp file.\n"); + fclose(tmp); + } + } + if (ion < 0) + return false; + + assert(!lastblock); + + lastblock = new Block(); + size = newsize; + return false; + } + + if (newsize > size) { + increaseBuffer(); + size = newsize; + return false; + } else { + decreaseBuffer(newsize); + if (ftruncate(ion, length*blocksize) == -1) + perror("ftruncate"); + size = newsize; + + return true; + } +} + +void moveBlock(FILE *fion, int cursor, int newpos, char *buffer2) +{ + int res = KDE_fseek(fion, cursor * blocksize, SEEK_SET); + if (res) + perror("fseek"); + res = fread(buffer2, blocksize, 1, fion); + if (res != 1) + perror("fread"); + + res = KDE_fseek(fion, newpos * blocksize, SEEK_SET); + if (res) + perror("fseek"); + res = fwrite(buffer2, blocksize, 1, fion); + if (res != 1) + perror("fwrite"); +} + +void BlockArray::decreaseBuffer(size_t newsize) +{ + if (index < newsize) // still fits in whole + return; + + int offset = (current - (newsize - 1) + size) % size; + + if (!offset) + return; + + // The Block constructor could do somthing in future... + char *buffer1 = new char[blocksize]; + + FILE *fion = fdopen(dup(ion), "w+b"); + if (!fion) { + delete [] buffer1; + perror("fdopen/dup"); + return; + } + + int firstblock; + if (current <= newsize) { + firstblock = current + 1; + } else { + firstblock = 0; + } + + size_t oldpos; + for (size_t i = 0, cursor=firstblock; i < newsize; i++) { + oldpos = (size + cursor + offset) % size; + moveBlock(fion, oldpos, cursor, buffer1); + if (oldpos < newsize) { + cursor = oldpos; + } else + cursor++; + } + + current = newsize - 1; + length = newsize; + + delete [] buffer1; + + fclose(fion); + +} + +void BlockArray::increaseBuffer() +{ + if (index < size) // not even wrapped once + return; + + int offset = (current + size + 1) % size; + if (!offset) // no moving needed + return; + + // The Block constructor could do somthing in future... + char *buffer1 = new char[blocksize]; + char *buffer2 = new char[blocksize]; + + int runs = 1; + int bpr = size; // blocks per run + + if (size % offset == 0) { + bpr = size / offset; + runs = offset; + } + + FILE *fion = fdopen(dup(ion), "w+b"); + if (!fion) { + perror("fdopen/dup"); + delete [] buffer1; + delete [] buffer2; + return; + } + + int res; + for (int i = 0; i < runs; i++) + { + // free one block in chain + int firstblock = (offset + i) % size; + res = KDE_fseek(fion, firstblock * blocksize, SEEK_SET); + if (res) + perror("fseek"); + res = fread(buffer1, blocksize, 1, fion); + if (res != 1) + perror("fread"); + int newpos = 0; + for (int j = 1, cursor=firstblock; j < bpr; j++) + { + cursor = (cursor + offset) % size; + newpos = (cursor - offset + size) % size; + moveBlock(fion, cursor, newpos, buffer2); + } + res = KDE_fseek(fion, i * blocksize, SEEK_SET); + if (res) + perror("fseek"); + res = fwrite(buffer1, blocksize, 1, fion); + if (res != 1) + perror("fwrite"); + } + current = size - 1; + length = size; + + delete [] buffer1; + delete [] buffer2; + + fclose(fion); + +} +
new file mode 100644 --- /dev/null +++ b/gui/src/BlockArray.h @@ -0,0 +1,115 @@ +/* + This file is part of Konsole, an X terminal. + Copyright 2000 by Stephan Kulow <coolo@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef BLOCKARRAY_H +#define BLOCKARRAY_H + +#include <unistd.h> + +#define BlockSize (1 << 12) +#define ENTRIES ((BlockSize - sizeof(size_t) ) / sizeof(unsigned char)) + +struct Block { + Block() { size = 0; } + unsigned char data[ENTRIES]; + size_t size; +}; + +// /////////////////////////////////////////////////////// + +class BlockArray { +public: + /** + * Creates a history file for holding + * maximal size blocks. If more blocks + * are requested, then it drops earlier + * added ones. + */ + BlockArray(); + + /// destructor + ~BlockArray(); + + /** + * adds the Block at the end of history. + * This may drop other blocks. + * + * The ownership on the block is transferred. + * An unique index number is returned for accessing + * it later (if not yet dropped then) + * + * Note, that the block may be dropped completely + * if history is turned off. + */ + size_t append(Block *block); + + /** + * gets the block at the index. Function may return + * 0 if the block isn't available any more. + * + * The returned block is strictly readonly as only + * maped in memory - and will be invalid on the next + * operation on this class. + */ + const Block *at(size_t index); + + /** + * reorders blocks as needed. If newsize is null, + * the history is emptied completely. The indices + * returned on append won't change their semantic, + * but they may not be valid after this call. + */ + bool setHistorySize(size_t newsize); + + size_t newBlock(); + + Block *lastBlock() const; + + /** + * Convenient function to set the size in KBytes + * instead of blocks + */ + bool setSize(size_t newsize); + + size_t len() const { return length; } + + bool has(size_t index) const; + + size_t getCurrent() const { return current; } + +private: + void unmap(); + void increaseBuffer(); + void decreaseBuffer(size_t newsize); + + size_t size; + // current always shows to the last inserted block + size_t current; + size_t index; + + Block *lastmap; + size_t lastmap_index; + Block *lastblock; + + int ion; + size_t length; + +}; +#endif
new file mode 100644 --- /dev/null +++ b/gui/src/BrowserWidget.cpp @@ -0,0 +1,90 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include "BrowserWidget.h" +#include <QVBoxLayout> +#include <QAction> +#include <QStyle> +#include <QApplication> + +BrowserDockWidget::BrowserDockWidget(QWidget *parent, BrowserWidget *browserWidget) + : QDockWidget(parent) { + m_browserWidget = browserWidget; + setWidget(m_browserWidget); +} + +BrowserDockWidget::~BrowserDockWidget() { +} + +BrowserWidget *BrowserDockWidget::browserWidget() { + return m_browserWidget; +} + +BrowserWidget::BrowserWidget(QWidget *parent) + : QWidget(parent) { + construct(); +} + +void BrowserWidget::construct() { + QStyle *style = QApplication::style(); + m_navigationToolBar = new QToolBar(this); + m_webView = new QWebView(this); + m_urlLineEdit = new QLineEdit(this); + m_statusBar = new QStatusBar(this); + + QAction *backAction = new QAction(style->standardIcon(QStyle::SP_ArrowLeft), + "", m_navigationToolBar); + QAction *forwardAction = new QAction(style->standardIcon(QStyle::SP_ArrowRight), + "", m_navigationToolBar); + + m_navigationToolBar->addAction(backAction); + m_navigationToolBar->addAction(forwardAction); + m_navigationToolBar->addWidget(m_urlLineEdit); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(m_navigationToolBar); + layout->addWidget(m_webView); + layout->addWidget(m_statusBar); + layout->setMargin(2); + setLayout(layout); + + connect(backAction, SIGNAL(triggered()), m_webView, SLOT(back())); + connect(forwardAction, SIGNAL(triggered()), m_webView, SLOT(forward())); + connect(m_webView, SIGNAL(urlChanged(QUrl)), this, SLOT(setUrl(QUrl))); + connect(m_urlLineEdit, SIGNAL(returnPressed()), this, SLOT(jumpToWebsite())); + //connect(m_webView, SIGNAL(statusBarMessage(QString)), this, SLOT(showMessage(QString))); +} + +void BrowserWidget::setUrl(QUrl url) { + m_urlLineEdit->setText(url.toString()); +} + +void BrowserWidget::jumpToWebsite() { + QString url = m_urlLineEdit->text(); + if(!url.startsWith("http://")) + url = "http://" + url; + load(url); +} + +void BrowserWidget::showStatusMessage(QString message) { + m_statusBar->showMessage(message, 1000); +} + +void BrowserWidget::load(QUrl url) { + m_webView->load(url); +}
new file mode 100644 --- /dev/null +++ b/gui/src/BrowserWidget.h @@ -0,0 +1,61 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#ifndef BROWSERMDISUBWINDOW_H +#define BROWSERMDISUBWINDOW_H + +#include <QWidget> +#include <QToolBar> +#include <QLineEdit> +#include <QtWebKit/QWebView> +#include <QStatusBar> +#include <QDockWidget> + +class BrowserWidget; +class BrowserDockWidget : public QDockWidget { +public: + BrowserDockWidget(QWidget *parent, BrowserWidget *browserWidget); + ~BrowserDockWidget(); + + BrowserWidget *browserWidget(); + +private: + BrowserWidget *m_browserWidget; +}; + +class BrowserWidget : public QWidget { + Q_OBJECT +public: + BrowserWidget(QWidget *parent = 0); + void load(QUrl url); + +public slots: + void setUrl(QUrl url); + void jumpToWebsite(); + void showStatusMessage(QString message); + +private: + void construct(); + + QLineEdit *m_urlLineEdit; + QToolBar *m_navigationToolBar; + QWebView *m_webView; + QStatusBar *m_statusBar; +}; + +#endif // BROWSERMDISUBWINDOW_H
new file mode 100644 --- /dev/null +++ b/gui/src/Character.h @@ -0,0 +1,220 @@ +/* + This file is part of Konsole, KDE's terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef CHARACTER_H +#define CHARACTER_H + +// Qt +#include <QtCore/QHash> + +// Local +#include "CharacterColor.h" + +typedef unsigned char LineProperty; + +static const int LINE_DEFAULT = 0; +static const int LINE_WRAPPED = (1 << 0); +static const int LINE_DOUBLEWIDTH = (1 << 1); +static const int LINE_DOUBLEHEIGHT = (1 << 2); + +#define DEFAULT_RENDITION 0 +#define RE_BOLD (1 << 0) +#define RE_BLINK (1 << 1) +#define RE_UNDERLINE (1 << 2) +#define RE_REVERSE (1 << 3) // Screen only +#define RE_INTENSIVE (1 << 3) // Widget only +#define RE_CURSOR (1 << 4) +#define RE_EXTENDED_CHAR (1 << 5) + +/** + * A single character in the terminal which consists of a unicode character + * value, foreground and background colors and a set of rendition attributes + * which specify how it should be drawn. + */ +class Character +{ +public: + /** + * Constructs a new character. + * + * @param _c The unicode character value of this character. + * @param _f The foreground color used to draw the character. + * @param _b The color used to draw the character's background. + * @param _r A set of rendition flags which specify how this character is to be drawn. + */ + inline Character(quint16 _c = ' ', + CharacterColor _f = CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR), + CharacterColor _b = CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR), + quint8 _r = DEFAULT_RENDITION) + : character(_c), rendition(_r), foregroundColor(_f), backgroundColor(_b) {} + + union + { + /** The unicode character value for this character. */ + quint16 character; + /** + * Experimental addition which allows a single Character instance to contain more than + * one unicode character. + * + * charSequence is a hash code which can be used to look up the unicode + * character sequence in the ExtendedCharTable used to create the sequence. + */ + quint16 charSequence; + }; + + /** A combination of RENDITION flags which specify options for drawing the character. */ + quint8 rendition; + + /** The foreground color used to draw this character. */ + CharacterColor foregroundColor; + /** The color used to draw this character's background. */ + CharacterColor backgroundColor; + + /** + * Returns true if this character has a transparent background when + * it is drawn with the specified @p palette. + */ + bool isTransparent(const ColorEntry* palette) const; + /** + * Returns true if this character should always be drawn in bold when + * it is drawn with the specified @p palette, independent of whether + * or not the character has the RE_BOLD rendition flag. + */ + ColorEntry::FontWeight fontWeight(const ColorEntry* base) const; + + /** + * returns true if the format (color, rendition flag) of the compared characters is equal + */ + bool equalsFormat(const Character &other) const; + + /** + * Compares two characters and returns true if they have the same unicode character value, + * rendition and colors. + */ + friend bool operator == (const Character& a, const Character& b); + /** + * Compares two characters and returns true if they have different unicode character values, + * renditions or colors. + */ + friend bool operator != (const Character& a, const Character& b); +}; + +inline bool operator == (const Character& a, const Character& b) +{ + return a.character == b.character && + a.rendition == b.rendition && + a.foregroundColor == b.foregroundColor && + a.backgroundColor == b.backgroundColor; +} + +inline bool operator != (const Character& a, const Character& b) +{ + return a.character != b.character || + a.rendition != b.rendition || + a.foregroundColor != b.foregroundColor || + a.backgroundColor != b.backgroundColor; +} + +inline bool Character::isTransparent(const ColorEntry* base) const +{ + return ((backgroundColor._colorSpace == COLOR_SPACE_DEFAULT) && + base[backgroundColor._u+0+(backgroundColor._v?BASE_COLORS:0)].transparent) + || ((backgroundColor._colorSpace == COLOR_SPACE_SYSTEM) && + base[backgroundColor._u+2+(backgroundColor._v?BASE_COLORS:0)].transparent); +} + +inline bool Character::equalsFormat(const Character& other) const +{ + return + backgroundColor==other.backgroundColor && + foregroundColor==other.foregroundColor && + rendition==other.rendition; +} + +inline ColorEntry::FontWeight Character::fontWeight(const ColorEntry* base) const +{ + if (backgroundColor._colorSpace == COLOR_SPACE_DEFAULT) + return base[backgroundColor._u+0+(backgroundColor._v?BASE_COLORS:0)].fontWeight; + else if (backgroundColor._colorSpace == COLOR_SPACE_SYSTEM) + return base[backgroundColor._u+2+(backgroundColor._v?BASE_COLORS:0)].fontWeight; + else + return ColorEntry::UseCurrentFormat; +} + +extern unsigned short vt100_graphics[32]; + + +/** + * A table which stores sequences of unicode characters, referenced + * by hash keys. The hash key itself is the same size as a unicode + * character ( ushort ) so that it can occupy the same space in + * a structure. + */ +class ExtendedCharTable +{ +public: + /** Constructs a new character table. */ + ExtendedCharTable(); + ~ExtendedCharTable(); + + /** + * Adds a sequences of unicode characters to the table and returns + * a hash code which can be used later to look up the sequence + * using lookupExtendedChar() + * + * If the same sequence already exists in the table, the hash + * of the existing sequence will be returned. + * + * @param unicodePoints An array of unicode character points + * @param length Length of @p unicodePoints + */ + ushort createExtendedChar(ushort* unicodePoints , ushort length); + /** + * Looks up and returns a pointer to a sequence of unicode characters + * which was added to the table using createExtendedChar(). + * + * @param hash The hash key returned by createExtendedChar() + * @param length This variable is set to the length of the + * character sequence. + * + * @return A unicode character sequence of size @p length. + */ + ushort* lookupExtendedChar(ushort hash , ushort& length) const; + + /** The global ExtendedCharTable instance. */ + static ExtendedCharTable instance; +private: + // calculates the hash key of a sequence of unicode points of size 'length' + ushort extendedCharHash(ushort* unicodePoints , ushort length) const; + // tests whether the entry in the table specified by 'hash' matches the + // character sequence 'unicodePoints' of size 'length' + bool extendedCharMatch(ushort hash , ushort* unicodePoints , ushort length) const; + // internal, maps hash keys to character sequence buffers. The first ushort + // in each value is the length of the buffer, followed by the ushorts in the buffer + // themselves. + QHash<ushort,ushort*> extendedCharTable; +}; + +Q_DECLARE_TYPEINFO(Character, Q_MOVABLE_TYPE); + +#endif // CHARACTER_H +
new file mode 100644 --- /dev/null +++ b/gui/src/CharacterColor.h @@ -0,0 +1,290 @@ +/* + This file is part of Konsole, KDE's terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef CHARACTERCOLOR_H +#define CHARACTERCOLOR_H + +// Qt +#include <QtGui/QColor> + +/** + * An entry in a terminal display's color palette. + * + * A color palette is an array of 16 ColorEntry instances which map + * system color indexes (from 0 to 15) into actual colors. + * + * Each entry can be set as bold, in which case any text + * drawn using the color should be drawn in bold. + * + * Each entry can also be transparent, in which case the terminal + * display should avoid drawing the background for any characters + * using the entry as a background. + */ +class ColorEntry +{ +public: + /** Specifies the weight to use when drawing text with this color. */ + enum FontWeight + { + /** Always draw text in this color with a bold weight. */ + Bold, + /** Always draw text in this color with a normal weight. */ + Normal, + /** + * Use the current font weight set by the terminal application. + * This is the default behavior. + */ + UseCurrentFormat + }; + + /** + * Constructs a new color palette entry. + * + * @param c The color value for this entry. + * @param tr Specifies that the color should be transparent when used as a background color. + * @param weight Specifies the font weight to use when drawing text with this color. + */ + ColorEntry(QColor c, bool tr, FontWeight weight = UseCurrentFormat) + : color(c), transparent(tr), fontWeight(weight) {} + + /** + * Constructs a new color palette entry with an undefined color, and + * with the transparent and bold flags set to false. + */ + ColorEntry() : transparent(false), fontWeight(UseCurrentFormat) {} + + /** + * Sets the color, transparency and boldness of this color to those of @p rhs. + */ + void operator=(const ColorEntry& rhs) + { + color = rhs.color; + transparent = rhs.transparent; + fontWeight = rhs.fontWeight; + } + + /** The color value of this entry for display. */ + QColor color; + + /** + * If true character backgrounds using this color should be transparent. + * This is not applicable when the color is used to render text. + */ + bool transparent; + /** + * Specifies the font weight to use when drawing text with this color. + * This is not applicable when the color is used to draw a character's background. + */ + FontWeight fontWeight; +}; + + +// Attributed Character Representations /////////////////////////////// + +// Colors + +#define BASE_COLORS (2+8) +#define INTENSITIES 2 +#define TABLE_COLORS (INTENSITIES*BASE_COLORS) + +#define DEFAULT_FORE_COLOR 0 +#define DEFAULT_BACK_COLOR 1 + +//a standard set of colors using black text on a white background. +//defined in TerminalDisplay.cpp + +extern const ColorEntry base_color_table[TABLE_COLORS]; + +/* CharacterColor is a union of the various color spaces. + + Assignment is as follows: + + Type - Space - Values + + 0 - Undefined - u: 0, v:0 w:0 + 1 - Default - u: 0..1 v:intense w:0 + 2 - System - u: 0..7 v:intense w:0 + 3 - Index(256) - u: 16..255 v:0 w:0 + 4 - RGB - u: 0..255 v:0..256 w:0..256 + + Default colour space has two separate colours, namely + default foreground and default background colour. +*/ + +#define COLOR_SPACE_UNDEFINED 0 +#define COLOR_SPACE_DEFAULT 1 +#define COLOR_SPACE_SYSTEM 2 +#define COLOR_SPACE_256 3 +#define COLOR_SPACE_RGB 4 + +/** + * Describes the color of a single character in the terminal. + */ +class CharacterColor +{ + friend class Character; + +public: + /** Constructs a new CharacterColor whoose color and color space are undefined. */ + CharacterColor() + : _colorSpace(COLOR_SPACE_UNDEFINED), + _u(0), + _v(0), + _w(0) + {} + + /** + * Constructs a new CharacterColor using the specified @p colorSpace and with + * color value @p co + * + * The meaning of @p co depends on the @p colorSpace used. + * + * TODO : Document how @p co relates to @p colorSpace + * + * TODO : Add documentation about available color spaces. + */ + CharacterColor(quint8 colorSpace, int co) + : _colorSpace(colorSpace), + _u(0), + _v(0), + _w(0) + { + switch (colorSpace) + { + case COLOR_SPACE_DEFAULT: + _u = co & 1; + break; + case COLOR_SPACE_SYSTEM: + _u = co & 7; + _v = (co >> 3) & 1; + break; + case COLOR_SPACE_256: + _u = co & 255; + break; + case COLOR_SPACE_RGB: + _u = co >> 16; + _v = co >> 8; + _w = co; + break; + default: + _colorSpace = COLOR_SPACE_UNDEFINED; + } + } + + /** + * Returns true if this character color entry is valid. + */ + bool isValid() + { + return _colorSpace != COLOR_SPACE_UNDEFINED; + } + + /** + * Toggles the value of this color between a normal system color and the corresponding intensive + * system color. + * + * This is only applicable if the color is using the COLOR_SPACE_DEFAULT or COLOR_SPACE_SYSTEM + * color spaces. + */ + void toggleIntensive(); + + /** + * Returns the color within the specified color @p palette + * + * The @p palette is only used if this color is one of the 16 system colors, otherwise + * it is ignored. + */ + QColor color(const ColorEntry* palette) const; + + /** + * Compares two colors and returns true if they represent the same color value and + * use the same color space. + */ + friend bool operator == (const CharacterColor& a, const CharacterColor& b); + /** + * Compares two colors and returns true if they represent different color values + * or use different color spaces. + */ + friend bool operator != (const CharacterColor& a, const CharacterColor& b); + +private: + quint8 _colorSpace; + + // bytes storing the character color + quint8 _u; + quint8 _v; + quint8 _w; +}; + +inline bool operator == (const CharacterColor& a, const CharacterColor& b) +{ + return a._colorSpace == b._colorSpace && + a._u == b._u && + a._v == b._v && + a._w == b._w; +} +inline bool operator != (const CharacterColor& a, const CharacterColor& b) +{ + return !operator==(a,b); +} + +inline const QColor color256(quint8 u, const ColorEntry* base) +{ + // 0.. 16: system colors + if (u < 8) return base[u+2 ].color; u -= 8; + if (u < 8) return base[u+2+BASE_COLORS].color; u -= 8; + + // 16..231: 6x6x6 rgb color cube + if (u < 216) return QColor(((u/36)%6) ? (40*((u/36)%6)+55) : 0, + ((u/ 6)%6) ? (40*((u/ 6)%6)+55) : 0, + ((u/ 1)%6) ? (40*((u/ 1)%6)+55) : 0); u -= 216; + + // 232..255: gray, leaving out black and white + int gray = u*10+8; return QColor(gray,gray,gray); +} + +inline QColor CharacterColor::color(const ColorEntry* base) const +{ + switch (_colorSpace) + { + case COLOR_SPACE_DEFAULT: return base[_u+0+(_v?BASE_COLORS:0)].color; + case COLOR_SPACE_SYSTEM: return base[_u+2+(_v?BASE_COLORS:0)].color; + case COLOR_SPACE_256: return color256(_u,base); + case COLOR_SPACE_RGB: return QColor(_u,_v,_w); + case COLOR_SPACE_UNDEFINED: return QColor(); + } + + Q_ASSERT(false); // invalid color space + + return QColor(); +} + +inline void CharacterColor::toggleIntensive() +{ + if (_colorSpace == COLOR_SPACE_SYSTEM || _colorSpace == COLOR_SPACE_DEFAULT) + { + _v = !_v; + } +} + +#endif // CHARACTERCOLOR_H +
new file mode 100644 --- /dev/null +++ b/gui/src/ColorTables.h @@ -0,0 +1,56 @@ +#ifndef _COLOR_TABLE_H +#define _COLOR_TABLE_H + +#include "CharacterColor.h" + +static const ColorEntry whiteonblack_color_table[TABLE_COLORS] = +{ + // normal + ColorEntry(QColor(0xFF,0xFF,0xFF), 0, 0 ), ColorEntry( QColor(0x00,0x00,0x00), 1, 0 ), // Dfore, Dback + ColorEntry(QColor(0x00,0x00,0x00), 0, 0 ), ColorEntry( QColor(0xB2,0x18,0x18), 0, 0 ), // Black, Red + ColorEntry(QColor(0x18,0xB2,0x18), 0, 0 ), ColorEntry( QColor(0xB2,0x68,0x18), 0, 0 ), // Green, Yellow + ColorEntry(QColor(0x18,0x18,0xB2), 0, 0 ), ColorEntry( QColor(0xB2,0x18,0xB2), 0, 0 ), // Blue, Magenta + ColorEntry(QColor(0x18,0xB2,0xB2), 0, 0 ), ColorEntry( QColor(0xB2,0xB2,0xB2), 0, 0 ), // Cyan, White + // intensiv + ColorEntry(QColor(0x00,0x00,0x00), 0, 1 ), ColorEntry( QColor(0xFF,0xFF,0xFF), 1, 0 ), + ColorEntry(QColor(0x68,0x68,0x68), 0, 0 ), ColorEntry( QColor(0xFF,0x54,0x54), 0, 0 ), + ColorEntry(QColor(0x54,0xFF,0x54), 0, 0 ), ColorEntry( QColor(0xFF,0xFF,0x54), 0, 0 ), + ColorEntry(QColor(0x54,0x54,0xFF), 0, 0 ), ColorEntry( QColor(0xFF,0x54,0xFF), 0, 0 ), + ColorEntry(QColor(0x54,0xFF,0xFF), 0, 0 ), ColorEntry( QColor(0xFF,0xFF,0xFF), 0, 0 ) +}; + +static const ColorEntry greenonblack_color_table[TABLE_COLORS] = +{ + ColorEntry(QColor( 24, 240, 24), 0, 0), ColorEntry(QColor( 0, 0, 0), 1, 0), + ColorEntry(QColor( 0, 0, 0), 0, 0), ColorEntry(QColor( 178, 24, 24), 0, 0), + ColorEntry(QColor( 24, 178, 24), 0, 0), ColorEntry(QColor( 178, 104, 24), 0, 0), + ColorEntry(QColor( 24, 24, 178), 0, 0), ColorEntry(QColor( 178, 24, 178), 0, 0), + ColorEntry(QColor( 24, 178, 178), 0, 0), ColorEntry(QColor( 178, 178, 178), 0, 0), + // intensive colors + ColorEntry(QColor( 24, 240, 24), 0, 1 ), ColorEntry(QColor( 0, 0, 0), 1, 0 ), + ColorEntry(QColor( 104, 104, 104), 0, 0 ), ColorEntry(QColor( 255, 84, 84), 0, 0 ), + ColorEntry(QColor( 84, 255, 84), 0, 0 ), ColorEntry(QColor( 255, 255, 84), 0, 0 ), + ColorEntry(QColor( 84, 84, 255), 0, 0 ), ColorEntry(QColor( 255, 84, 255), 0, 0 ), + ColorEntry(QColor( 84, 255, 255), 0, 0 ), ColorEntry(QColor( 255, 255, 255), 0, 0 ) +}; + +static const ColorEntry blackonlightyellow_color_table[TABLE_COLORS] = +{ + ColorEntry(QColor( 0, 0, 0), 0, 0), ColorEntry(QColor( 255, 255, 221), 1, 0), + ColorEntry(QColor( 0, 0, 0), 0, 0), ColorEntry(QColor( 178, 24, 24), 0, 0), + ColorEntry(QColor( 24, 178, 24), 0, 0), ColorEntry(QColor( 178, 104, 24), 0, 0), + ColorEntry(QColor( 24, 24, 178), 0, 0), ColorEntry(QColor( 178, 24, 178), 0, 0), + ColorEntry(QColor( 24, 178, 178), 0, 0), ColorEntry(QColor( 178, 178, 178), 0, 0), + ColorEntry(QColor( 0, 0, 0), 0, 1), ColorEntry(QColor( 255, 255, 221), 1, 0), + ColorEntry(QColor(104, 104, 104), 0, 0), ColorEntry(QColor( 255, 84, 84), 0, 0), + ColorEntry(QColor( 84, 255, 84), 0, 0), ColorEntry(QColor( 255, 255, 84), 0, 0), + ColorEntry(QColor( 84, 84, 255), 0, 0), ColorEntry(QColor( 255, 84, 255), 0, 0), + ColorEntry(QColor( 84, 255, 255), 0, 0), ColorEntry(QColor( 255, 255, 255), 0, 0) +}; + + + + + +#endif +
new file mode 100644 --- /dev/null +++ b/gui/src/DefaultTranslatorText.h @@ -0,0 +1,2 @@ +"keyboard \"Fallback Key Translator\"\n" +"key Tab : \"\\t\" \0"
new file mode 100644 --- /dev/null +++ b/gui/src/Emulation.cpp @@ -0,0 +1,404 @@ +/* + Copyright 2007-2008 Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + Copyright 1996 by Matthias Ettrich <ettrich@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "Emulation.h" + +// System +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +// Qt +#include <QtGui/QApplication> +#include <QtGui/QClipboard> +#include <QtCore/QHash> +#include <QtGui/QKeyEvent> +#include <QtCore/QRegExp> +#include <QtCore/QTextStream> +#include <QtCore/QThread> + +#include <QtCore/QTime> + +// Konsole +#include "KeyboardTranslator.h" +#include "Screen.h" +#include "TerminalCharacterDecoder.h" +#include "ScreenWindow.h" + +Emulation::Emulation() : + _currentScreen(0), + _codec(0), + _decoder(0), + _keyTranslator(0), + _usesMouse(false) +{ + // create screens with a default size + _screen[0] = new Screen(40,80); + _screen[1] = new Screen(40,80); + _currentScreen = _screen[0]; + + QObject::connect(&_bulkTimer1, SIGNAL(timeout()), this, SLOT(showBulk()) ); + QObject::connect(&_bulkTimer2, SIGNAL(timeout()), this, SLOT(showBulk()) ); + + // listen for mouse status changes + connect( this , SIGNAL(programUsesMouseChanged(bool)) , + SLOT(usesMouseChanged(bool)) ); +} + +bool Emulation::programUsesMouse() const +{ + return _usesMouse; +} + +void Emulation::usesMouseChanged(bool usesMouse) +{ + _usesMouse = usesMouse; +} + +ScreenWindow* Emulation::createWindow() +{ + ScreenWindow* window = new ScreenWindow(); + window->setScreen(_currentScreen); + _windows << window; + + connect(window , SIGNAL(selectionChanged()), + this , SLOT(bufferedUpdate())); + + connect(this , SIGNAL(outputChanged()), + window , SLOT(notifyOutputChanged()) ); + return window; +} + +Emulation::~Emulation() +{ + QListIterator<ScreenWindow*> windowIter(_windows); + + while (windowIter.hasNext()) + { + delete windowIter.next(); + } + + delete _screen[0]; + delete _screen[1]; + delete _decoder; +} + +void Emulation::setScreen(int n) +{ + Screen *old = _currentScreen; + _currentScreen = _screen[n & 1]; + if (_currentScreen != old) + { + // tell all windows onto this emulation to switch to the newly active screen + foreach(ScreenWindow* window,_windows) + window->setScreen(_currentScreen); + } +} + +void Emulation::clearHistory() +{ + _screen[0]->setScroll( _screen[0]->getScroll() , false ); +} +void Emulation::setHistory(const HistoryType& t) +{ + _screen[0]->setScroll(t); + + showBulk(); +} + +const HistoryType& Emulation::history() const +{ + return _screen[0]->getScroll(); +} + +void Emulation::setCodec(const QTextCodec * qtc) +{ + if (qtc) + _codec = qtc; + else + setCodec(LocaleCodec); + + delete _decoder; + _decoder = _codec->makeDecoder(); + + emit useUtf8Request(utf8()); +} + +void Emulation::setCodec(EmulationCodec codec) +{ + if ( codec == Utf8Codec ) + setCodec( QTextCodec::codecForName("utf8") ); + else if ( codec == LocaleCodec ) + setCodec( QTextCodec::codecForLocale() ); +} + +void Emulation::setKeyBindings(const QString& name) +{ + _keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name); + if (!_keyTranslator) + { + _keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator(); + } +} + +QString Emulation::keyBindings() const +{ + return _keyTranslator->name(); +} + +void Emulation::receiveChar(int c) +// process application unicode input to terminal +// this is a trivial scanner +{ + c &= 0xff; + switch (c) + { + case '\b' : _currentScreen->backspace(); break; + case '\t' : _currentScreen->tab(); break; + case '\n' : _currentScreen->newLine(); break; + case '\r' : _currentScreen->toStartOfLine(); break; + case 0x07 : emit stateSet(NOTIFYBELL); + break; + default : _currentScreen->displayCharacter(c); break; + }; +} + +void Emulation::sendKeyEvent( QKeyEvent* ev ) +{ + emit stateSet(NOTIFYNORMAL); + + if (!ev->text().isEmpty()) + { // A block of text + // Note that the text is proper unicode. + // We should do a conversion here + emit sendData(ev->text().toUtf8(), ev->text().length()); + } +} + +void Emulation::sendString(const char*,int) +{ + // default implementation does nothing +} + +void Emulation::sendMouseEvent(int /*buttons*/, int /*column*/, int /*row*/, int /*eventType*/) +{ + // default implementation does nothing +} + +/* + We are doing code conversion from locale to unicode first. +TODO: Character composition from the old code. See #96536 +*/ + +void Emulation::receiveData(const char* text, int length) +{ + emit stateSet(NOTIFYACTIVITY); + + bufferedUpdate(); + + QString unicodeText = _decoder->toUnicode(text,length); + + //send characters to terminal emulator + for (int i=0;i<unicodeText.length();i++) + receiveChar(unicodeText[i].unicode()); + + //look for z-modem indicator + //-- someone who understands more about z-modems that I do may be able to move + //this check into the above for loop? + for (int i=0;i<length;i++) + { + if (text[i] == '\030') + { + if ((length-i-1 > 3) && (strncmp(text+i+1, "B00", 3) == 0)) + emit zmodemDetected(); + } + } +} + +void Emulation::writeToStream( TerminalCharacterDecoder* _decoder , + int startLine , + int endLine) +{ + _currentScreen->writeLinesToStream(_decoder,startLine,endLine); +} + +int Emulation::lineCount() const +{ + // sum number of lines currently on _screen plus number of lines in history + return _currentScreen->getLines() + _currentScreen->getHistLines(); +} + +#define BULK_TIMEOUT1 10 +#define BULK_TIMEOUT2 40 + +void Emulation::showBulk() +{ + _bulkTimer1.stop(); + _bulkTimer2.stop(); + + emit outputChanged(); + + _currentScreen->resetScrolledLines(); + _currentScreen->resetDroppedLines(); +} + +void Emulation::bufferedUpdate() +{ + _bulkTimer1.setSingleShot(true); + _bulkTimer1.start(BULK_TIMEOUT1); + if (!_bulkTimer2.isActive()) + { + _bulkTimer2.setSingleShot(true); + _bulkTimer2.start(BULK_TIMEOUT2); + } +} + +char Emulation::eraseChar() const +{ + return '\b'; +} + +void Emulation::setImageSize(int lines, int columns) +{ + if ((lines < 1) || (columns < 1)) + return; + + QSize screenSize[2] = { QSize(_screen[0]->getColumns(), + _screen[0]->getLines()), + QSize(_screen[1]->getColumns(), + _screen[1]->getLines()) }; + QSize newSize(columns,lines); + + if (newSize == screenSize[0] && newSize == screenSize[1]) + return; + + _screen[0]->resizeImage(lines,columns); + _screen[1]->resizeImage(lines,columns); + + emit imageSizeChanged(lines,columns); + + bufferedUpdate(); +} + +QSize Emulation::imageSize() const +{ + return QSize(_currentScreen->getColumns(), _currentScreen->getLines()); +} + +ushort ExtendedCharTable::extendedCharHash(ushort* unicodePoints , ushort length) const +{ + ushort hash = 0; + for ( ushort i = 0 ; i < length ; i++ ) + { + hash = 31*hash + unicodePoints[i]; + } + return hash; +} +bool ExtendedCharTable::extendedCharMatch(ushort hash , ushort* unicodePoints , ushort length) const +{ + ushort* entry = extendedCharTable[hash]; + + // compare given length with stored sequence length ( given as the first ushort in the + // stored buffer ) + if ( entry == 0 || entry[0] != length ) + return false; + // if the lengths match, each character must be checked. the stored buffer starts at + // entry[1] + for ( int i = 0 ; i < length ; i++ ) + { + if ( entry[i+1] != unicodePoints[i] ) + return false; + } + return true; +} +ushort ExtendedCharTable::createExtendedChar(ushort* unicodePoints , ushort length) +{ + // look for this sequence of points in the table + ushort hash = extendedCharHash(unicodePoints,length); + + // check existing entry for match + while ( extendedCharTable.contains(hash) ) + { + if ( extendedCharMatch(hash,unicodePoints,length) ) + { + // this sequence already has an entry in the table, + // return its hash + return hash; + } + else + { + // if hash is already used by another, different sequence of unicode character + // points then try next hash + hash++; + } + } + + + // add the new sequence to the table and + // return that index + ushort* buffer = new ushort[length+1]; + buffer[0] = length; + for ( int i = 0 ; i < length ; i++ ) + buffer[i+1] = unicodePoints[i]; + + extendedCharTable.insert(hash,buffer); + + return hash; +} + +ushort* ExtendedCharTable::lookupExtendedChar(ushort hash , ushort& length) const +{ + // lookup index in table and if found, set the length + // argument and return a pointer to the character sequence + + ushort* buffer = extendedCharTable[hash]; + if ( buffer ) + { + length = buffer[0]; + return buffer+1; + } + else + { + length = 0; + return 0; + } +} + +ExtendedCharTable::ExtendedCharTable() +{ +} +ExtendedCharTable::~ExtendedCharTable() +{ + // free all allocated character buffers + QHashIterator<ushort,ushort*> iter(extendedCharTable); + while ( iter.hasNext() ) + { + iter.next(); + delete[] iter.value(); + } +} + +// global instance +ExtendedCharTable ExtendedCharTable::instance; + +
new file mode 100644 --- /dev/null +++ b/gui/src/Emulation.h @@ -0,0 +1,462 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef EMULATION_H +#define EMULATION_H + +// System +#include <stdio.h> + +// Qt +#include <QtGui/QKeyEvent> +#include <QtCore/QTextCodec> +#include <QtCore/QTextStream> +#include <QtCore/QTimer> + + +class KeyboardTranslator; +class HistoryType; +class Screen; +class ScreenWindow; +class TerminalCharacterDecoder; + +/** + * This enum describes the available states which + * the terminal emulation may be set to. + * + * These are the values used by Emulation::stateChanged() + */ +enum +{ + /** The emulation is currently receiving user input. */ + NOTIFYNORMAL=0, + /** + * The terminal program has triggered a bell event + * to get the user's attention. + */ + NOTIFYBELL=1, + /** + * The emulation is currently receiving data from its + * terminal input. + */ + NOTIFYACTIVITY=2, + + // unused here? + NOTIFYSILENCE=3 +}; + +/** + * Base class for terminal emulation back-ends. + * + * The back-end is responsible for decoding an incoming character stream and + * producing an output image of characters. + * + * When input from the terminal is received, the receiveData() slot should be called with + * the data which has arrived. The emulation will process the data and update the + * screen image accordingly. The codec used to decode the incoming character stream + * into the unicode characters used internally can be specified using setCodec() + * + * The size of the screen image can be specified by calling setImageSize() with the + * desired number of lines and columns. When new lines are added, old content + * is moved into a history store, which can be set by calling setHistory(). + * + * The screen image can be accessed by creating a ScreenWindow onto this emulation + * by calling createWindow(). Screen windows provide access to a section of the + * output. Each screen window covers the same number of lines and columns as the + * image size returned by imageSize(). The screen window can be moved up and down + * and provides transparent access to both the current on-screen image and the + * previous output. The screen windows emit an outputChanged signal + * when the section of the image they are looking at changes. + * Graphical views can then render the contents of a screen window, listening for notifications + * of output changes from the screen window which they are associated with and updating + * accordingly. + * + * The emulation also is also responsible for converting input from the connected views such + * as keypresses and mouse activity into a character string which can be sent + * to the terminal program. Key presses can be processed by calling the sendKeyEvent() slot, + * while mouse events can be processed using the sendMouseEvent() slot. When the character + * stream has been produced, the emulation will emit a sendData() signal with a pointer + * to the character buffer. This data should be fed to the standard input of the terminal + * process. The translation of key presses into an output character stream is performed + * using a lookup in a set of key bindings which map key sequences to output + * character sequences. The name of the key bindings set used can be specified using + * setKeyBindings() + * + * The emulation maintains certain state information which changes depending on the + * input received. The emulation can be reset back to its starting state by calling + * reset(). + * + * The emulation also maintains an activity state, which specifies whether + * terminal is currently active ( when data is received ), normal + * ( when the terminal is idle or receiving user input ) or trying + * to alert the user ( also known as a "Bell" event ). The stateSet() signal + * is emitted whenever the activity state is set. This can be used to determine + * how long the emulation has been active/idle for and also respond to + * a 'bell' event in different ways. + */ +class Emulation : public QObject +{ +Q_OBJECT + +public: + + /** Constructs a new terminal emulation */ + Emulation(); + ~Emulation(); + + /** + * Creates a new window onto the output from this emulation. The contents + * of the window are then rendered by views which are set to use this window using the + * TerminalDisplay::setScreenWindow() method. + */ + ScreenWindow* createWindow(); + + /** Returns the size of the screen image which the emulation produces */ + QSize imageSize() const; + + /** + * Returns the total number of lines, including those stored in the history. + */ + int lineCount() const; + + /** + * Sets the history store used by this emulation. When new lines + * are added to the output, older lines at the top of the screen are transferred to a history + * store. + * + * The number of lines which are kept and the storage location depend on the + * type of store. + */ + void setHistory(const HistoryType&); + /** Returns the history store used by this emulation. See setHistory() */ + const HistoryType& history() const; + /** Clears the history scroll. */ + void clearHistory(); + + /** + * Copies the output history from @p startLine to @p endLine + * into @p stream, using @p decoder to convert the terminal + * characters into text. + * + * @param decoder A decoder which converts lines of terminal characters with + * appearance attributes into output text. PlainTextDecoder is the most commonly + * used decoder. + * @param startLine Index of first line to copy + * @param endLine Index of last line to copy + */ + virtual void writeToStream(TerminalCharacterDecoder* decoder,int startLine,int endLine); + + /** Returns the codec used to decode incoming characters. See setCodec() */ + const QTextCodec* codec() const { return _codec; } + /** Sets the codec used to decode incoming characters. */ + void setCodec(const QTextCodec*); + + /** + * Convenience method. + * Returns true if the current codec used to decode incoming + * characters is UTF-8 + */ + bool utf8() const + { Q_ASSERT(_codec); return _codec->mibEnum() == 106; } + + + /** TODO Document me */ + virtual char eraseChar() const; + + /** + * Sets the key bindings used to key events + * ( received through sendKeyEvent() ) into character + * streams to send to the terminal. + */ + void setKeyBindings(const QString& name); + /** + * Returns the name of the emulation's current key bindings. + * See setKeyBindings() + */ + QString keyBindings() const; + + /** + * Copies the current image into the history and clears the screen. + */ + virtual void clearEntireScreen() =0; + + /** Resets the state of the terminal. */ + virtual void reset() =0; + + /** + * Returns true if the active terminal program wants + * mouse input events. + * + * The programUsesMouseChanged() signal is emitted when this + * changes. + */ + bool programUsesMouse() const; + +public slots: + + /** Change the size of the emulation's image */ + virtual void setImageSize(int lines, int columns); + + /** + * Interprets a sequence of characters and sends the result to the terminal. + * This is equivalent to calling sendKeyEvent() for each character in @p text in succession. + */ + virtual void sendText(const QString& text) = 0; + + /** + * Interprets a key press event and emits the sendData() signal with + * the resulting character stream. + */ + virtual void sendKeyEvent(QKeyEvent*); + + /** + * Converts information about a mouse event into an xterm-compatible escape + * sequence and emits the character sequence via sendData() + */ + virtual void sendMouseEvent(int buttons, int column, int line, int eventType); + + /** + * Sends a string of characters to the foreground terminal process. + * + * @param string The characters to send. + * @param length Length of @p string or if set to a negative value, @p string will + * be treated as a null-terminated string and its length will be determined automatically. + */ + virtual void sendString(const char* string, int length = -1) = 0; + + /** + * Processes an incoming stream of characters. receiveData() decodes the incoming + * character buffer using the current codec(), and then calls receiveChar() for + * each unicode character in the resulting buffer. + * + * receiveData() also starts a timer which causes the outputChanged() signal + * to be emitted when it expires. The timer allows multiple updates in quick + * succession to be buffered into a single outputChanged() signal emission. + * + * @param buffer A string of characters received from the terminal program. + * @param len The length of @p buffer + */ + void receiveData(const char* buffer,int len); + +signals: + + /** + * Emitted when a buffer of data is ready to send to the + * standard input of the terminal. + * + * @param data The buffer of data ready to be sent + * @param len The length of @p data in bytes + */ + void sendData(const char* data,int len); + + /** + * Requests that sending of input to the emulation + * from the terminal process be suspended or resumed. + * + * @param suspend If true, requests that sending of + * input from the terminal process' stdout be + * suspended. Otherwise requests that sending of + * input be resumed. + */ + void lockPtyRequest(bool suspend); + + /** + * Requests that the pty used by the terminal process + * be set to UTF 8 mode. + * + * TODO: More documentation + */ + void useUtf8Request(bool); + + /** + * Emitted when the activity state of the emulation is set. + * + * @param state The new activity state, one of NOTIFYNORMAL, NOTIFYACTIVITY + * or NOTIFYBELL + */ + void stateSet(int state); + + /** TODO Document me */ + void zmodemDetected(); + + + /** + * Requests that the color of the text used + * to represent the tabs associated with this + * emulation be changed. This is a Konsole-specific + * extension from pre-KDE 4 times. + * + * TODO: Document how the parameter works. + */ + void changeTabTextColorRequest(int color); + + /** + * This is emitted when the program running in the shell indicates whether or + * not it is interested in mouse events. + * + * @param usesMouse This will be true if the program wants to be informed about + * mouse events or false otherwise. + */ + void programUsesMouseChanged(bool usesMouse); + + /** + * Emitted when the contents of the screen image change. + * The emulation buffers the updates from successive image changes, + * and only emits outputChanged() at sensible intervals when + * there is a lot of terminal activity. + * + * Normally there is no need for objects other than the screen windows + * created with createWindow() to listen for this signal. + * + * ScreenWindow objects created using createWindow() will emit their + * own outputChanged() signal in response to this signal. + */ + void outputChanged(); + + /** + * Emitted when the program running in the terminal wishes to update the + * session's title. This also allows terminal programs to customize other + * aspects of the terminal emulation display. + * + * This signal is emitted when the escape sequence "\033]ARG;VALUE\007" + * is received in the input string, where ARG is a number specifying what + * should change and VALUE is a string specifying the new value. + * + * TODO: The name of this method is not very accurate since this method + * is used to perform a whole range of tasks besides just setting + * the user-title of the session. + * + * @param title Specifies what to change. + * <ul> + * <li>0 - Set window icon text and session title to @p newTitle</li> + * <li>1 - Set window icon text to @p newTitle</li> + * <li>2 - Set session title to @p newTitle</li> + * <li>11 - Set the session's default background color to @p newTitle, + * where @p newTitle can be an HTML-style string ("#RRGGBB") or a named + * color (eg 'red', 'blue'). + * See http://doc.trolltech.com/4.2/qcolor.html#setNamedColor for more + * details. + * </li> + * <li>31 - Supposedly treats @p newTitle as a URL and opens it (NOT IMPLEMENTED)</li> + * <li>32 - Sets the icon associated with the session. @p newTitle is the name + * of the icon to use, which can be the name of any icon in the current KDE icon + * theme (eg: 'konsole', 'kate', 'folder_home')</li> + * </ul> + * @param newTitle Specifies the new title + */ + + void titleChanged(int title,const QString& newTitle); + + /** + * Emitted when the program running in the terminal changes the + * screen size. + */ + void imageSizeChanged(int lineCount , int columnCount); + + /** + * Emitted when the terminal program requests to change various properties + * of the terminal display. + * + * A profile change command occurs when a special escape sequence, followed + * by a string containing a series of name and value pairs is received. + * This string can be parsed using a ProfileCommandParser instance. + * + * @param text A string expected to contain a series of key and value pairs in + * the form: name=value;name2=value2 ... + */ + void profileChangeCommandReceived(const QString& text); + + /** + * Emitted when a flow control key combination ( Ctrl+S or Ctrl+Q ) is pressed. + * @param suspendKeyPressed True if Ctrl+S was pressed to suspend output or Ctrl+Q to + * resume output. + */ + void flowControlKeyPressed(bool suspendKeyPressed); + +protected: + virtual void setMode(int mode) = 0; + virtual void resetMode(int mode) = 0; + + /** + * Processes an incoming character. See receiveData() + * @p ch A unicode character code. + */ + virtual void receiveChar(int ch); + + /** + * Sets the active screen. The terminal has two screens, primary and alternate. + * The primary screen is used by default. When certain interactive programs such + * as Vim are run, they trigger a switch to the alternate screen. + * + * @param index 0 to switch to the primary screen, or 1 to switch to the alternate screen + */ + void setScreen(int index); + + enum EmulationCodec + { + LocaleCodec = 0, + Utf8Codec = 1 + }; + void setCodec(EmulationCodec codec); // codec number, 0 = locale, 1=utf8 + + + QList<ScreenWindow*> _windows; + + Screen* _currentScreen; // pointer to the screen which is currently active, + // this is one of the elements in the screen[] array + + Screen* _screen[2]; // 0 = primary screen ( used by most programs, including the shell + // scrollbars are enabled in this mode ) + // 1 = alternate ( used by vi , emacs etc. + // scrollbars are not enabled in this mode ) + + + //decodes an incoming C-style character stream into a unicode QString using + //the current text codec. (this allows for rendering of non-ASCII characters in text files etc.) + const QTextCodec* _codec; + QTextDecoder* _decoder; + const KeyboardTranslator* _keyTranslator; // the keyboard layout + +protected slots: + /** + * Schedules an update of attached views. + * Repeated calls to bufferedUpdate() in close succession will result in only a single update, + * much like the Qt buffered update of widgets. + */ + void bufferedUpdate(); + +private slots: + + // triggered by timer, causes the emulation to send an updated screen image to each + // view + void showBulk(); + + void usesMouseChanged(bool usesMouse); + +private: + bool _usesMouse; + QTimer _bulkTimer1; + QTimer _bulkTimer2; + +}; + +#endif // ifndef EMULATION_H
new file mode 100644 --- /dev/null +++ b/gui/src/FileEditorDockWidget.cpp @@ -0,0 +1,146 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include "FileEditorDockWidget.h" +#include <QVBoxLayout> +#include <QApplication> +#include <QFile> +#include <QFileDialog> +#include <QMessageBox> +#include <QAction> + +FileEditorDockWidget::FileEditorDockWidget(QWidget *parent) + : QDockWidget(parent) { + construct(); +} + +void FileEditorDockWidget::loadFile(QString fileName) { + m_fileName = fileName; + setWindowTitle(fileName); + m_simpleEditor->load(fileName); +} + +void FileEditorDockWidget::newFile() { + if(m_modified) { + int decision + = QMessageBox::question(this, + "Open New File", + "Do you want to save the current file?", + QMessageBox::Yes, QMessageBox::No); + + if(decision == QMessageBox::Yes) { + saveFile(); + if(m_modified) { + // If the user attempted to save the file, but it's still + // modified, then probably something went wrong, so we quit here. + return; + } + } + } + + m_fileName = "<unnamed>"; + setWindowTitle(m_fileName); + m_simpleEditor->setPlainText(""); +} + +void FileEditorDockWidget::saveFile() { + QString saveFileName = QFileDialog::getSaveFileName(this, "Save File", m_fileName); + if(saveFileName.isEmpty()) + return; + + QFile file(saveFileName); + file.open(QFile::WriteOnly); + + if(file.write(m_simpleEditor->toPlainText().toLocal8Bit()) == -1) { + QMessageBox::warning(this, + "Error Saving File", + QString("The file could not be saved: %1.").arg(file.errorString())); + } else { + m_simpleEditor->document()->setModified(false); + } + + file.close(); +} + +void FileEditorDockWidget::showToolTipNew() { + m_statusBar->showMessage("Create a new file.", 2000); +} + +void FileEditorDockWidget::showToolTipSave() { + m_statusBar->showMessage("Save the file.", 2000); +} + +void FileEditorDockWidget::showToolTipUndo() { + m_statusBar->showMessage("Revert previous changes.", 2000); +} + +void FileEditorDockWidget::showToolTipRedo() { + m_statusBar->showMessage("Append previous changes.", 2000); +} + +void FileEditorDockWidget::registerModified(bool modified) { + m_modified = modified; +} + +void FileEditorDockWidget::construct() { + QStyle *style = QApplication::style(); + setWidget(new QWidget()); + m_toolBar = new QToolBar(this); + m_simpleEditor = new SimpleEditor(this); + m_statusBar = new QStatusBar(this); + m_numberedTextView = new NumberedCodeEdit(this, m_simpleEditor); + + m_simpleEditor->setFont(QFont("Courier")); + m_simpleEditor->setLineWrapMode(QPlainTextEdit::NoWrap); + + QAction *newAction = new QAction(style->standardIcon(QStyle::SP_FileIcon), + "", m_toolBar); + QAction *saveAction = new QAction(style->standardIcon(QStyle::SP_DriveHDIcon), + "", m_toolBar); + QAction *undoAction = new QAction(style->standardIcon(QStyle::SP_ArrowLeft), + "", m_toolBar); + QAction *redoAction = new QAction(style->standardIcon(QStyle::SP_ArrowRight), + "", m_toolBar); + + m_toolBar->addAction(newAction); + m_toolBar->addAction(saveAction); + m_toolBar->addAction(undoAction); + m_toolBar->addAction(redoAction); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(m_toolBar); + layout->addWidget(m_numberedTextView); + layout->addWidget(m_statusBar); + layout->setMargin(2); + widget()->setLayout(layout); + + connect(newAction, SIGNAL(triggered()), this, SLOT(newFile())); + connect(undoAction, SIGNAL(triggered()), m_simpleEditor, SLOT(undo())); + connect(redoAction, SIGNAL(triggered()), m_simpleEditor, SLOT(redo())); + connect(saveAction, SIGNAL(triggered()), this, SLOT(saveFile())); + + connect(newAction, SIGNAL(hovered()), this, SLOT(showToolTipNew())); + connect(undoAction, SIGNAL(hovered()), this, SLOT(showToolTipUndo())); + connect(redoAction, SIGNAL(hovered()), this, SLOT(showToolTipRedo())); + connect(saveAction, SIGNAL(hovered()), this, SLOT(showToolTipSave())); + + connect(m_simpleEditor, SIGNAL(modificationChanged(bool)), this, SLOT(registerModified(bool))); + + m_fileName = ""; + setWindowTitle(m_fileName); +}
new file mode 100644 --- /dev/null +++ b/gui/src/FileEditorDockWidget.h @@ -0,0 +1,54 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#ifndef FILEEDITORDOCKWIDGET_H +#define FILEEDITORDOCKWIDGET_H + +#include <QDockWidget> +#include <QToolBar> +#include <QStatusBar> +#include "SimpleEditor.h" +#include "NumberedCodeEdit.h" + +class FileEditorDockWidget : public QDockWidget { + Q_OBJECT +public: + FileEditorDockWidget(QWidget *parent = 0); + void loadFile(QString fileName); + +public slots: + void newFile(); + void saveFile(); + + void showToolTipNew(); + void showToolTipSave(); + void showToolTipUndo(); + void showToolTipRedo(); + + void registerModified(bool modified); +private: + void construct(); + QToolBar *m_toolBar; + SimpleEditor *m_simpleEditor; + NumberedCodeEdit *m_numberedTextView; + QStatusBar *m_statusBar; + QString m_fileName; + bool m_modified; +}; + +#endif // FILEEDITORDOCKWIDGET_H
new file mode 100644 --- /dev/null +++ b/gui/src/FilesDockWidget.cpp @@ -0,0 +1,98 @@ +#include "FilesDockWidget.h" + +#include <QApplication> +#include <QFileInfo> + +FilesDockWidget::FilesDockWidget(QWidget *parent) + : QDockWidget(parent) { + setObjectName("FilesDockWidget"); + setWindowTitle(tr("Current Folder")); + setWidget(new QWidget(this)); + + // Create a toolbar + m_navigationToolBar = new QToolBar("", widget()); + m_navigationToolBar->setAllowedAreas(Qt::TopToolBarArea); + m_navigationToolBar->setMovable(false); + m_navigationToolBar->setIconSize(QSize (20,20)); + + // Add a button to the toolbar with the QT standard icon for up-directory + // TODO: Maybe change this to be an up-directory icon that is OS specific??? + QStyle *style = QApplication::style(); + m_directoryIcon = style->standardIcon(QStyle::SP_FileDialogToParent); + m_directoryUpAction = new QAction(m_directoryIcon, "", m_navigationToolBar); + m_currentDirectory = new QLineEdit(m_navigationToolBar); + + m_navigationToolBar->addAction(m_directoryUpAction); + m_navigationToolBar->addWidget(m_currentDirectory); + connect(m_directoryUpAction, SIGNAL(triggered()), this, SLOT(onUpDirectory())); + + // TODO: Add other buttons for creating directories + + // Create the QFileSystemModel starting in the home directory + QString homePath = QDir::homePath(); + // TODO: This should occur after Octave has been initialized and the startup directory of Octave is established + + m_fileSystemModel = new QFileSystemModel(this); + m_fileSystemModel->setFilter(QDir::NoDotAndDotDot | QDir::AllEntries); + QModelIndex rootPathIndex = m_fileSystemModel->setRootPath(homePath); + + // Attach the model to the QTreeView and set the root index + m_fileTreeView = new QTreeView(widget()); + m_fileTreeView->setModel(m_fileSystemModel); + m_fileTreeView->setRootIndex(rootPathIndex); + m_fileTreeView->setSortingEnabled(true); + m_fileTreeView->setAlternatingRowColors(true); + m_fileTreeView->setAnimated(true); + setCurrentDirectory(m_fileSystemModel->fileInfo(rootPathIndex).absoluteFilePath()); + + connect(m_fileTreeView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &))); + + // Layout the widgets vertically with the toolbar on top + QVBoxLayout *layout = new QVBoxLayout(); + layout->setSpacing(0); + layout->addWidget(m_navigationToolBar); + layout->addWidget(m_fileTreeView); + widget()->setLayout(layout); + // TODO: Add right-click contextual menus for copying, pasting, deleting files (and others) + + connect(m_currentDirectory, SIGNAL(returnPressed()), this, SLOT(currentDirectoryEntered())); + //m_currentDirectory->setEnabled(false); +} + +void FilesDockWidget::itemDoubleClicked(const QModelIndex &index) +{ + QFileInfo fileInfo = m_fileSystemModel->fileInfo(index); + if (fileInfo.isDir()) { + m_fileSystemModel->setRootPath(fileInfo.absolutePath()); + m_fileTreeView->setRootIndex(index); + setCurrentDirectory(m_fileSystemModel->fileInfo(index).absoluteFilePath()); + } else { + QFileInfo fileInfo = m_fileSystemModel->fileInfo(index); + emit openFile(fileInfo.filePath()); + } +} + +void FilesDockWidget::setCurrentDirectory(QString currentDirectory) { + m_currentDirectory->setText(currentDirectory); +} + +void FilesDockWidget::onUpDirectory(void) { + // Move up an inm_fileTreeView->setRootIndex(m_fileSystemModel->index(dir.absolutePath()));dex node + QDir dir = QDir(m_fileSystemModel->filePath(m_fileTreeView->rootIndex())); + dir.cdUp(); + m_fileSystemModel->setRootPath(dir.absolutePath()); + m_fileTreeView->setRootIndex(m_fileSystemModel->index(dir.absolutePath())); + setCurrentDirectory(dir.absolutePath()); +} + +void FilesDockWidget::currentDirectoryEntered() { + QFileInfo fileInfo(m_currentDirectory->text()); + if (fileInfo.isDir()) { + m_fileTreeView->setRootIndex(m_fileSystemModel->index(fileInfo.absolutePath())); + m_fileSystemModel->setRootPath(fileInfo.absolutePath()); + setCurrentDirectory(fileInfo.absoluteFilePath()); + } else { + if(QFile::exists(fileInfo.absoluteFilePath())) + emit openFile(fileInfo.absoluteFilePath()); + } +}
new file mode 100644 --- /dev/null +++ b/gui/src/FilesDockWidget.h @@ -0,0 +1,86 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 John P. Swensen, Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#ifndef FILESDOCKWIDGET_H +#define FILESDOCKWIDGET_H + +#include <QListView> +#include <QDate> +#include <QObject> +#include <QWidget> +#include <QListWidget> +#include <QFileSystemModel> +#include <QToolBar> +#include <QToolButton> +#include <QVBoxLayout> +#include <QAction> +#include <QTreeView> + +#include <vector> +#include <string> + +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#include "octave/config.h" +#include "octave/octave.h" +#include "octave/str-vec.h" +#include "octave/cmd-hist.h" +#include <QDockWidget> +#include <QLineEdit> + +class FilesDockWidget : public QDockWidget { + Q_OBJECT +public : + FilesDockWidget(QWidget *parent = 0); +public slots: + /** Slot for handling a change in directory via double click. */ + void itemDoubleClicked(const QModelIndex &index); + + /** Slot for handling the up-directory button in the toolbar. */ + void onUpDirectory(); + + void setCurrentDirectory(QString currentDirectory); + + void currentDirectoryEntered(); + +signals: + void openFile(QString fileName); + +private: + // TODO: Add toolbar with buttons for navigating the path, creating dirs, etc + + /** Toolbar for file and directory manipulation. */ + QToolBar *m_navigationToolBar; + + /** Variables for the up-directory action. */ + QIcon m_directoryIcon; + QAction *m_directoryUpAction; + QToolButton *upDirectoryButton; + + /** The file system model. */ + QFileSystemModel *m_fileSystemModel; + + /** The file system view. */ + QTreeView *m_fileTreeView; + QLineEdit *m_currentDirectory; +}; + +#endif // FILESDOCKWIDGET_H
new file mode 100644 --- /dev/null +++ b/gui/src/Filter.cpp @@ -0,0 +1,534 @@ +/* + Copyright 2007-2008 by Robert Knight <robertknight@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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "Filter.h" + +// System +#include <iostream> + +// Qt +#include <QtGui/QAction> +#include <QtGui/QApplication> +#include <QtGui/QClipboard> +#include <QtCore/QString> +#include <QtCore/QTextStream> +#include <QtCore/QSharedData> +#include <QtCore/QFile> + +// Konsole +#include "TerminalCharacterDecoder.h" +#include "konsole_wcwidth.h" +#include "konsole_export.h" + +FilterChain::~FilterChain() +{ + QMutableListIterator<Filter*> iter(*this); + + while ( iter.hasNext() ) + { + Filter* filter = iter.next(); + iter.remove(); + delete filter; + } +} + +void FilterChain::addFilter(Filter* filter) +{ + append(filter); +} +void FilterChain::removeFilter(Filter* filter) +{ + removeAll(filter); +} +bool FilterChain::containsFilter(Filter* filter) +{ + return contains(filter); +} +void FilterChain::reset() +{ + QListIterator<Filter*> iter(*this); + while (iter.hasNext()) + iter.next()->reset(); +} +void FilterChain::setBuffer(const QString* buffer , const QList<int>* linePositions) +{ + QListIterator<Filter*> iter(*this); + while (iter.hasNext()) + iter.next()->setBuffer(buffer,linePositions); +} +void FilterChain::process() +{ + QListIterator<Filter*> iter(*this); + while (iter.hasNext()) + iter.next()->process(); +} +void FilterChain::clear() +{ + QList<Filter*>::clear(); +} +Filter::HotSpot* FilterChain::hotSpotAt(int line , int column) const +{ + QListIterator<Filter*> iter(*this); + while (iter.hasNext()) + { + Filter* filter = iter.next(); + Filter::HotSpot* spot = filter->hotSpotAt(line,column); + if ( spot != 0 ) + { + return spot; + } + } + + return 0; +} + +QList<Filter::HotSpot*> FilterChain::hotSpots() const +{ + QList<Filter::HotSpot*> list; + QListIterator<Filter*> iter(*this); + while (iter.hasNext()) + { + Filter* filter = iter.next(); + list << filter->hotSpots(); + } + return list; +} +//QList<Filter::HotSpot*> FilterChain::hotSpotsAtLine(int line) const; + +TerminalImageFilterChain::TerminalImageFilterChain() +: _buffer(0) +, _linePositions(0) +{ +} + +TerminalImageFilterChain::~TerminalImageFilterChain() +{ + delete _buffer; + delete _linePositions; +} + +void TerminalImageFilterChain::setImage(const Character* const image , int lines , int columns, const QVector<LineProperty>& lineProperties) +{ + if (empty()) + return; + + // reset all filters and hotspots + reset(); + + PlainTextDecoder decoder; + decoder.setTrailingWhitespace(false); + + // setup new shared buffers for the filters to process on + QString* newBuffer = new QString(); + QList<int>* newLinePositions = new QList<int>(); + setBuffer( newBuffer , newLinePositions ); + + // free the old buffers + delete _buffer; + delete _linePositions; + + _buffer = newBuffer; + _linePositions = newLinePositions; + + QTextStream lineStream(_buffer); + decoder.begin(&lineStream); + + for (int i=0 ; i < lines ; i++) + { + _linePositions->append(_buffer->length()); + decoder.decodeLine(image + i*columns,columns,LINE_DEFAULT); + + // pretend that each line ends with a newline character. + // this prevents a link that occurs at the end of one line + // being treated as part of a link that occurs at the start of the next line + // + // the downside is that links which are spread over more than one line are not + // highlighted. + // + // TODO - Use the "line wrapped" attribute associated with lines in a + // terminal image to avoid adding this imaginary character for wrapped + // lines + if ( !(lineProperties.value(i,LINE_DEFAULT) & LINE_WRAPPED) ) + lineStream << QChar('\n'); + } + decoder.end(); +} + +Filter::Filter() : +_linePositions(0), +_buffer(0) +{ +} + +Filter::~Filter() +{ + QListIterator<HotSpot*> iter(_hotspotList); + while (iter.hasNext()) + { + delete iter.next(); + } +} +void Filter::reset() +{ + _hotspots.clear(); + _hotspotList.clear(); +} + +void Filter::setBuffer(const QString* buffer , const QList<int>* linePositions) +{ + _buffer = buffer; + _linePositions = linePositions; +} + +void Filter::getLineColumn(int position , int& startLine , int& startColumn) +{ + Q_ASSERT( _linePositions ); + Q_ASSERT( _buffer ); + + + for (int i = 0 ; i < _linePositions->count() ; i++) + { + int nextLine = 0; + + if ( i == _linePositions->count()-1 ) + nextLine = _buffer->length() + 1; + else + nextLine = _linePositions->value(i+1); + + if ( _linePositions->value(i) <= position && position < nextLine ) + { + startLine = i; + startColumn = string_width(buffer()->mid(_linePositions->value(i),position - _linePositions->value(i))); + return; + } + } +} + + +/*void Filter::addLine(const QString& text) +{ + _linePositions << _buffer.length(); + _buffer.append(text); +}*/ + +const QString* Filter::buffer() +{ + return _buffer; +} +Filter::HotSpot::~HotSpot() +{ +} +void Filter::addHotSpot(HotSpot* spot) +{ + _hotspotList << spot; + + for (int line = spot->startLine() ; line <= spot->endLine() ; line++) + { + _hotspots.insert(line,spot); + } +} +QList<Filter::HotSpot*> Filter::hotSpots() const +{ + return _hotspotList; +} +QList<Filter::HotSpot*> Filter::hotSpotsAtLine(int line) const +{ + return _hotspots.values(line); +} + +Filter::HotSpot* Filter::hotSpotAt(int line , int column) const +{ + QListIterator<HotSpot*> spotIter(_hotspots.values(line)); + + while (spotIter.hasNext()) + { + HotSpot* spot = spotIter.next(); + + if ( spot->startLine() == line && spot->startColumn() > column ) + continue; + if ( spot->endLine() == line && spot->endColumn() < column ) + continue; + + return spot; + } + + return 0; +} + +Filter::HotSpot::HotSpot(int startLine , int startColumn , int endLine , int endColumn) + : _startLine(startLine) + , _startColumn(startColumn) + , _endLine(endLine) + , _endColumn(endColumn) + , _type(NotSpecified) +{ +} +QString Filter::HotSpot::tooltip() const +{ + return QString(); +} +QList<QAction*> Filter::HotSpot::actions() +{ + return QList<QAction*>(); +} +int Filter::HotSpot::startLine() const +{ + return _startLine; +} +int Filter::HotSpot::endLine() const +{ + return _endLine; +} +int Filter::HotSpot::startColumn() const +{ + return _startColumn; +} +int Filter::HotSpot::endColumn() const +{ + return _endColumn; +} +Filter::HotSpot::Type Filter::HotSpot::type() const +{ + return _type; +} +void Filter::HotSpot::setType(Type type) +{ + _type = type; +} + +RegExpFilter::RegExpFilter() +{ +} + +RegExpFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn) + : Filter::HotSpot(startLine,startColumn,endLine,endColumn) +{ + setType(Marker); +} + +void RegExpFilter::HotSpot::activate(QObject*) +{ +} + +void RegExpFilter::HotSpot::setCapturedTexts(const QStringList& texts) +{ + _capturedTexts = texts; +} +QStringList RegExpFilter::HotSpot::capturedTexts() const +{ + return _capturedTexts; +} + +void RegExpFilter::setRegExp(const QRegExp& regExp) +{ + _searchText = regExp; +} +QRegExp RegExpFilter::regExp() const +{ + return _searchText; +} +/*void RegExpFilter::reset(int) +{ + _buffer = QString(); +}*/ +void RegExpFilter::process() +{ + int pos = 0; + const QString* text = buffer(); + + Q_ASSERT( text ); + + // ignore any regular expressions which match an empty string. + // otherwise the while loop below will run indefinitely + static const QString emptyString(""); + if ( _searchText.exactMatch(emptyString) ) + return; + + while(pos >= 0) + { + pos = _searchText.indexIn(*text,pos); + + if ( pos >= 0 ) + { + int startLine = 0; + int endLine = 0; + int startColumn = 0; + int endColumn = 0; + + getLineColumn(pos,startLine,startColumn); + getLineColumn(pos + _searchText.matchedLength(),endLine,endColumn); + + RegExpFilter::HotSpot* spot = newHotSpot(startLine,startColumn, + endLine,endColumn); + spot->setCapturedTexts(_searchText.capturedTexts()); + + addHotSpot( spot ); + pos += _searchText.matchedLength(); + + // if matchedLength == 0, the program will get stuck in an infinite loop + if ( _searchText.matchedLength() == 0 ) + pos = -1; + } + } +} + +RegExpFilter::HotSpot* RegExpFilter::newHotSpot(int startLine,int startColumn, + int endLine,int endColumn) +{ + return new RegExpFilter::HotSpot(startLine,startColumn, + endLine,endColumn); +} +RegExpFilter::HotSpot* UrlFilter::newHotSpot(int startLine,int startColumn,int endLine, + int endColumn) +{ + return new UrlFilter::HotSpot(startLine,startColumn, + endLine,endColumn); +} +UrlFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn) +: RegExpFilter::HotSpot(startLine,startColumn,endLine,endColumn) +, _urlObject(new FilterObject(this)) +{ + setType(Link); +} +QString UrlFilter::HotSpot::tooltip() const +{ + QString url = capturedTexts().first(); + + const UrlType kind = urlType(); + + if ( kind == StandardUrl ) + return QString(); + else if ( kind == Email ) + return QString(); + else + return QString(); +} +UrlFilter::HotSpot::UrlType UrlFilter::HotSpot::urlType() const +{ + QString url = capturedTexts().first(); + + if ( FullUrlRegExp.exactMatch(url) ) + return StandardUrl; + else if ( EmailAddressRegExp.exactMatch(url) ) + return Email; + else + return Unknown; +} + +void UrlFilter::HotSpot::activate(QObject* object) +{ + QString url = capturedTexts().first(); + + const UrlType kind = urlType(); + + const QString& actionName = object ? object->objectName() : QString(); + + if ( actionName == "copy-action" ) + { + QApplication::clipboard()->setText(url); + return; + } + + if ( !object || actionName == "open-action" ) + { + if ( kind == StandardUrl ) + { + // if the URL path does not include the protocol ( eg. "www.kde.org" ) then + // prepend http:// ( eg. "www.kde.org" --> "http://www.kde.org" ) + if (!url.contains("://")) + { + url.prepend("http://"); + } + } + else if ( kind == Email ) + { + url.prepend("mailto:"); + } + + //new KRun(url,QApplication::activeWindow()); + } +} + +// Note: Altering these regular expressions can have a major effect on the performance of the filters +// used for finding URLs in the text, especially if they are very general and could match very long +// pieces of text. +// Please be careful when altering them. + +//regexp matches: +// full url: +// protocolname:// or www. followed by anything other than whitespaces, <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, comma and dot +const QRegExp UrlFilter::FullUrlRegExp("(www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]]"); +// email address: +// [word chars, dots or dashes]@[word chars, dots or dashes].[word chars] +const QRegExp UrlFilter::EmailAddressRegExp("\\b(\\w|\\.|-)+@(\\w|\\.|-)+\\.\\w+\\b"); + +// matches full url or email address +const QRegExp UrlFilter::CompleteUrlRegExp('('+FullUrlRegExp.pattern()+'|'+ + EmailAddressRegExp.pattern()+')'); + +UrlFilter::UrlFilter() +{ + setRegExp( CompleteUrlRegExp ); +} +UrlFilter::HotSpot::~HotSpot() +{ + delete _urlObject; +} +void FilterObject::activated() +{ + _filter->activate(sender()); +} +QList<QAction*> UrlFilter::HotSpot::actions() +{ + QList<QAction*> list; + + const UrlType kind = urlType(); + + QAction* openAction = new QAction(_urlObject); + QAction* copyAction = new QAction(_urlObject);; + + Q_ASSERT( kind == StandardUrl || kind == Email ); + + if ( kind == StandardUrl ) + { + openAction->setText(i18n("Open Link")); + copyAction->setText(i18n("Copy Link Address")); + } + else if ( kind == Email ) + { + openAction->setText(i18n("Send Email To...")); + copyAction->setText(i18n("Copy Email Address")); + } + + // object names are set here so that the hotspot performs the + // correct action when activated() is called with the triggered + // action passed as a parameter. + openAction->setObjectName( QLatin1String("open-action" )); + copyAction->setObjectName( QLatin1String("copy-action" )); + + QObject::connect( openAction , SIGNAL(triggered()) , _urlObject , SLOT(activated()) ); + QObject::connect( copyAction , SIGNAL(triggered()) , _urlObject , SLOT(activated()) ); + + list << openAction; + list << copyAction; + + return list; +} +
new file mode 100644 --- /dev/null +++ b/gui/src/Filter.h @@ -0,0 +1,379 @@ +/* + Copyright 2007-2008 by Robert Knight <robertknight@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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef FILTER_H +#define FILTER_H + +// Qt +#include <QtGui/QAction> +#include <QtCore/QList> +#include <QtCore/QObject> +#include <QtCore/QStringList> +#include <QtCore/QHash> +#include <QtCore/QRegExp> + +// Local +#include "Character.h" + + +/** + * A filter processes blocks of text looking for certain patterns (such as URLs or keywords from a list) + * and marks the areas which match the filter's patterns as 'hotspots'. + * + * Each hotspot has a type identifier associated with it ( such as a link or a highlighted section ), + * and an action. When the user performs some activity such as a mouse-click in a hotspot area ( the exact + * action will depend on what is displaying the block of text which the filter is processing ), the hotspot's + * activate() method should be called. Depending on the type of hotspot this will trigger a suitable response. + * + * For example, if a hotspot represents a URL then a suitable action would be opening that URL in a web browser. + * Hotspots may have more than one action, in which case the list of actions can be obtained using the + * actions() method. + * + * Different subclasses of filter will return different types of hotspot. + * Subclasses must reimplement the process() method to examine a block of text and identify sections of interest. + * When processing the text they should create instances of Filter::HotSpot subclasses for sections of interest + * and add them to the filter's list of hotspots using addHotSpot() + */ +class Filter +{ +public: + /** + * Represents an area of text which matched the pattern a particular filter has been looking for. + * + * Each hotspot has a type identifier associated with it ( such as a link or a highlighted section ), + * and an action. When the user performs some activity such as a mouse-click in a hotspot area ( the exact + * action will depend on what is displaying the block of text which the filter is processing ), the hotspot's + * activate() method should be called. Depending on the type of hotspot this will trigger a suitable response. + * + * For example, if a hotspot represents a URL then a suitable action would be opening that URL in a web browser. + * Hotspots may have more than one action, in which case the list of actions can be obtained using the + * actions() method. These actions may then be displayed in a popup menu or toolbar for example. + */ + class HotSpot + { + public: + /** + * Constructs a new hotspot which covers the area from (@p startLine,@p startColumn) to (@p endLine,@p endColumn) + * in a block of text. + */ + HotSpot(int startLine , int startColumn , int endLine , int endColumn); + virtual ~HotSpot(); + + enum Type + { + // the type of the hotspot is not specified + NotSpecified, + // this hotspot represents a clickable link + Link, + // this hotspot represents a marker + Marker + }; + + /** Returns the line when the hotspot area starts */ + int startLine() const; + /** Returns the line where the hotspot area ends */ + int endLine() const; + /** Returns the column on startLine() where the hotspot area starts */ + int startColumn() const; + /** Returns the column on endLine() where the hotspot area ends */ + int endColumn() const; + /** + * Returns the type of the hotspot. This is usually used as a hint for views on how to represent + * the hotspot graphically. eg. Link hotspots are typically underlined when the user mouses over them + */ + Type type() const; + /** + * Causes the an action associated with a hotspot to be triggered. + * + * @param object The object which caused the hotspot to be triggered. This is + * typically null ( in which case the default action should be performed ) or + * one of the objects from the actions() list. In which case the associated + * action should be performed. + */ + virtual void activate(QObject* object = 0) = 0; + /** + * Returns a list of actions associated with the hotspot which can be used in a + * menu or toolbar + */ + virtual QList<QAction*> actions(); + + /** + * Returns the text of a tooltip to be shown when the mouse moves over the hotspot, or + * an empty string if there is no tooltip associated with this hotspot. + * + * The default implementation returns an empty string. + */ + virtual QString tooltip() const; + + protected: + /** Sets the type of a hotspot. This should only be set once */ + void setType(Type type); + + private: + int _startLine; + int _startColumn; + int _endLine; + int _endColumn; + Type _type; + + }; + + /** Constructs a new filter. */ + Filter(); + virtual ~Filter(); + + /** Causes the filter to process the block of text currently in its internal buffer */ + virtual void process() = 0; + + /** + * Empties the filters internal buffer and resets the line count back to 0. + * All hotspots are deleted. + */ + void reset(); + + /** Adds a new line of text to the filter and increments the line count */ + //void addLine(const QString& string); + + /** Returns the hotspot which covers the given @p line and @p column, or 0 if no hotspot covers that area */ + HotSpot* hotSpotAt(int line , int column) const; + + /** Returns the list of hotspots identified by the filter */ + QList<HotSpot*> hotSpots() const; + + /** Returns the list of hotspots identified by the filter which occur on a given line */ + QList<HotSpot*> hotSpotsAtLine(int line) const; + + /** + * TODO: Document me + */ + void setBuffer(const QString* buffer , const QList<int>* linePositions); + +protected: + /** Adds a new hotspot to the list */ + void addHotSpot(HotSpot*); + /** Returns the internal buffer */ + const QString* buffer(); + /** Converts a character position within buffer() to a line and column */ + void getLineColumn(int position , int& startLine , int& startColumn); + +private: + QMultiHash<int,HotSpot*> _hotspots; + QList<HotSpot*> _hotspotList; + + const QList<int>* _linePositions; + const QString* _buffer; +}; + +/** + * A filter which searches for sections of text matching a regular expression and creates a new RegExpFilter::HotSpot + * instance for them. + * + * Subclasses can reimplement newHotSpot() to return custom hotspot types when matches for the regular expression + * are found. + */ +class RegExpFilter : public Filter +{ +public: + /** + * Type of hotspot created by RegExpFilter. The capturedTexts() method can be used to find the text + * matched by the filter's regular expression. + */ + class HotSpot : public Filter::HotSpot + { + public: + HotSpot(int startLine, int startColumn, int endLine , int endColumn); + virtual void activate(QObject* object = 0); + + /** Sets the captured texts associated with this hotspot */ + void setCapturedTexts(const QStringList& texts); + /** Returns the texts found by the filter when matching the filter's regular expression */ + QStringList capturedTexts() const; + private: + QStringList _capturedTexts; + }; + + /** Constructs a new regular expression filter */ + RegExpFilter(); + + /** + * Sets the regular expression which the filter searches for in blocks of text. + * + * Regular expressions which match the empty string are treated as not matching + * anything. + */ + void setRegExp(const QRegExp& text); + /** Returns the regular expression which the filter searches for in blocks of text */ + QRegExp regExp() const; + + /** + * Reimplemented to search the filter's text buffer for text matching regExp() + * + * If regexp matches the empty string, then process() will return immediately + * without finding results. + */ + virtual void process(); + +protected: + /** + * Called when a match for the regular expression is encountered. Subclasses should reimplement this + * to return custom hotspot types + */ + virtual RegExpFilter::HotSpot* newHotSpot(int startLine,int startColumn, + int endLine,int endColumn); + +private: + QRegExp _searchText; +}; + +class FilterObject; + +/** A filter which matches URLs in blocks of text */ +class UrlFilter : public RegExpFilter +{ +public: + /** + * Hotspot type created by UrlFilter instances. The activate() method opens a web browser + * at the given URL when called. + */ + class HotSpot : public RegExpFilter::HotSpot + { + public: + HotSpot(int startLine,int startColumn,int endLine,int endColumn); + virtual ~HotSpot(); + + virtual QList<QAction*> actions(); + + /** + * Open a web browser at the current URL. The url itself can be determined using + * the capturedTexts() method. + */ + virtual void activate(QObject* object = 0); + + virtual QString tooltip() const; + private: + enum UrlType + { + StandardUrl, + Email, + Unknown + }; + UrlType urlType() const; + + FilterObject* _urlObject; + }; + + UrlFilter(); + +protected: + virtual RegExpFilter::HotSpot* newHotSpot(int,int,int,int); + +private: + + static const QRegExp FullUrlRegExp; + static const QRegExp EmailAddressRegExp; + + // combined OR of FullUrlRegExp and EmailAddressRegExp + static const QRegExp CompleteUrlRegExp; +}; + +class FilterObject : public QObject +{ +Q_OBJECT +public: + FilterObject(Filter::HotSpot* filter) : _filter(filter) {} +private slots: + void activated(); +private: + Filter::HotSpot* _filter; +}; + +/** + * A chain which allows a group of filters to be processed as one. + * The chain owns the filters added to it and deletes them when the chain itself is destroyed. + * + * Use addFilter() to add a new filter to the chain. + * When new text to be filtered arrives, use addLine() to add each additional + * line of text which needs to be processed and then after adding the last line, use + * process() to cause each filter in the chain to process the text. + * + * After processing a block of text, the reset() method can be used to set the filter chain's + * internal cursor back to the first line. + * + * The hotSpotAt() method will return the first hotspot which covers a given position. + * + * The hotSpots() and hotSpotsAtLine() method return all of the hotspots in the text and on + * a given line respectively. + */ +class FilterChain : protected QList<Filter*> +{ +public: + virtual ~FilterChain(); + + /** Adds a new filter to the chain. The chain will delete this filter when it is destroyed */ + void addFilter(Filter* filter); + /** Removes a filter from the chain. The chain will no longer delete the filter when destroyed */ + void removeFilter(Filter* filter); + /** Returns true if the chain contains @p filter */ + bool containsFilter(Filter* filter); + /** Removes all filters from the chain */ + void clear(); + + /** Resets each filter in the chain */ + void reset(); + /** + * Processes each filter in the chain + */ + void process(); + + /** Sets the buffer for each filter in the chain to process. */ + void setBuffer(const QString* buffer , const QList<int>* linePositions); + + /** Returns the first hotspot which occurs at @p line, @p column or 0 if no hotspot was found */ + Filter::HotSpot* hotSpotAt(int line , int column) const; + /** Returns a list of all the hotspots in all the chain's filters */ + QList<Filter::HotSpot*> hotSpots() const; + /** Returns a list of all hotspots at the given line in all the chain's filters */ + QList<Filter::HotSpot> hotSpotsAtLine(int line) const; + +}; + +/** A filter chain which processes character images from terminal displays */ +class TerminalImageFilterChain : public FilterChain +{ +public: + TerminalImageFilterChain(); + virtual ~TerminalImageFilterChain(); + + /** + * Set the current terminal image to @p image. + * + * @param image The terminal image + * @param lines The number of lines in the terminal image + * @param columns The number of columns in the terminal image + * @param lineProperties The line properties to set for image + */ + void setImage(const Character* const image , int lines , int columns, + const QVector<LineProperty>& lineProperties); + +private: + QString* _buffer; + QList<int>* _linePositions; +}; + +#endif //FILTER_H
new file mode 100644 --- /dev/null +++ b/gui/src/History.cpp @@ -0,0 +1,696 @@ +/* + This file is part of Konsole, an X terminal. + Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "History.h" + +// System +#include <iostream> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <unistd.h> +#include <errno.h> + + +// Reasonable line size +#define LINE_SIZE 1024 + +/* + An arbitrary long scroll. + + One can modify the scroll only by adding either cells + or newlines, but access it randomly. + + The model is that of an arbitrary wide typewriter scroll + in that the scroll is a serie of lines and each line is + a serie of cells with no overwriting permitted. + + The implementation provides arbitrary length and numbers + of cells and line/column indexed read access to the scroll + at constant costs. + +KDE4: Can we use QTemporaryFile here, instead of KTempFile? + +FIXME: some complain about the history buffer comsuming the + memory of their machines. This problem is critical + since the history does not behave gracefully in cases + where the memory is used up completely. + + I put in a workaround that should handle it problem + now gracefully. I'm not satisfied with the solution. + +FIXME: Terminating the history is not properly indicated + in the menu. We should throw a signal. + +FIXME: There is noticeable decrease in speed, also. Perhaps, + there whole feature needs to be revisited therefore. + Disadvantage of a more elaborated, say block-oriented + scheme with wrap around would be it's complexity. +*/ + +//FIXME: tempory replacement for tmpfile +// this is here one for debugging purpose. + +//#define tmpfile xTmpFile + +// History File /////////////////////////////////////////// + +/* + A Row(X) data type which allows adding elements to the end. +*/ + +HistoryFile::HistoryFile() + : ion(-1), + length(0), + fileMap(0) +{ + if (tmpFile.open()) + { + tmpFile.setAutoRemove(true); + ion = tmpFile.handle(); + } +} + +HistoryFile::~HistoryFile() +{ + if (fileMap) + unmap(); +} + +//TODO: Mapping the entire file in will cause problems if the history file becomes exceedingly large, +//(ie. larger than available memory). HistoryFile::map() should only map in sections of the file at a time, +//to avoid this. +void HistoryFile::map() +{ + assert( fileMap == 0 ); + + fileMap = (char*)mmap( 0 , length , PROT_READ , MAP_PRIVATE , ion , 0 ); + + //if mmap'ing fails, fall back to the read-lseek combination + if ( fileMap == MAP_FAILED ) + { + readWriteBalance = 0; + fileMap = 0; + qDebug() << ": mmap'ing history failed. errno = " << errno; + } +} + +void HistoryFile::unmap() +{ + int result = munmap( fileMap , length ); + assert( result == 0 ); + + fileMap = 0; +} + +bool HistoryFile::isMapped() +{ + return (fileMap != 0); +} + +void HistoryFile::add(const unsigned char* bytes, int len) +{ + if ( fileMap ) + unmap(); + + readWriteBalance++; + + int rc = 0; + + rc = lseek(ion,length,SEEK_SET); if (rc < 0) { perror("HistoryFile::add.seek"); return; } + rc = write(ion,bytes,len); if (rc < 0) { perror("HistoryFile::add.write"); return; } + length += rc; +} + +void HistoryFile::get(unsigned char* bytes, int len, int loc) +{ + //count number of get() calls vs. number of add() calls. + //If there are many more get() calls compared with add() + //calls (decided by using MAP_THRESHOLD) then mmap the log + //file to improve performance. + readWriteBalance--; + if ( !fileMap && readWriteBalance < MAP_THRESHOLD ) + map(); + + if ( fileMap ) + { + for (int i=0;i<len;i++) + bytes[i]=fileMap[loc+i]; + } + else + { + int rc = 0; + + if (loc < 0 || len < 0 || loc + len > length) + fprintf(stderr,"getHist(...,%d,%d): invalid args.\n",len,loc); + rc = lseek(ion,loc,SEEK_SET); if (rc < 0) { perror("HistoryFile::get.seek"); return; } + rc = read(ion,bytes,len); if (rc < 0) { perror("HistoryFile::get.read"); return; } + } +} + +int HistoryFile::len() +{ + return length; +} + + +// History Scroll abstract base class ////////////////////////////////////// + + +HistoryScroll::HistoryScroll(HistoryType* t) + : m_histType(t) +{ +} + +HistoryScroll::~HistoryScroll() +{ + delete m_histType; +} + +bool HistoryScroll::hasScroll() +{ + return true; +} + +// History Scroll File ////////////////////////////////////// + +/* + The history scroll makes a Row(Row(Cell)) from + two history buffers. The index buffer contains + start of line positions which refere to the cells + buffer. + + Note that index[0] addresses the second line + (line #1), while the first line (line #0) starts + at 0 in cells. +*/ + +HistoryScrollFile::HistoryScrollFile(const QString &logFileName) + : HistoryScroll(new HistoryTypeFile(logFileName)), + m_logFileName(logFileName) +{ +} + +HistoryScrollFile::~HistoryScrollFile() +{ +} + +int HistoryScrollFile::getLines() +{ + return index.len() / sizeof(int); +} + +int HistoryScrollFile::getLineLen(int lineno) +{ + return (startOfLine(lineno+1) - startOfLine(lineno)) / sizeof(Character); +} + +bool HistoryScrollFile::isWrappedLine(int lineno) +{ + if (lineno>=0 && lineno <= getLines()) { + unsigned char flag; + lineflags.get((unsigned char*)&flag,sizeof(unsigned char),(lineno)*sizeof(unsigned char)); + return flag; + } + return false; +} + +int HistoryScrollFile::startOfLine(int lineno) +{ + if (lineno <= 0) return 0; + if (lineno <= getLines()) + { + + if (!index.isMapped()) + index.map(); + + int res; + index.get((unsigned char*)&res,sizeof(int),(lineno-1)*sizeof(int)); + return res; + } + return cells.len(); +} + +void HistoryScrollFile::getCells(int lineno, int colno, int count, Character res[]) +{ + cells.get((unsigned char*)res,count*sizeof(Character),startOfLine(lineno)+colno*sizeof(Character)); +} + +void HistoryScrollFile::addCells(const Character text[], int count) +{ + cells.add((unsigned char*)text,count*sizeof(Character)); +} + +void HistoryScrollFile::addLine(bool previousWrapped) +{ + if (index.isMapped()) + index.unmap(); + + int locn = cells.len(); + index.add((unsigned char*)&locn,sizeof(int)); + unsigned char flags = previousWrapped ? 0x01 : 0x00; + lineflags.add((unsigned char*)&flags,sizeof(unsigned char)); +} + + +// History Scroll Buffer ////////////////////////////////////// +HistoryScrollBuffer::HistoryScrollBuffer(unsigned int maxLineCount) + : HistoryScroll(new HistoryTypeBuffer(maxLineCount)) + ,_historyBuffer() + ,_maxLineCount(0) + ,_usedLines(0) + ,_head(0) +{ + setMaxNbLines(maxLineCount); +} + +HistoryScrollBuffer::~HistoryScrollBuffer() +{ + delete[] _historyBuffer; +} + +void HistoryScrollBuffer::addCellsVector(const QVector<Character>& cells) +{ + _head++; + if ( _usedLines < _maxLineCount ) + _usedLines++; + + if ( _head >= _maxLineCount ) + { + _head = 0; + } + + _historyBuffer[bufferIndex(_usedLines-1)] = cells; + _wrappedLine[bufferIndex(_usedLines-1)] = false; +} +void HistoryScrollBuffer::addCells(const Character a[], int count) +{ + HistoryLine newLine(count); + qCopy(a,a+count,newLine.begin()); + + addCellsVector(newLine); +} + +void HistoryScrollBuffer::addLine(bool previousWrapped) +{ + _wrappedLine[bufferIndex(_usedLines-1)] = previousWrapped; +} + +int HistoryScrollBuffer::getLines() +{ + return _usedLines; +} + +int HistoryScrollBuffer::getLineLen(int lineNumber) +{ + Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount ); + + if ( lineNumber < _usedLines ) + { + return _historyBuffer[bufferIndex(lineNumber)].size(); + } + else + { + return 0; + } +} + +bool HistoryScrollBuffer::isWrappedLine(int lineNumber) +{ + Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount ); + + if (lineNumber < _usedLines) + { + //kDebug() << "Line" << lineNumber << "wrapped is" << _wrappedLine[bufferIndex(lineNumber)]; + return _wrappedLine[bufferIndex(lineNumber)]; + } + else + return false; +} + +void HistoryScrollBuffer::getCells(int lineNumber, int startColumn, int count, Character* buffer) +{ + if ( count == 0 ) return; + + Q_ASSERT( lineNumber < _maxLineCount ); + + if (lineNumber >= _usedLines) + { + memset(buffer, 0, count * sizeof(Character)); + return; + } + + const HistoryLine& line = _historyBuffer[bufferIndex(lineNumber)]; + + //kDebug() << "startCol " << startColumn; + //kDebug() << "line.size() " << line.size(); + //kDebug() << "count " << count; + + Q_ASSERT( startColumn <= line.size() - count ); + + memcpy(buffer, line.constData() + startColumn , count * sizeof(Character)); +} + +void HistoryScrollBuffer::setMaxNbLines(unsigned int lineCount) +{ + HistoryLine* oldBuffer = _historyBuffer; + HistoryLine* newBuffer = new HistoryLine[lineCount]; + + for ( int i = 0 ; i < qMin(_usedLines,(int)lineCount) ; i++ ) + { + newBuffer[i] = oldBuffer[bufferIndex(i)]; + } + + _usedLines = qMin(_usedLines,(int)lineCount); + _maxLineCount = lineCount; + _head = ( _usedLines == _maxLineCount ) ? 0 : _usedLines-1; + + _historyBuffer = newBuffer; + delete[] oldBuffer; + + _wrappedLine.resize(lineCount); +} + +int HistoryScrollBuffer::bufferIndex(int lineNumber) +{ + Q_ASSERT( lineNumber >= 0 ); + Q_ASSERT( lineNumber < _maxLineCount ); + Q_ASSERT( (_usedLines == _maxLineCount) || lineNumber <= _head ); + + if ( _usedLines == _maxLineCount ) + { + return (_head+lineNumber+1) % _maxLineCount; + } + else + { + return lineNumber; + } +} + + +// History Scroll None ////////////////////////////////////// + +HistoryScrollNone::HistoryScrollNone() + : HistoryScroll(new HistoryTypeNone()) +{ +} + +HistoryScrollNone::~HistoryScrollNone() +{ +} + +bool HistoryScrollNone::hasScroll() +{ + return false; +} + +int HistoryScrollNone::getLines() +{ + return 0; +} + +int HistoryScrollNone::getLineLen(int) +{ + return 0; +} + +bool HistoryScrollNone::isWrappedLine(int /*lineno*/) +{ + return false; +} + +void HistoryScrollNone::getCells(int, int, int, Character []) +{ +} + +void HistoryScrollNone::addCells(const Character [], int) +{ +} + +void HistoryScrollNone::addLine(bool) +{ +} + +// History Scroll BlockArray ////////////////////////////////////// + +HistoryScrollBlockArray::HistoryScrollBlockArray(size_t size) + : HistoryScroll(new HistoryTypeBlockArray(size)) +{ + m_blockArray.setHistorySize(size); // nb. of lines. +} + +HistoryScrollBlockArray::~HistoryScrollBlockArray() +{ +} + +int HistoryScrollBlockArray::getLines() +{ + return m_lineLengths.count(); +} + +int HistoryScrollBlockArray::getLineLen(int lineno) +{ + if ( m_lineLengths.contains(lineno) ) + return m_lineLengths[lineno]; + else + return 0; +} + +bool HistoryScrollBlockArray::isWrappedLine(int /*lineno*/) +{ + return false; +} + +void HistoryScrollBlockArray::getCells(int lineno, int colno, + int count, Character res[]) +{ + if (!count) return; + + const Block *b = m_blockArray.at(lineno); + + if (!b) { + memset(res, 0, count * sizeof(Character)); // still better than random data + return; + } + + assert(((colno + count) * sizeof(Character)) < ENTRIES); + memcpy(res, b->data + (colno * sizeof(Character)), count * sizeof(Character)); +} + +void HistoryScrollBlockArray::addCells(const Character a[], int count) +{ + Block *b = m_blockArray.lastBlock(); + + if (!b) return; + + // put cells in block's data + assert((count * sizeof(Character)) < ENTRIES); + + memset(b->data, 0, ENTRIES); + + memcpy(b->data, a, count * sizeof(Character)); + b->size = count * sizeof(Character); + + size_t res = m_blockArray.newBlock(); + assert (res > 0); + Q_UNUSED( res ); + + m_lineLengths.insert(m_blockArray.getCurrent(), count); +} + +void HistoryScrollBlockArray::addLine(bool) +{ +} + +////////////////////////////////////////////////////////////////////// +// History Types +////////////////////////////////////////////////////////////////////// + +HistoryType::HistoryType() +{ +} + +HistoryType::~HistoryType() +{ +} + +////////////////////////////// + +HistoryTypeNone::HistoryTypeNone() +{ +} + +bool HistoryTypeNone::isEnabled() const +{ + return false; +} + +HistoryScroll* HistoryTypeNone::scroll(HistoryScroll *old) const +{ + delete old; + return new HistoryScrollNone(); +} + +int HistoryTypeNone::maximumLineCount() const +{ + return 0; +} + +////////////////////////////// + +HistoryTypeBlockArray::HistoryTypeBlockArray(size_t size) + : m_size(size) +{ +} + +bool HistoryTypeBlockArray::isEnabled() const +{ + return true; +} + +int HistoryTypeBlockArray::maximumLineCount() const +{ + return m_size; +} + +HistoryScroll* HistoryTypeBlockArray::scroll(HistoryScroll *old) const +{ + delete old; + return new HistoryScrollBlockArray(m_size); +} + + +////////////////////////////// + +HistoryTypeBuffer::HistoryTypeBuffer(unsigned int nbLines) + : m_nbLines(nbLines) +{ +} + +bool HistoryTypeBuffer::isEnabled() const +{ + return true; +} + +int HistoryTypeBuffer::maximumLineCount() const +{ + return m_nbLines; +} + +HistoryScroll* HistoryTypeBuffer::scroll(HistoryScroll *old) const +{ + if (old) + { + HistoryScrollBuffer *oldBuffer = dynamic_cast<HistoryScrollBuffer*>(old); + if (oldBuffer) + { + oldBuffer->setMaxNbLines(m_nbLines); + return oldBuffer; + } + + HistoryScroll *newScroll = new HistoryScrollBuffer(m_nbLines); + int lines = old->getLines(); + int startLine = 0; + if (lines > (int) m_nbLines) + startLine = lines - m_nbLines; + + Character line[LINE_SIZE]; + for(int i = startLine; i < lines; i++) + { + int size = old->getLineLen(i); + if (size > LINE_SIZE) + { + Character *tmp_line = new Character[size]; + old->getCells(i, 0, size, tmp_line); + newScroll->addCells(tmp_line, size); + newScroll->addLine(old->isWrappedLine(i)); + delete [] tmp_line; + } + else + { + old->getCells(i, 0, size, line); + newScroll->addCells(line, size); + newScroll->addLine(old->isWrappedLine(i)); + } + } + delete old; + return newScroll; + } + return new HistoryScrollBuffer(m_nbLines); +} + +////////////////////////////// + +HistoryTypeFile::HistoryTypeFile(const QString& fileName) + : m_fileName(fileName) +{ +} + +bool HistoryTypeFile::isEnabled() const +{ + return true; +} + +const QString& HistoryTypeFile::getFileName() const +{ + return m_fileName; +} + +HistoryScroll* HistoryTypeFile::scroll(HistoryScroll *old) const +{ + if (dynamic_cast<HistoryFile *>(old)) + return old; // Unchanged. + + HistoryScroll *newScroll = new HistoryScrollFile(m_fileName); + + Character line[LINE_SIZE]; + int lines = (old != 0) ? old->getLines() : 0; + for(int i = 0; i < lines; i++) + { + int size = old->getLineLen(i); + if (size > LINE_SIZE) + { + Character *tmp_line = new Character[size]; + old->getCells(i, 0, size, tmp_line); + newScroll->addCells(tmp_line, size); + newScroll->addLine(old->isWrappedLine(i)); + delete [] tmp_line; + } + else + { + old->getCells(i, 0, size, line); + newScroll->addCells(line, size); + newScroll->addLine(old->isWrappedLine(i)); + } + } + + delete old; + return newScroll; +} + +int HistoryTypeFile::maximumLineCount() const +{ + return 0; +}
new file mode 100644 --- /dev/null +++ b/gui/src/History.h @@ -0,0 +1,307 @@ +/* + This file is part of Konsole, an X terminal. + Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef HISTORY_H +#define HISTORY_H + +// Qt +#include <QtCore/QBitRef> +#include <QtCore/QHash> +#include <QtCore> + +// Konsole +#include "BlockArray.h" +#include "Character.h" + +class HistoryFile +{ +public: + HistoryFile(); + virtual ~HistoryFile(); + + virtual void add(const unsigned char* bytes, int len); + virtual void get(unsigned char* bytes, int len, int loc); + virtual int len(); + + //mmaps the file in read-only mode + void map(); + //un-mmaps the file + void unmap(); + //returns true if the file is mmap'ed + bool isMapped(); + + +private: + int ion; + int length; + QTemporaryFile tmpFile; + + //pointer to start of mmap'ed file data, or 0 if the file is not mmap'ed + char* fileMap; + + //incremented whenver 'add' is called and decremented whenever + //'get' is called. + //this is used to detect when a large number of lines are being read and processed from the history + //and automatically mmap the file for better performance (saves the overhead of many lseek-read calls). + int readWriteBalance; + + //when readWriteBalance goes below this threshold, the file will be mmap'ed automatically + static const int MAP_THRESHOLD = -1000; +}; + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// +// Abstract base class for file and buffer versions +////////////////////////////////////////////////////////////////////// +class HistoryType; + +class HistoryScroll +{ +public: + HistoryScroll(HistoryType*); + virtual ~HistoryScroll(); + + virtual bool hasScroll(); + + // access to history + virtual int getLines() = 0; + virtual int getLineLen(int lineno) = 0; + virtual void getCells(int lineno, int colno, int count, Character res[]) = 0; + virtual bool isWrappedLine(int lineno) = 0; + + // backward compatibility (obsolete) + Character getCell(int lineno, int colno) { Character res; getCells(lineno,colno,1,&res); return res; } + + // adding lines. + virtual void addCells(const Character a[], int count) = 0; + // convenience method - this is virtual so that subclasses can take advantage + // of QVector's implicit copying + virtual void addCellsVector(const QVector<Character>& cells) + { + addCells(cells.data(),cells.size()); + } + + virtual void addLine(bool previousWrapped=false) = 0; + + // + // FIXME: Passing around constant references to HistoryType instances + // is very unsafe, because those references will no longer + // be valid if the history scroll is deleted. + // + const HistoryType& getType() { return *m_histType; } + +protected: + HistoryType* m_histType; + +}; + + + +////////////////////////////////////////////////////////////////////// +// File-based history (e.g. file log, no limitation in length) +////////////////////////////////////////////////////////////////////// + +class HistoryScrollFile : public HistoryScroll +{ +public: + HistoryScrollFile(const QString &logFileName); + virtual ~HistoryScrollFile(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addLine(bool previousWrapped=false); + +private: + int startOfLine(int lineno); + + QString m_logFileName; + HistoryFile index; // lines Row(int) + HistoryFile cells; // text Row(Character) + HistoryFile lineflags; // flags Row(unsigned char) +}; + + +////////////////////////////////////////////////////////////////////// +// Buffer-based history (limited to a fixed nb of lines) +////////////////////////////////////////////////////////////////////// +class HistoryScrollBuffer : public HistoryScroll +{ +public: + typedef QVector<Character> HistoryLine; + + HistoryScrollBuffer(unsigned int maxNbLines = 1000); + virtual ~HistoryScrollBuffer(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addCellsVector(const QVector<Character>& cells); + virtual void addLine(bool previousWrapped=false); + + void setMaxNbLines(unsigned int nbLines); + unsigned int maxNbLines() { return _maxLineCount; } + + +private: + int bufferIndex(int lineNumber); + + HistoryLine* _historyBuffer; + QBitArray _wrappedLine; + int _maxLineCount; + int _usedLines; + int _head; +}; + +////////////////////////////////////////////////////////////////////// +// Nothing-based history (no history :-) +////////////////////////////////////////////////////////////////////// +class HistoryScrollNone : public HistoryScroll +{ +public: + HistoryScrollNone(); + virtual ~HistoryScrollNone(); + + virtual bool hasScroll(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addLine(bool previousWrapped=false); +}; + +////////////////////////////////////////////////////////////////////// +// BlockArray-based history +////////////////////////////////////////////////////////////////////// +class HistoryScrollBlockArray : public HistoryScroll +{ +public: + HistoryScrollBlockArray(size_t size); + virtual ~HistoryScrollBlockArray(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addLine(bool previousWrapped=false); + +protected: + BlockArray m_blockArray; + QHash<int,size_t> m_lineLengths; +}; + +////////////////////////////////////////////////////////////////////// +// History type +////////////////////////////////////////////////////////////////////// + +class HistoryType +{ +public: + HistoryType(); + virtual ~HistoryType(); + + /** + * Returns true if the history is enabled ( can store lines of output ) + * or false otherwise. + */ + virtual bool isEnabled() const = 0; + /** + * Returns true if the history size is unlimited. + */ + bool isUnlimited() const { return maximumLineCount() == 0; } + /** + * Returns the maximum number of lines which this history type + * can store or 0 if the history can store an unlimited number of lines. + */ + virtual int maximumLineCount() const = 0; + + virtual HistoryScroll* scroll(HistoryScroll *) const = 0; +}; + +class HistoryTypeNone : public HistoryType +{ +public: + HistoryTypeNone(); + + virtual bool isEnabled() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; +}; + +class HistoryTypeBlockArray : public HistoryType +{ +public: + HistoryTypeBlockArray(size_t size); + + virtual bool isEnabled() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; + +protected: + size_t m_size; +}; + +class HistoryTypeFile : public HistoryType +{ +public: + HistoryTypeFile(const QString& fileName=QString()); + + virtual bool isEnabled() const; + virtual const QString& getFileName() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; + +protected: + QString m_fileName; +}; + + +class HistoryTypeBuffer : public HistoryType +{ +public: + HistoryTypeBuffer(unsigned int nbLines); + + virtual bool isEnabled() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; + +protected: + unsigned int m_nbLines; +}; + +#endif // HISTORY_H
new file mode 100644 --- /dev/null +++ b/gui/src/HistoryDockWidget.cpp @@ -0,0 +1,61 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include "HistoryDockWidget.h" +#include <QHBoxLayout> + +HistoryDockWidget::HistoryDockWidget(QWidget *parent) + : QDockWidget(parent) { + setObjectName("HistoryDockWidget"); + construct(); +} + +void HistoryDockWidget::handleListViewItemDoubleClicked(QModelIndex modelIndex) { + QString command = m_historyListModel->data(modelIndex, 0).toString(); + emit commandDoubleClicked(command); +} + +void HistoryDockWidget::construct() { + m_historyListModel = new QStringListModel(); + m_historyListView = new QListView(this); + m_historyListView->setModel(m_historyListModel); + m_historyListView->setAlternatingRowColors(true); + m_historyListView->setEditTriggers(QAbstractItemView::NoEditTriggers); + QHBoxLayout *layout = new QHBoxLayout(); + + setWindowTitle(tr("Command History")); + setWidget(new QWidget()); + + layout->addWidget(m_historyListView); + layout->setMargin(2); + + widget()->setLayout(layout); + connect(m_historyListView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(handleListViewItemDoubleClicked(QModelIndex))); +} + +void HistoryDockWidget::updateHistory(string_vector historyEntries) { + QStringList stringList = m_historyListModel->stringList(); + for(int i = 0; i < historyEntries.length(); i++) { + QString command(historyEntries[i].c_str()); + if(!command.startsWith("#")) { + stringList.push_front(command); + } + } + m_historyListModel->setStringList(stringList); + emit information(tr("History updated.")); +}
new file mode 100644 --- /dev/null +++ b/gui/src/HistoryDockWidget.h @@ -0,0 +1,55 @@ +#ifndef HISTORYDOCKWIDGET_H +#define HISTORYDOCKWIDGET_H + +#include <QDockWidget> +#include <QListView> +#include <QStringListModel> + +// Octave includes +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef PACKAGE_URL +#include "octave/config.h" + +#include "octave/debug.h" +#include "octave/octave.h" +#include "octave/symtab.h" +#include "octave/parse.h" +#include "octave/unwind-prot.h" +#include "octave/toplev.h" +#include "octave/load-path.h" +#include "octave/error.h" +#include "octave/quit.h" +#include "octave/variables.h" +#include "octave/sighandlers.h" +#include "octave/sysdep.h" +#include "octave/str-vec.h" +#include "octave/cmd-hist.h" +#include "octave/cmd-edit.h" +#include "octave/oct-env.h" +#include "octave/symtab.h" +#include "cmd-edit.h" + +class HistoryDockWidget : public QDockWidget { + Q_OBJECT +public: + HistoryDockWidget(QWidget *parent = 0); + void updateHistory(string_vector historyEntries); + +signals: + void information(QString message); + void commandDoubleClicked(QString command); + +private slots: + void handleListViewItemDoubleClicked(QModelIndex modelIndex); + +private: + void construct(); + QListView *m_historyListView; + QStringListModel *m_historyListModel; +}; + +#endif // HISTORYDOCKWIDGET_H
new file mode 100644 --- /dev/null +++ b/gui/src/ImageViewerDockWidget.cpp @@ -0,0 +1,23 @@ +#include "ImageViewerDockWidget.h" +#include <QLabel> +#include <QPixmap> +#include <QScrollArea> + +ImageViewerDockWidget::ImageViewerDockWidget(QPixmap pixmap, QWidget *parent) + : QDockWidget(parent), + m_pixmap(pixmap) { + construct(); +} + +void ImageViewerDockWidget::construct() { + QLabel *label = new QLabel(); + label->setBackgroundRole(QPalette::Base); + label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + label->setScaledContents(true); + label->setPixmap(m_pixmap); + + QScrollArea *scrollArea = new QScrollArea(this); + scrollArea->setBackgroundRole(QPalette::Dark); + scrollArea->setWidget(label); + setWidget(scrollArea); +}
new file mode 100644 --- /dev/null +++ b/gui/src/ImageViewerDockWidget.h @@ -0,0 +1,15 @@ +#ifndef IMAGEVIEWERDOCKWIDGET_H +#define IMAGEVIEWERDOCKWIDGET_H + +#include <QDockWidget> + +class ImageViewerDockWidget : public QDockWidget { +public: + ImageViewerDockWidget(QPixmap pixmap, QWidget *parent = 0); + +private: + void construct(); + QPixmap m_pixmap; +}; + +#endif // IMAGEVIEWERDOCKWIDGET_H
new file mode 100644 --- /dev/null +++ b/gui/src/KeyboardTranslator.cpp @@ -0,0 +1,970 @@ +/* + This source file is part of Konsole, a terminal emulator. + + Copyright 2007-2008 by Robert Knight <robertknight@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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "KeyboardTranslator.h" + +// System +#include <ctype.h> +#include <stdio.h> + +// Qt +#include <QtCore/QBuffer> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QTextStream> +#include <QtGui/QKeySequence> +#include <QtCore/QDir> +#include <QtCore/QStringList> +#include <QtCore/QDebug> +#include <QtCore/QDataStream> + +const QByteArray KeyboardTranslatorManager::defaultTranslatorText( +"keyboard \"Fallback Key Translator\"\n" +"key Tab : \"\\t\"" +); + +KeyboardTranslatorManager::KeyboardTranslatorManager() + : _haveLoadedAll(false) +{ +} +KeyboardTranslatorManager::~KeyboardTranslatorManager() +{ + qDeleteAll(_translators); +} +QString KeyboardTranslatorManager::findTranslatorPath(const QString& name) +{ + return QString("../kb-layouts/" + name + ".keytab"); + // return KGlobal::dirs()->findResource("data","konsole/"+name+".keytab"); +} +void KeyboardTranslatorManager::findTranslators() +{ + //QStringList list = KGlobal::dirs()->findAllResources("data", + // "konsole/*.keytab", + // KStandardDirs::NoDuplicates); + + QDir dir("../kb-layouts/"); + QStringList filters; + filters << "*.keytab"; + dir.setNameFilters(filters); + QStringList list = dir.entryList(filters); + + // add the name of each translator to the list and associated + // the name with a null pointer to indicate that the translator + // has not yet been loaded from disk + QStringListIterator listIter(list); + while (listIter.hasNext()) + { + QString translatorPath = listIter.next(); + + QString name = QFileInfo(translatorPath).baseName(); + + if ( !_translators.contains(name) ) + _translators.insert(name,0); + } + + _haveLoadedAll = true; +} + +const KeyboardTranslator* KeyboardTranslatorManager::findTranslator(const QString& name) +{ + if ( name.isEmpty() ) + return defaultTranslator(); + + if ( _translators.contains(name) && _translators[name] != 0 ) + return _translators[name]; + + KeyboardTranslator* translator = loadTranslator(name); + + if ( translator != 0 ) + _translators[name] = translator; + //else if ( !name.isEmpty() ) + // kWarning() << "Unable to load translator" << name; + + return translator; +} + +bool KeyboardTranslatorManager::saveTranslator(const KeyboardTranslator* translator) +{ + //const QString path = KGlobal::dirs()->saveLocation("data","konsole/")+translator->name() + // +".keytab"; + const QString path = ".keytab"; + + //kDebug() << "Saving translator to" << path; + + QFile destination(path); + if (!destination.open(QIODevice::WriteOnly | QIODevice::Text)) + { + //kWarning() << "Unable to save keyboard translation:" + // << destination.errorString(); + return false; + } + + { + KeyboardTranslatorWriter writer(&destination); + writer.writeHeader(translator->description()); + + QListIterator<KeyboardTranslator::Entry> iter(translator->entries()); + while ( iter.hasNext() ) + writer.writeEntry(iter.next()); + } + + destination.close(); + + return true; +} + +KeyboardTranslator* KeyboardTranslatorManager::loadTranslator(const QString& name) +{ + const QString& path = findTranslatorPath(name); + + QFile source(path); + if (name.isEmpty() || !source.open(QIODevice::ReadOnly | QIODevice::Text)) + return 0; + + return loadTranslator(&source,name); +} + +const KeyboardTranslator* KeyboardTranslatorManager::defaultTranslator() +{ + // Try to find the default.keytab file if it exists, otherwise + // fall back to the hard-coded one + const KeyboardTranslator* translator = findTranslator("default"); + if (!translator) + { + QBuffer textBuffer; + textBuffer.setData(defaultTranslatorText); + textBuffer.open(QIODevice::ReadOnly); + translator = loadTranslator(&textBuffer,"fallback"); + } + return translator; +} + +KeyboardTranslator* KeyboardTranslatorManager::loadTranslator(QIODevice* source,const QString& name) +{ + KeyboardTranslator* translator = new KeyboardTranslator(name); + KeyboardTranslatorReader reader(source); + translator->setDescription( reader.description() ); + while ( reader.hasNextEntry() ) + translator->addEntry(reader.nextEntry()); + + source->close(); + + if ( !reader.parseError() ) + { + return translator; + } + else + { + delete translator; + return 0; + } +} + +KeyboardTranslatorWriter::KeyboardTranslatorWriter(QIODevice* destination) +: _destination(destination) +{ + Q_ASSERT( destination && destination->isWritable() ); + + _writer = new QTextStream(_destination); +} +KeyboardTranslatorWriter::~KeyboardTranslatorWriter() +{ + delete _writer; +} +void KeyboardTranslatorWriter::writeHeader( const QString& description ) +{ + *_writer << "keyboard \"" << description << '\"' << '\n'; +} +void KeyboardTranslatorWriter::writeEntry( const KeyboardTranslator::Entry& entry ) +{ + QString result; + if ( entry.command() != KeyboardTranslator::NoCommand ) + result = entry.resultToString(); + else + result = '\"' + entry.resultToString() + '\"'; + + *_writer << "key " << entry.conditionToString() << " : " << result << '\n'; +} + + +// each line of the keyboard translation file is one of: +// +// - keyboard "name" +// - key KeySequence : "characters" +// - key KeySequence : CommandName +// +// KeySequence begins with the name of the key ( taken from the Qt::Key enum ) +// and is followed by the keyboard modifiers and state flags ( with + or - in front +// of each modifier or flag to indicate whether it is required ). All keyboard modifiers +// and flags are optional, if a particular modifier or state is not specified it is +// assumed not to be a part of the sequence. The key sequence may contain whitespace +// +// eg: "key Up+Shift : scrollLineUp" +// "key Next-Shift : "\E[6~" +// +// (lines containing only whitespace are ignored, parseLine assumes that comments have +// already been removed) +// + +KeyboardTranslatorReader::KeyboardTranslatorReader( QIODevice* source ) + : _source(source) + , _hasNext(false) +{ + // read input until we find the description + while ( _description.isEmpty() && !source->atEnd() ) + { + QList<Token> tokens = tokenize( QString(source->readLine()) ); + if ( !tokens.isEmpty() && tokens.first().type == Token::TitleKeyword ) + _description = QString(tokens[1].text.toLatin1().data()); + } + // read first entry (if any) + readNext(); +} +void KeyboardTranslatorReader::readNext() +{ + // find next entry + while ( !_source->atEnd() ) + { + const QList<Token>& tokens = tokenize( QString(_source->readLine()) ); + if ( !tokens.isEmpty() && tokens.first().type == Token::KeyKeyword ) + { + KeyboardTranslator::States flags = KeyboardTranslator::NoState; + KeyboardTranslator::States flagMask = KeyboardTranslator::NoState; + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + Qt::KeyboardModifiers modifierMask = Qt::NoModifier; + + int keyCode = Qt::Key_unknown; + + decodeSequence(tokens[1].text.toLower(), + keyCode, + modifiers, + modifierMask, + flags, + flagMask); + + KeyboardTranslator::Command command = KeyboardTranslator::NoCommand; + QByteArray text; + + // get text or command + if ( tokens[2].type == Token::OutputText ) + { + text = tokens[2].text.toLocal8Bit(); + } + else if ( tokens[2].type == Token::Command ) + { + // identify command + // if (!parseAsCommand(tokens[2].text,command)) + // kWarning() << "Command" << tokens[2].text << "not understood."; + } + + KeyboardTranslator::Entry newEntry; + newEntry.setKeyCode( keyCode ); + newEntry.setState( flags ); + newEntry.setStateMask( flagMask ); + newEntry.setModifiers( modifiers ); + newEntry.setModifierMask( modifierMask ); + newEntry.setText( text ); + newEntry.setCommand( command ); + + _nextEntry = newEntry; + + _hasNext = true; + + return; + } + } + + _hasNext = false; +} + +bool KeyboardTranslatorReader::parseAsCommand(const QString& text,KeyboardTranslator::Command& command) +{ + if ( text.compare("erase",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::EraseCommand; + else if ( text.compare("scrollpageup",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollPageUpCommand; + else if ( text.compare("scrollpagedown",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollPageDownCommand; + else if ( text.compare("scrolllineup",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollLineUpCommand; + else if ( text.compare("scrolllinedown",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollLineDownCommand; + else if ( text.compare("scrolllock",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollLockCommand; + else + return false; + + return true; +} + +bool KeyboardTranslatorReader::decodeSequence(const QString& text, + int& keyCode, + Qt::KeyboardModifiers& modifiers, + Qt::KeyboardModifiers& modifierMask, + KeyboardTranslator::States& flags, + KeyboardTranslator::States& flagMask) +{ + bool isWanted = true; + bool endOfItem = false; + QString buffer; + + Qt::KeyboardModifiers tempModifiers = modifiers; + Qt::KeyboardModifiers tempModifierMask = modifierMask; + KeyboardTranslator::States tempFlags = flags; + KeyboardTranslator::States tempFlagMask = flagMask; + + for ( int i = 0 ; i < text.count() ; i++ ) + { + const QChar& ch = text[i]; + bool isFirstLetter = i == 0; + bool isLastLetter = ( i == text.count()-1 ); + endOfItem = true; + if ( ch.isLetterOrNumber() ) + { + endOfItem = false; + buffer.append(ch); + } else if ( isFirstLetter ) + { + buffer.append(ch); + } + + if ( (endOfItem || isLastLetter) && !buffer.isEmpty() ) + { + Qt::KeyboardModifier itemModifier = Qt::NoModifier; + int itemKeyCode = 0; + KeyboardTranslator::State itemFlag = KeyboardTranslator::NoState; + + if ( parseAsModifier(buffer,itemModifier) ) + { + tempModifierMask |= itemModifier; + + if ( isWanted ) + tempModifiers |= itemModifier; + } + else if ( parseAsStateFlag(buffer,itemFlag) ) + { + tempFlagMask |= itemFlag; + + if ( isWanted ) + tempFlags |= itemFlag; + } + else if ( parseAsKeyCode(buffer,itemKeyCode) ) + keyCode = itemKeyCode; + //else + // kWarning() << "Unable to parse key binding item:" << buffer; + + buffer.clear(); + } + + // check if this is a wanted / not-wanted flag and update the + // state ready for the next item + if ( ch == '+' ) + isWanted = true; + else if ( ch == '-' ) + isWanted = false; + } + + modifiers = tempModifiers; + modifierMask = tempModifierMask; + flags = tempFlags; + flagMask = tempFlagMask; + + return true; +} + +bool KeyboardTranslatorReader::parseAsModifier(const QString& item , Qt::KeyboardModifier& modifier) +{ + if ( item == "shift" ) + modifier = Qt::ShiftModifier; + else if ( item == "ctrl" || item == "control" ) + modifier = Qt::ControlModifier; + else if ( item == "alt" ) + modifier = Qt::AltModifier; + else if ( item == "meta" ) + modifier = Qt::MetaModifier; + else if ( item == "keypad" ) + modifier = Qt::KeypadModifier; + else + return false; + + return true; +} +bool KeyboardTranslatorReader::parseAsStateFlag(const QString& item , KeyboardTranslator::State& flag) +{ + if ( item == "appcukeys" || item == "appcursorkeys" ) + flag = KeyboardTranslator::CursorKeysState; + else if ( item == "ansi" ) + flag = KeyboardTranslator::AnsiState; + else if ( item == "newline" ) + flag = KeyboardTranslator::NewLineState; + else if ( item == "appscreen" ) + flag = KeyboardTranslator::AlternateScreenState; + else if ( item == "anymod" || item == "anymodifier" ) + flag = KeyboardTranslator::AnyModifierState; + else if ( item == "appkeypad" ) + flag = KeyboardTranslator::ApplicationKeypadState; + else + return false; + + return true; +} +bool KeyboardTranslatorReader::parseAsKeyCode(const QString& item , int& keyCode) +{ + QKeySequence sequence = QKeySequence::fromString(item); + if ( !sequence.isEmpty() ) + { + keyCode = sequence[0]; + + if ( sequence.count() > 1 ) + { + //kWarning() << "Unhandled key codes in sequence: " << item; + } + } + // additional cases implemented for backwards compatibility with KDE 3 + else if ( item == "prior" ) + keyCode = Qt::Key_PageUp; + else if ( item == "next" ) + keyCode = Qt::Key_PageDown; + else + return false; + + return true; +} + +QString KeyboardTranslatorReader::description() const +{ + return _description; +} +bool KeyboardTranslatorReader::hasNextEntry() +{ + return _hasNext; +} +KeyboardTranslator::Entry KeyboardTranslatorReader::createEntry( const QString& condition , + const QString& result ) +{ + QString entryString("keyboard \"temporary\"\nkey "); + entryString.append(condition); + entryString.append(" : "); + + // if 'result' is the name of a command then the entry result will be that command, + // otherwise the result will be treated as a string to echo when the key sequence + // specified by 'condition' is pressed + KeyboardTranslator::Command command; + if (parseAsCommand(result,command)) + entryString.append(result); + else + entryString.append('\"' + result + '\"'); + + QByteArray array = entryString.toUtf8(); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); + KeyboardTranslatorReader reader(&buffer); + + KeyboardTranslator::Entry entry; + if ( reader.hasNextEntry() ) + entry = reader.nextEntry(); + + return entry; +} + +KeyboardTranslator::Entry KeyboardTranslatorReader::nextEntry() +{ + Q_ASSERT( _hasNext ); + KeyboardTranslator::Entry entry = _nextEntry; + readNext(); + return entry; +} +bool KeyboardTranslatorReader::parseError() +{ + return false; +} +QList<KeyboardTranslatorReader::Token> KeyboardTranslatorReader::tokenize(const QString& line) +{ + QString text = line; + + // remove comments + bool inQuotes = false; + int commentPos = -1; + for (int i=text.length()-1;i>=0;i--) + { + QChar ch = text[i]; + if (ch == '\"') + inQuotes = !inQuotes; + else if (ch == '#' && !inQuotes) + commentPos = i; + } + if (commentPos != -1) + text.remove(commentPos,text.length()); + + text = text.simplified(); + + // title line: keyboard "title" + static QRegExp title("keyboard\\s+\"(.*)\""); + // key line: key KeySequence : "output" + // key line: key KeySequence : command + static QRegExp key("key\\s+([\\w\\+\\s\\-\\*\\.]+)\\s*:\\s*(\"(.*)\"|\\w+)"); + + QList<Token> list; + if ( text.isEmpty() ) + { + return list; + } + + if ( title.exactMatch(text) ) + { + Token titleToken = { Token::TitleKeyword , QString() }; + Token textToken = { Token::TitleText , title.capturedTexts()[1] }; + + list << titleToken << textToken; + } + else if ( key.exactMatch(text) ) + { + Token keyToken = { Token::KeyKeyword , QString() }; + Token sequenceToken = { Token::KeySequence , key.capturedTexts()[1].remove(' ') }; + + list << keyToken << sequenceToken; + + if ( key.capturedTexts()[3].isEmpty() ) + { + // capturedTexts()[2] is a command + Token commandToken = { Token::Command , key.capturedTexts()[2] }; + list << commandToken; + } + else + { + // capturedTexts()[3] is the output string + Token outputToken = { Token::OutputText , key.capturedTexts()[3] }; + list << outputToken; + } + } + else + { + //kWarning() << "Line in keyboard translator file could not be understood:" << text; + } + + return list; +} + +QList<QString> KeyboardTranslatorManager::allTranslators() +{ + if ( !_haveLoadedAll ) + { + findTranslators(); + } + + return _translators.keys(); +} + +KeyboardTranslator::Entry::Entry() +: _keyCode(0) +, _modifiers(Qt::NoModifier) +, _modifierMask(Qt::NoModifier) +, _state(NoState) +, _stateMask(NoState) +, _command(NoCommand) +{ +} + +bool KeyboardTranslator::Entry::operator==(const Entry& rhs) const +{ + return _keyCode == rhs._keyCode && + _modifiers == rhs._modifiers && + _modifierMask == rhs._modifierMask && + _state == rhs._state && + _stateMask == rhs._stateMask && + _command == rhs._command && + _text == rhs._text; +} + +bool KeyboardTranslator::Entry::matches(int keyCode , + Qt::KeyboardModifiers modifiers, + States testState) const +{ + if ( _keyCode != keyCode ) + return false; + + if ( (modifiers & _modifierMask) != (_modifiers & _modifierMask) ) + return false; + + // if modifiers is non-zero, the 'any modifier' state is implicit + if ( modifiers != 0 ) + testState |= AnyModifierState; + + if ( (testState & _stateMask) != (_state & _stateMask) ) + return false; + + // special handling for the 'Any Modifier' state, which checks for the presence of + // any or no modifiers. In this context, the 'keypad' modifier does not count. + bool anyModifiersSet = modifiers != 0 && modifiers != Qt::KeypadModifier; + bool wantAnyModifier = _state & KeyboardTranslator::AnyModifierState; + if ( _stateMask & KeyboardTranslator::AnyModifierState ) + { + if ( wantAnyModifier != anyModifiersSet ) + return false; + } + + return true; +} +QByteArray KeyboardTranslator::Entry::escapedText(bool expandWildCards,Qt::KeyboardModifiers modifiers) const +{ + QByteArray result(text(expandWildCards,modifiers)); + + for ( int i = 0 ; i < result.count() ; i++ ) + { + char ch = result[i]; + char replacement = 0; + + switch ( ch ) + { + case 27 : replacement = 'E'; break; + case 8 : replacement = 'b'; break; + case 12 : replacement = 'f'; break; + case 9 : replacement = 't'; break; + case 13 : replacement = 'r'; break; + case 10 : replacement = 'n'; break; + default: + // any character which is not printable is replaced by an equivalent + // \xhh escape sequence (where 'hh' are the corresponding hex digits) + if ( !QChar(ch).isPrint() ) + replacement = 'x'; + } + + if ( replacement == 'x' ) + { + result.replace(i,1,"\\x"+QByteArray(1,ch).toHex()); + } else if ( replacement != 0 ) + { + result.remove(i,1); + result.insert(i,'\\'); + result.insert(i+1,replacement); + } + } + + return result; +} +QByteArray KeyboardTranslator::Entry::unescape(const QByteArray& input) const +{ + QByteArray result(input); + + for ( int i = 0 ; i < result.count()-1 ; i++ ) + { + + QByteRef ch = result[i]; + if ( ch == '\\' ) + { + char replacement[2] = {0,0}; + int charsToRemove = 2; + bool escapedChar = true; + + switch ( result[i+1] ) + { + case 'E' : replacement[0] = 27; break; + case 'b' : replacement[0] = 8 ; break; + case 'f' : replacement[0] = 12; break; + case 't' : replacement[0] = 9 ; break; + case 'r' : replacement[0] = 13; break; + case 'n' : replacement[0] = 10; break; + case 'x' : + { + // format is \xh or \xhh where 'h' is a hexadecimal + // digit from 0-9 or A-F which should be replaced + // with the corresponding character value + char hexDigits[3] = {0}; + + if ( (i < result.count()-2) && isxdigit(result[i+2]) ) + hexDigits[0] = result[i+2]; + if ( (i < result.count()-3) && isxdigit(result[i+3]) ) + hexDigits[1] = result[i+3]; + + unsigned charValue = 0; + sscanf(hexDigits,"%x",&charValue); + + replacement[0] = (char)charValue; + charsToRemove = 2 + strlen(hexDigits); + } + break; + default: + escapedChar = false; + } + + if ( escapedChar ) + result.replace(i,charsToRemove,replacement); + } + } + + return result; +} + +void KeyboardTranslator::Entry::insertModifier( QString& item , int modifier ) const +{ + if ( !(modifier & _modifierMask) ) + return; + + if ( modifier & _modifiers ) + item += '+'; + else + item += '-'; + + if ( modifier == Qt::ShiftModifier ) + item += "Shift"; + else if ( modifier == Qt::ControlModifier ) + item += "Ctrl"; + else if ( modifier == Qt::AltModifier ) + item += "Alt"; + else if ( modifier == Qt::MetaModifier ) + item += "Meta"; + else if ( modifier == Qt::KeypadModifier ) + item += "KeyPad"; +} +void KeyboardTranslator::Entry::insertState( QString& item , int state ) const +{ + if ( !(state & _stateMask) ) + return; + + if ( state & _state ) + item += '+' ; + else + item += '-' ; + + if ( state == KeyboardTranslator::AlternateScreenState ) + item += "AppScreen"; + else if ( state == KeyboardTranslator::NewLineState ) + item += "NewLine"; + else if ( state == KeyboardTranslator::AnsiState ) + item += "Ansi"; + else if ( state == KeyboardTranslator::CursorKeysState ) + item += "AppCursorKeys"; + else if ( state == KeyboardTranslator::AnyModifierState ) + item += "AnyModifier"; + else if ( state == KeyboardTranslator::ApplicationKeypadState ) + item += "AppKeypad"; +} +QString KeyboardTranslator::Entry::resultToString(bool expandWildCards,Qt::KeyboardModifiers modifiers) const +{ + if ( !_text.isEmpty() ) + return escapedText(expandWildCards,modifiers); + else if ( _command == EraseCommand ) + return "Erase"; + else if ( _command == ScrollPageUpCommand ) + return "ScrollPageUp"; + else if ( _command == ScrollPageDownCommand ) + return "ScrollPageDown"; + else if ( _command == ScrollLineUpCommand ) + return "ScrollLineUp"; + else if ( _command == ScrollLineDownCommand ) + return "ScrollLineDown"; + else if ( _command == ScrollLockCommand ) + return "ScrollLock"; + + return QString(); +} +QString KeyboardTranslator::Entry::conditionToString() const +{ + QString result = QKeySequence(_keyCode).toString(); + + insertModifier( result , Qt::ShiftModifier ); + insertModifier( result , Qt::ControlModifier ); + insertModifier( result , Qt::AltModifier ); + insertModifier( result , Qt::MetaModifier ); + insertModifier( result , Qt::KeypadModifier ); + + insertState( result , KeyboardTranslator::AlternateScreenState ); + insertState( result , KeyboardTranslator::NewLineState ); + insertState( result , KeyboardTranslator::AnsiState ); + insertState( result , KeyboardTranslator::CursorKeysState ); + insertState( result , KeyboardTranslator::AnyModifierState ); + insertState( result , KeyboardTranslator::ApplicationKeypadState ); + + return result; +} + +KeyboardTranslator::KeyboardTranslator(const QString& name) +: _name(name) +{ +} + +void KeyboardTranslator::setDescription(const QString& description) +{ + _description = description; +} +QString KeyboardTranslator::description() const +{ + return _description; +} +void KeyboardTranslator::setName(const QString& name) +{ + _name = name; +} +QString KeyboardTranslator::name() const +{ + return _name; +} + +QList<KeyboardTranslator::Entry> KeyboardTranslator::entries() const +{ + return _entries.values(); +} + +void KeyboardTranslator::addEntry(const Entry& entry) +{ + const int keyCode = entry.keyCode(); + _entries.insert(keyCode,entry); +} +void KeyboardTranslator::replaceEntry(const Entry& existing , const Entry& replacement) +{ + if ( !existing.isNull() ) + _entries.remove(existing.keyCode(),existing); + _entries.insert(replacement.keyCode(),replacement); +} +void KeyboardTranslator::removeEntry(const Entry& entry) +{ + _entries.remove(entry.keyCode(),entry); +} +KeyboardTranslator::Entry KeyboardTranslator::findEntry(int keyCode, Qt::KeyboardModifiers modifiers, States state) const +{ + foreach(const Entry& entry, _entries.values(keyCode)) + { + if ( entry.matches(keyCode,modifiers,state) ) + return entry; + } + return Entry(); // entry not found +} +void KeyboardTranslatorManager::addTranslator(KeyboardTranslator* translator) +{ + _translators.insert(translator->name(),translator); + + // if ( !saveTranslator(translator) ) + // kWarning() << "Unable to save translator" << translator->name() + // << "to disk."; +} +bool KeyboardTranslatorManager::deleteTranslator(const QString& name) +{ + Q_ASSERT( _translators.contains(name) ); + + // locate and delete + QString path = findTranslatorPath(name); + if ( QFile::remove(path) ) + { + _translators.remove(name); + return true; + } + else + { + //kWarning() << "Failed to remove translator - " << path; + return false; + } +} + +/** + * @internal + */ +typedef void (*KdeCleanUpFunction)(); + +/** + * @internal + * + * Helper class for K_GLOBAL_STATIC to clean up the object on library unload or application + * shutdown. + */ +class KCleanUpGlobalStatic +{ + public: + KdeCleanUpFunction func; + + inline ~KCleanUpGlobalStatic() { func(); } +}; + + + +#ifdef Q_CC_MSVC +/** + * @internal + * + * MSVC seems to give anonymous structs the same name which fails at link time. So instead we name + * the struct and hope that by adding the line number to the name it's unique enough to never clash. + */ +# define K_GLOBAL_STATIC_STRUCT_NAME(NAME) _k_##NAME##__LINE__ +#else +/** + * @internal + * + * Make the struct of the K_GLOBAL_STATIC anonymous. + */ +# define K_GLOBAL_STATIC_STRUCT_NAME(NAME) +#endif + + + +#define K_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \ +static QBasicAtomicPointer<TYPE > _k_static_##NAME = Q_BASIC_ATOMIC_INITIALIZER(0); \ +static bool _k_static_##NAME##_destroyed; \ +static struct K_GLOBAL_STATIC_STRUCT_NAME(NAME) \ +{ \ + inline bool isDestroyed() const \ + { \ + return _k_static_##NAME##_destroyed; \ + } \ + inline bool exists() const \ + { \ + return _k_static_##NAME != 0; \ + } \ + inline operator TYPE*() \ + { \ + return operator->(); \ + } \ + inline TYPE *operator->() \ + { \ + if (!_k_static_##NAME) { \ + if (isDestroyed()) { \ + qFatal("Fatal Error: Accessed global static '%s *%s()' after destruction. " \ + "Defined at %s:%d", #TYPE, #NAME, __FILE__, __LINE__); \ + } \ + TYPE *x = new TYPE ARGS; \ + if (!_k_static_##NAME.testAndSetOrdered(0, x) \ + && _k_static_##NAME != x ) { \ + delete x; \ + } else { \ + static KCleanUpGlobalStatic cleanUpObject = { destroy }; \ + } \ + } \ + return _k_static_##NAME; \ + } \ + inline TYPE &operator*() \ + { \ + return *operator->(); \ + } \ + static void destroy() \ + { \ + _k_static_##NAME##_destroyed = true; \ + TYPE *x = _k_static_##NAME; \ + _k_static_##NAME = 0; \ + delete x; \ + } \ +} NAME; + +#define K_GLOBAL_STATIC(TYPE, NAME) K_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ()) + +K_GLOBAL_STATIC( KeyboardTranslatorManager , theKeyboardTranslatorManager ) +KeyboardTranslatorManager* KeyboardTranslatorManager::instance() +{ + return theKeyboardTranslatorManager; +}
new file mode 100644 --- /dev/null +++ b/gui/src/KeyboardTranslator.h @@ -0,0 +1,577 @@ +/* + This source file is part of Konsole, a terminal emulator. + + Copyright 2007-2008 by Robert Knight <robertknight@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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef KEYBOARDTRANSLATOR_H +#define KEYBOARDTRANSLATOR_H + +// Qt +#include <QtCore/QHash> +#include <QtCore/QList> +#include <QtGui/QKeySequence> +#include <QtCore/QMetaType> +#include <QtCore/QVarLengthArray> + +class QIODevice; +class QTextStream; + +/** + * A convertor which maps between key sequences pressed by the user and the + * character strings which should be sent to the terminal and commands + * which should be invoked when those character sequences are pressed. + * + * Konsole supports multiple keyboard translators, allowing the user to + * specify the character sequences which are sent to the terminal + * when particular key sequences are pressed. + * + * A key sequence is defined as a key code, associated keyboard modifiers + * (Shift,Ctrl,Alt,Meta etc.) and state flags which indicate the state + * which the terminal must be in for the key sequence to apply. + */ +class KeyboardTranslator +{ +public: + /** + * The meaning of a particular key sequence may depend upon the state which + * the terminal emulation is in. Therefore findEntry() may return a different + * Entry depending upon the state flags supplied. + * + * This enum describes the states which may be associated with with a particular + * entry in the keyboard translation entry. + */ + enum State + { + /** Indicates that no special state is active */ + NoState = 0, + /** + * TODO More documentation + */ + NewLineState = 1, + /** + * Indicates that the terminal is in 'Ansi' mode. + * TODO: More documentation + */ + AnsiState = 2, + /** + * TODO More documentation + */ + CursorKeysState = 4, + /** + * Indicates that the alternate screen ( typically used by interactive programs + * such as screen or vim ) is active + */ + AlternateScreenState = 8, + /** Indicates that any of the modifier keys is active. */ + AnyModifierState = 16, + /** Indicates that the numpad is in application mode. */ + ApplicationKeypadState = 32 + }; + Q_DECLARE_FLAGS(States,State) + + /** + * This enum describes commands which are associated with particular key sequences. + */ + enum Command + { + /** Indicates that no command is associated with this command sequence */ + NoCommand = 0, + /** TODO Document me */ + SendCommand = 1, + /** Scroll the terminal display up one page */ + ScrollPageUpCommand = 2, + /** Scroll the terminal display down one page */ + ScrollPageDownCommand = 4, + /** Scroll the terminal display up one line */ + ScrollLineUpCommand = 8, + /** Scroll the terminal display down one line */ + ScrollLineDownCommand = 16, + /** Toggles scroll lock mode */ + ScrollLockCommand = 32, + /** Echos the operating system specific erase character. */ + EraseCommand = 64 + }; + Q_DECLARE_FLAGS(Commands,Command) + + /** + * Represents an association between a key sequence pressed by the user + * and the character sequence and commands associated with it for a particular + * KeyboardTranslator. + */ + class Entry + { + public: + /** + * Constructs a new entry for a keyboard translator. + */ + Entry(); + + /** + * Returns true if this entry is null. + * This is true for newly constructed entries which have no properties set. + */ + bool isNull() const; + + /** Returns the commands associated with this entry */ + Command command() const; + /** Sets the command associated with this entry. */ + void setCommand(Command command); + + /** + * Returns the character sequence associated with this entry, optionally replacing + * wildcard '*' characters with numbers to indicate the keyboard modifiers being pressed. + * + * TODO: The numbers used to replace '*' characters are taken from the Konsole/KDE 3 code. + * Document them. + * + * @param expandWildCards Specifies whether wild cards (occurrences of the '*' character) in + * the entry should be replaced with a number to indicate the modifier keys being pressed. + * + * @param modifiers The keyboard modifiers being pressed. + */ + QByteArray text(bool expandWildCards = false, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; + + /** Sets the character sequence associated with this entry */ + void setText(const QByteArray& text); + + /** + * Returns the character sequence associated with this entry, + * with any non-printable characters replaced with escape sequences. + * + * eg. \\E for Escape, \\t for tab, \\n for new line. + * + * @param expandWildCards See text() + * @param modifiers See text() + */ + QByteArray escapedText(bool expandWildCards = false, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; + + /** Returns the character code ( from the Qt::Key enum ) associated with this entry */ + int keyCode() const; + /** Sets the character code associated with this entry */ + void setKeyCode(int keyCode); + + /** + * Returns a bitwise-OR of the enabled keyboard modifiers associated with this entry. + * If a modifier is set in modifierMask() but not in modifiers(), this means that the entry + * only matches when that modifier is NOT pressed. + * + * If a modifier is not set in modifierMask() then the entry matches whether the modifier + * is pressed or not. + */ + Qt::KeyboardModifiers modifiers() const; + + /** Returns the keyboard modifiers which are valid in this entry. See modifiers() */ + Qt::KeyboardModifiers modifierMask() const; + + /** See modifiers() */ + void setModifiers( Qt::KeyboardModifiers modifiers ); + /** See modifierMask() and modifiers() */ + void setModifierMask( Qt::KeyboardModifiers modifiers ); + + /** + * Returns a bitwise-OR of the enabled state flags associated with this entry. + * If flag is set in stateMask() but not in state(), this means that the entry only + * matches when the terminal is NOT in that state. + * + * If a state is not set in stateMask() then the entry matches whether the terminal + * is in that state or not. + */ + States state() const; + + /** Returns the state flags which are valid in this entry. See state() */ + States stateMask() const; + + /** See state() */ + void setState( States state ); + /** See stateMask() */ + void setStateMask( States mask ); + + /** + * Returns the key code and modifiers associated with this entry + * as a QKeySequence + */ + //QKeySequence keySequence() const; + + /** + * Returns this entry's conditions ( ie. its key code, modifier and state criteria ) + * as a string. + */ + QString conditionToString() const; + + /** + * Returns this entry's result ( ie. its command or character sequence ) + * as a string. + * + * @param expandWildCards See text() + * @param modifiers See text() + */ + QString resultToString(bool expandWildCards = false, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; + + /** + * Returns true if this entry matches the given key sequence, specified + * as a combination of @p keyCode , @p modifiers and @p state. + */ + bool matches( int keyCode , + Qt::KeyboardModifiers modifiers , + States flags ) const; + + bool operator==(const Entry& rhs) const; + + private: + void insertModifier( QString& item , int modifier ) const; + void insertState( QString& item , int state ) const; + QByteArray unescape(const QByteArray& text) const; + + int _keyCode; + Qt::KeyboardModifiers _modifiers; + Qt::KeyboardModifiers _modifierMask; + States _state; + States _stateMask; + + Command _command; + QByteArray _text; + }; + + /** Constructs a new keyboard translator with the given @p name */ + KeyboardTranslator(const QString& name); + + //KeyboardTranslator(const KeyboardTranslator& other); + + /** Returns the name of this keyboard translator */ + QString name() const; + + /** Sets the name of this keyboard translator */ + void setName(const QString& name); + + /** Returns the descriptive name of this keyboard translator */ + QString description() const; + + /** Sets the descriptive name of this keyboard translator */ + void setDescription(const QString& description); + + /** + * Looks for an entry in this keyboard translator which matches the given + * key code, keyboard modifiers and state flags. + * + * Returns the matching entry if found or a null Entry otherwise ( ie. + * entry.isNull() will return true ) + * + * @param keyCode A key code from the Qt::Key enum + * @param modifiers A combination of modifiers + * @param state Optional flags which specify the current state of the terminal + */ + Entry findEntry(int keyCode , + Qt::KeyboardModifiers modifiers , + States state = NoState) const; + + /** + * Adds an entry to this keyboard translator's table. Entries can be looked up according + * to their key sequence using findEntry() + */ + void addEntry(const Entry& entry); + + /** + * Replaces an entry in the translator. If the @p existing entry is null, + * then this is equivalent to calling addEntry(@p replacement) + */ + void replaceEntry(const Entry& existing , const Entry& replacement); + + /** + * Removes an entry from the table. + */ + void removeEntry(const Entry& entry); + + /** Returns a list of all entries in the translator. */ + QList<Entry> entries() const; + +private: + + QMultiHash<int,Entry> _entries; // entries in this keyboard translation, + // entries are indexed according to + // their keycode + QString _name; + QString _description; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(KeyboardTranslator::States) +Q_DECLARE_OPERATORS_FOR_FLAGS(KeyboardTranslator::Commands) + +/** + * Parses the contents of a Keyboard Translator (.keytab) file and + * returns the entries found in it. + * + * Usage example: + * + * @code + * QFile source( "/path/to/keytab" ); + * source.open( QIODevice::ReadOnly ); + * + * KeyboardTranslator* translator = new KeyboardTranslator( "name-of-translator" ); + * + * KeyboardTranslatorReader reader(source); + * while ( reader.hasNextEntry() ) + * translator->addEntry(reader.nextEntry()); + * + * source.close(); + * + * if ( !reader.parseError() ) + * { + * // parsing succeeded, do something with the translator + * } + * else + * { + * // parsing failed + * } + * @endcode + */ +class KeyboardTranslatorReader +{ +public: + /** Constructs a new reader which parses the given @p source */ + KeyboardTranslatorReader( QIODevice* source ); + + /** + * Returns the description text. + * TODO: More documentation + */ + QString description() const; + + /** Returns true if there is another entry in the source stream */ + bool hasNextEntry(); + /** Returns the next entry found in the source stream */ + KeyboardTranslator::Entry nextEntry(); + + /** + * Returns true if an error occurred whilst parsing the input or + * false if no error occurred. + */ + bool parseError(); + + /** + * Parses a condition and result string for a translator entry + * and produces a keyboard translator entry. + * + * The condition and result strings are in the same format as in + */ + static KeyboardTranslator::Entry createEntry( const QString& condition , + const QString& result ); +private: + struct Token + { + enum Type + { + TitleKeyword, + TitleText, + KeyKeyword, + KeySequence, + Command, + OutputText + }; + Type type; + QString text; + }; + QList<Token> tokenize(const QString&); + void readNext(); + bool decodeSequence(const QString& , + int& keyCode, + Qt::KeyboardModifiers& modifiers, + Qt::KeyboardModifiers& modifierMask, + KeyboardTranslator::States& state, + KeyboardTranslator::States& stateFlags); + + static bool parseAsModifier(const QString& item , Qt::KeyboardModifier& modifier); + static bool parseAsStateFlag(const QString& item , KeyboardTranslator::State& state); + static bool parseAsKeyCode(const QString& item , int& keyCode); + static bool parseAsCommand(const QString& text , KeyboardTranslator::Command& command); + + QIODevice* _source; + QString _description; + KeyboardTranslator::Entry _nextEntry; + bool _hasNext; +}; + +/** Writes a keyboard translation to disk. */ +class KeyboardTranslatorWriter +{ +public: + /** + * Constructs a new writer which saves data into @p destination. + * The caller is responsible for closing the device when writing is complete. + */ + KeyboardTranslatorWriter(QIODevice* destination); + ~KeyboardTranslatorWriter(); + + /** + * Writes the header for the keyboard translator. + * @param description Description of the keyboard translator. + */ + void writeHeader( const QString& description ); + /** Writes a translator entry. */ + void writeEntry( const KeyboardTranslator::Entry& entry ); + +private: + QIODevice* _destination; + QTextStream* _writer; +}; + +/** + * Manages the keyboard translations available for use by terminal sessions, + * see KeyboardTranslator. + */ +class KeyboardTranslatorManager +{ +public: + /** + * Constructs a new KeyboardTranslatorManager and loads the list of + * available keyboard translations. + * + * The keyboard translations themselves are not loaded until they are + * first requested via a call to findTranslator() + */ + KeyboardTranslatorManager(); + ~KeyboardTranslatorManager(); + + /** + * Adds a new translator. If a translator with the same name + * already exists, it will be replaced by the new translator. + * + * TODO: More documentation. + */ + void addTranslator(KeyboardTranslator* translator); + + /** + * Deletes a translator. Returns true on successful deletion or false otherwise. + * + * TODO: More documentation + */ + bool deleteTranslator(const QString& name); + + /** Returns the default translator for Konsole. */ + const KeyboardTranslator* defaultTranslator(); + + /** + * Returns the keyboard translator with the given name or 0 if no translator + * with that name exists. + * + * The first time that a translator with a particular name is requested, + * the on-disk .keyboard file is loaded and parsed. + */ + const KeyboardTranslator* findTranslator(const QString& name); + /** + * Returns a list of the names of available keyboard translators. + * + * The first time this is called, a search for available + * translators is started. + */ + QList<QString> allTranslators(); + + /** Returns the global KeyboardTranslatorManager instance. */ + static KeyboardTranslatorManager* instance(); + +private: + static const QByteArray defaultTranslatorText; + + void findTranslators(); // locate the available translators + KeyboardTranslator* loadTranslator(const QString& name); // loads the translator + // with the given name + KeyboardTranslator* loadTranslator(QIODevice* device,const QString& name); + + bool saveTranslator(const KeyboardTranslator* translator); + QString findTranslatorPath(const QString& name); + + QHash<QString,KeyboardTranslator*> _translators; // maps translator-name -> KeyboardTranslator + // instance + bool _haveLoadedAll; +}; + +inline int KeyboardTranslator::Entry::keyCode() const { return _keyCode; } +inline void KeyboardTranslator::Entry::setKeyCode(int keyCode) { _keyCode = keyCode; } + +inline void KeyboardTranslator::Entry::setModifiers( Qt::KeyboardModifiers modifier ) +{ + _modifiers = modifier; +} +inline Qt::KeyboardModifiers KeyboardTranslator::Entry::modifiers() const { return _modifiers; } + +inline void KeyboardTranslator::Entry::setModifierMask( Qt::KeyboardModifiers mask ) +{ + _modifierMask = mask; +} +inline Qt::KeyboardModifiers KeyboardTranslator::Entry::modifierMask() const { return _modifierMask; } + +inline bool KeyboardTranslator::Entry::isNull() const +{ + return ( *this == Entry() ); +} + +inline void KeyboardTranslator::Entry::setCommand( Command command ) +{ + _command = command; +} +inline KeyboardTranslator::Command KeyboardTranslator::Entry::command() const { return _command; } + +inline void KeyboardTranslator::Entry::setText( const QByteArray& text ) +{ + _text = unescape(text); +} +inline int oneOrZero(int value) +{ + return value ? 1 : 0; +} +inline QByteArray KeyboardTranslator::Entry::text(bool expandWildCards,Qt::KeyboardModifiers modifiers) const +{ + QByteArray expandedText = _text; + + if (expandWildCards) + { + int modifierValue = 1; + modifierValue += oneOrZero(modifiers & Qt::ShiftModifier); + modifierValue += oneOrZero(modifiers & Qt::AltModifier) << 1; + modifierValue += oneOrZero(modifiers & Qt::ControlModifier) << 2; + + for (int i=0;i<_text.length();i++) + { + if (expandedText[i] == '*') + expandedText[i] = '0' + modifierValue; + } + } + + return expandedText; +} + +inline void KeyboardTranslator::Entry::setState( States state ) +{ + _state = state; +} +inline KeyboardTranslator::States KeyboardTranslator::Entry::state() const { return _state; } + +inline void KeyboardTranslator::Entry::setStateMask( States stateMask ) +{ + _stateMask = stateMask; +} +inline KeyboardTranslator::States KeyboardTranslator::Entry::stateMask() const { return _stateMask; } + + +Q_DECLARE_METATYPE(KeyboardTranslator::Entry) +Q_DECLARE_METATYPE(const KeyboardTranslator*) + +#endif // KEYBOARDTRANSLATOR_H +
new file mode 100644 --- /dev/null +++ b/gui/src/LineFont.h @@ -0,0 +1,21 @@ +// WARNING: Autogenerated by "fontembedder ./linefont.src". +// You probably do not want to hand-edit this! + +static const quint32 LineChars[] = { + 0x00007c00, 0x000fffe0, 0x00421084, 0x00e739ce, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00427000, 0x004e7380, 0x00e77800, 0x00ef7bc0, + 0x00421c00, 0x00439ce0, 0x00e73c00, 0x00e7bde0, 0x00007084, 0x000e7384, 0x000079ce, 0x000f7bce, + 0x00001c84, 0x00039ce4, 0x00003dce, 0x0007bdee, 0x00427084, 0x004e7384, 0x004279ce, 0x00e77884, + 0x00e779ce, 0x004f7bce, 0x00ef7bc4, 0x00ef7bce, 0x00421c84, 0x00439ce4, 0x00423dce, 0x00e73c84, + 0x00e73dce, 0x0047bdee, 0x00e7bde4, 0x00e7bdee, 0x00427c00, 0x0043fce0, 0x004e7f80, 0x004fffe0, + 0x004fffe0, 0x00e7fde0, 0x006f7fc0, 0x00efffe0, 0x00007c84, 0x0003fce4, 0x000e7f84, 0x000fffe4, + 0x00007dce, 0x0007fdee, 0x000f7fce, 0x000fffee, 0x00427c84, 0x0043fce4, 0x004e7f84, 0x004fffe4, + 0x00427dce, 0x00e77c84, 0x00e77dce, 0x0047fdee, 0x004e7fce, 0x00e7fde4, 0x00ef7f84, 0x004fffee, + 0x00efffe4, 0x00e7fdee, 0x00ef7fce, 0x00efffee, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x000f83e0, 0x00a5294a, 0x004e1380, 0x00a57800, 0x00ad0bc0, 0x004390e0, 0x00a53c00, 0x00a5a1e0, + 0x000e1384, 0x0000794a, 0x000f0b4a, 0x000390e4, 0x00003d4a, 0x0007a16a, 0x004e1384, 0x00a5694a, + 0x00ad2b4a, 0x004390e4, 0x00a52d4a, 0x00a5a16a, 0x004f83e0, 0x00a57c00, 0x00ad83e0, 0x000f83e4, + 0x00007d4a, 0x000f836a, 0x004f93e4, 0x00a57d4a, 0x00ad836a, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00001c00, 0x00001084, 0x00007000, 0x00421000, + 0x00039ce0, 0x000039ce, 0x000e7380, 0x00e73800, 0x000e7f80, 0x00e73884, 0x0003fce0, 0x004239ce +};
new file mode 100644 --- /dev/null +++ b/gui/src/MainWindow.cpp @@ -0,0 +1,190 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include <QMenuBar> +#include <QMenu> +#include <QAction> +#include <QSettings> +#include <QDesktopServices> +#include <QFileDialog> +#include "MainWindow.h" +#include "FileEditorDockWidget.h" +#include "ImageViewerDockWidget.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), + m_isRunning(true) { + setObjectName("MainWindow"); + + QDesktopServices desktopServices; + m_settingsFile = desktopServices.storageLocation(QDesktopServices::HomeLocation) + "/.quint/settings.ini"; + construct(); + establishOctaveLink(); +} + +MainWindow::~MainWindow() { +} + +void MainWindow::handleOpenFileRequest(QString fileName) { + reportStatusMessage(tr("Opening file.")); + QPixmap pixmap; + if(pixmap.load(fileName)) { + ImageViewerDockWidget *imageViewerDockWidget = new ImageViewerDockWidget(pixmap, this); + imageViewerDockWidget->setWindowTitle(fileName); + addDockWidget(Qt::RightDockWidgetArea, imageViewerDockWidget); + } else { + FileEditorDockWidget *fileEditorDockWidget = new FileEditorDockWidget(this); + fileEditorDockWidget->loadFile(fileName); + addDockWidget(Qt::RightDockWidgetArea, fileEditorDockWidget); + } +} + +void MainWindow::reportStatusMessage(QString statusMessage) { + m_statusBar->showMessage(statusMessage, 1000); +} + +void MainWindow::handleSaveWorkspaceRequest() { + QDesktopServices desktopServices; + QString selectedFile = QFileDialog::getSaveFileName(this, tr("Save Workspace"), + desktopServices.storageLocation(QDesktopServices::HomeLocation) + "/.quint/workspace"); + m_octaveTerminalDockWidget->octaveTerminal()->sendText(QString("save \'%1\'\n").arg(selectedFile)); + m_octaveTerminalDockWidget->octaveTerminal()->setFocus(); +} + +void MainWindow::handleLoadWorkspaceRequest() { + QDesktopServices desktopServices; + QString selectedFile = QFileDialog::getOpenFileName(this, tr("Load Workspace"), + desktopServices.storageLocation(QDesktopServices::HomeLocation) + "/.quint/workspace"); + m_octaveTerminalDockWidget->octaveTerminal()->sendText(QString("load \'%1\'\n").arg(selectedFile)); + m_octaveTerminalDockWidget->octaveTerminal()->setFocus(); +} + +void MainWindow::handleClearWorkspaceRequest() { + m_octaveTerminalDockWidget->octaveTerminal()->sendText("clear\n"); + m_octaveTerminalDockWidget->octaveTerminal()->setFocus(); +} + +void MainWindow::handleCommandDoubleClicked(QString command) { + m_octaveTerminalDockWidget->octaveTerminal()->sendText(command); + m_octaveTerminalDockWidget->octaveTerminal()->setFocus(); +} + +void MainWindow::closeEvent(QCloseEvent *closeEvent) { + m_isRunning = false; + reportStatusMessage(tr("Saving data and shutting down.")); + writeSettings(); + + m_octaveCallbackThread->terminate(); + m_octaveCallbackThread->wait(); + + m_octaveMainThread->terminate(); + QMainWindow::closeEvent(closeEvent); +} + +void MainWindow::readSettings() { + QSettings settings(m_settingsFile, QSettings::IniFormat); + restoreGeometry(settings.value("MainWindow/geometry").toByteArray()); + restoreState(settings.value("MainWindow/windowState").toByteArray()); +} + +void MainWindow::writeSettings() { + QSettings settings(m_settingsFile, QSettings::IniFormat); + settings.setValue("MainWindow/geometry", saveGeometry()); + settings.setValue("MainWindow/windowState", saveState()); +} + +void MainWindow::construct() { + setWindowTitle("Octave"); + setWindowIcon(QIcon("../media/quint_icon_small.png")); + QStyle *style = QApplication::style(); + resize(800, 600); + + m_octaveTerminalDockWidget = new OctaveTerminalDockWidget(this, new OctaveTerminal(this)); + m_variablesDockWidget = new VariablesDockWidget(this); + m_historyDockWidget = new HistoryDockWidget(this); + m_filesDockWidget = new FilesDockWidget(this); + m_browserDockWidget = new BrowserDockWidget(this, new BrowserWidget(this)); + m_serviceDockWidget = new BrowserDockWidget(this, new BrowserWidget(this)); + + m_browserDockWidget->setObjectName("BrowserWidget"); + m_browserDockWidget->setWindowTitle(tr("Documentation")); + m_serviceDockWidget->setObjectName("ServiceWidget"); + m_serviceDockWidget->setWindowTitle(tr("Service")); + + // This is needed, since a QMainWindow without a central widget is not supported. + setCentralWidget(new QWidget(this)); + centralWidget()->setObjectName("CentralWidget"); + centralWidget()->hide(); + + setDockOptions(QMainWindow::AllowTabbedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AnimatedDocks); + + addDockWidget(Qt::RightDockWidgetArea, m_octaveTerminalDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_variablesDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_historyDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_filesDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_browserDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_serviceDockWidget); + + // TODO: Add meaningfull toolbar items. + m_generalPurposeToolbar = new QToolBar(tr("Octave Toolbar"), this); + QAction *commandAction = new QAction(style->standardIcon(QStyle::SP_CommandLink), + "", m_generalPurposeToolbar); + QAction *computerAction = new QAction(style->standardIcon(QStyle::SP_ComputerIcon), + "", m_generalPurposeToolbar); + m_generalPurposeToolbar->addAction(commandAction); + m_generalPurposeToolbar->addAction(computerAction); + addToolBar(m_generalPurposeToolbar); + + // Create status bar. + + m_statusBar = new QStatusBar(this); + setStatusBar(m_statusBar); + + readSettings(); + + connect(m_filesDockWidget, SIGNAL(openFile(QString)), this, SLOT(handleOpenFileRequest(QString))); + connect(m_historyDockWidget, SIGNAL(information(QString)), this, SLOT(reportStatusMessage(QString))); + connect(m_historyDockWidget, SIGNAL(commandDoubleClicked(QString)), this, SLOT(handleCommandDoubleClicked(QString))); + connect(m_variablesDockWidget, SIGNAL(saveWorkspace()), this, SLOT(handleSaveWorkspaceRequest())); + connect(m_variablesDockWidget, SIGNAL(loadWorkspace()), this, SLOT(handleLoadWorkspaceRequest())); + connect(m_variablesDockWidget, SIGNAL(clearWorkspace()), this, SLOT(handleClearWorkspaceRequest())); + + m_browserDockWidget->browserWidget()->load(QUrl("http://www.gnu.org/software/octave/doc/interpreter/")); + m_serviceDockWidget->browserWidget()->load(QUrl("http://powerup.ath.cx/bugtracker")); + +} + +void MainWindow::establishOctaveLink() { + m_octaveMainThread = new OctaveMainThread(this); + m_octaveMainThread->start(); + + m_octaveCallbackThread = new OctaveCallbackThread(this, this); + m_octaveCallbackThread->start(); + + command_editor::add_event_hook(OctaveLink::readlineEventHook); + + int fdm, fds; + if(openpty(&fdm, &fds, 0, 0, 0) < 0) { + assert(0); + } + dup2 (fds, 0); + dup2 (fds, 1); + dup2 (fds, 2); + m_octaveTerminalDockWidget->octaveTerminal()->openTeletype(fdm); + reportStatusMessage(tr("Established link to Octave.")); +}
new file mode 100644 --- /dev/null +++ b/gui/src/MainWindow.h @@ -0,0 +1,180 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QtGui/QMainWindow> +#include <QThread> +#include <QTabWidget> +#include <QMdiArea> +#include <QStatusBar> +#include <QToolBar> +#include <QQueue> +#include "OctaveTerminal.h" +#include "OctaveLink.h" +#include "VariablesDockWidget.h" +#include "HistoryDockWidget.h" +#include "FilesDockWidget.h" +#include "SimpleEditor.h" +#include "BrowserWidget.h" + +// Octave includes +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef PACKAGE_URL +#include "octave/config.h" + +#include "octave/debug.h" +#include "octave/octave.h" +#include "octave/symtab.h" +#include "octave/parse.h" +#include "octave/unwind-prot.h" +#include "octave/toplev.h" +#include "octave/load-path.h" +#include "octave/error.h" +#include "octave/quit.h" +#include "octave/variables.h" +#include "octave/sighandlers.h" +#include "octave/sysdep.h" +#include "octave/str-vec.h" +#include "octave/cmd-hist.h" +#include "octave/cmd-edit.h" +#include "octave/oct-env.h" +#include "octave/symtab.h" +#include "cmd-edit.h" + +typedef struct yy_buffer_state *YY_BUFFER_STATE; +extern OCTINTERP_API YY_BUFFER_STATE create_buffer (FILE *f); +extern OCTINTERP_API void switch_to_buffer (YY_BUFFER_STATE buf); +extern OCTINTERP_API FILE *get_input_from_stdin (void); + +// System +#include <termios.h> +#include <sys/types.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <iostream> +#include <vector> +#include "pty.h" + +class OctaveMainThread; +class OctaveCallbackThread; + +/** + * \class MainWindow + * + * Represents the main window. + */ +class MainWindow : public QMainWindow { + Q_OBJECT +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + + bool isRunning() { return m_isRunning; } + OctaveTerminal *octaveTerminal() { return m_octaveTerminalDockWidget->octaveTerminal(); } + VariablesDockWidget *variablesDockWidget() { return m_variablesDockWidget; } + HistoryDockWidget *historyDockWidget() { return m_historyDockWidget; } + FilesDockWidget *filesDockWidget() { return m_filesDockWidget; } + +public slots: + void handleOpenFileRequest(QString fileName); + void reportStatusMessage(QString statusMessage); + void handleSaveWorkspaceRequest(); + void handleLoadWorkspaceRequest(); + void handleClearWorkspaceRequest(); + void handleCommandDoubleClicked(QString command); + +protected: + void closeEvent(QCloseEvent *closeEvent); + void readSettings(); + void writeSettings(); + +private: + void construct(); + void establishOctaveLink(); + OctaveTerminalDockWidget *m_octaveTerminalDockWidget; + VariablesDockWidget *m_variablesDockWidget; + HistoryDockWidget *m_historyDockWidget; + FilesDockWidget *m_filesDockWidget; + BrowserDockWidget *m_browserDockWidget; + BrowserDockWidget *m_serviceDockWidget; + + QStatusBar *m_statusBar; + QToolBar *m_generalPurposeToolbar; + QString m_settingsFile; + + // Threads for running octave and managing the data interaction. + OctaveMainThread *m_octaveMainThread; + OctaveCallbackThread *m_octaveCallbackThread; + bool m_isRunning; +}; + +class OctaveMainThread : public QThread { + Q_OBJECT +public: + OctaveMainThread(QObject *parent) + : QThread(parent) { + } +protected: + void run() { + int argc = 3; + const char* argv[] = {"octave", "--interactive", "--line-editing"}; + octave_main(argc, (char**)argv, 1); + main_loop(); + clean_up_and_exit(0); + } +}; + +class OctaveCallbackThread : public QThread { + Q_OBJECT +public: + OctaveCallbackThread(QObject *parent, MainWindow *mainWindow) + : QThread(parent), + m_mainWindow(mainWindow) { + } + +protected: + void run() { + while(m_mainWindow->isRunning()) { + + // Get a full variable list. + QList<SymbolRecord> symbolTable = OctaveLink::instance()->currentSymbolTable(); + if(symbolTable.size()) { + m_mainWindow->variablesDockWidget()->setVariablesList(symbolTable); + } + + // Collect history list. + string_vector historyList = OctaveLink::instance()->currentHistory(); + if(historyList.length()) { + m_mainWindow->historyDockWidget()->updateHistory(historyList); + } + + usleep(100000); + } + } +private: + MainWindow *m_mainWindow; +}; + +#endif // MAINWINDOW_H
new file mode 100644 --- /dev/null +++ b/gui/src/NumberedCodeEdit.cpp @@ -0,0 +1,591 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005, 2006 KJSEmbed Authors + See included AUTHORS file. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include <QTextDocument> +#include <QTextBlock> +#include <QHBoxLayout> +#include <QScrollBar> +#include <QPainter> +#include <QAbstractTextDocumentLayout> +#include <QToolTip> +#include <QTextStream> +#include <QProcess> +#include <QRegExp> +#include <QMessageBox> +#include <QFileInfo> + +#include "NumberedCodeEdit.h" +#include "config.h" + +NumberBar::NumberBar( QWidget *parent ) + : QWidget( parent ), edit(0), currentLine(-1), bugLine(-1) +{ + // Make room for 4 digits and the breakpoint icon + setFixedWidth( fontMetrics().width( QString("0000") + 10 + 32 ) ); + stopMarker = QPixmap();// QString(ICON_PATH) + "/stop.png" ); + currentMarker = QPixmap();// QString(ICON_PATH) + "/bookmark.png" ); + bugMarker = QPixmap();// QString(ICON_PATH) + "/bug.png" ); +} + +NumberBar::~NumberBar() +{ +} + +void NumberBar::setCurrentLine( int lineno ) +{ + currentLine = lineno; + update(); +} + +void NumberBar::setBugLine( int lineno ) +{ + bugLine = lineno; +} + +void NumberBar::toggleBreakpoint( int lineno ) +{ + if(lineno > 0) + { + int i = breakpoints.indexOf(lineno); + + if(i > -1) + breakpoints.removeAt(i); + else + breakpoints.push_back(lineno); + } + update(); +} + +void NumberBar::setTextEdit( SimpleEditor *edit ) +{ + this->edit = edit; + setFixedWidth( edit->fontMetrics().width( QString("0000") + 10 + 32 ) ); + connect( edit->document()->documentLayout(), SIGNAL( update(const QRectF &) ), + this, SLOT( update() ) ); + connect( edit->verticalScrollBar(), SIGNAL(valueChanged(int) ), + this, SLOT( update() ) ); +} + +void NumberBar::paintEvent( QPaintEvent * ) +{ + QVector<qreal> lines_list; + int first_line_no; + edit->publicBlockBoundingRectList(lines_list, first_line_no); + + const QFontMetrics fm = edit->fontMetrics(); + const int ascent = fontMetrics().ascent(); // height = ascent + descent + + QPainter p(this); + p.setPen(palette().windowText().color()); + + bugRect = QRect(); + stopRect = QRect(); + currentRect = QRect(); + + int position_y; + int lineCount; + + const int lines_list_size=lines_list.size(); + + for(int i=0;i<lines_list_size;i++) + { + position_y=qRound( lines_list[i] ); + lineCount=first_line_no+i; + + const QString txt = QString::number( lineCount ); + p.drawText( width() - fm.width(txt)- 2, position_y+ascent, txt ); + + // Bug marker + if ( bugLine == lineCount ) { + p.drawPixmap( 1, position_y, bugMarker ); + bugRect = QRect( 19, position_y, bugMarker.width(), bugMarker.height() ); + } + + // Stop marker + if ( breakpoints.contains(lineCount) ) { + p.drawPixmap( 1, position_y, stopMarker ); + stopRect = QRect( 1, position_y,stopMarker.width(), stopMarker.height() ); + } + + // Current line marker + if ( currentLine == lineCount ) { + p.drawPixmap( 1, position_y, currentMarker ); + currentRect = QRect( 1, position_y, currentMarker.width(), currentMarker.height() ); + } + } + + /* + + int contentsY = edit->verticalScrollBar()->value(); + qreal pageBottom = contentsY + edit->viewport()->height(); + const QFontMetrics fm = fontMetrics(); + const int ascent = fontMetrics().ascent() + 1; // height = ascent + descent + 1 + int lineCount = 1; + + QPainter p(this); + p.setPen(palette().windowText().color()); + + bugRect = QRect(); + stopRect = QRect(); + currentRect = QRect(); + + for ( QTextBlock block = edit->document()->begin(); + block.isValid(); block = block.next(), ++lineCount ) { + + const QRectF boundingRect = edit->publicBlockBoundingRect( block ); + + QPointF position = boundingRect.topLeft(); + if ( position.y() + boundingRect.height() < contentsY ) + continue; + if ( position.y() > pageBottom ) + break; + + const QString txt = QString::number( lineCount ); + p.drawText( width() - fm.width(txt), qRound( position.y() ) - contentsY + ascent, txt ); + + // Bug marker + if ( bugLine == lineCount ) { + p.drawPixmap( 1, qRound( position.y() ) - contentsY, bugMarker ); + bugRect = QRect( 1, qRound( position.y() ) - contentsY, bugMarker.width(), bugMarker.height() ); + } + + // Stop marker + if ( breakpoints.contains(lineCount) ) { + p.drawPixmap( 19, qRound( position.y() ) - contentsY, stopMarker ); + stopRect = QRect( 19, qRound( position.y() ) - contentsY, stopMarker.width(), stopMarker.height() ); + } + + // Current line marker + if ( currentLine == lineCount ) { + p.drawPixmap( 19, qRound( position.y() ) - contentsY, currentMarker ); + currentRect = QRect( 19, qRound( position.y() ) - contentsY, currentMarker.width(), currentMarker.height() ); + } + } + */ +} + +bool NumberBar::event( QEvent *event ) +{ + if ( event->type() == QEvent::ToolTip ) { + QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); + + if ( stopRect.contains( helpEvent->pos() ) ) { + QToolTip::showText( helpEvent->globalPos(), tr("Stop Here")); + } + else if ( currentRect.contains( helpEvent->pos() ) ) { + QToolTip::showText( helpEvent->globalPos(), tr("Current Line")); + } + else if ( bugRect.contains( helpEvent->pos() ) ) { + QToolTip::showText( helpEvent->globalPos(), tr("Error Line" )); + } + } + + return QWidget::event(event); +} + +QList<int> *NumberBar::getBreakpoints() +{ + return &breakpoints; +} + + + +NumberedCodeEdit::NumberedCodeEdit( QWidget *parent, SimpleEditor *textEdit ) + : QFrame( parent ) +{ + setFrameStyle( QFrame::StyledPanel | QFrame::Sunken ); + setLineWidth( 2 ); + + view=textEdit; + view->installEventFilter( this ); + + connect( view->document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(textChanged(int,int,int)) ); + + connect( view, SIGNAL(cursorPositionChanged()), this, SLOT(cursor_moved_cb()) ); + + // Setup the line number pane + + numbers = new NumberBar( this ); + numbers->setTextEdit( view ); + //numbers=NULL; + + + vbox = new QVBoxLayout(this); + vbox->setSpacing( 0 ); + vbox->setMargin( 0 ); + + hbox = new QHBoxLayout; + vbox->addLayout(hbox); + + hbox->setSpacing( 0 ); + hbox->setMargin( 0 ); + hbox->addWidget( numbers ); + hbox->addWidget( view ); + + textModifiedOk=false; + + QHBoxLayout *messages_layout= new QHBoxLayout; + vbox->addLayout(messages_layout); + messages_layout->setSpacing( 0 ); + messages_layout->setMargin( 0 ); + } + + +NumberedCodeEdit::~NumberedCodeEdit() +{ + hide(); + //printf("Borrado ntv\n"); +} + +void NumberedCodeEdit::setCurrentLine( int lineno ) +{ + currentLine = lineno; + if(numbers!=NULL) numbers->setCurrentLine( lineno ); + + //Move cursor to lineno + if(lineno>-1) + { + QTextCursor cursor=textEdit()->textCursor(); + + cursor.movePosition(QTextCursor::Start); + + for(int i=1;i<lineno;i++) + cursor.movePosition(QTextCursor::NextBlock); + + textEdit()->setTextCursor(cursor); + } + + textChanged( 0, 0, 1 ); +} + +void NumberedCodeEdit::toggleBreakpoint( int lineno ) +{ + if(numbers!=NULL) numbers->toggleBreakpoint( lineno ); +} + +void NumberedCodeEdit::setBugLine( int lineno ) +{ + if(numbers!=NULL) numbers->setBugLine( lineno ); +} + +void NumberedCodeEdit::textChanged( int /*pos*/, int removed, int added ) +{ + //Q_UNUSED( pos ); + + if ( removed == 0 && added == 0 ) + return; + + //QTextBlock block = highlight.block(); + //QTextBlock block = view->document()->begin(); + //QTextBlockFormat fmt = block.blockFormat(); + //QColor bg = view->palette().base().color(); + //fmt.setBackground( bg ); + //highlight.setBlockFormat( fmt ); + /* + QTextBlockFormat fmt; + + int lineCount = 1; + for ( QTextBlock block = view->document()->begin(); + block.isValid() && block!=view->document()->end(); block = block.next(), ++lineCount ) { + + if ( lineCount == currentLine ) + { + fmt = block.blockFormat(); + QColor bg = view->palette().highlight().color(); + fmt.setBackground( bg ); + + highlight = QTextCursor( block ); + highlight.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor ); + highlight.setBlockFormat( fmt ); + + break; + } + } + */ + + if( !textModifiedOk && view->document()->isModified() ) + { + textModifiedOk=true; + emit textModified(); + } +} + +bool NumberedCodeEdit::eventFilter( QObject *obj, QEvent *event ) +{ + if ( obj != view ) + return QFrame::eventFilter(obj, event); + + if ( event->type() == QEvent::ToolTip ) { + QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); + + QTextCursor cursor = view->cursorForPosition( helpEvent->pos() ); + cursor.movePosition( QTextCursor::StartOfWord, QTextCursor::MoveAnchor ); + cursor.movePosition( QTextCursor::EndOfWord, QTextCursor::KeepAnchor ); + + QString word = cursor.selectedText(); + emit mouseHover( word ); + emit mouseHover( helpEvent->pos(), word ); + + // QToolTip::showText( helpEvent->globalPos(), word ); // For testing + } + + return false; +} + +QList<int> *NumberedCodeEdit::getBreakpoints() +{ + QList<int> *br=NULL; + if(numbers!=NULL) br=numbers->getBreakpoints(); + return br; +} + +void NumberedCodeEdit::open(QString path) +{ + FILE *fl; + + fl = fopen(path.toLocal8Bit().constData(), "rt"); + if(fl) + { + fclose(fl); + filePath = path; + + textEdit()->load(path); + + textModifiedOk=false; + textEdit()->document()->setModified(false); + }else{ + throw path; + } +} + +bool NumberedCodeEdit::save(QString path) +{ + FILE *fl; + + if(path.isEmpty()) path = filePath; + QRegExp re("[A-Za-z_][A-Za-z0-9_]*\\.m"); + + if( ! re.exactMatch( QFileInfo(path).fileName() ) ) + { + QMessageBox msgBox; + msgBox.setText( tr("This file name is not valid.") ); + msgBox.setInformativeText(tr("Octave doesn't understand this file name:\n")+path+tr("\nPlease, change it.\n Do you want to save your changes?")); + msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Save); + int ret = msgBox.exec(); + switch (ret) + { + case QMessageBox::Save: + // Save was clicked + break; + case QMessageBox::Cancel: + // Cancel was clicked + return false; + break; + default: + // should never be reached + break; + } + } + + + fl = fopen(path.toLocal8Bit().constData(), "wt"); + if(fl) + { + filePath = path; + QTextStream *stream = new QTextStream(fl); + (*stream) << textEdit()->document()->toPlainText(); + delete stream; + fclose(fl); + textModifiedOk=false; + view->document()->setModified(false); + }else{ + return false; + } + + return true; +} + +QString NumberedCodeEdit::path() +{ + return filePath; +} + +void NumberedCodeEdit::setPath(QString path) +{ + filePath=path; + textEdit()->setFile(path); +} + +void NumberedCodeEdit::setModified(bool modify) +{ + textModifiedOk=modify; +} + +bool NumberedCodeEdit::modified() +{ + return textModifiedOk; +} + +void NumberedCodeEdit::cursor_moved_cb() +{ + QTextCursor cursor=view->textCursor(); + QTextBlock actual_block=cursor.block(); + int lineCount=1; + QTextBlock block = view->document()->begin(); + + for ( ;block.isValid() && actual_block!=block; block = block.next()) lineCount++ ; +} + +static QString startLineInsertText(QString str, QString textToInsert) +{ + str.replace(QChar(0x2029), "\n"); + //printf("str=%s\n", str.toLocal8Bit().data() ); + + QStringList list = str.split("\n"); + + for(int i=0;i<list.size();i++) + { + QString s=list[i]; + + int x; + + for(x=0;x<s.size();x++) + { + if( s.at(x)!=' ' && s.at(x)!='\t' ) break; + } + + QString s1=s.left(x); + QString s2=s.right(s.size()-x); + list[i]=s1+textToInsert+s2; + } + + return list.join("\n"); +} + +static QString startLineRemoveText(QString str, QStringList textToRemove) +{ + str.replace(QChar(0x2029), "\n"); + + QStringList list = str.split("\n"); + + for(int i=0;i<list.size();i++) + { + QString s=list[i]; + + int x; + + for(x=0;x<s.size();x++) + { + if( s.at(x)!=' ' && s.at(x)!='\t' ) break; + } + + QString s1=s.left(x); + QString s2=s.right(s.size()-x); + + for(int k=0;k<textToRemove.size();k++) + { + if(s1.endsWith(textToRemove[k])) + { + s1=s1.left(s1.size()-textToRemove[k].size()); + break; + } + else if(s2.startsWith(textToRemove[k])) + { + s2=s2.right(s2.size()-textToRemove[k].size()); + break; + } + } + + //printf("s1=%s s2=%s \n", s1.toLocal8Bit().data(), s2.toLocal8Bit().data()); + list[i]=s1+s2; + } + + return list.join("\n"); +} + +void NumberedCodeEdit::indent() +{ + QTextCursor cursor(textEdit()->textCursor()); + + if( !cursor.hasSelection() ) return; + + QString str=cursor.selectedText(); + + str=startLineInsertText(str, "\t"); + + cursor.insertText(str); + cursor.setPosition(cursor.position()-str.size(), QTextCursor::KeepAnchor); + textEdit()->setTextCursor(cursor); +} + +void NumberedCodeEdit::unindent() +{ + //QTextDocument *doc=textEdit()->document(); + + QTextCursor cursor(textEdit()->textCursor()); + + if( !cursor.hasSelection() ) return; + + QString str=cursor.selectedText(); + + QStringList textToRemove; + textToRemove << "\t" << " "; + str=startLineRemoveText(str, textToRemove); + + cursor.insertText(str); + cursor.setPosition(cursor.position()-str.size(), QTextCursor::KeepAnchor); + textEdit()->setTextCursor(cursor); +} + +void NumberedCodeEdit::comment() +{ + //QTextDocument *doc=textEdit()->document(); + + QTextCursor cursor(textEdit()->textCursor()); + + if( !cursor.hasSelection() ) return; + + QString str=cursor.selectedText(); + + str=startLineInsertText(str, "%"); + + cursor.insertText(str); + cursor.setPosition(cursor.position()-str.size(), QTextCursor::KeepAnchor); + textEdit()->setTextCursor(cursor); +} + +void NumberedCodeEdit::uncomment() +{ + //QTextDocument *doc=textEdit()->document(); + + QTextCursor cursor(textEdit()->textCursor()); + + if( !cursor.hasSelection() ) return; + + QString str=cursor.selectedText(); + + QStringList textToRemove; + textToRemove << "%" << "#"; + str=startLineRemoveText(str, textToRemove); + + cursor.insertText(str); + cursor.setPosition(cursor.position()-str.size(), QTextCursor::KeepAnchor); + textEdit()->setTextCursor(cursor); +}
new file mode 100644 --- /dev/null +++ b/gui/src/NumberedCodeEdit.h @@ -0,0 +1,169 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005, 2006 KJSEmbed Authors + See included AUTHORS file. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +// -*- c++ -*- +#ifndef NUMBERED_TEXT_VIEW_H +#define NUMBERED_TEXT_VIEW_H + +#include <QFrame> +#include <QPixmap> +#include <QTextCursor> +#include <QLabel> +#include <QVBoxLayout> +#include <QHBoxLayout> +#include "SimpleEditor.h" + +class SimpleEditor; +class QHBoxLayout; + +/** + * @internal Used to display the numbers. + */ +class NumberBar : public QWidget +{ + Q_OBJECT + +public: + NumberBar( QWidget *parent ); + ~NumberBar(); + + void setCurrentLine( int lineno ); + void setBugLine( int lineno ); + void toggleBreakpoint( int lineno ); + QList<int> *getBreakpoints(); + + void setTextEdit( SimpleEditor *edit ); + void paintEvent( QPaintEvent *ev ); + +protected: + bool event( QEvent *ev ); + +private: + SimpleEditor *edit; + QPixmap stopMarker; + QPixmap currentMarker; + QPixmap bugMarker; + QList<int> breakpoints; + int currentLine; + int bugLine; + QRect stopRect; + QRect currentRect; + QRect bugRect; +}; + +/** + * Displays a CodeEdit with line numbers. + */ +class NumberedCodeEdit : public QFrame +{ + Q_OBJECT + +public: + NumberedCodeEdit( QWidget *parent = 0 , SimpleEditor *textEdit=new SimpleEditor() ); + ~NumberedCodeEdit(); + + QList<int> *getBreakpoints(); + + void open(QString path); + + /**Saves file to path. @return true if all is OK.*/ + bool save(QString path = QString()); + + QString path(); + void setPath(QString path); + + bool modified(); + void setModified(bool modify); + + /** Returns the CodeEdit of the main view. */ + SimpleEditor *textEdit() const { return view; } + + /** + * Sets the line that should have the current line indicator. + * A value of -1 indicates no line should show the indicator. + */ + void setCurrentLine( int lineno ); + + /** + * Toggle breakpoint + */ + void toggleBreakpoint( int lineno ); + + /** + * Sets the line that should have the bug line indicator. + * A value of -1 indicates no line should show the indicator. + */ + void setBugLine( int lineno ); + + /** @internal Used to get tooltip events from the view for the hover signal. */ + bool eventFilter( QObject *obj, QEvent *event ); + + /**Indent selected text.*/ + void indent(); + + /**UnIndent selected text.*/ + void unindent(); + + /**Comment selected text.*/ + void comment(); + + /**UnComment selected text.*/ + void uncomment(); + +signals: + /** + * Emitted when the mouse is hovered over the text edit component. + * @param word The word under the mouse pointer + */ + void mouseHover( const QString &word ); + + /** + * Emitted when the mouse is hovered over the text edit component. + * @param pos The position of the mouse pointer. + * @param word The word under the mouse pointer + */ + void mouseHover( const QPoint &pos, const QString &word ); + + /** + * Emitted when file is changed. + */ + void textModified(); + +protected slots: + /** @internal Used to update the highlight on the current line. */ + void textChanged( int pos, int added, int removed ); +public slots: + void cursor_moved_cb(); + +private: + QString filePath; + QLabel *line_column_label; + SimpleEditor *view; + NumberBar *numbers; + QHBoxLayout *hbox; + QVBoxLayout *vbox; + int currentLine; + QTextCursor highlight; + bool textModifiedOk; +}; + + +#endif // NUMBERED_TEXT_VIEW_H + +
new file mode 100644 --- /dev/null +++ b/gui/src/OctaveLink.cpp @@ -0,0 +1,132 @@ +/* + +Copyright (C) 2007,2008,2009 John P. Swensen + +Octave is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. + +Octave 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 Octave; see the file COPYING. If not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ + +// Born July 13, 2007. + +#include "OctaveLink.h" + +OctaveLink OctaveLink::m_singleton; + + +OctaveLink::OctaveLink() + : QObject(), + m_previousHistoryLength(0) { + m_symbolTableSemaphore = new QSemaphore(1); + m_historySemaphore = new QSemaphore(1); +} + +OctaveLink::~OctaveLink() { +} + +int OctaveLink::readlineEventHook() { + OctaveLink::instance()->processOctaveServerData(); + return 0; +} + +QString OctaveLink::octaveValueAsQString(OctaveValue octaveValue) { + // Convert single qouted string. + if(octaveValue.is_sq_string()) { + return QString("\'%1\'").arg(octaveValue.string_value().c_str()); + + // Convert double qouted string. + } else if(octaveValue.is_dq_string()) { + return QString("\"%1\"").arg(octaveValue.string_value().c_str()); + + // Convert real scalar. + } else if(octaveValue.is_real_scalar()) { + return QString("%1").arg(octaveValue.scalar_value()); + + // Convert complex scalar. + } else if(octaveValue.is_complex_scalar()) { + return QString("%1 + %2i").arg(octaveValue.scalar_value()).arg(octaveValue.complex_value().imag()); + + // Convert range. + } else if(octaveValue.is_range()) { + return QString("%1 : %2 : %3").arg(octaveValue.range_value().base()) + .arg(octaveValue.range_value().inc()) + .arg(octaveValue.range_value().limit()); + + // Convert real matrix. + } else if(octaveValue.is_real_matrix()) { + // TODO: Convert real matrix into a string. + return QString("{matrix}"); + + // Convert complex matrix. + } else if(octaveValue.is_complex_matrix()) { + // TODO: Convert complex matrix into a string. + return QString("{complex matrix}"); + + // If everything else does not fit, we could not recognize the type. + } else { + return QString("<Type not recognized>"); + } +} + +void OctaveLink::fetchSymbolTable() { + m_symbolTableSemaphore->acquire(); + m_symbolTableBuffer.clear(); + std::list<SymbolRecord> allVariables = symbol_table::all_variables(); + std::list<SymbolRecord>::iterator iterator; + for(iterator = allVariables.begin(); iterator != allVariables.end(); iterator++) + m_symbolTableBuffer.append(*iterator); + m_symbolTableSemaphore->release(); + emit symbolTableChanged(); +} + + +void OctaveLink::fetchHistory() { + int currentLen = command_history::length(); + if(currentLen != m_previousHistoryLength) { + m_historySemaphore->acquire(); + for(int i = m_previousHistoryLength; i < currentLen; i++) { + m_historyBuffer.append(command_history::get_entry(i)); + } + m_historySemaphore->release(); + m_previousHistoryLength = currentLen; + emit historyChanged(); + } +} + +QList<SymbolRecord> OctaveLink::currentSymbolTable() { + QList<SymbolRecord> m_symbolTableCopy; + + // Generate a deep copy of the current symbol table. + m_symbolTableSemaphore->acquire(); + foreach(SymbolRecord symbolRecord, m_symbolTableBuffer) + m_symbolTableCopy.append(symbolRecord); + m_symbolTableSemaphore->release(); + + return m_symbolTableCopy; +} + +string_vector OctaveLink::currentHistory() { + m_historySemaphore->acquire(); + string_vector retval(m_historyBuffer); + m_historyBuffer = string_vector(); + m_historySemaphore->release(); + return retval; +} + +void OctaveLink::processOctaveServerData() { + fetchSymbolTable(); + fetchHistory(); +} +
new file mode 100644 --- /dev/null +++ b/gui/src/OctaveLink.h @@ -0,0 +1,139 @@ +/* + * + * Copyright (C) 2007, 2008, 2009 John P. Swensen + * + * This file is as a part of OctaveDE. + * + * Octave is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * Octave 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 Octave; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * */ +#ifndef OCTAVELINK_H +#define OCTAVELINK_H + +// Octave includes +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef PACKAGE_URL +#include <octave/config.h> +#include "octave/cmd-edit.h" +#include "octave/error.h" +#include "octave/file-io.h" +#include "octave/input.h" +#include "octave/lex.h" +#include "octave/load-path.h" +#include "octave/octave.h" +#include "octave/oct-hist.h" +#include "octave/oct-map.h" +#include "octave/oct-obj.h" +#include "octave/ops.h" +#include "octave/ov.h" +#include "octave/ov-usr-fcn.h" +#include "octave/symtab.h" +#include "octave/pt.h" +#include "octave/pt-eval.h" +#include "octave/config.h" +#include "octave/Range.h" +#include "octave/toplev.h" +#include "octave/procstream.h" +#include "octave/sighandlers.h" +#include "octave/debug.h" +#include "octave/sysdep.h" +#include "octave/ov.h" +#include "octave/unwind-prot.h" +#include "octave/utils.h" +#include "octave/variables.h" + +// Standard includes +#include <iostream> +#include <string> +#include <vector> +#include <readline/readline.h> + +// Qt includes +#include <QMutexLocker> +#include <QMutex> +#include <QFileInfo> +#include <QList> +#include <QString> +#include <QVector> +#include <QSemaphore> +#include <QObject> + +typedef symbol_table::symbol_record SymbolRecord; +typedef octave_value OctaveValue; + +/** + * \class OctaveLink + * Manages a link to an octave instance. + */ +class OctaveLink : QObject +{ + Q_OBJECT +public: + static OctaveLink *instance() { return &m_singleton; } + static int readlineEventHook(void); + static QString octaveValueAsQString(OctaveValue octaveValue); + + /** + * Returns a copy of the current symbol table buffer. + * \return Copy of the current symbol table buffer. + */ + QList<SymbolRecord> currentSymbolTable(); + + /** + * Returns a copy of the current history buffer. + * \return Copy of the current history buffer. + */ + string_vector currentHistory(); + + void processOctaveServerData(); + + /** + * Updates the current symbol table with new data + * from octave. + */ + void fetchSymbolTable(); + + /** + * Updates the current history buffer with new data + * from octave. + */ + void fetchHistory(); + +signals: + void symbolTableChanged(); + void historyChanged(); + +private: + OctaveLink(); + ~OctaveLink(); + + /** Variable related member variables. */ + QSemaphore *m_symbolTableSemaphore; + QList<SymbolRecord> m_symbolTableBuffer; + + /** History related member variables. */ + QSemaphore *m_historySemaphore; + string_vector m_historyBuffer; + int m_previousHistoryLength; + + static OctaveLink m_singleton; +}; +#endif // OCTAVELINK_H +
new file mode 100644 --- /dev/null +++ b/gui/src/OctaveTerminal.cpp @@ -0,0 +1,52 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include "OctaveTerminal.h" +#include <QHBoxLayout> +#include <QVBoxLayout> +#include <QStringListModel> +#include <QStringList> + +OctaveTerminalDockWidget::OctaveTerminalDockWidget(QWidget *parent, OctaveTerminal *octaveTerminal) + : QDockWidget(parent) { + setObjectName("OctaveTerminalDockWidget"); + setWindowTitle(tr("Octave terminal")); + m_octaveTerminal = octaveTerminal; + setWidget(m_octaveTerminal); +} + +OctaveTerminalDockWidget::~OctaveTerminalDockWidget() { +} + +OctaveTerminal *OctaveTerminalDockWidget::octaveTerminal() { + return m_octaveTerminal; +} + +OctaveTerminal::OctaveTerminal(QWidget *parent) + : QTerminalWidget(0, parent) { + construct(); +} + +OctaveTerminal::~OctaveTerminal() { +} + +void OctaveTerminal::construct() { + setScrollBarPosition(QTerminalWidget::ScrollBarRight); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} +
new file mode 100644 --- /dev/null +++ b/gui/src/OctaveTerminal.h @@ -0,0 +1,46 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#ifndef OCTAVETERMINAL_H +#define OCTAVETERMINAL_H + +#include "QTerminalWidget.h" +#include <QDockWidget> + +class OctaveTerminal; +class OctaveTerminalDockWidget : public QDockWidget { +public: + OctaveTerminalDockWidget(QWidget *parent, OctaveTerminal *octaveTerminal); + ~OctaveTerminalDockWidget(); + + OctaveTerminal *octaveTerminal(); + +private: + OctaveTerminal *m_octaveTerminal; +}; + +class OctaveTerminal : public QTerminalWidget { + Q_OBJECT +public: + OctaveTerminal(QWidget *parent = 0); + ~OctaveTerminal(); + +private: + void construct(); +}; +#endif // OCTAVETERMINAL_H
new file mode 100644 --- /dev/null +++ b/gui/src/ProcessInfo.cpp @@ -0,0 +1,1054 @@ +/* + Copyright 2007-2008 by Robert Knight <robertknight@gmail.countm> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "ProcessInfo.h" + +// Unix +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <pwd.h> + +// Qt +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QRegExp> +#include <QtCore/QTextStream> +#include <QtCore/QStringList> +#include <QtCore/QSet> + +// KDE +#include "konsole_export.h" + +#if defined(Q_OS_MAC) +#include <sys/sysctl.h> +#include <libproc.h> +#ifdef HAVE_SYS_PROC_INFO_H +#include <sys/proc_info.h> +#endif +#ifdef HAVE_SYS_PROC_H +#include <sys/proc.h> +#endif +//#include <kde_file.h> +#define KDE_struct_stat struct stat +#define KDE_stat ::stat +#endif + +#if defined(Q_OS_FREEBSD) +#include <sys/sysctl.h> //krazy:exclude=includes +#include <sys/types.h> +#include <sys/user.h> +#include <sys/syslimits.h> +#include <libutil.h> +#endif + +ProcessInfo::ProcessInfo(int pid , bool enableEnvironmentRead) + : _fields( ARGUMENTS | ENVIRONMENT ) // arguments and environments + // are currently always valid, + // they just return an empty + // vector / map respectively + // if no arguments + // or environment bindings + // have been explicitly set + , _enableEnvironmentRead(enableEnvironmentRead) + , _pid(pid) + , _parentPid(0) + , _foregroundPid(0) + , _userId(0) + , _lastError(NoError) + , _userName(QString()) + , _userHomeDir(QString()) +{ +} + +ProcessInfo::Error ProcessInfo::error() const { return _lastError; } +void ProcessInfo::setError(Error error) { _lastError = error; } + +void ProcessInfo::update() +{ + readProcessInfo(_pid,_enableEnvironmentRead); +} + +QString ProcessInfo::validCurrentDir() const +{ + bool ok = false; + + // read current dir, if an error occurs try the parent as the next + // best option + int currentPid = parentPid(&ok); + QString dir = currentDir(&ok); + while ( !ok && currentPid != 0 ) + { + ProcessInfo* current = ProcessInfo::newInstance(currentPid); + current->update(); + currentPid = current->parentPid(&ok); + dir = current->currentDir(&ok); + delete current; + } + + return dir; +} + +QString ProcessInfo::format(const QString& input) const +{ + bool ok = false; + + QString output(input); + + // search for and replace known marker + output.replace("%u",userName()); + output.replace("%n",name(&ok)); + output.replace("%c",formatCommand(name(&ok),arguments(&ok),ShortCommandFormat)); + output.replace("%C",formatCommand(name(&ok),arguments(&ok),LongCommandFormat)); + + QString dir = validCurrentDir(); + if (output.contains("%D")) + { + QString homeDir = userHomeDir(); + QString tempDir = dir; + // Change User's Home Dir w/ ~ only at the beginning + if (tempDir.startsWith(homeDir)) + { + tempDir.remove(0, homeDir.length()); + tempDir.prepend('~'); + } + output.replace("%D", tempDir); + } + output.replace("%d", dir); + + // remove any remaining %[LETTER] sequences + // output.replace(QRegExp("%\\w"), QString()); + + return output; +} + +QString ProcessInfo::formatCommand(const QString& name, + const QVector<QString>& arguments, + CommandFormat format) const +{ + Q_UNUSED(name); + Q_UNUSED(format); + + // TODO Implement me + return QStringList(QList<QString>::fromVector(arguments)).join(" "); +} + +QVector<QString> ProcessInfo::arguments(bool* ok) const +{ + *ok = _fields & ARGUMENTS; + + return _arguments; +} + +QMap<QString,QString> ProcessInfo::environment(bool* ok) const +{ + *ok = _fields & ENVIRONMENT; + + return _environment; +} + +bool ProcessInfo::isValid() const +{ + return _fields & PROCESS_ID; +} + +int ProcessInfo::pid(bool* ok) const +{ + *ok = _fields & PROCESS_ID; + + return _pid; +} + +int ProcessInfo::parentPid(bool* ok) const +{ + *ok = _fields & PARENT_PID; + + return _parentPid; +} + +int ProcessInfo::foregroundPid(bool* ok) const +{ + *ok = _fields & FOREGROUND_PID; + + return _foregroundPid; +} + +QString ProcessInfo::name(bool* ok) const +{ + *ok = _fields & NAME; + + return _name; +} + +int ProcessInfo::userId(bool* ok) const +{ + *ok = _fields & UID; + + return _userId; +} + +QString ProcessInfo::userName() const +{ + return _userName; +} + +QString ProcessInfo::userHomeDir() const +{ + return _userHomeDir; +} + +void ProcessInfo::setPid(int pid) +{ + _pid = pid; + _fields |= PROCESS_ID; +} + +void ProcessInfo::setUserId(int uid) +{ + _userId = uid; + _fields |= UID; +} + +void ProcessInfo::setUserName(const QString& name) +{ + _userName = name; + setUserHomeDir(); +} + +void ProcessInfo::setUserHomeDir() +{ + QString usersName = userName(); + // JPS: I don't know a good QT replacement + //if (!usersName.isEmpty()) + // _userHomeDir = KUser(usersName).homeDir(); + //else + _userHomeDir = QDir::homePath(); +} + +void ProcessInfo::setParentPid(int pid) +{ + _parentPid = pid; + _fields |= PARENT_PID; +} +void ProcessInfo::setForegroundPid(int pid) +{ + _foregroundPid = pid; + _fields |= FOREGROUND_PID; +} + +QString ProcessInfo::currentDir(bool* ok) const +{ + if (ok) + *ok = _fields & CURRENT_DIR; + + return _currentDir; +} +void ProcessInfo::setCurrentDir(const QString& dir) +{ + _fields |= CURRENT_DIR; + _currentDir = dir; +} + +void ProcessInfo::setName(const QString& name) +{ + _name = name; + _fields |= NAME; +} +void ProcessInfo::addArgument(const QString& argument) +{ + _arguments << argument; +} + +void ProcessInfo::addEnvironmentBinding(const QString& name , const QString& value) +{ + _environment.insert(name,value); +} + +void ProcessInfo::setFileError( QFile::FileError error ) +{ + switch ( error ) + { + case PermissionsError: + setError( PermissionsError ); + break; + case NoError: + setError( NoError ); + break; + default: + setError( UnknownError ); + } +} + +// +// ProcessInfo::newInstance() is way at the bottom so it can see all of the +// implementations of the UnixProcessInfo abstract class. +// + +NullProcessInfo::NullProcessInfo(int pid,bool enableEnvironmentRead) + : ProcessInfo(pid,enableEnvironmentRead) +{ +} + +bool NullProcessInfo::readProcessInfo(int /*pid*/ , bool /*enableEnvironmentRead*/) +{ + return false; +} + +void NullProcessInfo::readUserName() +{ +} + +UnixProcessInfo::UnixProcessInfo(int pid,bool enableEnvironmentRead) + : ProcessInfo(pid,enableEnvironmentRead) +{ +} + +bool UnixProcessInfo::readProcessInfo(int pid , bool enableEnvironmentRead) +{ + bool ok = readProcInfo(pid); + if (ok) + { + ok |= readArguments(pid); + ok |= readCurrentDir(pid); + if ( enableEnvironmentRead ) + { + ok |= readEnvironment(pid); + } + } + return ok; +} + +void UnixProcessInfo::readUserName() +{ + bool ok = false; + int uid = userId(&ok); + if (!ok) return; + + struct passwd passwdStruct; + struct passwd *getpwResult; + char *getpwBuffer; + long getpwBufferSize; + int getpwStatus; + + getpwBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (getpwBufferSize == -1) + getpwBufferSize = 16384; + + getpwBuffer = new char[getpwBufferSize]; + if (getpwBuffer == NULL) + return; + getpwStatus = getpwuid_r(uid, &passwdStruct, getpwBuffer, getpwBufferSize, &getpwResult); + if (getpwResult != NULL) + setUserName(QString(passwdStruct.pw_name)); + else + setUserName(QString()); + delete [] getpwBuffer; +} + + +class LinuxProcessInfo : public UnixProcessInfo +{ +public: + LinuxProcessInfo(int pid, bool env) : + UnixProcessInfo(pid,env) + { + } + +private: + virtual bool readProcInfo(int pid) + { + // indicies of various fields within the process status file which + // contain various information about the process + const int PARENT_PID_FIELD = 3; + const int PROCESS_NAME_FIELD = 1; + const int GROUP_PROCESS_FIELD = 7; + + QString parentPidString; + QString processNameString; + QString foregroundPidString; + QString uidLine; + QString uidString; + QStringList uidStrings; + + // For user id read process status file ( /proc/<pid>/status ) + // Can not use getuid() due to it does not work for 'su' + QFile statusInfo( QString("/proc/%1/status").arg(pid) ); + if ( statusInfo.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&statusInfo); + QString statusLine; + do { + statusLine = stream.readLine(0); + if (statusLine.startsWith(QLatin1String("Uid:"))) + uidLine = statusLine; + } while (!statusLine.isNull() && uidLine.isNull()); + + uidStrings << uidLine.split('\t', QString::SkipEmptyParts); + // Must be 5 entries: 'Uid: %d %d %d %d' and + // uid string must be less than 5 chars (uint) + if (uidStrings.size() == 5) + uidString = uidStrings[1]; + if (uidString.size() > 5) + uidString.clear(); + + bool ok = false; + int uid = uidString.toInt(&ok); + if (ok) + setUserId(uid); + readUserName(); + } + else + { + setFileError( statusInfo.error() ); + return false; + } + + + // read process status file ( /proc/<pid/stat ) + // + // the expected file format is a list of fields separated by spaces, using + // parenthesies to escape fields such as the process name which may itself contain + // spaces: + // + // FIELD FIELD (FIELD WITH SPACES) FIELD FIELD + // + QFile processInfo( QString("/proc/%1/stat").arg(pid) ); + if ( processInfo.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&processInfo); + QString data = stream.readAll(); + + int stack = 0; + int field = 0; + int pos = 0; + + while (pos < data.count()) + { + QChar c = data[pos]; + + if ( c == '(' ) + stack++; + else if ( c == ')' ) + stack--; + else if ( stack == 0 && c == ' ' ) + field++; + else + { + switch ( field ) + { + case PARENT_PID_FIELD: + parentPidString.append(c); + break; + case PROCESS_NAME_FIELD: + processNameString.append(c); + break; + case GROUP_PROCESS_FIELD: + foregroundPidString.append(c); + break; + } + } + + pos++; + } + } + else + { + setFileError( processInfo.error() ); + return false; + } + + // check that data was read successfully + bool ok = false; + int foregroundPid = foregroundPidString.toInt(&ok); + if (ok) + setForegroundPid(foregroundPid); + + int parentPid = parentPidString.toInt(&ok); + if (ok) + setParentPid(parentPid); + + if (!processNameString.isEmpty()) + setName(processNameString); + + // update object state + setPid(pid); + + return ok; + } + + virtual bool readArguments(int pid) + { + // read command-line arguments file found at /proc/<pid>/cmdline + // the expected format is a list of strings delimited by null characters, + // and ending in a double null character pair. + + QFile argumentsFile( QString("/proc/%1/cmdline").arg(pid) ); + if ( argumentsFile.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&argumentsFile); + QString data = stream.readAll(); + + QStringList argList = data.split( QChar('\0') ); + + foreach ( const QString &entry , argList ) + { + if (!entry.isEmpty()) + addArgument(entry); + } + } + else + { + setFileError( argumentsFile.error() ); + } + + return true; + } + + virtual bool readCurrentDir(int pid) + { + QFileInfo info( QString("/proc/%1/cwd").arg(pid) ); + + const bool readable = info.isReadable(); + + if ( readable && info.isSymLink() ) + { + setCurrentDir( info.symLinkTarget() ); + return true; + } + else + { + if ( !readable ) + setError( PermissionsError ); + else + setError( UnknownError ); + + return false; + } + } + + virtual bool readEnvironment(int pid) + { + // read environment bindings file found at /proc/<pid>/environ + // the expected format is a list of KEY=VALUE strings delimited by null + // characters and ending in a double null character pair. + + QFile environmentFile( QString("/proc/%1/environ").arg(pid) ); + if ( environmentFile.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&environmentFile); + QString data = stream.readAll(); + + QStringList bindingList = data.split( QChar('\0') ); + + foreach( const QString &entry , bindingList ) + { + QString name; + QString value; + + int splitPos = entry.indexOf('='); + + if ( splitPos != -1 ) + { + name = entry.mid(0,splitPos); + value = entry.mid(splitPos+1,-1); + + addEnvironmentBinding(name,value); + } + } + } + else + { + setFileError( environmentFile.error() ); + } + + return true; + } +} ; + +#if defined(Q_OS_FREEBSD) +class FreeBSDProcessInfo : public UnixProcessInfo +{ +public: + FreeBSDProcessInfo(int pid, bool readEnvironment) : + UnixProcessInfo(pid, readEnvironment) + { + } + +private: + virtual bool readProcInfo(int pid) + { + int managementInfoBase[4]; + size_t mibLength; + struct kinfo_proc* kInfoProc; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_PID; + managementInfoBase[3] = pid; + + if (sysctl(managementInfoBase, 4, NULL, &mibLength, NULL, 0) == -1) + return false; + + kInfoProc = new struct kinfo_proc [mibLength]; + + if (sysctl(managementInfoBase, 4, kInfoProc, &mibLength, NULL, 0) == -1) + { + delete [] kInfoProc; + return false; + } + +#if defined(__DragonFly__) + setName(kInfoProc->kp_comm); + setPid(kInfoProc->kp_pid); + setParentPid(kInfoProc->kp_ppid); + setForegroundPid(kInfoProc->kp_pgid); + setUserId(kInfoProc->kp_uid); +#else + setName(kInfoProc->ki_comm); + setPid(kInfoProc->ki_pid); + setParentPid(kInfoProc->ki_ppid); + setForegroundPid(kInfoProc->ki_pgid); + setUserId(kInfoProc->ki_uid); +#endif + + readUserName(); + + delete [] kInfoProc; + return true; + } + + virtual bool readArguments(int pid) + { + char args[ARG_MAX]; + int managementInfoBase[4]; + size_t len; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_PID; + managementInfoBase[3] = pid; + + len = sizeof(args); + if (sysctl(managementInfoBase, 4, args, &len, NULL, 0) == -1) + return false; + + const QStringList argumentList = QString(args).split(QChar('\0')); + + for (QStringList::const_iterator it = argumentList.begin(); it != argumentList.end(); ++it) + { + addArgument(*it); + } + + return true; + } + + virtual bool readEnvironment(int pid) + { + // Not supported in FreeBSD? + return false; + } + + virtual bool readCurrentDir(int pid) + { +#if defined(__DragonFly__) + char buf[PATH_MAX]; + int managementInfoBase[4]; + size_t len; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_CWD; + managementInfoBase[3] = pid; + + len = sizeof(buf); + if (sysctl(managementInfoBase, 4, buf, &len, NULL, 0) == -1) + return false; + + setCurrentDir(buf); + + return true; +#else + int numrecords; + struct kinfo_file* info = 0; + + info = kinfo_getfile(pid, &numrecords); + + if (!info) + return false; + + for (int i = 0; i < numrecords; ++i) + { + if (info[i].kf_fd == KF_FD_TYPE_CWD) + { + setCurrentDir(info[i].kf_path); + + free(info); + return true; + } + } + + free(info); + return false; +#endif + } +} ; +#endif + +#if defined(Q_OS_MAC) +class MacProcessInfo : public UnixProcessInfo +{ +public: + MacProcessInfo(int pid, bool env) : + UnixProcessInfo(pid, env) + { + } + +private: + virtual bool readProcInfo(int pid) + { + int managementInfoBase[4]; + size_t mibLength; + struct kinfo_proc* kInfoProc; + KDE_struct_stat statInfo; + + // Find the tty device of 'pid' (Example: /dev/ttys001) + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_PID; + managementInfoBase[3] = pid; + + if (sysctl(managementInfoBase, 4, NULL, &mibLength, NULL, 0) == -1) + { + return false; + } + else + { + kInfoProc = new struct kinfo_proc [mibLength]; + if (sysctl(managementInfoBase, 4, kInfoProc, &mibLength, NULL, 0) == -1) + { + delete [] kInfoProc; + return false; + } + else + { + QString deviceNumber = QString(devname(((&kInfoProc->kp_eproc)->e_tdev), S_IFCHR)); + QString fullDeviceName = QString("/dev/") + deviceNumber.rightJustified(3, '0'); + delete [] kInfoProc; + + QByteArray deviceName = fullDeviceName.toLatin1(); + const char* ttyName = deviceName.data(); + + if (KDE_stat(ttyName, &statInfo) != 0) + return false; + + // Find all processes attached to ttyName + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_TTY; + managementInfoBase[3] = statInfo.st_rdev; + + mibLength = 0; + if (sysctl(managementInfoBase, sizeof(managementInfoBase)/sizeof(int), NULL, &mibLength, NULL, 0) == -1) + return false; + + kInfoProc = new struct kinfo_proc [mibLength]; + if (sysctl(managementInfoBase, sizeof(managementInfoBase)/sizeof(int), kInfoProc, &mibLength, NULL, 0) == -1) + return false; + + // The foreground program is the first one + setName(QString(kInfoProc->kp_proc.p_comm)); + + delete [] kInfoProc; + } + } + return true; + } + + virtual bool readArguments(int pid) + { + Q_UNUSED(pid); + return false; + } + virtual bool readCurrentDir(int pid) + { + struct proc_vnodepathinfo vpi; + int nb = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi)); + if (nb == sizeof(vpi)) + { + setCurrentDir(QString(vpi.pvi_cdir.vip_path)); + return true; + } + return false; + } + virtual bool readEnvironment(int pid) + { + Q_UNUSED(pid); + return false; + } +} ; +#endif + +#ifdef Q_OS_SOLARIS + // The procfs structure definition requires off_t to be + // 32 bits, which only applies if FILE_OFFSET_BITS=32. + // Futz around here to get it to compile regardless, + // although some of the structure sizes might be wrong. + // Fortunately, the structures we actually use don't use + // off_t, and we're safe. + #if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS==64) + #undef _FILE_OFFSET_BITS + #endif + #include <procfs.h> +#else + // On non-Solaris platforms, define a fake psinfo structure + // so that the SolarisProcessInfo class can be compiled + // + // That avoids the trap where you change the API and + // don't notice it in #ifdeffed platform-specific parts + // of the code. + struct psinfo { + int pr_ppid; + int pr_pgid; + char* pr_fname; + char* pr_psargs; + } ; + static const int PRARGSZ=1; +#endif + +class SolarisProcessInfo : public UnixProcessInfo +{ +public: + SolarisProcessInfo(int pid, bool readEnvironment) + : UnixProcessInfo(pid,readEnvironment) + { + } +private: + virtual bool readProcInfo(int pid) + { + QFile psinfo( QString("/proc/%1/psinfo").arg(pid) ); + if ( psinfo.open( QIODevice::ReadOnly ) ) + { + struct psinfo info; + if (psinfo.read((char *)&info,sizeof(info)) != sizeof(info)) + { + return false; + } + + setParentPid(info.pr_ppid); + setForegroundPid(info.pr_pgid); + setName(info.pr_fname); + setPid(pid); + + // Bogus, because we're treating the arguments as one single string + info.pr_psargs[PRARGSZ-1]=0; + addArgument(info.pr_psargs); + } + return true; + } + + virtual bool readArguments(int /*pid*/) + { + // Handled in readProcInfo() + return false; + } + + virtual bool readEnvironment(int /*pid*/) + { + // Not supported in Solaris + return false; + } + + virtual bool readCurrentDir(int pid) + { + QFileInfo info( QString("/proc/%1/path/cwd").arg(pid) ); + const bool readable = info.isReadable(); + + if ( readable && info.isSymLink() ) + { + setCurrentDir( info.symLinkTarget() ); + return true; + } + else + { + if ( !readable ) + setError( PermissionsError ); + else + setError( UnknownError ); + + return false; + } + } +} ; + +SSHProcessInfo::SSHProcessInfo(const ProcessInfo& process) + : _process(process) +{ + bool ok = false; + + // check that this is a SSH process + const QString& name = _process.name(&ok); + + if ( !ok || name != "ssh" ) + { + //if ( !ok ) + // kWarning() << "Could not read process info"; + //else + // kWarning() << "Process is not a SSH process"; + + return; + } + + // read arguments + const QVector<QString>& args = _process.arguments(&ok); + + // SSH options + // these are taken from the SSH manual ( accessed via 'man ssh' ) + + // options which take no arguments + static const QString noOptionsArguments("1246AaCfgkMNnqsTtVvXxY"); + // options which take one argument + static const QString singleOptionArguments("bcDeFiLlmOopRSw"); + + if ( ok ) + { + // find the username, host and command arguments + // + // the username/host is assumed to be the first argument + // which is not an option + // ( ie. does not start with a dash '-' character ) + // or an argument to a previous option. + // + // the command, if specified, is assumed to be the argument following + // the username and host + // + // note that we skip the argument at index 0 because that is the + // program name ( expected to be 'ssh' in this case ) + for ( int i = 1 ; i < args.count() ; i++ ) + { + // if this argument is an option then skip it, plus any + // following arguments which refer to this option + if ( args[i].startsWith('-') ) + { + QChar argChar = ( args[i].length() > 1 ) ? args[i][1] : '\0'; + + if ( noOptionsArguments.contains(argChar) ) + continue; + else if ( singleOptionArguments.contains(argChar) ) + { + i++; + continue; + } + } + + // check whether the host has been found yet + // if not, this must be the username/host argument + if ( _host.isEmpty() ) + { + // check to see if only a hostname is specified, or whether + // both a username and host are specified ( in which case they + // are separated by an '@' character: username@host ) + + int separatorPosition = args[i].indexOf('@'); + if ( separatorPosition != -1 ) + { + // username and host specified + _user = args[i].left(separatorPosition); + _host = args[i].mid(separatorPosition+1); + } + else + { + // just the host specified + _host = args[i]; + } + } + else + { + // host has already been found, this must be the command argument + _command = args[i]; + } + + } + } + else + { + //kWarning() << "Could not read arguments"; + + return; + } +} + +QString SSHProcessInfo::userName() const +{ + return _user; +} +QString SSHProcessInfo::host() const +{ + return _host; +} +QString SSHProcessInfo::command() const +{ + return _command; +} +QString SSHProcessInfo::format(const QString& input) const +{ + QString output(input); + + // test whether host is an ip address + // in which case 'short host' and 'full host' + // markers in the input string are replaced with + // the full address + bool isIpAddress = false; + + struct in_addr address; + if ( inet_aton(_host.toLocal8Bit().constData(),&address) != 0 ) + isIpAddress = true; + else + isIpAddress = false; + + // search for and replace known markers + output.replace("%u",_user); + + if ( isIpAddress ) + output.replace("%h",_host); + else + output.replace("%h",_host.left(_host.indexOf('.'))); + + output.replace("%H",_host); + output.replace("%c",_command); + + return output; +} + +ProcessInfo* ProcessInfo::newInstance(int pid,bool enableEnvironmentRead) +{ +#ifdef Q_OS_LINUX + return new LinuxProcessInfo(pid,enableEnvironmentRead); +#elif defined(Q_OS_SOLARIS) + return new SolarisProcessInfo(pid,enableEnvironmentRead); +#elif defined(Q_OS_MAC) + return new MacProcessInfo(pid,enableEnvironmentRead); +#elif defined(Q_OS_FREEBSD) + return new FreeBSDProcessInfo(pid,enableEnvironmentRead); +#else + return new NullProcessInfo(pid,enableEnvironmentRead); +#endif +} +
new file mode 100644 --- /dev/null +++ b/gui/src/ProcessInfo.h @@ -0,0 +1,454 @@ +/* + Copyright 2007-2008 by Robert Knight <robertknight@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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef PROCESSINFO_H +#define PROCESSINFO_H + +// Qt +#include <QtCore/QFile> +#include <QtCore/QMap> +#include <QtCore/QString> +#include <QtCore/QVector> + +/** + * Takes a snapshot of the state of a process and provides access to + * information such as the process name, parent process, + * the foreground process in the controlling terminal, + * the arguments with which the process was started and the + * environment. + * + * To create a new snapshot, construct a new ProcessInfo instance, + * using ProcessInfo::newInstance(), + * passing the process identifier of the process you are interested in. + * + * After creating a new instance, call the update() method to take a + * snapshot of the current state of the process. + * + * Before calling any additional methods, check that the process state + * was read successfully using the isValid() method. + * + * Each accessor method which provides information about the process state ( such as pid(), + * currentDir(), name() ) takes a pointer to a boolean as an argument. If the information + * requested was read successfully then the boolean is set to true, otherwise it is set + * to false, in which case the return value from the function should be ignored. + * If this boolean is set to false, it may indicate an error reading the process information, + * or it may indicate that the information is not available on the current platform. + * + * eg. + * + * @code + * ProcessInfo* info = ProcessInfo::newInstance(pid); + * info->update(); + * + * if ( info->isValid() ) + * { + * bool ok; + * QString value = info->name(&ok); + * + * if ( ok ) kDebug() << "process name - " << name; + * int parentPid = info->parentPid(&ok); + * if ( ok ) kDebug() << "parent process - " << parentPid; + * int foregroundPid = info->foregroundColororegroundPid(&ok); + * if ( ok ) kDebug() << "foreground process - " << foregroundPid; + * } + * @endcode + */ +class ProcessInfo +{ +public: + /** + * Constructs a new instance of a suitable ProcessInfo sub-class for + * the current platform which provides information about a given process. + * + * @param pid The pid of the process to examine + * @param readEnvironment Specifies whether environment bindings should + * be read. If this is false, then environment() calls will + * always fail. This is an optimization to avoid the overhead + * of reading the (potentially large) environment data when it + * is not required. + */ + static ProcessInfo* newInstance(int pid,bool readEnvironment = false); + + virtual ~ProcessInfo() {} + + /** + * Updates the information about the process. This must + * be called before attempting to use any of the accessor methods. + */ + void update(); + + /** Returns true if the process state was read successfully. */ + bool isValid() const; + /** + * Returns the process id. + * + * @param ok Set to true if the process id was read successfully or false otherwise + */ + int pid(bool* ok) const; + /** + * Returns the id of the parent process id was read successfully or false otherwise + * + * @param ok Set to true if the parent process id + */ + int parentPid(bool* ok) const; + + /** + * Returns the id of the current foreground process + * + * NOTE: Using the foregroundProcessGroup() method of the Pty + * instance associated with the terminal of interest is preferred + * over using this method. + * + * @param ok Set to true if the foreground process id was read successfully or false otherwise + */ + int foregroundPid(bool* ok) const; + + /* Returns the user id of the process */ + int userId(bool* ok) const; + + /** Returns the user's name of the process */ + QString userName() const; + + /** Returns the user's home directory of the process */ + QString userHomeDir() const; + + /** Returns the name of the current process */ + QString name(bool* ok) const; + + /** + * Returns the command-line arguments which the process + * was started with. + * + * The first argument is the name used to launch the process. + * + * @param ok Set to true if the arguments were read successfully or false otherwise. + */ + QVector<QString> arguments(bool* ok) const; + /** + * Returns the environment bindings which the process + * was started with. + * In the returned map, the key is the name of the environment variable, + * and the value is the corresponding value. + * + * @param ok Set to true if the environment bindings were read successfully or false otherwise + */ + QMap<QString,QString> environment(bool* ok) const; + + /** + * Returns the current working directory of the process + * + * @param ok Set to true if the current working directory was read successfully or false otherwise + */ + QString currentDir(bool* ok) const; + + /** + * Returns the current working directory of the process (or its parent) + */ + QString validCurrentDir() const; + + /** Forces the user home directory to be calculated */ + void setUserHomeDir(); + + /** + * Parses an input string, looking for markers beginning with a '%' + * character and returns a string with the markers replaced + * with information from this process description. + * <br> + * The markers recognised are: + * <ul> + * <li> %u - Name of the user which owns the process. </li> + * <li> %n - Replaced with the name of the process. </li> + * <li> %d - Replaced with the last part of the path name of the + * process' current working directory. + * + * (eg. if the current directory is '/home/bob' then + * 'bob' would be returned) + * </li> + * <li> %D - Replaced with the current working directory of the process. </li> + * </ul> + */ + QString format(const QString& text) const; + + /** + * This enum describes the errors which can occur when trying to read + * a process's information. + */ + enum Error + { + /** No error occurred. */ + NoError, + /** The nature of the error is unknown. */ + UnknownError, + /** Konsole does not have permission to obtain the process information. */ + PermissionsError + }; + + /** + * Returns the last error which occurred. + */ + Error error() const; + +protected: + /** + * Constructs a new process instance. You should not call the constructor + * of ProcessInfo or its subclasses directly. Instead use the + * static ProcessInfo::newInstance() method which will return + * a suitable ProcessInfo instance for the current platform. + */ + explicit ProcessInfo(int pid , bool readEnvironment = false); + + /** + * This is called on construction to read the process state + * Subclasses should reimplement this function to provide + * platform-specific process state reading functionality. + * + * When called, readProcessInfo() should attempt to read all + * of the necessary state information. If the attempt is successful, + * it should set the process id using setPid(), and update + * the other relevant information using setParentPid(), setName(), + * setArguments() etc. + * + * Calls to isValid() will return true only if the process id + * has been set using setPid() + * + * @param pid The process id of the process to read + * @param readEnvironment Specifies whether the environment bindings + * for the process should be read + */ + virtual bool readProcessInfo(int pid , bool readEnvironment) = 0; + + /* Read the user name */ + virtual void readUserName(void) = 0; + + /** Sets the process id associated with this ProcessInfo instance */ + void setPid(int pid); + /** Sets the parent process id as returned by parentPid() */ + void setParentPid(int pid); + /** Sets the foreground process id as returend by foregroundPid() */ + void setForegroundPid(int pid); + /** Sets the user id associated with this ProcessInfo instance */ + void setUserId(int uid); + /** Sets the user name of the process as set by readUserName() */ + void setUserName(const QString& name); + /** Sets the name of the process as returned by name() */ + void setName(const QString& name); + /** Sets the current working directory for the process */ + void setCurrentDir(const QString& dir); + + /** Sets the error */ + void setError( Error error ); + + /** Convenience method. Sets the error based on a QFile error code. */ + void setFileError( QFile::FileError error ); + + /** + * Adds a commandline argument for the process, as returned + * by arguments() + */ + void addArgument(const QString& argument); + /** + * Adds an environment binding for the process, as returned by + * environment() + * + * @param name The name of the environment variable, eg. "PATH" + * @param value The value of the environment variable, eg. "/bin" + */ + void addEnvironmentBinding(const QString& name , const QString& value); + +private: + enum CommandFormat + { + ShortCommandFormat, + LongCommandFormat + }; + // takes a process name and its arguments and produces formatted output + QString formatCommand(const QString& name , const QVector<QString>& arguments , + CommandFormat format) const; + + // valid bits for _fields variable, ensure that + // _fields is changed to an int if more than 8 fields are added + enum FIELD_BITS + { + PROCESS_ID = 1, + PARENT_PID = 2, + FOREGROUND_PID = 4, + ARGUMENTS = 8, + ENVIRONMENT = 16, + NAME = 32, + CURRENT_DIR = 64, + UID =128 + }; + + char _fields; // a bitmap indicating which fields are valid + // used to set the "ok" parameters for the public + // accessor functions + + bool _enableEnvironmentRead; // specifies whether to read the environment + // bindings when update() is called + int _pid; + int _parentPid; + int _foregroundPid; + int _userId; + + Error _lastError; + + QString _name; + QString _userName; + QString _userHomeDir; + QString _currentDir; + + QVector<QString> _arguments; + QMap<QString,QString> _environment; +}; + +/** + * Implementation of ProcessInfo which does nothing. + * Used on platforms where a suitable ProcessInfo subclass is not + * available. + * + * isValid() will always return false for instances of NullProcessInfo + */ +class NullProcessInfo : public ProcessInfo +{ +public: + /** + * Constructs a new NullProcessInfo instance. + * See ProcessInfo::newInstance() + */ + explicit NullProcessInfo(int pid,bool readEnvironment = false); +protected: + virtual bool readProcessInfo(int pid,bool readEnvironment); + virtual void readUserName(void); +}; + +/** + * Implementation of ProcessInfo for Unix platforms which uses + * the /proc filesystem + */ +class UnixProcessInfo : public ProcessInfo +{ +public: + /** + * Constructs a new instance of UnixProcessInfo. + * See ProcessInfo::newInstance() + */ + explicit UnixProcessInfo(int pid,bool readEnvironment = false); + +protected: + /** + * Implementation of ProcessInfo::readProcessInfo(); calls the + * four private methods below in turn. + */ + virtual bool readProcessInfo(int pid , bool readEnvironment); + + virtual void readUserName(void); + +private: + /** + * Read the standard process information -- PID, parent PID, foreground PID. + * @param pid process ID to use + * @return true on success + */ + virtual bool readProcInfo(int pid)=0; + + /** + * Read the environment of the process. Sets _environment. + * @param pid process ID to use + * @return true on success + */ + virtual bool readEnvironment(int pid)=0; + + /** + * Determine what arguments were passed to the process. Sets _arguments. + * @param pid process ID to use + * @return true on success + */ + virtual bool readArguments(int pid)=0; + + /** + * Determine the current directory of the process. + * @param pid process ID to use + * @return true on success + */ + virtual bool readCurrentDir(int pid)=0; +}; + +/** + * Lightweight class which provides additional information about SSH processes. + */ +class SSHProcessInfo +{ +public: + /** + * Constructs a new SSHProcessInfo instance which provides additional + * information about the specified SSH process. + * + * @param process A ProcessInfo instance for a SSH process. + */ + SSHProcessInfo(const ProcessInfo& process); + + /** + * Returns the user name which the user initially logged into on + * the remote computer. + */ + QString userName() const; + + /** + * Returns the host which the user has connected to. + */ + QString host() const; + + /** + * Returns the command which the user specified to execute on the + * remote computer when starting the SSH process. + */ + QString command() const; + + /** + * Operates in the same way as ProcessInfo::format(), except + * that the set of markers understood is different: + * + * %u - Replaced with user name which the user initially logged + * into on the remote computer. + * %h - Replaced with the first part of the host name which + * is connected to. + * %H - Replaced with the full host name of the computer which + * is connected to. + * %c - Replaced with the command which the user specified + * to execute when starting the SSH process. + */ + QString format(const QString& input) const; + +private: + const ProcessInfo& _process; + QString _user; + QString _host; + QString _command; +}; + +#endif //PROCESSINFO_H + +/* + Local Variables: + mode: c++ + c-file-style: "stroustrup" + indent-tabs-mode: nil + tab-width: 4 + End: +*/
new file mode 100644 --- /dev/null +++ b/gui/src/Pty.cpp @@ -0,0 +1,310 @@ +/* + This file is part of Konsole, an X terminal. + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "kprocess_p.h" +#include "kptyprocess.h" +#include "Pty.h" + +// System +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <termios.h> +#include <signal.h> + +// Qt +#include <QtCore/QStringList> + +#include "kpty.h" +#include "kptydevice.h" + + +void Pty::setWindowSize(int lines, int cols) +{ + _windowColumns = cols; + _windowLines = lines; + + if (pty()->masterFd() >= 0) + pty()->setWinSize(lines, cols); +} +QSize Pty::windowSize() const +{ + return QSize(_windowColumns,_windowLines); +} + +void Pty::setFlowControlEnabled(bool enable) +{ + _xonXoff = enable; + + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + if (!enable) + ttmode.c_iflag &= ~(IXOFF | IXON); + else + ttmode.c_iflag |= (IXOFF | IXON); + //if (!pty()->tcSetAttr(&ttmode)) + // kWarning() << "Unable to set terminal attributes."; + } +} +bool Pty::flowControlEnabled() const +{ + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + return ttmode.c_iflag & IXOFF && + ttmode.c_iflag & IXON; + } + //kWarning() << "Unable to get flow control status, terminal not connected."; + return false; +} + +void Pty::setUtf8Mode(bool enable) +{ +#ifdef IUTF8 // XXX not a reasonable place to check it. + _utf8 = enable; + + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + if (!enable) + ttmode.c_iflag &= ~IUTF8; + else + ttmode.c_iflag |= IUTF8; + // if (!pty()->tcSetAttr(&ttmode)) + // kWarning() << "Unable to set terminal attributes."; + } +#endif +} + +void Pty::setErase(char erase) +{ + _eraseChar = erase; + + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + ttmode.c_cc[VERASE] = erase; + //if (!pty()->tcSetAttr(&ttmode)) + // kWarning() << "Unable to set terminal attributes."; + } +} + +char Pty::erase() const +{ + if (pty()->masterFd() >= 0) + { + struct ::termios ttyAttributes; + pty()->tcGetAttr(&ttyAttributes); + return ttyAttributes.c_cc[VERASE]; + } + + return _eraseChar; +} + +void Pty::addEnvironmentVariables(const QStringList& environment) +{ + QListIterator<QString> iter(environment); + while (iter.hasNext()) + { + QString pair = iter.next(); + + // split on the first '=' character + int pos = pair.indexOf('='); + + if ( pos >= 0 ) + { + QString variable = pair.left(pos); + QString value = pair.mid(pos+1); + + setEnv(variable,value); + } + } +} + +int Pty::start(const QString& program, + const QStringList& programArguments, + const QStringList& environment, + ulong winid, + bool addToUtmp, + const QString& dbusService, + const QString& dbusSession) +{ + clearProgram(); + + // For historical reasons, the first argument in programArguments is the + // name of the program to execute, so create a list consisting of all + // but the first argument to pass to setProgram() + Q_ASSERT(programArguments.count() >= 1); + setProgram(program.toLatin1(),programArguments.mid(1)); + + addEnvironmentVariables(environment); + + if ( !dbusService.isEmpty() ) + setEnv("KONSOLE_DBUS_SERVICE",dbusService); + if ( !dbusSession.isEmpty() ) + setEnv("KONSOLE_DBUS_SESSION", dbusSession); + + setEnv("WINDOWID", QString::number(winid)); + + // unless the LANGUAGE environment variable has been set explicitly + // set it to a null string + // this fixes the problem where KCatalog sets the LANGUAGE environment + // variable during the application's startup to something which + // differs from LANG,LC_* etc. and causes programs run from + // the terminal to display messages in the wrong language + // + // this can happen if LANG contains a language which KDE + // does not have a translation for + // + // BR:149300 + setEnv("LANGUAGE",QString(),false /* do not overwrite existing value if any */); + + setUseUtmp(addToUtmp); + + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + if (!_xonXoff) + ttmode.c_iflag &= ~(IXOFF | IXON); + else + ttmode.c_iflag |= (IXOFF | IXON); +#ifdef IUTF8 // XXX not a reasonable place to check it. + if (!_utf8) + ttmode.c_iflag &= ~IUTF8; + else + ttmode.c_iflag |= IUTF8; +#endif + + if (_eraseChar != 0) + ttmode.c_cc[VERASE] = _eraseChar; + + //if (!pty()->tcSetAttr(&ttmode)) + // kWarning() << "Unable to set terminal attributes."; + + pty()->setWinSize(_windowLines, _windowColumns); + + KProcess::start(); + + if (!waitForStarted()) + return -1; + + return 0; +} + +void Pty::setWriteable(bool writeable) +{ + //KDE_struct_stat sbuf; + struct stat sbuf; + //KDE_stat(pty()->ttyName(), &sbuf); + ::stat(pty()->ttyName(), &sbuf); + if (writeable) + chmod(pty()->ttyName(), sbuf.st_mode | S_IWGRP); + else + chmod(pty()->ttyName(), sbuf.st_mode & ~(S_IWGRP|S_IWOTH)); +} + +Pty::Pty(int masterFd, QObject* parent) + : KPtyProcess(masterFd,parent) +{ + init(); +} +Pty::Pty(QObject* parent) + : KPtyProcess(parent) +{ + init(); +} +void Pty::init() +{ + _windowColumns = 0; + _windowLines = 0; + _eraseChar = 0; + _xonXoff = true; + _utf8 =true; + + connect(pty(), SIGNAL(readyRead()) , this , SLOT(dataReceived())); + setPtyChannels(KPtyProcess::AllChannels); +} + +Pty::~Pty() +{ +} + +void Pty::sendData(const char* data, int length) +{ + if (!length) + return; + + if (!pty()->write(data,length)) + { + //kWarning() << "Pty::doSendJobs - Could not send input data to terminal process."; + return; + } +} + +void Pty::dataReceived() +{ + QByteArray data = pty()->readAll(); + emit receivedData(data.constData(),data.count()); +} + +void Pty::lockPty(bool lock) +{ + Q_UNUSED(lock); + +// TODO: Support for locking the Pty + //if (lock) + //suspend(); + //else + //resume(); +} + +int Pty::foregroundProcessGroup() const +{ + int pid = tcgetpgrp(pty()->masterFd()); + + if ( pid != -1 ) + { + return pid; + } + + return 0; +} + +void Pty::setupChildProcess() +{ + KPtyProcess::setupChildProcess(); + + // reset all signal handlers + // this ensures that terminal applications respond to + // signals generated via key sequences such as Ctrl+C + // (which sends SIGINT) + struct sigaction action; + sigemptyset(&action.sa_mask); + action.sa_handler = SIG_DFL; + action.sa_flags = 0; + for (int signal=1;signal < NSIG; signal++) + sigaction(signal,&action,0L); +}
new file mode 100644 --- /dev/null +++ b/gui/src/Pty.h @@ -0,0 +1,201 @@ +/* + This file is part of Konsole, KDE's terminal emulator. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef PTY_H +#define PTY_H + +// Qt +#include <QtCore/QStringList> +#include <QtCore/QVector> +#include <QtCore/QList> +#include <QtCore/QSize> + +// KDE +#include "kprocess.h" +#include "kptyprocess.h" + +/** + * The Pty class is used to start the terminal process, + * send data to it, receive data from it and manipulate + * various properties of the pseudo-teletype interface + * used to communicate with the process. + * + * To use this class, construct an instance and connect + * to the sendData slot and receivedData signal to + * send data to or receive data from the process. + * + * To start the terminal process, call the start() method + * with the program name and appropriate arguments. + */ +//class KONSOLEPRIVATE_EXPORT Pty: public KPtyProcess +class Pty: public KPtyProcess +{ +Q_OBJECT + + public: + + /** + * Constructs a new Pty. + * + * Connect to the sendData() slot and receivedData() signal to prepare + * for sending and receiving data from the terminal process. + * + * To start the terminal process, call the run() method with the + * name of the program to start and appropriate arguments. + */ + explicit Pty(QObject* parent = 0); + + /** + * Construct a process using an open pty master. + * See KPtyProcess::KPtyProcess() + */ + explicit Pty(int ptyMasterFd, QObject* parent = 0); + + ~Pty(); + + /** + * Starts the terminal process. + * + * Returns 0 if the process was started successfully or non-zero + * otherwise. + * + * @param program Path to the program to start + * @param arguments Arguments to pass to the program being started + * @param environment A list of key=value pairs which will be added + * to the environment for the new process. At the very least this + * should include an assignment for the TERM environment variable. + * @param winid Specifies the value of the WINDOWID environment variable + * in the process's environment. + * @param addToUtmp Specifies whether a utmp entry should be created for + * the pty used. See K3Process::setUsePty() + * @param dbusService Specifies the value of the KONSOLE_DBUS_SERVICE + * environment variable in the process's environment. + * @param dbusSession Specifies the value of the KONSOLE_DBUS_SESSION + * environment variable in the process's environment. + */ + int start( const QString& program, + const QStringList& arguments, + const QStringList& environment, + ulong winid, + bool addToUtmp, + const QString& dbusService, + const QString& dbusSession + ); + + /** TODO: Document me */ + void setWriteable(bool writeable); + + /** + * Enables or disables Xon/Xoff flow control. The flow control setting + * may be changed later by a terminal application, so flowControlEnabled() + * may not equal the value of @p on in the previous call to setFlowControlEnabled() + */ + void setFlowControlEnabled(bool on); + + /** Queries the terminal state and returns true if Xon/Xoff flow control is enabled. */ + bool flowControlEnabled() const; + + /** + * Sets the size of the window (in lines and columns of characters) + * used by this teletype. + */ + void setWindowSize(int lines, int cols); + + /** Returns the size of the window used by this teletype. See setWindowSize() */ + QSize windowSize() const; + + /** TODO Document me */ + void setErase(char erase); + + /** */ + char erase() const; + + /** + * Returns the process id of the teletype's current foreground + * process. This is the process which is currently reading + * input sent to the terminal via. sendData() + * + * If there is a problem reading the foreground process group, + * 0 will be returned. + */ + int foregroundProcessGroup() const; + + public slots: + + /** + * Put the pty into UTF-8 mode on systems which support it. + */ + void setUtf8Mode(bool on); + + /** + * Suspend or resume processing of data from the standard + * output of the terminal process. + * + * See K3Process::suspend() and K3Process::resume() + * + * @param lock If true, processing of output is suspended, + * otherwise processing is resumed. + */ + void lockPty(bool lock); + + /** + * Sends data to the process currently controlling the + * teletype ( whose id is returned by foregroundProcessGroup() ) + * + * @param buffer Pointer to the data to send. + * @param length Length of @p buffer. + */ + void sendData(const char* buffer, int length); + + signals: + + /** + * Emitted when a new block of data is received from + * the teletype. + * + * @param buffer Pointer to the data received. + * @param length Length of @p buffer + */ + void receivedData(const char* buffer, int length); + + protected: + void setupChildProcess(); + + private slots: + // called when data is received from the terminal process + void dataReceived(); + + private: + void init(); + + // takes a list of key=value pairs and adds them + // to the environment for the process + void addEnvironmentVariables(const QStringList& environment); + + int _windowColumns; + int _windowLines; + char _eraseChar; + bool _xonXoff; + bool _utf8; +}; + +#endif // PTY_H
new file mode 100644 --- /dev/null +++ b/gui/src/QTerminalWidget.cpp @@ -0,0 +1,191 @@ +/* Copyright (C) 2008 e_k (e_k@users.sourceforge.net) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "QTerminalWidget.h" +#include "Session.h" +#include "TerminalDisplay.h" + +struct TermWidgetImpl +{ + TermWidgetImpl(QWidget* parent = 0); + + TerminalDisplay *m_terminalDisplay; + Session *m_session; + Session* createSession(); + TerminalDisplay* createTerminalDisplay(Session *session, QWidget* parent); +}; + +TermWidgetImpl::TermWidgetImpl(QWidget* parent) +{ + QPalette palette = QApplication::palette(); + m_session = createSession(); + m_terminalDisplay = createTerminalDisplay(this->m_session, parent); + m_terminalDisplay->setBackgroundColor(palette.color(QPalette::Base)); + m_terminalDisplay->setForegroundColor(palette.color(QPalette::Text)); +} + +Session *TermWidgetImpl::createSession() +{ + Session *session = new Session(); + session->setTitle(Session::NameRole, "QTerminalWidget"); + session->setProgram("/bin/bash"); + session->setArguments(QStringList()); + session->setAutoClose(true); + session->setCodec(QTextCodec::codecForName("UTF-8")); + session->setFlowControlEnabled(true); + session->setHistoryType(HistoryTypeBuffer(1000)); + session->setDarkBackground(true); + session->setKeyBindings(""); + return session; +} + +TerminalDisplay *TermWidgetImpl::createTerminalDisplay(Session *session, QWidget* parent) +{ + TerminalDisplay* display = new TerminalDisplay(parent); + display->setBellMode(TerminalDisplay::NotifyBell); + display->setTerminalSizeHint(true); + display->setTripleClickMode(TerminalDisplay::SelectWholeLine); + display->setTerminalSizeStartup(true); + display->setRandomSeed(session->sessionId() * 31); + return display; +} + +QTerminalWidget::QTerminalWidget(int startnow, QWidget *parent) + :QWidget(parent) +{ + m_impl = new TermWidgetImpl(this); + + initialize(); + + if(startnow && m_impl->m_session) { + m_impl->m_session->run(); + } + + setFocus(Qt::OtherFocusReason); + m_impl->m_terminalDisplay->resize(this->size()); + setFocusProxy(m_impl->m_terminalDisplay); +} + +void QTerminalWidget::startShellProgram() +{ + if(m_impl->m_session->isRunning()) + return; + + m_impl->m_session->run(); +} + +void QTerminalWidget::initialize() +{ + m_impl->m_terminalDisplay->setSize(80, 40); + + QFont font = QApplication::font(); + font.setFamily("Monospace"); + font.setPointSize(10); + font.setStyleHint(QFont::TypeWriter); + setTerminalFont(font); + setScrollBarPosition(NoScrollBar); + + m_impl->m_session->addView(m_impl->m_terminalDisplay); + + connect(m_impl->m_session, SIGNAL(finished()), this, SLOT(sessionFinished())); +} + +QTerminalWidget::~QTerminalWidget() +{ + emit destroyed(); +} + +void QTerminalWidget::setTerminalFont(QFont &font) +{ + if (!m_impl->m_terminalDisplay) + return; + m_impl->m_terminalDisplay->setVTFont(font); +} + +void QTerminalWidget::setShellProgram(QString progname) +{ + if (!m_impl->m_session) + return; + m_impl->m_session->setProgram(progname); +} + +void QTerminalWidget::openTeletype(int fd) +{ + if ( m_impl->m_session->isRunning() ) + return; + + m_impl->m_session->openTeletype(fd); +} + +void QTerminalWidget::setArgs(QStringList &args) +{ + if (!m_impl->m_session) + return; + m_impl->m_session->setArguments(args); +} + +void QTerminalWidget::setTextCodec(QTextCodec *codec) +{ + if (!m_impl->m_session) + return; + m_impl->m_session->setCodec(codec); +} + +void QTerminalWidget::setSize(int h, int v) +{ + if (!m_impl->m_terminalDisplay) + return; + m_impl->m_terminalDisplay->setSize(h, v); +} + +void QTerminalWidget::setHistorySize(int lines) +{ + if (lines < 0) + m_impl->m_session->setHistoryType(HistoryTypeFile()); + else + m_impl->m_session->setHistoryType(HistoryTypeBuffer(lines)); +} + +void QTerminalWidget::setScrollBarPosition(ScrollBarPosition pos) +{ + if (!m_impl->m_terminalDisplay) + return; + m_impl->m_terminalDisplay->setScrollBarPosition((TerminalDisplay::ScrollBarPosition)pos); +} + +void QTerminalWidget::sendText(const QString &text) +{ + m_impl->m_session->sendText(text); +} + +void QTerminalWidget::installEventFilterOnDisplay(QObject *object) { + m_impl->m_terminalDisplay->installEventFilter(object); +} + +void QTerminalWidget::resizeEvent(QResizeEvent*) +{ + m_impl->m_terminalDisplay->resize(this->size()); + m_impl->m_terminalDisplay->update(); +} + +void QTerminalWidget::sessionFinished() +{ + emit finished(); +} + +
new file mode 100644 --- /dev/null +++ b/gui/src/QTerminalWidget.h @@ -0,0 +1,93 @@ +/* Copyright (C) 2008 e_k (e_k@users.sourceforge.net) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef QTERMINALWIDGET_H +#define QTERMINALWIDGET_H + +#include <QtGui> + +struct TermWidgetImpl; +/** + * \class QTerminalWidget + * This class forms a widget class that can be inserted into other widgets. + */ +class QTerminalWidget : public QWidget +{ + Q_OBJECT +public: + /** + * \enum ScrollBarPosition + * Defines the scrollbar position of the terminal. + */ + enum ScrollBarPosition + { + NoScrollBar, + ScrollBarLeft, + ScrollBarRight + }; + + QTerminalWidget(int startnow = 1, QWidget *parent = 0); + ~QTerminalWidget(); + + void startShellProgram(); + void openTeletype(int fd); + + /** Default is application font with family Monospace, size 10. */ + void setTerminalFont(QFont &font); + + /** Shell program, default is /bin/bash. */ + void setShellProgram(QString progname); + + /** Shell program args, default is none. */ + void setArgs(QStringList &args); + + /** Text codec, default is UTF-8. */ + void setTextCodec(QTextCodec *codec); + + /** Resize terminal widget. */ + void setSize(int h, int v); + + /** History size for scrolling, values below zero mean infinite. */ + void setHistorySize(int lines); + + /** Presence of scrollbar. By default, there is no scrollbar present. */ + void setScrollBarPosition(ScrollBarPosition); + + /** Send some text to the terminal. */ + void sendText(const QString &text); + + /** Installs an event filter onto the display. */ + void installEventFilterOnDisplay(QObject *object); + +signals: + /** Emitted, when the current program has finished. */ + void finished(); + +protected: + virtual void resizeEvent(QResizeEvent *); + +protected slots: + void sessionFinished(); + +private: + /** Performs initial operations on this widget. */ + void initialize(); + TermWidgetImpl *m_impl; +}; + +#endif // QTERMINALWIDGET_H
new file mode 100644 --- /dev/null +++ b/gui/src/Quint.cpp @@ -0,0 +1,41 @@ +/* Quint - A graphical user interface for Octave + * Copyright (C) 2011 Jacob Dawid + * jacob.dawid@googlemail.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 <http://www.gnu.org/licenses/>. + */ + +#include <QtGui/QApplication> +#include <QTranslator> +#include <QSettings> +#include "MainWindow.h" + +int main(int argc, char *argv[]) +{ + QApplication application(argc, argv); + + QDesktopServices desktopServices; + QSettings settings( + desktopServices.storageLocation(QDesktopServices::HomeLocation) + + "/.quint/settings.ini", QSettings::IniFormat); + + QTranslator translator; + translator.load(QString("../languages/%1.qm").arg(settings.value("application/language").toString())); + application.installTranslator(&translator); + + MainWindow w; + w.show(); + + return application.exec(); +}
new file mode 100644 --- /dev/null +++ b/gui/src/Screen.cpp @@ -0,0 +1,1356 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight <robert.knight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + */ + +// Own +#include "Screen.h" + +// Standard +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include <ctype.h> + +// Qt +#include <QtCore/QTextStream> +#include <QtCore/QDate> + + +// Konsole +#include "konsole_wcwidth.h" +#include "TerminalCharacterDecoder.h" + +//FIXME: this is emulation specific. Use false for xterm, true for ANSI. +//FIXME: see if we can get this from terminfo. +#define BS_CLEARS false + +//Macro to convert x,y position on screen to position within an image. +// +//Originally the image was stored as one large contiguous block of +//memory, so a position within the image could be represented as an +//offset from the beginning of the block. For efficiency reasons this +//is no longer the case. +//Many internal parts of this class still use this representation for parameters and so on, +//notably moveImage() and clearImage(). +//This macro converts from an X,Y position into an image offset. +#ifndef loc +#define loc(X,Y) ((Y)*columns+(X)) +#endif + + +Character Screen::defaultChar = Character(' ', + CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR), + CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR), + DEFAULT_RENDITION); + +//#define REVERSE_WRAPPED_LINES // for wrapped line debug + + Screen::Screen(int l, int c) +: lines(l), + columns(c), + screenLines(new ImageLine[lines+1] ), + _scrolledLines(0), + _droppedLines(0), + history(new HistoryScrollNone()), + cuX(0), cuY(0), + currentRendition(0), + _topMargin(0), _bottomMargin(0), + selBegin(0), selTopLeft(0), selBottomRight(0), + blockSelectionMode(false), + effectiveForeground(CharacterColor()), effectiveBackground(CharacterColor()), effectiveRendition(0), + lastPos(-1) +{ + lineProperties.resize(lines+1); + for (int i=0;i<lines+1;i++) + lineProperties[i]=LINE_DEFAULT; + + initTabStops(); + clearSelection(); + reset(); +} + +/*! Destructor +*/ + +Screen::~Screen() +{ + delete[] screenLines; + delete history; +} + +void Screen::cursorUp(int n) + //=CUU +{ + if (n == 0) n = 1; // Default + int stop = cuY < _topMargin ? 0 : _topMargin; + cuX = qMin(columns-1,cuX); // nowrap! + cuY = qMax(stop,cuY-n); +} + +void Screen::cursorDown(int n) + //=CUD +{ + if (n == 0) n = 1; // Default + int stop = cuY > _bottomMargin ? lines-1 : _bottomMargin; + cuX = qMin(columns-1,cuX); // nowrap! + cuY = qMin(stop,cuY+n); +} + +void Screen::cursorLeft(int n) + //=CUB +{ + if (n == 0) n = 1; // Default + cuX = qMin(columns-1,cuX); // nowrap! + cuX = qMax(0,cuX-n); +} + +void Screen::cursorRight(int n) + //=CUF +{ + if (n == 0) n = 1; // Default + cuX = qMin(columns-1,cuX+n); +} + +void Screen::setMargins(int top, int bot) + //=STBM +{ + if (top == 0) top = 1; // Default + if (bot == 0) bot = lines; // Default + top = top - 1; // Adjust to internal lineno + bot = bot - 1; // Adjust to internal lineno + if ( !( 0 <= top && top < bot && bot < lines ) ) + { //Debug()<<" setRegion("<<top<<","<<bot<<") : bad range."; + return; // Default error action: ignore + } + _topMargin = top; + _bottomMargin = bot; + cuX = 0; + cuY = getMode(MODE_Origin) ? top : 0; + +} + +int Screen::topMargin() const +{ + return _topMargin; +} +int Screen::bottomMargin() const +{ + return _bottomMargin; +} + +void Screen::index() + //=IND +{ + if (cuY == _bottomMargin) + scrollUp(1); + else if (cuY < lines-1) + cuY += 1; +} + +void Screen::reverseIndex() + //=RI +{ + if (cuY == _topMargin) + scrollDown(_topMargin,1); + else if (cuY > 0) + cuY -= 1; +} + +void Screen::nextLine() + //=NEL +{ + toStartOfLine(); index(); +} + +void Screen::eraseChars(int n) +{ + if (n == 0) n = 1; // Default + int p = qMax(0,qMin(cuX+n-1,columns-1)); + clearImage(loc(cuX,cuY),loc(p,cuY),' '); +} + +void Screen::deleteChars(int n) +{ + Q_ASSERT( n >= 0 ); + + // always delete at least one char + if (n == 0) + n = 1; + + // if cursor is beyond the end of the line there is nothing to do + if ( cuX >= screenLines[cuY].count() ) + return; + + if ( cuX+n > screenLines[cuY].count() ) + n = screenLines[cuY].count() - cuX; + + Q_ASSERT( n >= 0 ); + Q_ASSERT( cuX+n <= screenLines[cuY].count() ); + + screenLines[cuY].remove(cuX,n); +} + +void Screen::insertChars(int n) +{ + if (n == 0) n = 1; // Default + + if ( screenLines[cuY].size() < cuX ) + screenLines[cuY].resize(cuX); + + screenLines[cuY].insert(cuX,n,' '); + + if ( screenLines[cuY].count() > columns ) + screenLines[cuY].resize(columns); +} + +void Screen::deleteLines(int n) +{ + if (n == 0) n = 1; // Default + scrollUp(cuY,n); +} + +void Screen::insertLines(int n) +{ + if (n == 0) n = 1; // Default + scrollDown(cuY,n); +} + +void Screen::setMode(int m) +{ + currentModes[m] = true; + switch(m) + { + case MODE_Origin : cuX = 0; cuY = _topMargin; break; //FIXME: home + } +} + +void Screen::resetMode(int m) +{ + currentModes[m] = false; + switch(m) + { + case MODE_Origin : cuX = 0; cuY = 0; break; //FIXME: home + } +} + +void Screen::saveMode(int m) +{ + savedModes[m] = currentModes[m]; +} + +void Screen::restoreMode(int m) +{ + currentModes[m] = savedModes[m]; +} + +bool Screen::getMode(int m) const +{ + return currentModes[m]; +} + +void Screen::saveCursor() +{ + savedState.cursorColumn = cuX; + savedState.cursorLine = cuY; + savedState.rendition = currentRendition; + savedState.foreground = currentForeground; + savedState.background = currentBackground; +} + +void Screen::restoreCursor() +{ + cuX = qMin(savedState.cursorColumn,columns-1); + cuY = qMin(savedState.cursorLine,lines-1); + currentRendition = savedState.rendition; + currentForeground = savedState.foreground; + currentBackground = savedState.background; + updateEffectiveRendition(); +} + +void Screen::resizeImage(int new_lines, int new_columns) +{ + if ((new_lines==lines) && (new_columns==columns)) return; + + if (cuY > new_lines-1) + { // attempt to preserve focus and lines + _bottomMargin = lines-1; //FIXME: margin lost + for (int i = 0; i < cuY-(new_lines-1); i++) + { + addHistLine(); scrollUp(0,1); + } + } + + // create new screen lines and copy from old to new + + ImageLine* newScreenLines = new ImageLine[new_lines+1]; + for (int i=0; i < qMin(lines-1,new_lines+1) ;i++) + newScreenLines[i]=screenLines[i]; + for (int i=lines;(i > 0) && (i<new_lines+1);i++) + newScreenLines[i].resize( new_columns ); + + lineProperties.resize(new_lines+1); + for (int i=lines;(i > 0) && (i<new_lines+1);i++) + lineProperties[i] = LINE_DEFAULT; + + clearSelection(); + + delete[] screenLines; + screenLines = newScreenLines; + + lines = new_lines; + columns = new_columns; + cuX = qMin(cuX,columns-1); + cuY = qMin(cuY,lines-1); + + // FIXME: try to keep values, evtl. + _topMargin=0; + _bottomMargin=lines-1; + initTabStops(); + clearSelection(); +} + +void Screen::setDefaultMargins() +{ + _topMargin = 0; + _bottomMargin = lines-1; +} + + +/* + Clarifying rendition here and in the display. + + currently, the display's color table is + 0 1 2 .. 9 10 .. 17 + dft_fg, dft_bg, dim 0..7, intensive 0..7 + + currentForeground, currentBackground contain values 0..8; + - 0 = default color + - 1..8 = ansi specified color + + re_fg, re_bg contain values 0..17 + due to the TerminalDisplay's color table + + rendition attributes are + + attr widget screen + -------------- ------ ------ + RE_UNDERLINE XX XX affects foreground only + RE_BLINK XX XX affects foreground only + RE_BOLD XX XX affects foreground only + RE_REVERSE -- XX + RE_TRANSPARENT XX -- affects background only + RE_INTENSIVE XX -- affects foreground only + + Note that RE_BOLD is used in both widget + and screen rendition. Since xterm/vt102 + is to poor to distinguish between bold + (which is a font attribute) and intensive + (which is a color attribute), we translate + this and RE_BOLD in falls eventually appart + into RE_BOLD and RE_INTENSIVE. + */ + +void Screen::reverseRendition(Character& p) const +{ + CharacterColor f = p.foregroundColor; + CharacterColor b = p.backgroundColor; + + p.foregroundColor = b; + p.backgroundColor = f; //p->r &= ~RE_TRANSPARENT; +} + +void Screen::updateEffectiveRendition() +{ + effectiveRendition = currentRendition; + if (currentRendition & RE_REVERSE) + { + effectiveForeground = currentBackground; + effectiveBackground = currentForeground; + } + else + { + effectiveForeground = currentForeground; + effectiveBackground = currentBackground; + } + + if (currentRendition & RE_BOLD) + effectiveForeground.toggleIntensive(); +} + +void Screen::copyFromHistory(Character* dest, int startLine, int count) const +{ + Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= history->getLines() ); + + for (int line = startLine; line < startLine + count; line++) + { + const int length = qMin(columns,history->getLineLen(line)); + const int destLineOffset = (line-startLine)*columns; + + history->getCells(line,0,length,dest + destLineOffset); + + for (int column = length; column < columns; column++) + dest[destLineOffset+column] = defaultChar; + + // invert selected text + if (selBegin !=-1) + { + for (int column = 0; column < columns; column++) + { + if (isSelected(column,line)) + { + reverseRendition(dest[destLineOffset + column]); + } + } + } + } +} + +void Screen::copyFromScreen(Character* dest , int startLine , int count) const +{ + Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= lines ); + + for (int line = startLine; line < (startLine+count) ; line++) + { + int srcLineStartIndex = line*columns; + int destLineStartIndex = (line-startLine)*columns; + + for (int column = 0; column < columns; column++) + { + int srcIndex = srcLineStartIndex + column; + int destIndex = destLineStartIndex + column; + + dest[destIndex] = screenLines[srcIndex/columns].value(srcIndex%columns,defaultChar); + + // invert selected text + if (selBegin != -1 && isSelected(column,line + history->getLines())) + reverseRendition(dest[destIndex]); + } + + } +} + +void Screen::getImage( Character* dest, int size, int startLine, int endLine ) const +{ + Q_ASSERT( startLine >= 0 ); + Q_ASSERT( endLine >= startLine && endLine < history->getLines() + lines ); + + const int mergedLines = endLine - startLine + 1; + + Q_ASSERT( size >= mergedLines * columns ); + Q_UNUSED( size ); + + const int linesInHistoryBuffer = qBound(0,history->getLines()-startLine,mergedLines); + const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer; + + // copy lines from history buffer + if (linesInHistoryBuffer > 0) + copyFromHistory(dest,startLine,linesInHistoryBuffer); + + // copy lines from screen buffer + if (linesInScreenBuffer > 0) + copyFromScreen(dest + linesInHistoryBuffer*columns, + startLine + linesInHistoryBuffer - history->getLines(), + linesInScreenBuffer); + + // invert display when in screen mode + if (getMode(MODE_Screen)) + { + for (int i = 0; i < mergedLines*columns; i++) + reverseRendition(dest[i]); // for reverse display + } + + // mark the character at the current cursor position + int cursorIndex = loc(cuX, cuY + linesInHistoryBuffer); + if(getMode(MODE_Cursor) && cursorIndex < columns*mergedLines) + dest[cursorIndex].rendition |= RE_CURSOR; +} + +QVector<LineProperty> Screen::getLineProperties( int startLine , int endLine ) const +{ + Q_ASSERT( startLine >= 0 ); + Q_ASSERT( endLine >= startLine && endLine < history->getLines() + lines ); + + const int mergedLines = endLine-startLine+1; + const int linesInHistory = qBound(0,history->getLines()-startLine,mergedLines); + const int linesInScreen = mergedLines - linesInHistory; + + QVector<LineProperty> result(mergedLines); + int index = 0; + + // copy properties for lines in history + for (int line = startLine; line < startLine + linesInHistory; line++) + { + //TODO Support for line properties other than wrapped lines + if (history->isWrappedLine(line)) + { + result[index] = (LineProperty)(result[index] | LINE_WRAPPED); + } + index++; + } + + // copy properties for lines in screen buffer + const int firstScreenLine = startLine + linesInHistory - history->getLines(); + for (int line = firstScreenLine; line < firstScreenLine+linesInScreen; line++) + { + result[index]=lineProperties[line]; + index++; + } + + return result; +} + +void Screen::reset(bool clearScreen) +{ + setMode(MODE_Wrap ); saveMode(MODE_Wrap ); // wrap at end of margin + resetMode(MODE_Origin); saveMode(MODE_Origin); // position refere to [1,1] + resetMode(MODE_Insert); saveMode(MODE_Insert); // overstroke + setMode(MODE_Cursor); // cursor visible + resetMode(MODE_Screen); // screen not inverse + resetMode(MODE_NewLine); + + _topMargin=0; + _bottomMargin=lines-1; + + setDefaultRendition(); + saveCursor(); + + if ( clearScreen ) + clear(); +} + +void Screen::clear() +{ + clearEntireScreen(); + home(); +} + +void Screen::backspace() +{ + cuX = qMin(columns-1,cuX); // nowrap! + cuX = qMax(0,cuX-1); + + if (screenLines[cuY].size() < cuX+1) + screenLines[cuY].resize(cuX+1); + + if (BS_CLEARS) + screenLines[cuY][cuX].character = ' '; +} + +void Screen::tab(int n) +{ + // note that TAB is a format effector (does not write ' '); + if (n == 0) n = 1; + while((n > 0) && (cuX < columns-1)) + { + cursorRight(1); + while((cuX < columns-1) && !tabStops[cuX]) + cursorRight(1); + n--; + } +} + +void Screen::backtab(int n) +{ + // note that TAB is a format effector (does not write ' '); + if (n == 0) n = 1; + while((n > 0) && (cuX > 0)) + { + cursorLeft(1); while((cuX > 0) && !tabStops[cuX]) cursorLeft(1); + n--; + } +} + +void Screen::clearTabStops() +{ + for (int i = 0; i < columns; i++) tabStops[i] = false; +} + +void Screen::changeTabStop(bool set) +{ + if (cuX >= columns) return; + tabStops[cuX] = set; +} + +void Screen::initTabStops() +{ + tabStops.resize(columns); + + // Arrg! The 1st tabstop has to be one longer than the other. + // i.e. the kids start counting from 0 instead of 1. + // Other programs might behave correctly. Be aware. + for (int i = 0; i < columns; i++) + tabStops[i] = (i%8 == 0 && i != 0); +} + +void Screen::newLine() +{ + if (getMode(MODE_NewLine)) + toStartOfLine(); + index(); +} + +void Screen::checkSelection(int from, int to) +{ + if (selBegin == -1) + return; + int scr_TL = loc(0, history->getLines()); + //Clear entire selection if it overlaps region [from, to] + if ( (selBottomRight >= (from+scr_TL)) && (selTopLeft <= (to+scr_TL)) ) + clearSelection(); +} + +void Screen::displayCharacter(unsigned short c) +{ + // Note that VT100 does wrapping BEFORE putting the character. + // This has impact on the assumption of valid cursor positions. + // We indicate the fact that a newline has to be triggered by + // putting the cursor one right to the last column of the screen. + + int w = konsole_wcwidth(c); + if (w <= 0) + return; + + if (cuX+w > columns) { + if (getMode(MODE_Wrap)) { + lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | LINE_WRAPPED); + nextLine(); + } + else + cuX = columns-w; + } + + // ensure current line vector has enough elements + int size = screenLines[cuY].size(); + if (size < cuX+w) + { + screenLines[cuY].resize(cuX+w); + } + + if (getMode(MODE_Insert)) insertChars(w); + + lastPos = loc(cuX,cuY); + + // check if selection is still valid. + checkSelection(lastPos, lastPos); + + Character& currentChar = screenLines[cuY][cuX]; + + currentChar.character = c; + currentChar.foregroundColor = effectiveForeground; + currentChar.backgroundColor = effectiveBackground; + currentChar.rendition = effectiveRendition; + + int i = 0; + int newCursorX = cuX + w--; + while(w) + { + i++; + + if ( screenLines[cuY].size() < cuX + i + 1 ) + screenLines[cuY].resize(cuX+i+1); + + Character& ch = screenLines[cuY][cuX + i]; + ch.character = 0; + ch.foregroundColor = effectiveForeground; + ch.backgroundColor = effectiveBackground; + ch.rendition = effectiveRendition; + + w--; + } + cuX = newCursorX; +} + +void Screen::compose(const QString& /*compose*/) +{ + Q_ASSERT( 0 /*Not implemented yet*/ ); + + /* if (lastPos == -1) + return; + + QChar c(image[lastPos].character); + compose.prepend(c); + //compose.compose(); ### FIXME! + image[lastPos].character = compose[0].unicode();*/ +} + +int Screen::scrolledLines() const +{ + return _scrolledLines; +} +int Screen::droppedLines() const +{ + return _droppedLines; +} +void Screen::resetDroppedLines() +{ + _droppedLines = 0; +} +void Screen::resetScrolledLines() +{ + _scrolledLines = 0; +} + +void Screen::scrollUp(int n) +{ + if (n == 0) n = 1; // Default + if (_topMargin == 0) addHistLine(); // history.history + scrollUp(_topMargin, n); +} + +QRect Screen::lastScrolledRegion() const +{ + return _lastScrolledRegion; +} + +void Screen::scrollUp(int from, int n) +{ + if (n <= 0 || from + n > _bottomMargin) return; + + _scrolledLines -= n; + _lastScrolledRegion = QRect(0,_topMargin,columns-1,(_bottomMargin-_topMargin)); + + //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. + moveImage(loc(0,from),loc(0,from+n),loc(columns-1,_bottomMargin)); + clearImage(loc(0,_bottomMargin-n+1),loc(columns-1,_bottomMargin),' '); +} + +void Screen::scrollDown(int n) +{ + if (n == 0) n = 1; // Default + scrollDown(_topMargin, n); +} + +void Screen::scrollDown(int from, int n) +{ + _scrolledLines += n; + + //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. + if (n <= 0) + return; + if (from > _bottomMargin) + return; + if (from + n > _bottomMargin) + n = _bottomMargin - from; + moveImage(loc(0,from+n),loc(0,from),loc(columns-1,_bottomMargin-n)); + clearImage(loc(0,from),loc(columns-1,from+n-1),' '); +} + +void Screen::setCursorYX(int y, int x) +{ + setCursorY(y); setCursorX(x); +} + +void Screen::setCursorX(int x) +{ + if (x == 0) x = 1; // Default + x -= 1; // Adjust + cuX = qMax(0,qMin(columns-1, x)); +} + +void Screen::setCursorY(int y) +{ + if (y == 0) y = 1; // Default + y -= 1; // Adjust + cuY = qMax(0,qMin(lines -1, y + (getMode(MODE_Origin) ? _topMargin : 0) )); +} + +void Screen::home() +{ + cuX = 0; + cuY = 0; +} + +void Screen::toStartOfLine() +{ + cuX = 0; +} + +int Screen::getCursorX() const +{ + return cuX; +} + +int Screen::getCursorY() const +{ + return cuY; +} + +void Screen::clearImage(int loca, int loce, char c) +{ + int scr_TL=loc(0,history->getLines()); + //FIXME: check positions + + //Clear entire selection if it overlaps region to be moved... + if ( (selBottomRight > (loca+scr_TL) )&&(selTopLeft < (loce+scr_TL)) ) + { + clearSelection(); + } + + int topLine = loca/columns; + int bottomLine = loce/columns; + + Character clearCh(c,currentForeground,currentBackground,DEFAULT_RENDITION); + + //if the character being used to clear the area is the same as the + //default character, the affected lines can simply be shrunk. + bool isDefaultCh = (clearCh == Character()); + + for (int y=topLine;y<=bottomLine;y++) + { + lineProperties[y] = 0; + + int endCol = ( y == bottomLine) ? loce%columns : columns-1; + int startCol = ( y == topLine ) ? loca%columns : 0; + + QVector<Character>& line = screenLines[y]; + + if ( isDefaultCh && endCol == columns-1 ) + { + line.resize(startCol); + } + else + { + if (line.size() < endCol + 1) + line.resize(endCol+1); + + Character* data = line.data(); + for (int i=startCol;i<=endCol;i++) + data[i]=clearCh; + } + } +} + +void Screen::moveImage(int dest, int sourceBegin, int sourceEnd) +{ + Q_ASSERT( sourceBegin <= sourceEnd ); + + int lines=(sourceEnd-sourceBegin)/columns; + + //move screen image and line properties: + //the source and destination areas of the image may overlap, + //so it matters that we do the copy in the right order - + //forwards if dest < sourceBegin or backwards otherwise. + //(search the web for 'memmove implementation' for details) + if (dest < sourceBegin) + { + for (int i=0;i<=lines;i++) + { + screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ]; + lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i]; + } + } + else + { + for (int i=lines;i>=0;i--) + { + screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ]; + lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i]; + } + } + + if (lastPos != -1) + { + int diff = dest - sourceBegin; // Scroll by this amount + lastPos += diff; + if ((lastPos < 0) || (lastPos >= (lines*columns))) + lastPos = -1; + } + + // Adjust selection to follow scroll. + if (selBegin != -1) + { + bool beginIsTL = (selBegin == selTopLeft); + int diff = dest - sourceBegin; // Scroll by this amount + int scr_TL=loc(0,history->getLines()); + int srca = sourceBegin+scr_TL; // Translate index from screen to global + int srce = sourceEnd+scr_TL; // Translate index from screen to global + int desta = srca+diff; + int deste = srce+diff; + + if ((selTopLeft >= srca) && (selTopLeft <= srce)) + selTopLeft += diff; + else if ((selTopLeft >= desta) && (selTopLeft <= deste)) + selBottomRight = -1; // Clear selection (see below) + + if ((selBottomRight >= srca) && (selBottomRight <= srce)) + selBottomRight += diff; + else if ((selBottomRight >= desta) && (selBottomRight <= deste)) + selBottomRight = -1; // Clear selection (see below) + + if (selBottomRight < 0) + { + clearSelection(); + } + else + { + if (selTopLeft < 0) + selTopLeft = 0; + } + + if (beginIsTL) + selBegin = selTopLeft; + else + selBegin = selBottomRight; + } +} + +void Screen::clearToEndOfScreen() +{ + clearImage(loc(cuX,cuY),loc(columns-1,lines-1),' '); +} + +void Screen::clearToBeginOfScreen() +{ + clearImage(loc(0,0),loc(cuX,cuY),' '); +} + +void Screen::clearEntireScreen() +{ + // Add entire screen to history + for (int i = 0; i < (lines-1); i++) + { + addHistLine(); scrollUp(0,1); + } + + clearImage(loc(0,0),loc(columns-1,lines-1),' '); +} + +/*! fill screen with 'E' + This is to aid screen alignment + */ + +void Screen::helpAlign() +{ + clearImage(loc(0,0),loc(columns-1,lines-1),'E'); +} + +void Screen::clearToEndOfLine() +{ + clearImage(loc(cuX,cuY),loc(columns-1,cuY),' '); +} + +void Screen::clearToBeginOfLine() +{ + clearImage(loc(0,cuY),loc(cuX,cuY),' '); +} + +void Screen::clearEntireLine() +{ + clearImage(loc(0,cuY),loc(columns-1,cuY),' '); +} + +void Screen::setRendition(int re) +{ + currentRendition |= re; + updateEffectiveRendition(); +} + +void Screen::resetRendition(int re) +{ + currentRendition &= ~re; + updateEffectiveRendition(); +} + +void Screen::setDefaultRendition() +{ + setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR); + setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR); + currentRendition = DEFAULT_RENDITION; + updateEffectiveRendition(); +} + +void Screen::setForeColor(int space, int color) +{ + currentForeground = CharacterColor(space, color); + + if ( currentForeground.isValid() ) + updateEffectiveRendition(); + else + setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR); +} + +void Screen::setBackColor(int space, int color) +{ + currentBackground = CharacterColor(space, color); + + if ( currentBackground.isValid() ) + updateEffectiveRendition(); + else + setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR); +} + +void Screen::clearSelection() +{ + selBottomRight = -1; + selTopLeft = -1; + selBegin = -1; +} + +void Screen::getSelectionStart(int& column , int& line) const +{ + if ( selTopLeft != -1 ) + { + column = selTopLeft % columns; + line = selTopLeft / columns; + } + else + { + column = cuX + getHistLines(); + line = cuY + getHistLines(); + } +} +void Screen::getSelectionEnd(int& column , int& line) const +{ + if ( selBottomRight != -1 ) + { + column = selBottomRight % columns; + line = selBottomRight / columns; + } + else + { + column = cuX + getHistLines(); + line = cuY + getHistLines(); + } +} +void Screen::setSelectionStart(const int x, const int y, const bool mode) +{ + selBegin = loc(x,y); + /* FIXME, HACK to correct for x too far to the right... */ + if (x == columns) selBegin--; + + selBottomRight = selBegin; + selTopLeft = selBegin; + blockSelectionMode = mode; +} + +void Screen::setSelectionEnd( const int x, const int y) +{ + if (selBegin == -1) + return; + + int endPos = loc(x,y); + + if (endPos < selBegin) + { + selTopLeft = endPos; + selBottomRight = selBegin; + } + else + { + /* FIXME, HACK to correct for x too far to the right... */ + if (x == columns) + endPos--; + + selTopLeft = selBegin; + selBottomRight = endPos; + } + + // Normalize the selection in column mode + if (blockSelectionMode) + { + int topRow = selTopLeft / columns; + int topColumn = selTopLeft % columns; + int bottomRow = selBottomRight / columns; + int bottomColumn = selBottomRight % columns; + + selTopLeft = loc(qMin(topColumn,bottomColumn),topRow); + selBottomRight = loc(qMax(topColumn,bottomColumn),bottomRow); + } +} + +bool Screen::isSelected( const int x,const int y) const +{ + bool columnInSelection = true; + if (blockSelectionMode) + { + columnInSelection = x >= (selTopLeft % columns) && + x <= (selBottomRight % columns); + } + + int pos = loc(x,y); + return pos >= selTopLeft && pos <= selBottomRight && columnInSelection; +} + +QString Screen::selectedText(bool preserveLineBreaks) const +{ + QString result; + QTextStream stream(&result, QIODevice::ReadWrite); + + PlainTextDecoder decoder; + decoder.begin(&stream); + writeSelectionToStream(&decoder , preserveLineBreaks); + decoder.end(); + + return result; +} + +bool Screen::isSelectionValid() const +{ + return selTopLeft >= 0 && selBottomRight >= 0; +} + +void Screen::writeSelectionToStream(TerminalCharacterDecoder* decoder , + bool preserveLineBreaks) const +{ + if (!isSelectionValid()) + return; + writeToStream(decoder,selTopLeft,selBottomRight,preserveLineBreaks); +} + +void Screen::writeToStream(TerminalCharacterDecoder* decoder, + int startIndex, int endIndex, + bool preserveLineBreaks) const +{ + int top = startIndex / columns; + int left = startIndex % columns; + + int bottom = endIndex / columns; + int right = endIndex % columns; + + Q_ASSERT( top >= 0 && left >= 0 && bottom >= 0 && right >= 0 ); + + for (int y=top;y<=bottom;y++) + { + int start = 0; + if ( y == top || blockSelectionMode ) start = left; + + int count = -1; + if ( y == bottom || blockSelectionMode ) count = right - start + 1; + + const bool appendNewLine = ( y != bottom ); + int copied = copyLineToStream( y, + start, + count, + decoder, + appendNewLine, + preserveLineBreaks ); + + // if the selection goes beyond the end of the last line then + // append a new line character. + // + // this makes it possible to 'select' a trailing new line character after + // the text on a line. + if ( y == bottom && + copied < count ) + { + Character newLineChar('\n'); + decoder->decodeLine(&newLineChar,1,0); + } + } +} + +int Screen::copyLineToStream(int line , + int start, + int count, + TerminalCharacterDecoder* decoder, + bool appendNewLine, + bool preserveLineBreaks) const +{ + //buffer to hold characters for decoding + //the buffer is static to avoid initialising every + //element on each call to copyLineToStream + //(which is unnecessary since all elements will be overwritten anyway) + static const int MAX_CHARS = 1024; + static Character characterBuffer[MAX_CHARS]; + + assert( count < MAX_CHARS ); + + LineProperty currentLineProperties = 0; + + //determine if the line is in the history buffer or the screen image + if (line < history->getLines()) + { + const int lineLength = history->getLineLen(line); + + // ensure that start position is before end of line + start = qMin(start,qMax(0,lineLength-1)); + + // retrieve line from history buffer. It is assumed + // that the history buffer does not store trailing white space + // at the end of the line, so it does not need to be trimmed here + if (count == -1) + { + count = lineLength-start; + } + else + { + count = qMin(start+count,lineLength)-start; + } + + // safety checks + assert( start >= 0 ); + assert( count >= 0 ); + assert( (start+count) <= history->getLineLen(line) ); + + history->getCells(line,start,count,characterBuffer); + + if ( history->isWrappedLine(line) ) + currentLineProperties |= LINE_WRAPPED; + } + else + { + if ( count == -1 ) + count = columns - start; + + assert( count >= 0 ); + + const int screenLine = line-history->getLines(); + + Character* data = screenLines[screenLine].data(); + int length = screenLines[screenLine].count(); + + //retrieve line from screen image + for (int i=start;i < qMin(start+count,length);i++) + { + characterBuffer[i-start] = data[i]; + } + + // count cannot be any greater than length + count = qBound(0,count,length-start); + + Q_ASSERT( screenLine < lineProperties.count() ); + currentLineProperties |= lineProperties[screenLine]; + } + + // add new line character at end + const bool omitLineBreak = (currentLineProperties & LINE_WRAPPED) || + !preserveLineBreaks; + + if ( !omitLineBreak && appendNewLine && (count+1 < MAX_CHARS) ) + { + characterBuffer[count] = '\n'; + count++; + } + + //decode line and write to text stream + decoder->decodeLine( (Character*) characterBuffer , + count, currentLineProperties ); + + return count; +} + +void Screen::writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const +{ + writeToStream(decoder,loc(0,fromLine),loc(columns-1,toLine)); +} + +void Screen::addHistLine() +{ + // add line to history buffer + // we have to take care about scrolling, too... + + if (hasScroll()) + { + int oldHistLines = history->getLines(); + + history->addCellsVector(screenLines[0]); + history->addLine( lineProperties[0] & LINE_WRAPPED ); + + int newHistLines = history->getLines(); + + bool beginIsTL = (selBegin == selTopLeft); + + // If the history is full, increment the count + // of dropped lines + if ( newHistLines == oldHistLines ) + _droppedLines++; + + // Adjust selection for the new point of reference + if (newHistLines > oldHistLines) + { + if (selBegin != -1) + { + selTopLeft += columns; + selBottomRight += columns; + } + } + + if (selBegin != -1) + { + // Scroll selection in history up + int top_BR = loc(0, 1+newHistLines); + + if (selTopLeft < top_BR) + selTopLeft -= columns; + + if (selBottomRight < top_BR) + selBottomRight -= columns; + + if (selBottomRight < 0) + clearSelection(); + else + { + if (selTopLeft < 0) + selTopLeft = 0; + } + + if (beginIsTL) + selBegin = selTopLeft; + else + selBegin = selBottomRight; + } + } + +} + +int Screen::getHistLines() const +{ + return history->getLines(); +} + +void Screen::setScroll(const HistoryType& t , bool copyPreviousScroll) +{ + clearSelection(); + + if ( copyPreviousScroll ) + history = t.scroll(history); + else + { + HistoryScroll* oldScroll = history; + history = t.scroll(0); + delete oldScroll; + } +} + +bool Screen::hasScroll() const +{ + return history->hasScroll(); +} + +const HistoryType& Screen::getScroll() const +{ + return history->getType(); +} + +void Screen::setLineProperty(LineProperty property , bool enable) +{ + if ( enable ) + lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | property); + else + lineProperties[cuY] = (LineProperty)(lineProperties[cuY] & ~property); +} +void Screen::fillWithDefaultChar(Character* dest, int count) +{ + for (int i=0;i<count;i++) + dest[i] = defaultChar; +}
new file mode 100644 --- /dev/null +++ b/gui/src/Screen.h @@ -0,0 +1,670 @@ +/* + This file is part of Konsole, KDE's terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef SCREEN_H +#define SCREEN_H + +// Qt +#include <QtCore/QRect> +#include <QtCore/QTextStream> +#include <QtCore/QVarLengthArray> + +// Konsole +#include "Character.h" +#include "History.h" + +#define MODE_Origin 0 +#define MODE_Wrap 1 +#define MODE_Insert 2 +#define MODE_Screen 3 +#define MODE_Cursor 4 +#define MODE_NewLine 5 +#define MODES_SCREEN 6 + +class TerminalCharacterDecoder; + +/** + \brief An image of characters with associated attributes. + + The terminal emulation ( Emulation ) receives a serial stream of + characters from the program currently running in the terminal. + From this stream it creates an image of characters which is ultimately + rendered by the display widget ( TerminalDisplay ). Some types of emulation + may have more than one screen image. + + getImage() is used to retrieve the currently visible image + which is then used by the display widget to draw the output from the + terminal. + + The number of lines of output history which are kept in addition to the current + screen image depends on the history scroll being used to store the output. + The scroll is specified using setScroll() + The output history can be retrieved using writeToStream() + + The screen image has a selection associated with it, specified using + setSelectionStart() and setSelectionEnd(). The selected text can be retrieved + using selectedText(). When getImage() is used to retrieve the visible image, + characters which are part of the selection have their colours inverted. +*/ +class Screen +{ +public: + /** Construct a new screen image of size @p lines by @p columns. */ + Screen(int lines, int columns); + ~Screen(); + + // VT100/2 Operations + // Cursor Movement + + /** + * Move the cursor up by @p n lines. The cursor will stop at the + * top margin. + */ + void cursorUp(int n); + /** + * Move the cursor down by @p n lines. The cursor will stop at the + * bottom margin. + */ + void cursorDown(int n); + /** + * Move the cursor to the left by @p n columns. + * The cursor will stop at the first column. + */ + void cursorLeft(int n); + /** + * Move the cursor to the right by @p n columns. + * The cursor will stop at the right-most column. + */ + void cursorRight(int n); + /** Position the cursor on line @p y. */ + void setCursorY(int y); + /** Position the cursor at column @p x. */ + void setCursorX(int x); + /** Position the cursor at line @p y, column @p x. */ + void setCursorYX(int y, int x); + /** + * Sets the margins for scrolling the screen. + * + * @param topLine The top line of the new scrolling margin. + * @param bottomLine The bottom line of the new scrolling margin. + */ + void setMargins(int topLine , int bottomLine); + /** Returns the top line of the scrolling region. */ + int topMargin() const; + /** Returns the bottom line of the scrolling region. */ + int bottomMargin() const; + + /** + * Resets the scrolling margins back to the top and bottom lines + * of the screen. + */ + void setDefaultMargins(); + + /** + * Moves the cursor down one line, if the MODE_NewLine mode + * flag is enabled then the cursor is returned to the leftmost + * column first. + * + * Equivalent to NextLine() if the MODE_NewLine flag is set + * or index() otherwise. + */ + void newLine(); + /** + * Moves the cursor down one line and positions it at the beginning + * of the line. Equivalent to calling Return() followed by index() + */ + void nextLine(); + + /** + * Move the cursor down one line. If the cursor is on the bottom + * line of the scrolling region (as returned by bottomMargin()) the + * scrolling region is scrolled up by one line instead. + */ + void index(); + /** + * Move the cursor up one line. If the cursor is on the top line + * of the scrolling region (as returned by topMargin()) the scrolling + * region is scrolled down by one line instead. + */ + void reverseIndex(); + + /** + * Scroll the scrolling region of the screen up by @p n lines. + * The scrolling region is initially the whole screen, but can be changed + * using setMargins() + */ + void scrollUp(int n); + /** + * Scroll the scrolling region of the screen down by @p n lines. + * The scrolling region is initially the whole screen, but can be changed + * using setMargins() + */ + void scrollDown(int n); + /** + * Moves the cursor to the beginning of the current line. + * Equivalent to setCursorX(0) + */ + void toStartOfLine(); + /** + * Moves the cursor one column to the left and erases the character + * at the new cursor position. + */ + void backspace(); + /** Moves the cursor @p n tab-stops to the right. */ + void tab(int n = 1); + /** Moves the cursor @p n tab-stops to the left. */ + void backtab(int n); + + // Editing + + /** + * Erase @p n characters beginning from the current cursor position. + * This is equivalent to over-writing @p n characters starting with the current + * cursor position with spaces. + * If @p n is 0 then one character is erased. + */ + void eraseChars(int n); + /** + * Delete @p n characters beginning from the current cursor position. + * If @p n is 0 then one character is deleted. + */ + void deleteChars(int n); + /** + * Insert @p n blank characters beginning from the current cursor position. + * The position of the cursor is not altered. + * If @p n is 0 then one character is inserted. + */ + void insertChars(int n); + /** + * Removes @p n lines beginning from the current cursor position. + * The position of the cursor is not altered. + * If @p n is 0 then one line is removed. + */ + void deleteLines(int n); + /** + * Inserts @p lines beginning from the current cursor position. + * The position of the cursor is not altered. + * If @p n is 0 then one line is inserted. + */ + void insertLines(int n); + /** Clears all the tab stops. */ + void clearTabStops(); + /** Sets or removes a tab stop at the cursor's current column. */ + void changeTabStop(bool set); + + /** Resets (clears) the specified screen @p mode. */ + void resetMode(int mode); + /** Sets (enables) the specified screen @p mode. */ + void setMode(int mode); + /** + * Saves the state of the specified screen @p mode. It can be restored + * using restoreMode() + */ + void saveMode(int mode); + /** Restores the state of a screen @p mode saved by calling saveMode() */ + void restoreMode(int mode); + /** Returns whether the specified screen @p mode is enabled or not .*/ + bool getMode(int mode) const; + + /** + * Saves the current position and appearance (text color and style) of the cursor. + * It can be restored by calling restoreCursor() + */ + void saveCursor(); + /** Restores the position and appearance of the cursor. See saveCursor() */ + void restoreCursor(); + + /** Clear the whole screen, moving the current screen contents into the history first. */ + void clearEntireScreen(); + /** + * Clear the area of the screen from the current cursor position to the end of + * the screen. + */ + void clearToEndOfScreen(); + /** + * Clear the area of the screen from the current cursor position to the start + * of the screen. + */ + void clearToBeginOfScreen(); + /** Clears the whole of the line on which the cursor is currently positioned. */ + void clearEntireLine(); + /** Clears from the current cursor position to the end of the line. */ + void clearToEndOfLine(); + /** Clears from the current cursor position to the beginning of the line. */ + void clearToBeginOfLine(); + + /** Fills the entire screen with the letter 'E' */ + void helpAlign(); + + /** + * Enables the given @p rendition flag. Rendition flags control the appearance + * of characters on the screen. + * + * @see Character::rendition + */ + void setRendition(int rendition); + /** + * Disables the given @p rendition flag. Rendition flags control the appearance + * of characters on the screen. + * + * @see Character::rendition + */ + void resetRendition(int rendition); + + /** + * Sets the cursor's foreground color. + * @param space The color space used by the @p color argument + * @param color The new foreground color. The meaning of this depends on + * the color @p space used. + * + * @see CharacterColor + */ + void setForeColor(int space, int color); + /** + * Sets the cursor's background color. + * @param space The color space used by the @p color argumnet. + * @param color The new background color. The meaning of this depends on + * the color @p space used. + * + * @see CharacterColor + */ + void setBackColor(int space, int color); + /** + * Resets the cursor's color back to the default and sets the + * character's rendition flags back to the default settings. + */ + void setDefaultRendition(); + + /** Returns the column which the cursor is positioned at. */ + int getCursorX() const; + /** Returns the line which the cursor is positioned on. */ + int getCursorY() const; + + /** Clear the entire screen and move the cursor to the home position. + * Equivalent to calling clearEntireScreen() followed by home(). + */ + void clear(); + /** + * Sets the position of the cursor to the 'home' position at the top-left + * corner of the screen (0,0) + */ + void home(); + /** + * Resets the state of the screen. This resets the various screen modes + * back to their default states. The cursor style and colors are reset + * (as if setDefaultRendition() had been called) + * + * <ul> + * <li>Line wrapping is enabled.</li> + * <li>Origin mode is disabled.</li> + * <li>Insert mode is disabled.</li> + * <li>Cursor mode is enabled. TODO Document me</li> + * <li>Screen mode is disabled. TODO Document me</li> + * <li>New line mode is disabled. TODO Document me</li> + * </ul> + * + * If @p clearScreen is true then the screen contents are erased entirely, + * otherwise they are unaltered. + */ + void reset(bool clearScreen = true); + + /** + * Displays a new character at the current cursor position. + * + * If the cursor is currently positioned at the right-edge of the screen and + * line wrapping is enabled then the character is added at the start of a new + * line below the current one. + * + * If the MODE_Insert screen mode is currently enabled then the character + * is inserted at the current cursor position, otherwise it will replace the + * character already at the current cursor position. + */ + void displayCharacter(unsigned short c); + + // Do composition with last shown character FIXME: Not implemented yet for KDE 4 + void compose(const QString& compose); + + /** + * Resizes the image to a new fixed size of @p new_lines by @p new_columns. + * In the case that @p new_columns is smaller than the current number of columns, + * existing lines are not truncated. This prevents characters from being lost + * if the terminal display is resized smaller and then larger again. + * + * The top and bottom margins are reset to the top and bottom of the new + * screen size. Tab stops are also reset and the current selection is + * cleared. + */ + void resizeImage(int new_lines, int new_columns); + + /** + * Returns the current screen image. + * The result is an array of Characters of size [getLines()][getColumns()] which + * must be freed by the caller after use. + * + * @param dest Buffer to copy the characters into + * @param size Size of @p dest in Characters + * @param startLine Index of first line to copy + * @param endLine Index of last line to copy + */ + void getImage( Character* dest , int size , int startLine , int endLine ) const; + + /** + * Returns the additional attributes associated with lines in the image. + * The most important attribute is LINE_WRAPPED which specifies that the + * line is wrapped, + * other attributes control the size of characters in the line. + */ + QVector<LineProperty> getLineProperties( int startLine , int endLine ) const; + + + /** Return the number of lines. */ + int getLines() const + { return lines; } + /** Return the number of columns. */ + int getColumns() const + { return columns; } + /** Return the number of lines in the history buffer. */ + int getHistLines() const; + /** + * Sets the type of storage used to keep lines in the history. + * If @p copyPreviousScroll is true then the contents of the previous + * history buffer are copied into the new scroll. + */ + void setScroll(const HistoryType& , bool copyPreviousScroll = true); + /** Returns the type of storage used to keep lines in the history. */ + const HistoryType& getScroll() const; + /** + * Returns true if this screen keeps lines that are scrolled off the screen + * in a history buffer. + */ + bool hasScroll() const; + + /** + * Sets the start of the selection. + * + * @param column The column index of the first character in the selection. + * @param line The line index of the first character in the selection. + * @param blockSelectionMode True if the selection is in column mode. + */ + void setSelectionStart(const int column, const int line, const bool blockSelectionMode); + + /** + * Sets the end of the current selection. + * + * @param column The column index of the last character in the selection. + * @param line The line index of the last character in the selection. + */ + void setSelectionEnd(const int column, const int line); + + /** + * Retrieves the start of the selection or the cursor position if there + * is no selection. + */ + void getSelectionStart(int& column , int& line) const; + + /** + * Retrieves the end of the selection or the cursor position if there + * is no selection. + */ + void getSelectionEnd(int& column , int& line) const; + + /** Clears the current selection */ + void clearSelection(); + + /** + * Returns true if the character at (@p column, @p line) is part of the + * current selection. + */ + bool isSelected(const int column,const int line) const; + + /** + * Convenience method. Returns the currently selected text. + * @param preserveLineBreaks Specifies whether new line characters should + * be inserted into the returned text at the end of each terminal line. + */ + QString selectedText(bool preserveLineBreaks) const; + + /** + * Copies part of the output to a stream. + * + * @param decoder A decoder which converts terminal characters into text + * @param fromLine The first line in the history to retrieve + * @param toLine The last line in the history to retrieve + */ + void writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const; + + /** + * Copies the selected characters, set using @see setSelBeginXY and @see setSelExtentXY + * into a stream. + * + * @param decoder A decoder which converts terminal characters into text. + * PlainTextDecoder is the most commonly used decoder which converts characters + * into plain text with no formatting. + * @param preserveLineBreaks Specifies whether new line characters should + * be inserted into the returned text at the end of each terminal line. + */ + void writeSelectionToStream(TerminalCharacterDecoder* decoder , bool + preserveLineBreaks = true) const; + + /** + * Checks if the text between from and to is inside the current + * selection. If this is the case, the selection is cleared. The + * from and to are coordinates in the current viewable window. + * The loc(x,y) macro can be used to generate these values from a + * column,line pair. + * + * @param from The start of the area to check. + * @param to The end of the area to check + */ + void checkSelection(int from, int to); + + /** + * Sets or clears an attribute of the current line. + * + * @param property The attribute to set or clear + * Possible properties are: + * LINE_WRAPPED: Specifies that the line is wrapped. + * LINE_DOUBLEWIDTH: Specifies that the characters in the current line + * should be double the normal width. + * LINE_DOUBLEHEIGHT:Specifies that the characters in the current line + * should be double the normal height. + * Double-height lines are formed of two lines containing the same characters, + * with both having the LINE_DOUBLEHEIGHT attribute. + * This allows other parts of the code to work on the + * assumption that all lines are the same height. + * + * @param enable true to apply the attribute to the current line or false to remove it + */ + void setLineProperty(LineProperty property , bool enable); + + /** + * Returns the number of lines that the image has been scrolled up or down by, + * since the last call to resetScrolledLines(). + * + * a positive return value indicates that the image has been scrolled up, + * a negative return value indicates that the image has been scrolled down. + */ + int scrolledLines() const; + + /** + * Returns the region of the image which was last scrolled. + * + * This is the area of the image from the top margin to the + * bottom margin when the last scroll occurred. + */ + QRect lastScrolledRegion() const; + + /** + * Resets the count of the number of lines that the image has been scrolled up or down by, + * see scrolledLines() + */ + void resetScrolledLines(); + + /** + * Returns the number of lines of output which have been + * dropped from the history since the last call + * to resetDroppedLines() + * + * If the history is not unlimited then it will drop + * the oldest lines of output if new lines are added when + * it is full. + */ + int droppedLines() const; + + /** + * Resets the count of the number of lines dropped from + * the history. + */ + void resetDroppedLines(); + + /** + * Fills the buffer @p dest with @p count instances of the default (ie. blank) + * Character style. + */ + static void fillWithDefaultChar(Character* dest, int count); + +private: + + //copies a line of text from the screen or history into a stream using a + //specified character decoder. Returns the number of lines actually copied, + //which may be less than 'count' if (start+count) is more than the number of characters on + //the line + // + //line - the line number to copy, from 0 (the earliest line in the history) up to + // history->getLines() + lines - 1 + //start - the first column on the line to copy + //count - the number of characters on the line to copy + //decoder - a decoder which converts terminal characters (an Character array) into text + //appendNewLine - if true a new line character (\n) is appended to the end of the line + int copyLineToStream(int line, + int start, + int count, + TerminalCharacterDecoder* decoder, + bool appendNewLine, + bool preserveLineBreaks) const; + + //fills a section of the screen image with the character 'c' + //the parameters are specified as offsets from the start of the screen image. + //the loc(x,y) macro can be used to generate these values from a column,line pair. + void clearImage(int loca, int loce, char c); + + //move screen image between 'sourceBegin' and 'sourceEnd' to 'dest'. + //the parameters are specified as offsets from the start of the screen image. + //the loc(x,y) macro can be used to generate these values from a column,line pair. + // + //NOTE: moveImage() can only move whole lines + void moveImage(int dest, int sourceBegin, int sourceEnd); + // scroll up 'i' lines in current region, clearing the bottom 'i' lines + void scrollUp(int from, int i); + // scroll down 'i' lines in current region, clearing the top 'i' lines + void scrollDown(int from, int i); + + void addHistLine(); + + void initTabStops(); + + void updateEffectiveRendition(); + void reverseRendition(Character& p) const; + + bool isSelectionValid() const; + // copies text from 'startIndex' to 'endIndex' to a stream + // startIndex and endIndex are positions generated using the loc(x,y) macro + void writeToStream(TerminalCharacterDecoder* decoder, int startIndex, + int endIndex, bool preserveLineBreaks = true) const; + // copies 'count' lines from the screen buffer into 'dest', + // starting from 'startLine', where 0 is the first line in the screen buffer + void copyFromScreen(Character* dest, int startLine, int count) const; + // copies 'count' lines from the history buffer into 'dest', + // starting from 'startLine', where 0 is the first line in the history + void copyFromHistory(Character* dest, int startLine, int count) const; + + + // screen image ---------------- + int lines; + int columns; + + typedef QVector<Character> ImageLine; // [0..columns] + ImageLine* screenLines; // [lines] + + int _scrolledLines; + QRect _lastScrolledRegion; + + int _droppedLines; + + QVarLengthArray<LineProperty,64> lineProperties; + + // history buffer --------------- + HistoryScroll* history; + + // cursor location + int cuX; + int cuY; + + // cursor color and rendition info + CharacterColor currentForeground; + CharacterColor currentBackground; + quint8 currentRendition; + + // margins ---------------- + int _topMargin; + int _bottomMargin; + + // states ---------------- + int currentModes[MODES_SCREEN]; + int savedModes[MODES_SCREEN]; + + // ---------------------------- + + QBitArray tabStops; + + // selection ------------------- + int selBegin; // The first location selected. + int selTopLeft; // TopLeft Location. + int selBottomRight; // Bottom Right Location. + bool blockSelectionMode; // Column selection mode + + // effective colors and rendition ------------ + CharacterColor effectiveForeground; // These are derived from + CharacterColor effectiveBackground; // the cu_* variables above + quint8 effectiveRendition; // to speed up operation + + class SavedState + { + public: + SavedState() + : cursorColumn(0),cursorLine(0),rendition(0) {} + + int cursorColumn; + int cursorLine; + quint8 rendition; + CharacterColor foreground; + CharacterColor background; + }; + SavedState savedState; + + // last position where we added a character + int lastPos; + + static Character defaultChar; +}; + +#endif // SCREEN_H
new file mode 100644 --- /dev/null +++ b/gui/src/ScreenWindow.cpp @@ -0,0 +1,292 @@ +/* + Copyright (C) 2007 by Robert Knight <robertknight@gmail.com> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "ScreenWindow.h" + +// Qt +#include <QtCore> + +// Konsole +#include "Screen.h" + +ScreenWindow::ScreenWindow(QObject* parent) + : QObject(parent) + , _windowBuffer(0) + , _windowBufferSize(0) + , _bufferNeedsUpdate(true) + , _windowLines(1) + , _currentLine(0) + , _trackOutput(true) + , _scrollCount(0) +{ +} +ScreenWindow::~ScreenWindow() +{ + delete[] _windowBuffer; +} +void ScreenWindow::setScreen(Screen* screen) +{ + Q_ASSERT( screen ); + + _screen = screen; +} + +Screen* ScreenWindow::screen() const +{ + return _screen; +} + +Character* ScreenWindow::getImage() +{ + // reallocate internal buffer if the window size has changed + int size = windowLines() * windowColumns(); + if (_windowBuffer == 0 || _windowBufferSize != size) + { + delete[] _windowBuffer; + _windowBufferSize = size; + _windowBuffer = new Character[size]; + _bufferNeedsUpdate = true; + } + + if (!_bufferNeedsUpdate) + return _windowBuffer; + + _screen->getImage(_windowBuffer,size, + currentLine(),endWindowLine()); + + // this window may look beyond the end of the screen, in which + // case there will be an unused area which needs to be filled + // with blank characters + fillUnusedArea(); + + _bufferNeedsUpdate = false; + return _windowBuffer; +} + +void ScreenWindow::fillUnusedArea() +{ + int screenEndLine = _screen->getHistLines() + _screen->getLines() - 1; + int windowEndLine = currentLine() + windowLines() - 1; + + int unusedLines = windowEndLine - screenEndLine; + int charsToFill = unusedLines * windowColumns(); + + Screen::fillWithDefaultChar(_windowBuffer + _windowBufferSize - charsToFill,charsToFill); +} + +// return the index of the line at the end of this window, or if this window +// goes beyond the end of the screen, the index of the line at the end +// of the screen. +// +// when passing a line number to a Screen method, the line number should +// never be more than endWindowLine() +// +int ScreenWindow::endWindowLine() const +{ + return qMin(currentLine() + windowLines() - 1, + lineCount() - 1); +} +QVector<LineProperty> ScreenWindow::getLineProperties() +{ + QVector<LineProperty> result = _screen->getLineProperties(currentLine(),endWindowLine()); + + if (result.count() != windowLines()) + result.resize(windowLines()); + + return result; +} + +QString ScreenWindow::selectedText( bool preserveLineBreaks ) const +{ + return _screen->selectedText( preserveLineBreaks ); +} + +void ScreenWindow::getSelectionStart( int& column , int& line ) +{ + _screen->getSelectionStart(column,line); + line -= currentLine(); +} +void ScreenWindow::getSelectionEnd( int& column , int& line ) +{ + _screen->getSelectionEnd(column,line); + line -= currentLine(); +} +void ScreenWindow::setSelectionStart( int column , int line , bool columnMode ) +{ + _screen->setSelectionStart( column , qMin(line + currentLine(),endWindowLine()) , columnMode); + + _bufferNeedsUpdate = true; + emit selectionChanged(); +} + +void ScreenWindow::setSelectionEnd( int column , int line ) +{ + _screen->setSelectionEnd( column , qMin(line + currentLine(),endWindowLine()) ); + + _bufferNeedsUpdate = true; + emit selectionChanged(); +} + +bool ScreenWindow::isSelected( int column , int line ) +{ + return _screen->isSelected( column , qMin(line + currentLine(),endWindowLine()) ); +} + +void ScreenWindow::clearSelection() +{ + _screen->clearSelection(); + + emit selectionChanged(); +} + +void ScreenWindow::setWindowLines(int lines) +{ + Q_ASSERT(lines > 0); + _windowLines = lines; +} +int ScreenWindow::windowLines() const +{ + return _windowLines; +} + +int ScreenWindow::windowColumns() const +{ + return _screen->getColumns(); +} + +int ScreenWindow::lineCount() const +{ + return _screen->getHistLines() + _screen->getLines(); +} + +int ScreenWindow::columnCount() const +{ + return _screen->getColumns(); +} + +QPoint ScreenWindow::cursorPosition() const +{ + QPoint position; + + position.setX( _screen->getCursorX() ); + position.setY( _screen->getCursorY() ); + + return position; +} + +int ScreenWindow::currentLine() const +{ + return qBound(0,_currentLine,lineCount()-windowLines()); +} + +void ScreenWindow::scrollBy( RelativeScrollMode mode , int amount ) +{ + if ( mode == ScrollLines ) + { + scrollTo( currentLine() + amount ); + } + else if ( mode == ScrollPages ) + { + scrollTo( currentLine() + amount * ( windowLines() / 2 ) ); + } +} + +bool ScreenWindow::atEndOfOutput() const +{ + return currentLine() == (lineCount()-windowLines()); +} + +void ScreenWindow::scrollTo( int line ) +{ + int maxCurrentLineNumber = lineCount() - windowLines(); + line = qBound(0,line,maxCurrentLineNumber); + + const int delta = line - _currentLine; + _currentLine = line; + + // keep track of number of lines scrolled by, + // this can be reset by calling resetScrollCount() + _scrollCount += delta; + + _bufferNeedsUpdate = true; + + emit scrolled(_currentLine); +} + +void ScreenWindow::setTrackOutput(bool trackOutput) +{ + _trackOutput = trackOutput; +} + +bool ScreenWindow::trackOutput() const +{ + return _trackOutput; +} + +int ScreenWindow::scrollCount() const +{ + return _scrollCount; +} + +void ScreenWindow::resetScrollCount() +{ + _scrollCount = 0; +} + +QRect ScreenWindow::scrollRegion() const +{ + bool equalToScreenSize = windowLines() == _screen->getLines(); + + if ( atEndOfOutput() && equalToScreenSize ) + return _screen->lastScrolledRegion(); + else + return QRect(0,0,windowColumns(),windowLines()); +} + +void ScreenWindow::notifyOutputChanged() +{ + // move window to the bottom of the screen and update scroll count + // if this window is currently tracking the bottom of the screen + if ( _trackOutput ) + { + _scrollCount -= _screen->scrolledLines(); + _currentLine = qMax(0,_screen->getHistLines() - (windowLines()-_screen->getLines())); + } + else + { + // if the history is not unlimited then it may + // have run out of space and dropped the oldest + // lines of output - in this case the screen + // window's current line number will need to + // be adjusted - otherwise the output will scroll + _currentLine = qMax(0,_currentLine - + _screen->droppedLines()); + + // ensure that the screen window's current position does + // not go beyond the bottom of the screen + _currentLine = qMin( _currentLine , _screen->getHistLines() ); + } + + _bufferNeedsUpdate = true; + + emit outputChanged(); +}
new file mode 100644 --- /dev/null +++ b/gui/src/ScreenWindow.h @@ -0,0 +1,251 @@ +/* + Copyright (C) 2007 by Robert Knight <robertknight@gmail.com> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef SCREENWINDOW_H +#define SCREENWINDOW_H + +// Qt +#include <QtCore/QObject> +#include <QtCore/QPoint> +#include <QtCore/QRect> + +// Konsole +#include "Character.h" + +class Screen; + +/** + * Provides a window onto a section of a terminal screen. + * This window can then be rendered by a terminal display widget ( TerminalDisplay ). + * + * To use the screen window, create a new ScreenWindow() instance and associated it with + * a terminal screen using setScreen(). + * Use the scrollTo() method to scroll the window up and down on the screen. + * Call the getImage() method to retrieve the character image which is currently visible in the window. + * + * setTrackOutput() controls whether the window moves to the bottom of the associated screen when new + * lines are added to it. + * + * Whenever the output from the underlying screen is changed, the notifyOutputChanged() slot should + * be called. This in turn will update the window's position and emit the outputChanged() signal + * if necessary. + */ +class ScreenWindow : public QObject +{ +Q_OBJECT + +public: + /** + * Constructs a new screen window with the given parent. + * A screen must be specified by calling setScreen() before calling getImage() or getLineProperties(). + * + * You should not call this constructor directly, instead use the Emulation::createWindow() method + * to create a window on the emulation which you wish to view. This allows the emulation + * to notify the window when the associated screen has changed and synchronize selection updates + * between all views on a session. + */ + ScreenWindow(QObject* parent = 0); + virtual ~ScreenWindow(); + + /** Sets the screen which this window looks onto */ + void setScreen(Screen* screen); + /** Returns the screen which this window looks onto */ + Screen* screen() const; + + /** + * Returns the image of characters which are currently visible through this window + * onto the screen. + * + * The buffer is managed by the ScreenWindow instance and does not need to be + * deleted by the caller. + */ + Character* getImage(); + + /** + * Returns the line attributes associated with the lines of characters which + * are currently visible through this window + */ + QVector<LineProperty> getLineProperties(); + + /** + * Returns the number of lines which the region of the window + * specified by scrollRegion() has been scrolled by since the last call + * to resetScrollCount(). scrollRegion() is in most cases the + * whole window, but will be a smaller area in, for example, applications + * which provide split-screen facilities. + * + * This is not guaranteed to be accurate, but allows views to optimise + * rendering by reducing the amount of costly text rendering that + * needs to be done when the output is scrolled. + */ + int scrollCount() const; + + /** + * Resets the count of scrolled lines returned by scrollCount() + */ + void resetScrollCount(); + + /** + * Returns the area of the window which was last scrolled, this is + * usually the whole window area. + * + * Like scrollCount(), this is not guaranteed to be accurate, + * but allows views to optimise rendering. + */ + QRect scrollRegion() const; + + /** + * Sets the start of the selection to the given @p line and @p column within + * the window. + */ + void setSelectionStart( int column , int line , bool columnMode ); + /** + * Sets the end of the selection to the given @p line and @p column within + * the window. + */ + void setSelectionEnd( int column , int line ); + /** + * Retrieves the start of the selection within the window. + */ + void getSelectionStart( int& column , int& line ); + /** + * Retrieves the end of the selection within the window. + */ + void getSelectionEnd( int& column , int& line ); + /** + * Returns true if the character at @p line , @p column is part of the selection. + */ + bool isSelected( int column , int line ); + /** + * Clears the current selection + */ + void clearSelection(); + + /** Sets the number of lines in the window */ + void setWindowLines(int lines); + /** Returns the number of lines in the window */ + int windowLines() const; + /** Returns the number of columns in the window */ + int windowColumns() const; + + /** Returns the total number of lines in the screen */ + int lineCount() const; + /** Returns the total number of columns in the screen */ + int columnCount() const; + + /** Returns the index of the line which is currently at the top of this window */ + int currentLine() const; + + /** + * Returns the position of the cursor + * within the window. + */ + QPoint cursorPosition() const; + + /** + * Convenience method. Returns true if the window is currently at the bottom + * of the screen. + */ + bool atEndOfOutput() const; + + /** Scrolls the window so that @p line is at the top of the window */ + void scrollTo( int line ); + + enum RelativeScrollMode + { + ScrollLines, + ScrollPages + }; + + /** + * Scrolls the window relative to its current position on the screen. + * + * @param mode Specifies whether @p amount refers to the number of lines or the number + * of pages to scroll. + * @param amount The number of lines or pages ( depending on @p mode ) to scroll by. If + * this number is positive, the view is scrolled down. If this number is negative, the view + * is scrolled up. + */ + void scrollBy( RelativeScrollMode mode , int amount ); + + /** + * Specifies whether the window should automatically move to the bottom + * of the screen when new output is added. + * + * If this is set to true, the window will be moved to the bottom of the associated screen ( see + * screen() ) when the notifyOutputChanged() method is called. + */ + void setTrackOutput(bool trackOutput); + /** + * Returns whether the window automatically moves to the bottom of the screen as + * new output is added. See setTrackOutput() + */ + bool trackOutput() const; + + /** + * Returns the text which is currently selected. + * + * @param preserveLineBreaks See Screen::selectedText() + */ + QString selectedText( bool preserveLineBreaks ) const; + +public slots: + /** + * Notifies the window that the contents of the associated terminal screen have changed. + * This moves the window to the bottom of the screen if trackOutput() is true and causes + * the outputChanged() signal to be emitted. + */ + void notifyOutputChanged(); + +signals: + /** + * Emitted when the contents of the associated terminal screen ( see screen() ) changes. + */ + void outputChanged(); + + /** + * Emitted when the screen window is scrolled to a different position. + * + * @param line The line which is now at the top of the window. + */ + void scrolled(int line); + + /** + * Emitted when the selection is changed. + */ + void selectionChanged(); + +private: + int endWindowLine() const; + void fillUnusedArea(); + + Screen* _screen; // see setScreen() , screen() + Character* _windowBuffer; + int _windowBufferSize; + bool _bufferNeedsUpdate; + + int _windowLines; + int _currentLine; // see scrollTo() , currentLine() + bool _trackOutput; // see setTrackOutput() , trackOutput() + int _scrollCount; // count of lines which the window has been scrolled by since + // the last call to resetScrollCount() +}; +#endif // SCREENWINDOW_H
new file mode 100644 --- /dev/null +++ b/gui/src/Session.cpp @@ -0,0 +1,1209 @@ +/* + This file is part of Konsole + + Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "Session.h" + +// Standard +#include <assert.h> +#include <stdlib.h> +#include <signal.h> + +// Qt +#include <QtGui/QApplication> +#include <QtCore/QByteRef> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QRegExp> +#include <QtCore/QStringList> +#include <QtCore/QDate> + +#include "kprocess.h" +#include "kptydevice.h" + +#include "ProcessInfo.h" +#include "Pty.h" +#include "TerminalDisplay.h" +#include "ShellCommand.h" +#include "Vt102Emulation.h" + +int Session::lastSessionId = 0; + +// HACK This is copied out of QUuid::createUuid with reseeding forced. +// Required because color schemes repeatedly seed the RNG... +// ...with a constant. +QUuid createUuid() +{ + static const int intbits = sizeof(int)*8; + static int randbits = 0; + if (!randbits) + { + int max = RAND_MAX; + do { ++randbits; } while ((max=max>>1)); + } + + qsrand(uint(QDateTime::currentDateTime().toTime_t())); + qrand(); // Skip first + + QUuid result; + uint *data = &(result.data1); + int chunks = 16 / sizeof(uint); + while (chunks--) { + uint randNumber = 0; + for (int filled = 0; filled < intbits; filled += randbits) + randNumber |= qrand()<<filled; + *(data+chunks) = randNumber; + } + + result.data4[0] = (result.data4[0] & 0x3F) | 0x80; // UV_DCE + result.data3 = (result.data3 & 0x0FFF) | 0x4000; // UV_Random + + return result; +} + +Session::Session(QObject* parent) : + QObject(parent) + , _shellProcess(0) + , _emulation(0) + , _monitorActivity(false) + , _monitorSilence(false) + , _notifiedActivity(false) + , _autoClose(true) + , _wantedClose(false) + , _silenceSeconds(10) + , _addToUtmp(true) + , _flowControl(true) + , _fullScripting(false) + , _sessionId(0) + , _sessionProcessInfo(0) + , _foregroundProcessInfo(0) + , _foregroundPid(0) + //, _zmodemBusy(false) + //, _zmodemProc(0) + //, _zmodemProgress(0) + , _hasDarkBackground(false) +{ + _uniqueIdentifier = createUuid(); + + //prepare DBus communication + //new SessionAdaptor(this); + _sessionId = ++lastSessionId; + + // JPS: commented out for lack of DBUS support by default on OSX + //QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/")+QString::number(_sessionId), this); + + //create emulation backend + _emulation = new Vt102Emulation(); + + connect( _emulation, SIGNAL( titleChanged( int, const QString & ) ), + this, SLOT( setUserTitle( int, const QString & ) ) ); + connect( _emulation, SIGNAL( stateSet(int) ), + this, SLOT( activityStateSet(int) ) ); + //connect( _emulation, SIGNAL( zmodemDetected() ), this , + // SLOT( fireZModemDetected() ) ); + connect( _emulation, SIGNAL( changeTabTextColorRequest( int ) ), + this, SIGNAL( changeTabTextColorRequest( int ) ) ); + connect( _emulation, SIGNAL(profileChangeCommandReceived(const QString&)), + this, SIGNAL( profileChangeCommandReceived(const QString&)) ); + connect( _emulation, SIGNAL(flowControlKeyPressed(bool)) , this, + SLOT(updateFlowControlState(bool)) ); + + //create new teletype for I/O with shell process + openTeletype(-1); + + //setup timer for monitoring session activity + _monitorTimer = new QTimer(this); + _monitorTimer->setSingleShot(true); + connect(_monitorTimer, SIGNAL(timeout()), this, SLOT(monitorTimerDone())); +} + +void Session::openTeletype(int fd) +{ + if (_shellProcess && isRunning()) + { + //kWarning() << "Attempted to open teletype in a running session."; + return; + } + + delete _shellProcess; + + if (fd < 0) + _shellProcess = new Pty(); + else + _shellProcess = new Pty(fd); + + _shellProcess->setUtf8Mode(_emulation->utf8()); + + //connect teletype to emulation backend + connect( _shellProcess,SIGNAL(receivedData(const char*,int)),this, + SLOT(onReceiveBlock(const char*,int)) ); + connect( _emulation,SIGNAL(sendData(const char*,int)),_shellProcess, + SLOT(sendData(const char*,int)) ); + connect( _emulation,SIGNAL(lockPtyRequest(bool)),_shellProcess,SLOT(lockPty(bool)) ); + connect( _emulation,SIGNAL(useUtf8Request(bool)),_shellProcess,SLOT(setUtf8Mode(bool)) ); + connect( _shellProcess,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(done(int)) ); + connect( _emulation,SIGNAL(imageSizeChanged(int,int)),this,SLOT(updateWindowSize(int,int)) ); +} + +WId Session::windowId() const +{ + // Returns a window ID for this session which is used + // to set the WINDOWID environment variable in the shell + // process. + // + // Sessions can have multiple views or no views, which means + // that a single ID is not always going to be accurate. + // + // If there are no views, the window ID is just 0. If + // there are multiple views, then the window ID for the + // top-level window which contains the first view is + // returned + + if ( _views.count() == 0 ) + return 0; + else + { + QWidget* window = _views.first(); + + Q_ASSERT( window ); + + while ( window->parentWidget() != 0 ) + window = window->parentWidget(); + + return window->winId(); + } +} + +void Session::setDarkBackground(bool darkBackground) +{ + _hasDarkBackground = darkBackground; +} +bool Session::hasDarkBackground() const +{ + return _hasDarkBackground; +} +bool Session::isRunning() const +{ + return _shellProcess->state() == QProcess::Running; +} + +void Session::setCodec(QTextCodec* codec) +{ + emulation()->setCodec(codec); +} + +bool Session::setCodec(QByteArray name) +{ + QTextCodec *codec = QTextCodec::codecForName(name); + if (codec) { + setCodec(codec); + return true; + } + return false; +} + +QByteArray Session::codec() +{ + return _emulation->codec()->name(); +} + +void Session::setProgram(const QString& program) +{ + _program = ShellCommand::expand(program); +} +void Session::setInitialWorkingDirectory(const QString& dir) +{ + //_initialWorkingDir = KShell::tildeExpand(ShellCommand::expand(dir)); + _initialWorkingDir = ShellCommand::expand(dir); +} +void Session::setArguments(const QStringList& arguments) +{ + _arguments = ShellCommand::expand(arguments); +} + +QString Session::currentWorkingDirectory() +{ + // only returned cached value + if (_currentWorkingDir.isEmpty()) updateWorkingDirectory(); + return _currentWorkingDir; +} +ProcessInfo* Session::updateWorkingDirectory() +{ + ProcessInfo *process = getProcessInfo(); + _currentWorkingDir = process->validCurrentDir(); + return process; +} + +QList<TerminalDisplay*> Session::views() const +{ + return _views; +} + +void Session::addView(TerminalDisplay* widget) +{ + Q_ASSERT( !_views.contains(widget) ); + + _views.append(widget); + + if ( _emulation != 0 ) + { + // connect emulation - view signals and slots + connect( widget , SIGNAL(keyPressedSignal(QKeyEvent*)) , _emulation , + SLOT(sendKeyEvent(QKeyEvent*)) ); + connect( widget , SIGNAL(mouseSignal(int,int,int,int)) , _emulation , + SLOT(sendMouseEvent(int,int,int,int)) ); + connect( widget , SIGNAL(sendStringToEmu(const char*)) , _emulation , + SLOT(sendString(const char*)) ); + + // allow emulation to notify view when the foreground process + // indicates whether or not it is interested in mouse signals + connect( _emulation , SIGNAL(programUsesMouseChanged(bool)) , widget , + SLOT(setUsesMouse(bool)) ); + + widget->setUsesMouse( _emulation->programUsesMouse() ); + + widget->setScreenWindow(_emulation->createWindow()); + } + + //connect view signals and slots + QObject::connect( widget ,SIGNAL(changedContentSizeSignal(int,int)),this, + SLOT(onViewSizeChange(int,int))); + + QObject::connect( widget ,SIGNAL(destroyed(QObject*)) , this , + SLOT(viewDestroyed(QObject*)) ); +} + +void Session::viewDestroyed(QObject* view) +{ + TerminalDisplay* display = (TerminalDisplay*)view; + + Q_ASSERT( _views.contains(display) ); + + removeView(display); +} + +void Session::removeView(TerminalDisplay* widget) +{ + _views.removeAll(widget); + + disconnect(widget,0,this,0); + + if ( _emulation != 0 ) + { + // disconnect + // - key presses signals from widget + // - mouse activity signals from widget + // - string sending signals from widget + // + // ... and any other signals connected in addView() + disconnect( widget, 0, _emulation, 0); + + // disconnect state change signals emitted by emulation + disconnect( _emulation , 0 , widget , 0); + } + + // close the session automatically when the last view is removed + if ( _views.count() == 0 ) + { + close(); + } +} + +QString Session::checkProgram(const QString& program) const +{ + // Upon a KPty error, there is no description on what that error was... + // Check to see if the given program is executable. + QString exec = QFile::encodeName(program); + + if (exec.isEmpty()) + return QString(); + + // if 'exec' is not specified, fall back to default shell. if that + // is not set then fall back to /bin/sh + if ( exec.isEmpty() ) + exec = qgetenv("SHELL"); + if ( exec.isEmpty() ) + exec = "/bin/sh"; + return program; +} + +void Session::terminalWarning(const QString& message) +{ + static const QByteArray warningText = QByteArray("@info:shell Alert the user with red color text"); + QByteArray messageText = message.toLocal8Bit(); + + static const char redPenOn[] = "\033[1m\033[31m"; + static const char redPenOff[] = "\033[0m"; + + _emulation->receiveData(redPenOn,strlen(redPenOn)); + _emulation->receiveData("\n\r\n\r",4); + _emulation->receiveData(warningText.constData(),strlen(warningText.constData())); + _emulation->receiveData(messageText.constData(),strlen(messageText.constData())); + _emulation->receiveData("\n\r\n\r",4); + _emulation->receiveData(redPenOff,strlen(redPenOff)); +} + +QString Session::shellSessionId() const +{ + QString friendlyUuid(_uniqueIdentifier.toString()); + friendlyUuid.remove('-').remove('{').remove('}'); + + return friendlyUuid; +} + +void Session::run() +{ + //check that everything is in place to run the session + if (_program.isEmpty()) + { + //kWarning() << "Session::run() - program to run not set."; + } + if (_arguments.isEmpty()) + { + //kWarning() << "Session::run() - no command line arguments specified."; + } + if (_uniqueIdentifier.isNull()) + { + _uniqueIdentifier = createUuid(); + } + + const int CHOICE_COUNT = 3; + QString programs[CHOICE_COUNT] = {_program,qgetenv("SHELL"),"/bin/sh"}; + QString exec; + int choice = 0; + while (choice < CHOICE_COUNT) + { + exec = checkProgram(programs[choice]); + if (exec.isEmpty()) + choice++; + else + break; + } + + // if a program was specified via setProgram(), but it couldn't be found, print a warning + if (choice != 0 && choice < CHOICE_COUNT && !_program.isEmpty()) + { + QString msg; + QTextStream msgStream(&msg); + msgStream << "Could not find '" << _program << "', starting '" << exec << "' instead. Please check your profile settings."; + terminalWarning(msg); + //terminalWarning(i18n("Could not find '%1', starting '%2' instead. Please check your profile settings.",_program.toLatin1().data(),exec.toLatin1().data())); + } + // if none of the choices are available, print a warning + else if (choice == CHOICE_COUNT) + { + terminalWarning(QString("Could not find an interactive shell to start.")); + return; + } + + // if no arguments are specified, fall back to program name + QStringList arguments = _arguments.join(QChar(' ')).isEmpty() ? + QStringList() << exec : _arguments; + + // JPS: commented out for lack of DBUS support by default on OSX + QString dbusService = ""; //QDBusConnection::sessionBus().baseService(); + if (!_initialWorkingDir.isEmpty()) + _shellProcess->setWorkingDirectory(_initialWorkingDir); + else + _shellProcess->setWorkingDirectory(QDir::homePath()); + + _shellProcess->setFlowControlEnabled(_flowControl); + _shellProcess->setErase(_emulation->eraseChar()); + + // this is not strictly accurate use of the COLORFGBG variable. This does not + // tell the terminal exactly which colors are being used, but instead approximates + // the color scheme as "black on white" or "white on black" depending on whether + // the background color is deemed dark or not + QString backgroundColorHint = _hasDarkBackground ? "COLORFGBG=15;0" : "COLORFGBG=0;15"; + _environment << backgroundColorHint; + _environment << QString("SHELL_SESSION_ID=%1").arg(shellSessionId()); + + int result = _shellProcess->start(exec, + arguments, + _environment, + windowId(), + _addToUtmp, + dbusService, + (QLatin1String("/Sessions/") + + QString::number(_sessionId))); + + if (result < 0) + { + QString msg; + QTextStream msgStream(&msg); + msgStream << QString("Could not start program '") << exec << QString("' with arguments '") << arguments.join(" ") << QString("'."); + terminalWarning (msg); + // terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec.toLatin1().data(), arguments.join(" ").toLatin1().data())); + return; + } + + _shellProcess->setWriteable(false); // We are reachable via kwrited. + + emit started(); +} + +void Session::setUserTitle( int what, const QString &caption ) +{ + //set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle ) + bool modified = false; + + if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) + { + if ( _userTitle != caption ) { + _userTitle = caption; + modified = true; + } + } + + if ((what == IconNameAndWindowTitle) || (what == IconName)) + { + if ( _iconText != caption ) { + _iconText = caption; + modified = true; + } + } + + if (what == TextColor || what == BackgroundColor) + { + QString colorString = caption.section(';',0,0); + QColor color = QColor(colorString); + if (color.isValid()) + { + if (what == TextColor) + emit changeForegroundColorRequest(color); + else + emit changeBackgroundColorRequest(color); + } + } + + if (what == SessionName) + { + if ( _nameTitle != caption ) { + setTitle(Session::NameRole,caption); + return; + } + } + + if (what == 31) + { + QString cwd=caption; + cwd=cwd.replace( QRegExp("^~"), QDir::homePath() ); + emit openUrlRequest(cwd); + } + + // change icon via \033]32;Icon\007 + if (what == 32) + { + if ( _iconName != caption ) { + _iconName = caption; + + modified = true; + } + } + + if (what == ProfileChange) + { + emit profileChangeCommandReceived(caption); + return; + } + + if ( modified ) + emit titleChanged(); +} + +QString Session::userTitle() const +{ + return _userTitle; +} +void Session::setTabTitleFormat(TabTitleContext context , const QString& format) +{ + if ( context == LocalTabTitle ) + _localTabTitleFormat = format; + else if ( context == RemoteTabTitle ) + _remoteTabTitleFormat = format; +} +QString Session::tabTitleFormat(TabTitleContext context) const +{ + if ( context == LocalTabTitle ) + return _localTabTitleFormat; + else if ( context == RemoteTabTitle ) + return _remoteTabTitleFormat; + + return QString(); +} + +void Session::monitorTimerDone() +{ + //FIXME: The idea here is that the notification popup will appear to tell the user than output from + //the terminal has stopped and the popup will disappear when the user activates the session. + // + //This breaks with the addition of multiple views of a session. The popup should disappear + //when any of the views of the session becomes active + + + //FIXME: Make message text for this notification and the activity notification more descriptive. + if (_monitorSilence) { + //KNotification::event("Silence", i18n("Silence in session '%1'", _nameTitle)propagateSize, QPixmap(), + // QApplication::activeWindow(), + // KNotification::CloseWhenWidgetActivated); + emit stateChanged(NOTIFYSILENCE); + } + else + { + emit stateChanged(NOTIFYNORMAL); + } + + _notifiedActivity=false; +} +void Session::updateFlowControlState(bool suspended) +{ + if (suspended) + { + if (flowControlEnabled()) + { + foreach(TerminalDisplay* display,_views) + { + if (display->flowControlWarningEnabled()) + display->outputSuspended(true); + } + } + } + else + { + foreach(TerminalDisplay* display,_views) + display->outputSuspended(false); + } +} +void Session::activityStateSet(int state) +{ + if (state==NOTIFYBELL) + { + emit bellRequest(QString("Bell in session '%1'").arg(_nameTitle.toLatin1().data())); + } + else if (state==NOTIFYACTIVITY) + { + if (_monitorSilence) { + _monitorTimer->start(_silenceSeconds*1000); + } + + if ( _monitorActivity ) { + //FIXME: See comments in Session::monitorTimerDone() + if (!_notifiedActivity) { + //KNotification::event("Activity", i18n("Activity in session '%1'", _nameTitle), QPixmap(), + // QApplication::activeWindow(), + //KNotification::CloseWhenWidgetActivated); + _notifiedActivity=true; + } + } + } + + if ( state==NOTIFYACTIVITY && !_monitorActivity ) + state = NOTIFYNORMAL; + if ( state==NOTIFYSILENCE && !_monitorSilence ) + state = NOTIFYNORMAL; + + emit stateChanged(state); +} + +void Session::onViewSizeChange(int /*height*/, int /*width*/) +{ + updateTerminalSize(); +} + +void Session::updateTerminalSize() +{ + QListIterator<TerminalDisplay*> viewIter(_views); + + int minLines = -1; + int minColumns = -1; + + // minimum number of lines and columns that views require for + // their size to be taken into consideration ( to avoid problems + // with new view widgets which haven't yet been set to their correct size ) + const int VIEW_LINES_THRESHOLD = 2; + const int VIEW_COLUMNS_THRESHOLD = 2; + + //select largest number of lines and columns that will fit in all visible views + while ( viewIter.hasNext() ) + { + TerminalDisplay* view = viewIter.next(); + if ( view->isHidden() == false && + view->lines() >= VIEW_LINES_THRESHOLD && + view->columns() >= VIEW_COLUMNS_THRESHOLD ) + { + minLines = (minLines == -1) ? view->lines() : qMin( minLines , view->lines() ); + minColumns = (minColumns == -1) ? view->columns() : qMin( minColumns , view->columns() ); + view->processFilters(); + } + } + + // backend emulation must have a _terminal of at least 1 column x 1 line in size + if ( minLines > 0 && minColumns > 0 ) + { + _emulation->setImageSize( minLines , minColumns ); + } +} +void Session::updateWindowSize(int lines, int columns) +{ + Q_ASSERT(lines > 0 && columns > 0); + _shellProcess->setWindowSize(lines,columns); +} +void Session::refresh() +{ + // attempt to get the shell process to redraw the display + // + // this requires the program running in the shell + // to cooperate by sending an update in response to + // a window size change + // + // the window size is changed twice, first made slightly larger and then + // resized back to its normal size so that there is actually a change + // in the window size (some shells do nothing if the + // new and old sizes are the same) + // + // if there is a more 'correct' way to do this, please + // send an email with method or patches to konsole-devel@kde.org + + const QSize existingSize = _shellProcess->windowSize(); + _shellProcess->setWindowSize(existingSize.height(),existingSize.width()+1); + _shellProcess->setWindowSize(existingSize.height(),existingSize.width()); +} + +bool Session::kill(int signal) +{ + int result = ::kill(_shellProcess->pid(),signal); + + if ( result == 0 ) + { + _shellProcess->waitForFinished(); + return true; + } + else + return false; +} + +void Session::close() +{ + _autoClose = true; + _wantedClose = true; + + if (!isRunning() || !kill(SIGHUP)) + { + if (isRunning()) + { + //kWarning() << "Process" << _shellProcess->pid() << "did not respond to SIGHUP"; + + // close the pty and wait to see if the process finishes. If it does, + // the done() slot will have been called so we can return. Otherwise, + // emit the finished() signal regardless + _shellProcess->pty()->close(); + if (_shellProcess->waitForFinished(3000)) + return; + + //kWarning() << "Unable to kill process" << _shellProcess->pid(); + } + + // Forced close. + QTimer::singleShot(1, this, SIGNAL(finished())); + } +} + +void Session::sendText(const QString &text) const +{ + _emulation->sendText(text); +} + +void Session::sendMouseEvent(int buttons, int column, int line, int eventType) +{ + _emulation->sendMouseEvent(buttons, column, line, eventType); +} + +Session::~Session() +{ + if (_foregroundProcessInfo) + delete _foregroundProcessInfo; + if (_sessionProcessInfo) + delete _sessionProcessInfo; + delete _emulation; + delete _shellProcess; + //delete _zmodemProc; +} + +void Session::done(int exitStatus) +{ + if (!_autoClose) + { + _userTitle = QString("@info:shell This session is done"); + emit titleChanged(); + return; + } + + QString message; + QTextStream msgStream(&message); + if (!_wantedClose || exitStatus != 0) + { + if (_shellProcess->exitStatus() == QProcess::NormalExit) + { + msgStream << "Program '" << _program << "' exited with statis " << exitStatus << "."; + //message = i18n("Program '%1' exited with status %2.", _program.toLatin1().data(), exitStatus); + + } + else + { + msgStream << "Program '" << _program << "' crashed."; + //message = i18n("Program '%1' crashed.", _program.toLatin1().data()); + + } + + //FIXME: See comments in Session::monitorTimerDone() + //KNotification::event("Finished", message , QPixmap(), + // QApplication::activeWindow(), + // KNotification::CloseWhenWidgetActivated); + } + + if ( !_wantedClose && _shellProcess->exitStatus() != QProcess::NormalExit ) + terminalWarning(message); + else + emit finished(); +} + +Emulation* Session::emulation() const +{ + return _emulation; +} + +QString Session::keyBindings() const +{ + return _emulation->keyBindings(); +} + +QStringList Session::environment() const +{ + return _environment; +} + +void Session::setEnvironment(const QStringList& environment) +{ + _environment = environment; +} + +int Session::sessionId() const +{ + return _sessionId; +} + +void Session::setKeyBindings(const QString &id) +{ + _emulation->setKeyBindings(id); +} + +void Session::setTitle(TitleRole role , const QString& newTitle) +{ + if ( title(role) != newTitle ) + { + if ( role == NameRole ) + _nameTitle = newTitle; + else if ( role == DisplayedTitleRole ) + _displayTitle = newTitle; + + emit titleChanged(); + } +} + +QString Session::title(TitleRole role) const +{ + if ( role == NameRole ) + return _nameTitle; + else if ( role == DisplayedTitleRole ) + return _displayTitle; + else + return QString(); +} + +ProcessInfo* Session::getProcessInfo() +{ + ProcessInfo* process; + + if (isForegroundProcessActive()) + process = _foregroundProcessInfo; + else + { + updateSessionProcessInfo(); + process = _sessionProcessInfo; + } + + return process; +} + +void Session::updateSessionProcessInfo() +{ + Q_ASSERT(_shellProcess); + if (!_sessionProcessInfo) + { + _sessionProcessInfo = ProcessInfo::newInstance(processId()); + _sessionProcessInfo->setUserHomeDir(); + } + _sessionProcessInfo->update(); +} + +bool Session::updateForegroundProcessInfo() +{ + bool valid = (_foregroundProcessInfo != 0); + + // has foreground process changed? + Q_ASSERT(_shellProcess); + int pid = _shellProcess->foregroundProcessGroup(); + if (pid != _foregroundPid) + { + if (valid) + delete _foregroundProcessInfo; + _foregroundProcessInfo = ProcessInfo::newInstance(pid); + _foregroundPid = pid; + valid = true; + } + + if (valid) + { + _foregroundProcessInfo->update(); + valid = _foregroundProcessInfo->isValid(); + } + + return valid; +} + +bool Session::isRemote() +{ + ProcessInfo* process = getProcessInfo(); + + bool ok = false; + return ( process->name(&ok) == "ssh" && ok ); +} + +QString Session::getDynamicTitle() +{ + // update current directory from process + ProcessInfo* process = updateWorkingDirectory(); + + // format tab titles using process info + bool ok = false; + QString title; + if ( process->name(&ok) == "ssh" && ok ) + { + SSHProcessInfo sshInfo(*process); + title = sshInfo.format(tabTitleFormat(Session::RemoteTabTitle)); + } + else + title = process->format(tabTitleFormat(Session::LocalTabTitle)); + + return title; +} + +void Session::setIconName(const QString& iconName) +{ + if ( iconName != _iconName ) + { + _iconName = iconName; + emit titleChanged(); + } +} + +void Session::setIconText(const QString& iconText) +{ + _iconText = iconText; +} + +QString Session::iconName() const +{ + return _iconName; +} + +QString Session::iconText() const +{ + return _iconText; +} + +void Session::setHistoryType(const HistoryType &hType) +{ + _emulation->setHistory(hType); +} + +const HistoryType& Session::historyType() const +{ + return _emulation->history(); +} + +void Session::clearHistory() +{ + _emulation->clearHistory(); +} + +QStringList Session::arguments() const +{ + return _arguments; +} + +QString Session::program() const +{ + return _program; +} + +// unused currently +bool Session::isMonitorActivity() const { return _monitorActivity; } +// unused currently +bool Session::isMonitorSilence() const { return _monitorSilence; } + +void Session::setMonitorActivity(bool _monitor) +{ + _monitorActivity=_monitor; + _notifiedActivity=false; + + activityStateSet(NOTIFYNORMAL); +} + +void Session::setMonitorSilence(bool _monitor) +{ + if (_monitorSilence==_monitor) + return; + + _monitorSilence=_monitor; + if (_monitorSilence) + { + _monitorTimer->start(_silenceSeconds*1000); + } + else + _monitorTimer->stop(); + + activityStateSet(NOTIFYNORMAL); +} + +void Session::setMonitorSilenceSeconds(int seconds) +{ + _silenceSeconds=seconds; + if (_monitorSilence) { + _monitorTimer->start(_silenceSeconds*1000); + } +} + +void Session::setAddToUtmp(bool set) +{ + _addToUtmp = set; +} + +void Session::setFlowControlEnabled(bool enabled) +{ + _flowControl = enabled; + + if (_shellProcess) + _shellProcess->setFlowControlEnabled(_flowControl); + emit flowControlEnabledChanged(enabled); +} +bool Session::flowControlEnabled() const +{ + if (_shellProcess) + return _shellProcess->flowControlEnabled(); + else + return _flowControl; +} + +void Session::onReceiveBlock( const char* buf, int len ) +{ + _emulation->receiveData( buf, len ); + emit receivedData( QString::fromLatin1( buf, len ) ); +} + +QSize Session::size() +{ + return _emulation->imageSize(); +} + +void Session::setSize(const QSize& size) +{ + if ((size.width() <= 1) || (size.height() <= 1)) + return; + + emit resizeRequest(size); +} +int Session::processId() const +{ + return _shellProcess->pid(); +} + +void Session::setTitle(int role , const QString& title) +{ + switch (role) { + case (0): + this->setTitle(Session::NameRole, title); + break; + case (1): + this->setTitle(Session::DisplayedTitleRole, title); + break; + } +} + +QString Session::title(int role) const +{ + switch (role) { + case (0): + return this->title(Session::NameRole); + case (1): + return this->title(Session::DisplayedTitleRole); + default: + return QString(); + } +} + +void Session::setTabTitleFormat(int context , const QString& format) +{ + switch (context) { + case (0): + this->setTabTitleFormat(Session::LocalTabTitle, format); + break; + case (1): + this->setTabTitleFormat(Session::RemoteTabTitle, format); + break; + } +} + +QString Session::tabTitleFormat(int context) const +{ + switch (context) { + case (0): + return this->tabTitleFormat(Session::LocalTabTitle); + case (1): + return this->tabTitleFormat(Session::RemoteTabTitle); + default: + return QString(); + } +} + +int Session::foregroundProcessId() +{ + int pid; + + bool ok = false; + pid = getProcessInfo()->pid(&ok); + if (!ok) + pid = -1; + + return pid; +} + +bool Session::isForegroundProcessActive() +{ + // foreground process info is always updated after this + return updateForegroundProcessInfo() && (processId() != _foregroundPid); +} + +QString Session::foregroundProcessName() +{ + QString name; + + if (updateForegroundProcessInfo()) + { + bool ok = false; + name = _foregroundProcessInfo->name(&ok); + if (!ok) + name.clear(); + } + + return name; +} + +SessionGroup::SessionGroup(QObject* parent) + : QObject(parent), _masterMode(0) +{ +} +SessionGroup::~SessionGroup() +{ +} +int SessionGroup::masterMode() const { return _masterMode; } +QList<Session*> SessionGroup::sessions() const { return _sessions.keys(); } +bool SessionGroup::masterStatus(Session* session) const { return _sessions[session]; } + +void SessionGroup::addSession(Session* session) +{ + connect(session,SIGNAL(finished()),this,SLOT(sessionFinished())); + _sessions.insert(session,false); +} +void SessionGroup::removeSession(Session* session) +{ + disconnect(session,SIGNAL(finished()),this,SLOT(sessionFinished())); + setMasterStatus(session,false); + _sessions.remove(session); +} +void SessionGroup::sessionFinished() +{ + Session* session = qobject_cast<Session*>(sender()); + Q_ASSERT(session); + removeSession(session); +} +void SessionGroup::setMasterMode(int mode) +{ + _masterMode = mode; +} +QList<Session*> SessionGroup::masters() const +{ + return _sessions.keys(true); +} +void SessionGroup::setMasterStatus(Session* session , bool master) +{ + const bool wasMaster = _sessions[session]; + + if (wasMaster == master) { + // No status change -> nothing to do. + return; + } + _sessions[session] = master; + + if(master) { + connect( session->emulation() , SIGNAL(sendData(const char*,int)) , this, + SLOT(forwardData(const char*,int)) ); + } + else { + disconnect( session->emulation() , SIGNAL(sendData(const char*,int)) , this, + SLOT(forwardData(const char*,int)) ); + } +} +void SessionGroup::forwardData(const char* data, int size) +{ + static bool _inForwardData = false; + if(_inForwardData) { // Avoid recursive calls among session groups! + // A recursive call happens when a master in group A calls forwardData() + // in group B. If one of the destination sessions in group B is also a + // master of a group including the master session of group A, this would + // again call forwardData() in group A, and so on. + return; + } + + _inForwardData = true; + QListIterator<Session*> iter(_sessions.keys()); + while(iter.hasNext()) { + Session* other = iter.next(); + if(!_sessions[other]) { + other->emulation()->sendString(data, size); + } + } + _inForwardData = false; +} + +
new file mode 100644 --- /dev/null +++ b/gui/src/Session.h @@ -0,0 +1,752 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef SESSION_H +#define SESSION_H + +// Qt +#include <QtCore/QStringList> +#include <QtCore/QByteRef> +#include <QtCore/QSize> +#include <QUuid> +#include <QWidget> + +// Konsole +#include "History.h" + +class KProcess; +class KUrl; +class Emulation; +class Pty; +class ProcessInfo; +class TerminalDisplay; + //class ZModemDialog; +/** + * Represents a terminal session consisting of a pseudo-teletype and a terminal emulation. + * The pseudo-teletype (or PTY) handles I/O between the terminal process and Konsole. + * The terminal emulation ( Emulation and subclasses ) processes the output stream from the + * PTY and produces a character image which is then shown on views connected to the session. + * + * Each Session can be connected to one or more views by using the addView() method. + * The attached views can then display output from the program running in the terminal + * or send input to the program in the terminal in the form of keypresses and mouse + * activity. + */ +class Session : public QObject +{ +Q_OBJECT +Q_CLASSINFO("D-Bus Interface", "org.kde.konsole.Session") + +public: + Q_PROPERTY(QString name READ nameTitle) + Q_PROPERTY(int processId READ processId) + Q_PROPERTY(QString keyBindings READ keyBindings WRITE setKeyBindings) + Q_PROPERTY(QSize size READ size WRITE setSize) + + /** + * Constructs a new session. + * + * To start the terminal process, call the run() method, + * after specifying the program and arguments + * using setProgram() and setArguments() + * + * If no program or arguments are specified explicitly, the Session + * falls back to using the program specified in the SHELL environment + * variable. + */ + explicit Session(QObject* parent = 0); + ~Session(); + + /** + * Connect to an existing terminal. When a new Session() is constructed it + * automatically searches for and opens a new teletype. If you want to + * use an existing teletype (given its file descriptor) call this after + * constructing the session. + * + * Calling openTeletype() while a session is running has no effect. + * + * @param masterFd The file descriptor of the pseudo-teletype master (See KPtyProcess::KPtyProcess()) + */ + void openTeletype(int masterFd); + + /** + * Returns true if the session is currently running. This will be true + * after run() has been called successfully. + */ + bool isRunning() const; + + /** + * Adds a new view for this session. + * + * The viewing widget will display the output from the terminal and + * input from the viewing widget (key presses, mouse activity etc.) + * will be sent to the terminal. + * + * Views can be removed using removeView(). The session is automatically + * closed when the last view is removed. + */ + void addView(TerminalDisplay* widget); + /** + * Removes a view from this session. When the last view is removed, + * the session will be closed automatically. + * + * @p widget will no longer display output from or send input + * to the terminal + */ + void removeView(TerminalDisplay* widget); + + /** + * Returns the views connected to this session + */ + QList<TerminalDisplay*> views() const; + + /** + * Returns the terminal emulation instance being used to encode / decode + * characters to / from the process. + */ + Emulation* emulation() const; + + /** Returns the unique ID for this session. */ + int sessionId() const; + + /** + * This enum describes the contexts for which separate + * tab title formats may be specified. + */ + enum TabTitleContext + { + /** Default tab title format */ + LocalTabTitle, + /** + * Tab title format used session currently contains + * a connection to a remote computer (via SSH) + */ + RemoteTabTitle + }; + + /** + * Returns true if the session currently contains a connection to a + * remote computer. It currently supports ssh. + */ + bool isRemote(); + + /** + * Sets the format used by this session for tab titles. + * + * @param context The context whoose format should be set. + * @param format The tab title format. This may be a mixture + * of plain text and dynamic elements denoted by a '%' character + * followed by a letter. (eg. %d for directory). The dynamic + * elements available depend on the @p context + */ + void setTabTitleFormat(TabTitleContext context , const QString& format); + /** Returns the format used by this session for tab titles. */ + QString tabTitleFormat(TabTitleContext context) const; + + + /** Returns the arguments passed to the shell process when run() is called. */ + QStringList arguments() const; + /** Returns the program name of the shell process started when run() is called. */ + QString program() const; + + /** + * Sets the command line arguments which the session's program will be passed when + * run() is called. + */ + void setArguments(const QStringList& arguments); + /** Sets the program to be executed when run() is called. */ + void setProgram(const QString& program); + + /** Returns the session's current working directory. */ + QString initialWorkingDirectory() { return _initialWorkingDir; } + + /** + * Sets the initial working directory for the session when it is run + * This has no effect once the session has been started. + */ + void setInitialWorkingDirectory( const QString& dir ); + + /** + * Returns the current directory of the foreground process in the session + */ + QString currentWorkingDirectory(); + + /** + * Sets the type of history store used by this session. + * Lines of output produced by the terminal are added + * to the history store. The type of history store + * used affects the number of lines which can be + * remembered before they are lost and the storage + * (in memory, on-disk etc.) used. + */ + void setHistoryType(const HistoryType& type); + /** + * Returns the type of history store used by this session. + */ + const HistoryType& historyType() const; + /** + * Clears the history store used by this session. + */ + void clearHistory(); + + /** + * Sets the key bindings used by this session. The bindings + * specify how input key sequences are translated into + * the character stream which is sent to the terminal. + * + * @param id The name of the key bindings to use. The + * names of available key bindings can be determined using the + * KeyboardTranslatorManager class. + */ + void setKeyBindings(const QString& id); + /** Returns the name of the key bindings used by this session. */ + QString keyBindings() const; + + /** + * This enum describes the available title roles. + */ + enum TitleRole + { + /** The name of the session. */ + NameRole, + /** The title of the session which is displayed in tabs etc. */ + DisplayedTitleRole + }; + + /** + * Return the session title set by the user (ie. the program running + * in the terminal), or an empty string if the user has not set a custom title + */ + QString userTitle() const; + + /** Convenience method used to read the name property. Returns title(Session::NameRole). */ + QString nameTitle() const { return title(Session::NameRole); } + /** Returns a title generated from tab format and process information. */ + QString getDynamicTitle(); + + /** Sets the name of the icon associated with this session. */ + void setIconName(const QString& iconName); + /** Returns the name of the icon associated with this session. */ + QString iconName() const; + + /** Return URL for the session. */ + //KUrl getUrl(); + + /** Sets the text of the icon associated with this session. */ + void setIconText(const QString& iconText); + /** Returns the text of the icon associated with this session. */ + QString iconText() const; + + /** Sets the session's title for the specified @p role to @p title. */ + void setTitle(TitleRole role , const QString& title); + + /** Returns the session's title for the specified @p role. */ + QString title(TitleRole role) const; + + /** + * Specifies whether a utmp entry should be created for the pty used by this session. + * If true, KPty::login() is called when the session is started. + */ + void setAddToUtmp(bool); + + /** + * Specifies whether to close the session automatically when the terminal + * process terminates. + */ + void setAutoClose(bool b) { _autoClose = b; } + + /** Returns true if the user has started a program in the session. */ + bool isForegroundProcessActive(); + + /** Returns the name of the current foreground process. */ + QString foregroundProcessName(); + + /** Returns the terminal session's window size in lines and columns. */ + QSize size(); + /** + * Emits a request to resize the session to accommodate + * the specified window size. + * + * @param size The size in lines and columns to request. + */ + void setSize(const QSize& size); + + /** + * Sets whether the session has a dark background or not. The session + * uses this information to set the COLORFGBG variable in the process's + * environment, which allows the programs running in the terminal to determine + * whether the background is light or dark and use appropriate colors by default. + * + * This has no effect once the session is running. + */ + void setDarkBackground(bool darkBackground); + /** + * Returns true if the session has a dark background. + * See setDarkBackground() + */ + bool hasDarkBackground() const; + + /** + * Attempts to get the shell program to redraw the current display area. + * This can be used after clearing the screen, for example, to get the + * shell to redraw the prompt line. + */ + void refresh(); + + // void startZModem(const QString &rz, const QString &dir, const QStringList &list); + // void cancelZModem(); + // bool isZModemBusy() { return _zmodemBusy; } + + /** + * Possible values of the @p what parameter for setUserTitle() + * See "Operating System Controls" section on http://rtfm.etla.org/xterm/ctlseq.html + */ + enum UserTitleChange + { + IconNameAndWindowTitle = 0, + IconName = 1, + WindowTitle = 2, + TextColor = 10, + BackgroundColor = 11, + SessionName = 30, + ProfileChange = 50 // this clashes with Xterm's font change command + }; + + // Sets the text codec used by this sessions terminal emulation. + void setCodec(QTextCodec* codec); + + // session management + //void saveSession(KConfigGroup& group); + //void restoreSession(KConfigGroup& group); + +public slots: + + /** + * Starts the terminal session. + * + * This creates the terminal process and connects the teletype to it. + */ + void run(); + + /** + * Returns the environment of this session as a list of strings like + * VARIABLE=VALUE + */ + Q_SCRIPTABLE QStringList environment() const; + + /** + * Sets the environment for this session. + * @p environment should be a list of strings like + * VARIABLE=VALUE + */ + Q_SCRIPTABLE void setEnvironment(const QStringList& environment); + + /** + * Closes the terminal session. This sends a hangup signal + * (SIGHUP) to the terminal process and causes the finished() + * signal to be emitted. If the process does not respond to the SIGHUP signal + * then the terminal connection (the pty) is closed and Konsole waits for the + * process to exit. + */ + Q_SCRIPTABLE void close(); + + /** + * Changes the session title or other customizable aspects of the terminal + * emulation display. For a list of what may be changed see the + * Emulation::titleChanged() signal. + * + * @param what The feature being changed. Value is one of UserTitleChange + * @param caption The text part of the terminal command + */ + void setUserTitle( int what , const QString &caption ); + + /** + * Enables monitoring for activity in the session. + * This will cause notifySessionState() to be emitted + * with the NOTIFYACTIVITY state flag when output is + * received from the terminal. + */ + Q_SCRIPTABLE void setMonitorActivity(bool); + + /** Returns true if monitoring for activity is enabled. */ + Q_SCRIPTABLE bool isMonitorActivity() const; + + /** + * Enables monitoring for silence in the session. + * This will cause notifySessionState() to be emitted + * with the NOTIFYSILENCE state flag when output is not + * received from the terminal for a certain period of + * time, specified with setMonitorSilenceSeconds() + */ + Q_SCRIPTABLE void setMonitorSilence(bool); + + /** + * Returns true if monitoring for inactivity (silence) + * in the session is enabled. + */ + Q_SCRIPTABLE bool isMonitorSilence() const; + + /** See setMonitorSilence() */ + Q_SCRIPTABLE void setMonitorSilenceSeconds(int seconds); + + /** + * Sets whether flow control is enabled for this terminal + * session. + */ + Q_SCRIPTABLE void setFlowControlEnabled(bool enabled); + + /** Returns whether flow control is enabled for this terminal session. */ + Q_SCRIPTABLE bool flowControlEnabled() const; + + /** + * Sends @p text to the current foreground terminal program. + */ + Q_SCRIPTABLE void sendText(const QString& text) const; + + /** + * Sends a mouse event of type @p eventType emitted by button + * @p buttons on @p column/@p line to the current foreground + * terminal program + */ + Q_SCRIPTABLE void sendMouseEvent(int buttons, int column, int line, int eventType); + + /** + * Returns the process id of the terminal process. + * This is the id used by the system API to refer to the process. + */ + Q_SCRIPTABLE int processId() const; + + /** + * Returns the process id of the terminal's foreground process. + * This is initially the same as processId() but can change + * as the user starts other programs inside the terminal. + */ + Q_SCRIPTABLE int foregroundProcessId(); + + /** Sets the text codec used by this sessions terminal emulation. + * Overloaded to accept a QByteArray for convenience since DBus + * does not accept QTextCodec directky. + */ + Q_SCRIPTABLE bool setCodec(QByteArray codec); + + /** Returns the codec used to decode incoming characters in this + * terminal emulation + */ + Q_SCRIPTABLE QByteArray codec(); + + /** Sets the session's title for the specified @p role to @p title. + * This is an overloaded member function for setTitle(TitleRole, QString) + * provided for convenience since enum data types may not be + * exported directly through DBus + */ + Q_SCRIPTABLE void setTitle(int role, const QString& title); + + /** Returns the session's title for the specified @p role. + * This is an overloaded member function for setTitle(TitleRole) + * provided for convenience since enum data types may not be + * exported directly through DBus + */ + Q_SCRIPTABLE QString title(int role) const; + + /** Returns the "friendly" version of the QUuid of this session. + * This is a QUuid with the braces and dashes removed, so it cannot be + * used to construct a new QUuid. The same text appears in the + * SHELL_SESSION_ID environment variable. + */ + Q_SCRIPTABLE QString shellSessionId() const; + + /** Sets the session's tab title format for the specified @p context to @p format. + * This is an overloaded member function for setTabTitleFormat(TabTitleContext, QString) + * provided for convenience since enum data types may not be + * exported directly through DBus + */ + Q_SCRIPTABLE void setTabTitleFormat(int context, const QString& format); + + /** Returns the session's tab title format for the specified @p context. + * This is an overloaded member function for tabTitleFormat(TitleRole) + * provided for convenience since enum data types may not be + * exported directly through DBus + */ + Q_SCRIPTABLE QString tabTitleFormat(int context) const; + +signals: + + /** Emitted when the terminal process starts. */ + void started(); + + /** + * Emitted when the terminal process exits. + */ + void finished(); + + /** + * Emitted when output is received from the terminal process. + */ + void receivedData( const QString& text ); + + /** Emitted when the session's title has changed. */ + void titleChanged(); + + /** + * Emitted when the activity state of this session changes. + * + * @param state The new state of the session. This may be one + * of NOTIFYNORMAL, NOTIFYSILENCE or NOTIFYACTIVITY + */ + void stateChanged(int state); + + /** Emitted when a bell event occurs in the session. */ + void bellRequest( const QString& message ); + + /** + * Requests that the color the text for any tabs associated with + * this session should be changed; + * + * TODO: Document what the parameter does + */ + void changeTabTextColorRequest(int); + + /** + * Requests that the background color of views on this session + * should be changed. + */ + void changeBackgroundColorRequest(const QColor&); + /** + * Requests that the text color of views on this session should + * be changed to @p color. + */ + void changeForegroundColorRequest(const QColor&); + + /** TODO: Document me. */ + void openUrlRequest(const QString& url); + + /** TODO: Document me. */ + //void zmodemDetected(); + + /** + * Emitted when the terminal process requests a change + * in the size of the terminal window. + * + * @param size The requested window size in terms of lines and columns. + */ + void resizeRequest(const QSize& size); + + /** + * Emitted when a profile change command is received from the terminal. + * + * @param text The text of the command. This is a string of the form + * "PropertyName=Value;PropertyName=Value ..." + */ + void profileChangeCommandReceived(const QString& text); + + /** + * Emitted when the flow control state changes. + * + * @param enabled True if flow control is enabled or false otherwise. + */ + void flowControlEnabledChanged(bool enabled); + +private slots: + void done(int); + + // void fireZModemDetected(); + + void onReceiveBlock( const char* buffer, int len ); + void monitorTimerDone(); + + void onViewSizeChange(int height, int width); + + void activityStateSet(int); + + //automatically detach views from sessions when view is destroyed + void viewDestroyed(QObject* view); + + //void zmodemReadStatus(); + //void zmodemReadAndSendBlock(); + //void zmodemRcvBlock(const char *data, int len); + //void zmodemFinished(); + + void updateFlowControlState(bool suspended); + void updateWindowSize(int lines, int columns); +private: + + void updateTerminalSize(); + WId windowId() const; + bool kill(int signal); + // print a warning message in the terminal. This is used + // if the program fails to start, or if the shell exits in + // an unsuccessful manner + void terminalWarning(const QString& message); + // checks that the binary 'program' is available and can be executed + // returns the binary name if available or an empty string otherwise + QString checkProgram(const QString& program) const; + ProcessInfo* getProcessInfo(); + void updateSessionProcessInfo(); + bool updateForegroundProcessInfo(); + ProcessInfo* updateWorkingDirectory(); + + QUuid _uniqueIdentifier; // SHELL_SESSION_ID + + Pty* _shellProcess; + Emulation* _emulation; + + QList<TerminalDisplay*> _views; + + bool _monitorActivity; + bool _monitorSilence; + bool _notifiedActivity; + bool _masterMode; + bool _autoClose; + bool _wantedClose; + QTimer* _monitorTimer; + + int _silenceSeconds; + + QString _nameTitle; + QString _displayTitle; + QString _userTitle; + + QString _localTabTitleFormat; + QString _remoteTabTitleFormat; + + QString _iconName; + QString _iconText; // as set by: echo -en '\033]1;IconText\007 + bool _addToUtmp; + bool _flowControl; + bool _fullScripting; + + QString _program; + QStringList _arguments; + + QStringList _environment; + int _sessionId; + + QString _initialWorkingDir; + QString _currentWorkingDir; + + ProcessInfo* _sessionProcessInfo; + ProcessInfo* _foregroundProcessInfo; + int _foregroundPid; + + // ZModem + // bool _zmodemBusy; + // KProcess* _zmodemProc; + // ZModemDialog* _zmodemProgress; + + // Color/Font Changes by ESC Sequences + + QColor _modifiedBackground; // as set by: echo -en '\033]11;Color\007 + + QString _profileKey; + + bool _hasDarkBackground; + + static int lastSessionId; + +}; + +/** + * Provides a group of sessions which is divided into master and slave sessions. + * Activity in master sessions can be propagated to all sessions within the group. + * The type of activity which is propagated and method of propagation is controlled + * by the masterMode() flags. + */ +class SessionGroup : public QObject +{ +Q_OBJECT + +public: + /** Constructs an empty session group. */ + SessionGroup(QObject* parent); + /** Destroys the session group and removes all connections between master and slave sessions. */ + ~SessionGroup(); + + /** Adds a session to the group. */ + void addSession( Session* session ); + /** Removes a session from the group. */ + void removeSession( Session* session ); + + /** Returns the list of sessions currently in the group. */ + QList<Session*> sessions() const; + + /** + * Sets whether a particular session is a master within the group. + * Changes or activity in the group's master sessions may be propagated + * to all the sessions in the group, depending on the current masterMode() + * + * @param session The session whoose master status should be changed. + * @param master True to make this session a master or false otherwise + */ + void setMasterStatus( Session* session , bool master ); + /** Returns the master status of a session. See setMasterStatus() */ + bool masterStatus( Session* session ) const; + + /** + * This enum describes the options for propagating certain activity or + * changes in the group's master sessions to all sessions in the group. + */ + enum MasterMode + { + /** + * Any input key presses in the master sessions are sent to all + * sessions in the group. + */ + CopyInputToAll = 1 + }; + + /** + * Specifies which activity in the group's master sessions is propagated + * to all sessions in the group. + * + * @param mode A bitwise OR of MasterMode flags. + */ + void setMasterMode( int mode ); + /** + * Returns a bitwise OR of the active MasterMode flags for this group. + * See setMasterMode() + */ + int masterMode() const; + +private slots: + void sessionFinished(); + void forwardData(const char* data, int size); + +private: + QList<Session*> masters() const; + + // maps sessions to their master status + QHash<Session*,bool> _sessions; + + int _masterMode; +}; + +#endif + +/* + Local Variables: + mode: c++ + c-file-style: "stroustrup" + indent-tabs-mode: nil + tab-width: 4 + End: +*/
new file mode 100644 --- /dev/null +++ b/gui/src/ShellCommand.cpp @@ -0,0 +1,165 @@ +/* + Copyright (C) 2007 by Robert Knight <robertknight@gmail.com> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "ShellCommand.h" + +//some versions of gcc(4.3) require explicit include +#include <cstdlib> + +// expands environment variables in 'text' +// function copied from kdelibs/kio/kio/kurlcompletion.cpp +static bool expandEnv(QString& text); + +ShellCommand::ShellCommand(const QString& fullCommand) +{ + bool inQuotes = false; + + QString builder; + + for ( int i = 0 ; i < fullCommand.count() ; i++ ) + { + QChar ch = fullCommand[i]; + + const bool isLastChar = ( i == fullCommand.count() - 1 ); + const bool isQuote = ( ch == '\'' || ch == '\"' ); + + if ( !isLastChar && isQuote ) + inQuotes = !inQuotes; + else + { + if ( (!ch.isSpace() || inQuotes) && !isQuote ) + builder.append(ch); + + if ( (ch.isSpace() && !inQuotes) || ( i == fullCommand.count()-1 ) ) + { + _arguments << builder; + builder.clear(); + } + } + } +} +ShellCommand::ShellCommand(const QString& command , const QStringList& arguments) +{ + _arguments = arguments; + + if ( !_arguments.isEmpty() ) + _arguments[0] == command; +} +QString ShellCommand::fullCommand() const +{ + return _arguments.join(QChar(' ')); +} +QString ShellCommand::command() const +{ + if ( !_arguments.isEmpty() ) + return _arguments[0]; + else + return QString(); +} +QStringList ShellCommand::arguments() const +{ + return _arguments; +} +bool ShellCommand::isRootCommand() const +{ + Q_ASSERT(0); // not implemented yet + return false; +} +bool ShellCommand::isAvailable() const +{ + Q_ASSERT(0); // not implemented yet + return false; +} +QStringList ShellCommand::expand(const QStringList& items) +{ + QStringList result; + + foreach( QString item , items ) + result << expand(item); + + return result; +} +QString ShellCommand::expand(const QString& text) +{ + QString result = text; + expandEnv(result); + return result; +} + +/* + * expandEnv + * + * Expand environment variables in text. Escaped '$' characters are ignored. + * Return true if any variables were expanded + */ +static bool expandEnv( QString &text ) +{ + // Find all environment variables beginning with '$' + // + int pos = 0; + + bool expanded = false; + + while ( (pos = text.indexOf(QLatin1Char('$'), pos)) != -1 ) { + + // Skip escaped '$' + // + if ( pos > 0 && text.at(pos-1) == QLatin1Char('\\') ) { + pos++; + } + // Variable found => expand + // + else { + // Find the end of the variable = next '/' or ' ' + // + int pos2 = text.indexOf( QLatin1Char(' '), pos+1 ); + int pos_tmp = text.indexOf( QLatin1Char('/'), pos+1 ); + + if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) ) + pos2 = pos_tmp; + + if ( pos2 == -1 ) + pos2 = text.length(); + + // Replace if the variable is terminated by '/' or ' ' + // and defined + // + if ( pos2 >= 0 ) { + int len = pos2 - pos; + QString key = text.mid( pos+1, len-1); + QString value = + QString::fromLocal8Bit( ::getenv(key.toLocal8Bit()) ); + + if ( !value.isEmpty() ) { + expanded = true; + text.replace( pos, len, value ); + pos = pos + value.length(); + } + else { + pos = pos2; + } + } + } + } + + return expanded; +}
new file mode 100644 --- /dev/null +++ b/gui/src/ShellCommand.h @@ -0,0 +1,88 @@ +/* + Copyright (C) 2007 by Robert Knight <robertknight@gmail.com> + + Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef SHELLCOMMAND_H +#define SHELLCOMMAND_H + +// Qt +#include <QtCore/QStringList> + +/** + * A class to parse and extract information about shell commands. + * + * ShellCommand can be used to: + * + * <ul> + * <li>Take a command-line (eg "/bin/sh -c /path/to/my/script") and split it + * into its component parts (eg. the command "/bin/sh" and the arguments + * "-c","/path/to/my/script") + * </li> + * <li>Take a command and a list of arguments and combine them to + * form a complete command line. + * </li> + * <li>Determine whether the binary specified by a command exists in the + * user's PATH. + * </li> + * <li>Determine whether a command-line specifies the execution of + * another command as the root user using su/sudo etc. + * </li> + * </ul> + */ +class ShellCommand +{ +public: + /** + * Constructs a ShellCommand from a command line. + * + * @param fullCommand The command line to parse. + */ + ShellCommand(const QString& fullCommand); + /** + * Constructs a ShellCommand with the specified @p command and @p arguments. + */ + ShellCommand(const QString& command , const QStringList& arguments); + + /** Returns the command. */ + QString command() const; + /** Returns the arguments. */ + QStringList arguments() const; + + /** + * Returns the full command line. + */ + QString fullCommand() const; + + /** Returns true if this is a root command. */ + bool isRootCommand() const; + /** Returns true if the program specified by @p command() exists. */ + bool isAvailable() const; + + /** Expands environment variables in @p text .*/ + static QString expand(const QString& text); + + /** Expands environment variables in each string in @p list. */ + static QStringList expand(const QStringList& items); + +private: + QStringList _arguments; +}; +#endif // SHELLCOMMAND_H +
new file mode 100644 --- /dev/null +++ b/gui/src/SimpleEditor.cpp @@ -0,0 +1,288 @@ +/* Copyright (C) 2010 P.L. Lucas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "SimpleEditor.h" +#include <QFile> +#include <QTextStream> +#include <QTextBlock> +#include <QFileInfo> +#include <QDir> + +SimpleEditor::SimpleEditor(QWidget *parent) + : QPlainTextEdit(parent), + m_syntaxHighlighter(0), + m_firstTimeUse(true) { + + m_completerModel = new QStringListModel (); + m_completer = new QCompleter(m_completerModel, this); + m_completer->setCompletionMode(QCompleter::PopupCompletion); + m_completer->setWidget(this); + m_autoIndentation = true; + m_automaticIndentationStatement = true; + + QFont font; + font.setFamily("Courier"); + font.setPointSize(10); + setFont(font); + + connect(m_completer, SIGNAL(activated(const QString &)), this, SLOT(activated(const QString &))); + connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChangedCallBack())); + connect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(autoComplete(int, int, int))); +} + +void SimpleEditor::loadSyntaxXMLDescription() { + QString installPath = QString("../syntax_files") + + QDir::separator(); + + QFileInfo file(m_currentFileName); + QString suffix = file.suffix(); + + if(m_commandsCompletionList.isEmpty()) { + QString home = QDir::home().path() + + QDir::separator() + + ".qtoctave" + + QDir::separator() + + "commands.txt"; + + QFile file(home); + + if(file.open(QFile::ReadOnly)) { + char buf[1024]; + while(file.readLine(buf, sizeof(buf)) >= 0) { + m_commandsCompletionList.append(QString(buf).trimmed()); + } + file.close(); + } + } + + QFileInfo xml(installPath + suffix + ".xml"); + if(xml.exists()) { + m_syntaxHighlighter = new SyntaxHighlighter(document()); + m_syntaxHighlighter->load(xml.absoluteFilePath()); + m_syntaxHighlighter->setDocument(document()); + } +} + +bool SimpleEditor::load(QString file) { + if(file.isEmpty()) { + setPlainText(""); + m_currentFileName = file; + return true; + } + + FILE *input = fopen(file.toLocal8Bit().data(),"r"); + if(!input) + return false; + fclose(input); + QFile in(file); + if(!in.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + QByteArray data = in.readAll(); + setPlainText(QString::fromLocal8Bit(data)); + m_currentFileName = file; + m_firstTimeUse = false; + + loadSyntaxXMLDescription(); + + return true; +} + +bool SimpleEditor::save() { + QFile::remove(m_currentFileName + "~"); + QFile::copy(m_currentFileName, m_currentFileName + "~"); + FILE *out=fopen(m_currentFileName.toLocal8Bit().data(),"w"); + if(!out) + return false; + fprintf(out, "%s", toPlainText().toLocal8Bit().data()); + fclose(out); + document()->setModified(false); + return true; +} + +void SimpleEditor::keyPressEvent(QKeyEvent * keyEvent) { + //In all cases completer popup must been hided. + if(keyEvent->key() != Qt::Key_Return && keyEvent->key() != Qt::Key_Enter) { + QAbstractItemView *view = m_completer->popup(); + if(view->isVisible()) view->hide(); + } + + if(keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { + QAbstractItemView *view = m_completer->popup(); + if(view->isVisible()) { + QString word = view->currentIndex().data().toString(); + if(word.isEmpty()) { + word = m_completer->currentCompletion(); + } + activated(word); + return; + } else if(m_autoIndentation) { + QTextCursor cursor = textCursor(); + QString line = cursor.block().text(); + QString line2 = line; + for(int i=0;i<line.length();i++) { + if(line[i] != ' ' && line[i] != '\t') { + line.resize(i); + break; + } + } + + cursor.insertText("\n" + line); + if(m_automaticIndentationStatement) { + QRegExp re("^while .*|^if .*|^for .*|^switch .*|^do$|^try|^function .*|^else$|^elseif .*"); + if(re.exactMatch(line2.trimmed())) { + cursor.insertText("\t"); + } + } + setTextCursor(cursor); + } else { + QPlainTextEdit::keyPressEvent(keyEvent); + } + } else if(keyEvent->key() == Qt::Key_Tab) { + QTextCursor cursor=textCursor(); + int start=cursor.selectionStart(); + int end=cursor.selectionEnd(); + if(start == end) { + QPlainTextEdit::keyPressEvent(keyEvent); + return; + } + cursor.beginEditBlock(); + cursor.setPosition(end); + end=cursor.blockNumber(); + cursor.setPosition(start); + cursor.movePosition(QTextCursor::StartOfBlock); + while(true) { + cursor.insertText("\t"); + if(cursor.blockNumber()>=end) { + break; + } + cursor.movePosition(QTextCursor::NextBlock); + } + cursor.endEditBlock(); + } else if(keyEvent->key()==Qt::Key_Backtab) { + QTextCursor cursor=textCursor(); + int start=cursor.selectionStart(); + int end=cursor.selectionEnd(); + if(start==end) { + QPlainTextEdit::keyPressEvent(keyEvent); + return; + } + cursor.beginEditBlock(); + cursor.setPosition(end); + end=cursor.blockNumber(); + cursor.setPosition(start); + cursor.movePosition(QTextCursor::StartOfBlock); + while(true) { + QString line=cursor.block().text(); + if(line.length()>0 && (line[0]==' ' || line[0] =='\t')) { + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + if(cursor.blockNumber()>=end) break; + cursor.movePosition(QTextCursor::NextBlock); + cursor.movePosition(QTextCursor::StartOfBlock); + } + cursor.endEditBlock(); + } else { + if(keyEvent->key()==(Qt::Key_B) && Qt::ControlModifier==keyEvent->modifiers()) { + autoComplete(0); + return; + } + QPlainTextEdit::keyPressEvent(keyEvent); + } +} + +void SimpleEditor::setCharFormat(QTextCharFormat charFormat) { + this->m_charFormat=charFormat; + QTextCursor cursor=textCursor(); + cursor.movePosition(QTextCursor::Start); + cursor.setCharFormat(charFormat); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + setFont(charFormat.font()); + + QFontMetrics fm(charFormat.font()); + int textWidthInPixels = fm.width(" "); + setTabStopWidth(textWidthInPixels); +} + +void SimpleEditor::activated(const QString& text) { + QAbstractItemView *view=m_completer->popup(); + QTextCursor cursor=textCursor(); + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); + cursor.insertText(text); + view->hide(); +} + +void SimpleEditor::autoComplete(int position, int charsRemoved, int charsAdded) { + if(charsAdded==1) + autoComplete(); +} + +void SimpleEditor::autoComplete(int size) { + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); + if(cursor.selectedText().endsWith(" ") + || cursor.selectedText().trimmed().length() < size) { + return; + } + + QStringList list=toPlainText().split(QRegExp("\\W+")); + list.removeDuplicates(); + list.removeOne(cursor.selectedText()); + list.sort(); + list.append(m_commandsCompletionList); + + m_completerModel->setStringList(list); + m_completer->setCompletionPrefix(cursor.selectedText()); + + if(m_completer->completionCount() > 0) { + QRect r=cursorRect(cursor); + r.setWidth(200); + m_completer->complete(r); + } +} + +QString SimpleEditor::getFileName() { + return m_currentFileName; +} + +void SimpleEditor::setFile(QString file) { + m_currentFileName = file; + loadSyntaxXMLDescription(); +} + +void SimpleEditor::cursorPositionChangedCallBack() { + if(m_syntaxHighlighter) + m_syntaxHighlighter->setFormatPairBrackets(this); +} + +void SimpleEditor::publicBlockBoundingRectList(QVector<qreal> &list, int &firstLine) { + qreal pageBottom = height(); + QPointF offset = contentOffset(); + QTextBlock block = firstVisibleBlock(); + firstLine = block.blockNumber() + 1; + qreal first_position = blockBoundingGeometry(block).topLeft().y(); + for(; block.isValid(); block = block.next()) { + QRectF position = blockBoundingGeometry(block); + qreal y = position.topLeft().y() + offset.y() - first_position; + if(y > pageBottom) + break; + list.append(y); + } +} +
new file mode 100644 --- /dev/null +++ b/gui/src/SimpleEditor.h @@ -0,0 +1,61 @@ +/* Copyright (C) 2010 P.L. Lucas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SIMPLEEDITOR_H +#define SIMPLEEDITOR_H + +#include <QPlainTextEdit> +#include <QCompleter> +#include <QStringListModel> +#include "SyntaxHighlighter.h" + +class SimpleEditor : public QPlainTextEdit { + Q_OBJECT +public: + SimpleEditor(QWidget * parent = 0); + bool load(QString file); + bool save(); + QString getFileName(); + void setFile(QString file); + void setCharFormat(QTextCharFormat m_charFormat); + void publicBlockBoundingRectList(QVector<qreal> &list, int &firstLine); + void loadSyntaxXMLDescription(); + +public slots: + void activated(const QString& text); + void cursorPositionChangedCallBack(); + void autoComplete(int size = 3); + void autoComplete(int position, int charsRemoved, int charsAdded); + +protected: + virtual void keyPressEvent(QKeyEvent * e); + +private: + bool m_firstTimeUse; + QString m_currentFileName; + QTextCharFormat m_charFormat; + QCompleter *m_completer; + QStringListModel *m_completerModel; + SyntaxHighlighter *m_syntaxHighlighter; + QStringList m_commandsCompletionList; + bool m_autoIndentation; + bool m_automaticIndentationStatement; +}; + +#endif // SIMPLEEDITOR_H +
new file mode 100644 --- /dev/null +++ b/gui/src/SyntaxHighlighter.cpp @@ -0,0 +1,486 @@ +/* Copyright (C) 2010 P.L. Lucas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#include "SyntaxHighlighter.h" +#include <QXmlStreamReader> +#include <QStack> +#include <QFile> +#include <stdio.h> + +SyntaxHighlighter::SyntaxHighlighter(QObject * parent):QSyntaxHighlighter(parent) +{ +} + + +bool SyntaxHighlighter::load(QString file) +{ + QXmlStreamReader xml; + QStack <QString> stack; + QFile fileDevice(file); + if (!fileDevice.open(QFile::ReadOnly | QFile::Text)) { + return false; + } + + xml.setDevice(&fileDevice); + + QMap <QString,QString> values; + + QVector<QString> xmlMainItems; + xmlMainItems << "item" << "block" << "bracket"; + + int ruleOrder=0; + + while (!xml.atEnd()) + { + QXmlStreamReader::TokenType tokenType=xml.readNext(); + switch(tokenType) + { + case QXmlStreamReader::StartElement: + if(xml.name()!="syntax") + { + if( xmlMainItems.contains(xml.name().toString()) ) + stack.push(xml.name().toString()); + else + values[xml.name().toString()]=xml.readElementText().trimmed(); + } + break; + case QXmlStreamReader::EndElement: + if(stack.isEmpty()) break; + QString name=stack.top(); + if(name==xml.name()) stack.pop(); + if(stack.isEmpty()) + { + QTextCharFormat format; + if(values.contains("bold") && values["bold"]=="true") format.setFontWeight(QFont::Bold); + if(values.contains("underline") && values["underline"]=="true") format.setFontUnderline(true); + if(values.contains("italic") && values["italic"]=="true") format.setFontItalic(true); + if(values.contains("foreground")) format.setForeground(QBrush(QColor(values["foreground"]))); + if(values.contains("background")) format.setBackground(QBrush(QColor(values["background"]))); + if(name=="item") + { + HighlightingRule rule; + rule.format=format; + rule.pattern=QRegExp(values["pattern"]); + rule.ruleOrder=ruleOrder++; + highlightingRules.append(rule); + values.clear(); + } + else if(name=="block" || name=="bracket") + { + HighlightingBlockRule rule; + rule.format=format; + rule.startPattern=QRegExp(values["startPattern"]); + rule.endPattern=QRegExp(values["endPattern"]); + rule.ruleOrder=ruleOrder++; + if(name=="block") highlightingBlockRules.append(rule); + else highlightingBracketsRules.append(rule); //Bracket rule + values.clear(); + } + } + break; + } + } + if (xml.hasError()) + { + // do error handling + printf("Error %s: %ld:%ld %s\n", file.toLocal8Bit().data(), xml.lineNumber(), xml.columnNumber(), xml.errorString().toLocal8Bit().data() ); + return false; + } + + return true; +} + +SyntaxHighlighter::Rule1st SyntaxHighlighter::highlight1stRule(const QString & text, int startIndex) +{ + Rule1st rule1st; + rule1st.startIndex=text.length(); + rule1st.rule=-1; + + for(int i=0; i<highlightingRules.size(); i++) + { + HighlightingRule *rule=&(highlightingRules[i]); + + QRegExp *expression = &(rule->pattern); + int index = rule->lastFound; + if(index>-1 && index<startIndex) + { + rule->lastFound = index = expression->indexIn(text, startIndex); + } + if ( index>-1 && index<rule1st.startIndex ) + { + rule1st.startIndex=index; + rule1st.length=expression->matchedLength(); + rule1st.rule=i; + rule1st.ruleOrder=rule->ruleOrder; + } + + if(index==startIndex) break; + } + + if(rule1st.rule==-1) rule1st.startIndex=-1; + + return rule1st; +} + +SyntaxHighlighter::Rule1st SyntaxHighlighter::highlight1stBlockRule(const QString & text, int startIndex) +{ + Rule1st rule1st; + rule1st.startIndex=text.length(); + rule1st.rule=-1; + + for(int i=0; i<highlightingBlockRules.size(); i++) + { + HighlightingBlockRule rule=highlightingBlockRules[i]; + + int index = rule.startPattern.indexIn(text, startIndex); + + if ( index>-1 && index<rule1st.startIndex ) + { + rule1st.startIndex=index; + rule1st.rule=i; + rule1st.ruleOrder=rule.ruleOrder; + } + + if(index==startIndex) break; + } + + if(rule1st.rule==-1) rule1st.startIndex=-1; + + return rule1st; +} + +/**Inserts brackets in position order in blockData->brackets + */ +static void insertInOrder(BlockData *blockData, BlockData::Bracket &bracket) +{ + if(blockData->brackets.isEmpty()) blockData->brackets.append(bracket); + else + { + int j=0; + + for(;j<blockData->brackets.size();j++) + { + if(blockData->brackets[j].pos>bracket.pos) + { + blockData->brackets.insert(j,bracket); + break; + } + } + if(j>=blockData->brackets.size()) + { + blockData->brackets.append(bracket); + } + } +} + + +void SyntaxHighlighter::findBrackets(const QString & text, int start, int end, BlockData *blockData) +{ + //blockData->brackets.clear(); + + if( end<0 || end>text.length() ) end=text.length(); + + if(start>end) return; + + for(int i=0; i<highlightingBracketsRules.size(); i++) + { + HighlightingBlockRule rule=highlightingBracketsRules[i]; + + int startIndex=start; + + int index = rule.startPattern.indexIn(text, startIndex); + + while( index>-1 && index<end ) + { + BlockData::Bracket bracket; + bracket.pos=index; + bracket.type=i; + bracket.length=rule.startPattern.matchedLength(); + bracket.startBracketOk=true; + + startIndex=index+bracket.length; + + insertInOrder(blockData, bracket); + index = rule.startPattern.indexIn(text, startIndex); + } + + startIndex=start; + + index = rule.endPattern.indexIn(text, startIndex); + + + + while( index>-1 && index<end ) + { + BlockData::Bracket bracket; + bracket.pos=index; + bracket.type=i; + bracket.length=rule.endPattern.matchedLength(); + bracket.startBracketOk=false; + insertInOrder(blockData, bracket); + startIndex=index+bracket.length; + index = rule.endPattern.indexIn(text, startIndex); + } + } +} + + +int SyntaxHighlighter::ruleSetFormat(Rule1st rule1st) +{ + HighlightingRule rule=highlightingRules[rule1st.rule]; + + setFormat(rule1st.startIndex, rule1st.length, rule.format); + + return rule1st.startIndex + rule1st.length; +} + + +int SyntaxHighlighter::blockRuleSetFormat(const QString & text, Rule1st rule1st) +{ + HighlightingBlockRule rule=highlightingBlockRules[rule1st.rule]; + + int endIndex = rule.endPattern.indexIn(text, rule1st.startIndex); + int commentLength; + if (endIndex == -1) + { + setCurrentBlockState(rule1st.rule); + commentLength = text.length() - rule1st.startIndex; + setFormat(rule1st.startIndex, commentLength, rule.format); + return text.length(); + } + else + { + commentLength = endIndex - rule1st.startIndex + + rule.endPattern.matchedLength(); + setFormat(rule1st.startIndex, commentLength, rule.format); + + return endIndex+1; + } +} + + +void SyntaxHighlighter::highlightBlock ( const QString & text ) +{ + + setCurrentBlockState(-1); + + int startIndex = 0; + + //Checks previous block state + if (previousBlockState() >= 0) + { + Rule1st rule1st; + rule1st.rule=previousBlockState(); + rule1st.startIndex=0; + + startIndex=blockRuleSetFormat(text,rule1st); + + //TODO: Posible fallo al establecer el estado del bloque + + if(startIndex==text.length()) return; + } + + //Gets BlockData + BlockData *blockData=new BlockData(); + + //Finds first rule to apply. + + Rule1st rule1st, blockRule1st; + + //Find initial matches + for(int i=0; i<highlightingRules.size(); i++) + { + HighlightingRule *rule= &(highlightingRules[i]); + QRegExp *expression = &(rule->pattern); + int index = expression->indexIn(text, startIndex); + rule->lastFound = index; + } + + rule1st=highlight1stRule( text, startIndex); + blockRule1st=highlight1stBlockRule( text, startIndex); + + while(rule1st.rule>=0 || blockRule1st.rule>=0) + { + if(rule1st.rule>=0 && blockRule1st.rule>=0) + { + if + ( + rule1st.startIndex<blockRule1st.startIndex + || + ( + rule1st.startIndex==blockRule1st.startIndex + && + rule1st.ruleOrder<blockRule1st.ruleOrder + ) + ) + { + findBrackets(text, startIndex, rule1st.startIndex, blockData); + startIndex=ruleSetFormat(rule1st); + rule1st=highlight1stRule( text, startIndex); + } + else + { + findBrackets(text, startIndex, blockRule1st.startIndex, blockData); + startIndex=blockRuleSetFormat(text,blockRule1st); + blockRule1st=highlight1stBlockRule( text, startIndex); + } + } + else if(rule1st.rule>=0) + { + findBrackets(text, startIndex, rule1st.startIndex, blockData); + startIndex=ruleSetFormat(rule1st); + rule1st=highlight1stRule( text, startIndex); + } + else + { + findBrackets(text, startIndex, blockRule1st.startIndex, blockData); + startIndex=blockRuleSetFormat(text,blockRule1st); + blockRule1st=highlight1stBlockRule( text, startIndex); + } + } + + findBrackets(text,startIndex, -1, blockData); + + setCurrentBlockUserData(blockData); +} + +/**Search brackets in one QTextBlock.*/ +static BlockData::Bracket *searchBracket(int i, int increment, int &bracketsCount, BlockData *blockData, BlockData::Bracket *bracket1) +{ + if(blockData==NULL) return NULL; + + if(i==-1) i=blockData->brackets.size()-1; + + for(; i>=0 && i<blockData->brackets.size(); i+=increment) + { + BlockData::Bracket *bracket=&(blockData->brackets[i]); + if(bracket->type==bracket1->type) + { + if(bracket->startBracketOk!=bracket1->startBracketOk) + bracketsCount--; + else + bracketsCount++; + + if(bracketsCount==0) + return bracket; + } + } + + //printf("[searchBracket] bracketsCount=%d\n", bracketsCount); + + return NULL; +} + +void SyntaxHighlighter::setFormatPairBrackets(QPlainTextEdit *textEdit) +{ + QList<QTextEdit::ExtraSelection> selections; + + textEdit->setExtraSelections(selections); + + QTextCursor cursor=textEdit->textCursor(); + QTextBlock block=cursor.block(); + BlockData *blockData=(BlockData *)block.userData(); + if(blockData==NULL) return; + + int pos=cursor.position()-block.position(); + + BlockData::Bracket *bracket1; + QTextBlock block_bracket1=block; + + int i=blockData->brackets.size()-1; + for(; i>=0; i--) + { + BlockData::Bracket *bracket=&(blockData->brackets[i]); + if(bracket->pos==pos) + { + bracket1=bracket; + break; + } + } + + if(i<0) return; + + int increment=(bracket1->startBracketOk) ? +1:-1; + int bracketsCount=0; + //Looks in this block the other bracket + BlockData::Bracket *bracket2=NULL; + QTextBlock block_bracket2=block; + + bracket2=searchBracket( i, increment, bracketsCount, blockData, bracket1); + + { //Search brackets in other blocks + while( bracket2==NULL ) + { + if(increment>0) + { + block_bracket2=block_bracket2.next(); + i=0; + } + else + { + block_bracket2=block_bracket2.previous(); + i=-1; + } + + if(!block_bracket2.isValid()) break; + + blockData=(BlockData *)block_bracket2.userData(); + /* + printf("[Syntax::setFormatPairBrackets] Interno brackets.size=%d\n", blockData->brackets.size()); + for(int x=0;x<blockData->brackets.size();x++) + { + BlockData::Bracket *bracket=&(blockData->brackets[x]); + printf("[Syntax::setFormatPairBrackets] bracket.pos=%d bracket.type=%d bracket.len=%d bracket.start=%d\n", bracket->pos, bracket->type, bracket->length, (bracket->startBracketOk) ); + } + */ + + bracket2=searchBracket( i, increment, bracketsCount, blockData, bracket1); + } + + if(bracket2==NULL) return; + } + + pos=cursor.position(); + + QTextEdit::ExtraSelection selection1; + + cursor.setPosition(pos+bracket1->length, QTextCursor::KeepAnchor); + selection1.cursor=cursor; + selection1.format=highlightingBracketsRules[bracket1->type].format; + + pos=bracket2->pos+block_bracket2.position(); + QTextEdit::ExtraSelection selection2; + cursor.setPosition(pos); + cursor.setPosition(pos+bracket2->length, QTextCursor::KeepAnchor); + selection2.cursor=cursor; + selection2.format=highlightingBracketsRules[bracket2->type].format; + + selections.append(selection1); selections.append(selection2); + + textEdit->setExtraSelections(selections); + +} + + + +BlockData::BlockData():QTextBlockUserData() +{ +} + +
new file mode 100644 --- /dev/null +++ b/gui/src/SyntaxHighlighter.h @@ -0,0 +1,107 @@ +/* Copyright (C) 2010 P.L. Lucas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __SYNTAX_H__ +#define __SYNTAX_H__ + +#include <QSyntaxHighlighter> +#include <QTextBlockUserData> +#include <QVector> +#include <QPlainTextEdit> + +class BlockData:public QTextBlockUserData +{ + public: + BlockData(); + + struct Bracket + { + int type; //Type of bracket + int pos; //Position of bracket + int length; //Number of chars of bracket + bool startBracketOk; //Is it a start or end bracket? + }; + + QVector <Bracket> brackets; +}; + +class SyntaxHighlighter:public QSyntaxHighlighter +{ + Q_OBJECT + + struct HighlightingRule + { + QRegExp pattern; + QTextCharFormat format; + int ruleOrder; + int lastFound; + }; + + QVector<HighlightingRule> highlightingRules; + + struct HighlightingBlockRule + { + QRegExp startPattern, endPattern; + QTextCharFormat format; + int ruleOrder; + }; + + QVector<HighlightingBlockRule> highlightingBlockRules; + QVector<HighlightingBlockRule> highlightingBracketsRules; + + struct Rule1st + { + int rule; + int startIndex; + int length; + int ruleOrder; + }; + + /**1st rule to apply from startIndex. + */ + Rule1st highlight1stRule(const QString & text, int startIndex); + + /**1st block rule to apply from startIndex. + */ + Rule1st highlight1stBlockRule(const QString & text, int startIndex); + + /** Set format using rule. + */ + int ruleSetFormat(Rule1st rule); + + /** Set format using block rule. + */ + int blockRuleSetFormat(const QString & text, Rule1st rule1st); + + /** Finds brackets and put them in BlockData. + */ + void findBrackets(const QString & text, int start, int end, BlockData *blockData); + + public: + + SyntaxHighlighter(QObject * parent = 0); + bool load(QString file); + + /**Formats pair of brackets + */ + void setFormatPairBrackets(QPlainTextEdit *textEdit); + + protected: + void highlightBlock ( const QString & text ); +}; +#endif
new file mode 100644 --- /dev/null +++ b/gui/src/TerminalCharacterDecoder.cpp @@ -0,0 +1,247 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 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 Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "TerminalCharacterDecoder.h" + +// Qt +#include <QtCore/QTextStream> + +// Konsole +#include "konsole_wcwidth.h" + +PlainTextDecoder::PlainTextDecoder() + : _output(0) + , _includeTrailingWhitespace(true) + , _recordLinePositions(false) +{ + +} +void PlainTextDecoder::setTrailingWhitespace(bool enable) +{ + _includeTrailingWhitespace = enable; +} +bool PlainTextDecoder::trailingWhitespace() const +{ + return _includeTrailingWhitespace; +} +void PlainTextDecoder::begin(QTextStream* output) +{ + _output = output; + if (!_linePositions.isEmpty()) + _linePositions.clear(); +} +void PlainTextDecoder::end() +{ + _output = 0; +} + +void PlainTextDecoder::setRecordLinePositions(bool record) +{ + _recordLinePositions = record; +} +QList<int> PlainTextDecoder::linePositions() const +{ + return _linePositions; +} +void PlainTextDecoder::decodeLine(const Character* const characters, int count, LineProperty /*properties*/ + ) +{ + Q_ASSERT( _output ); + + if (_recordLinePositions && _output->string()) + { + int pos = _output->string()->count(); + _linePositions << pos; + } + + //TODO should we ignore or respect the LINE_WRAPPED line property? + + //note: we build up a QString and send it to the text stream rather writing into the text + //stream a character at a time because it is more efficient. + //(since QTextStream always deals with QStrings internally anyway) + QString plainText; + plainText.reserve(count); + + int outputCount = count; + + // if inclusion of trailing whitespace is disabled then find the end of the + // line + if ( !_includeTrailingWhitespace ) + { + for (int i = count-1 ; i >= 0 ; i--) + { + if ( characters[i].character != ' ' ) + break; + else + outputCount--; + } + } + + for (int i=0;i<outputCount;) + { + plainText.append( QChar(characters[i].character) ); + i += qMax(1,konsole_wcwidth(characters[i].character)); + } + *_output << plainText; +} + +HTMLDecoder::HTMLDecoder() : + _output(0) + ,_colorTable(base_color_table) + ,_innerSpanOpen(false) + ,_lastRendition(DEFAULT_RENDITION) +{ + +} + +void HTMLDecoder::begin(QTextStream* output) +{ + _output = output; + + QString text; + + //open monospace span + openSpan(text,"font-family:monospace"); + + *output << text; +} + +void HTMLDecoder::end() +{ + Q_ASSERT( _output ); + + QString text; + + closeSpan(text); + + *_output << text; + + _output = 0; + +} + +//TODO: Support for LineProperty (mainly double width , double height) +void HTMLDecoder::decodeLine(const Character* const characters, int count, LineProperty /*properties*/ + ) +{ + Q_ASSERT( _output ); + + QString text; + + int spaceCount = 0; + + for (int i=0;i<count;i++) + { + QChar ch(characters[i].character); + + //check if appearance of character is different from previous char + if ( characters[i].rendition != _lastRendition || + characters[i].foregroundColor != _lastForeColor || + characters[i].backgroundColor != _lastBackColor ) + { + if ( _innerSpanOpen ) + closeSpan(text); + + _lastRendition = characters[i].rendition; + _lastForeColor = characters[i].foregroundColor; + _lastBackColor = characters[i].backgroundColor; + + //build up style string + QString style; + + bool useBold; + ColorEntry::FontWeight weight = characters[i].fontWeight(_colorTable); + if (weight == ColorEntry::UseCurrentFormat) + useBold = _lastRendition & RE_BOLD; + else + useBold = weight == ColorEntry::Bold; + + if (useBold) + style.append("font-weight:bold;"); + + if ( _lastRendition & RE_UNDERLINE ) + style.append("font-decoration:underline;"); + + //colours - a colour table must have been defined first + if ( _colorTable ) + { + style.append( QString("color:%1;").arg(_lastForeColor.color(_colorTable).name() ) ); + + if (!characters[i].isTransparent(_colorTable)) + { + style.append( QString("background-color:%1;").arg(_lastBackColor.color(_colorTable).name() ) ); + } + } + + //open the span with the current style + openSpan(text,style); + _innerSpanOpen = true; + } + + //handle whitespace + if (ch.isSpace()) + spaceCount++; + else + spaceCount = 0; + + + //output current character + if (spaceCount < 2) + { + //escape HTML tag characters and just display others as they are + if ( ch == '<' ) + text.append("<"); + else if (ch == '>') + text.append(">"); + else + text.append(ch); + } + else + { + text.append(" "); //HTML truncates multiple spaces, so use a space marker instead + } + + } + + //close any remaining open inner spans + if ( _innerSpanOpen ) + closeSpan(text); + + //start new line + text.append("<br>"); + + *_output << text; +} +void HTMLDecoder::openSpan(QString& text , const QString& style) +{ + text.append( QString("<span style=\"%1\">").arg(style) ); +} + +void HTMLDecoder::closeSpan(QString& text) +{ + text.append("</span>"); +} + +void HTMLDecoder::setColorTable(const ColorEntry* table) +{ + _colorTable = table; +}
new file mode 100644 --- /dev/null +++ b/gui/src/TerminalCharacterDecoder.h @@ -0,0 +1,145 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 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 Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef TERMINAL_CHARACTER_DECODER_H +#define TERMINAL_CHARACTER_DECODER_H + +#include "Character.h" + +#include <QList> + +class QTextStream; + +/** + * Base class for terminal character decoders + * + * The decoder converts lines of terminal characters which consist of a unicode character, foreground + * and background colours and other appearance-related properties into text strings. + * + * Derived classes may produce either plain text with no other colour or appearance information, or + * they may produce text which incorporates these additional properties. + */ +class TerminalCharacterDecoder +{ +public: + virtual ~TerminalCharacterDecoder() {} + + /** Begin decoding characters. The resulting text is appended to @p output. */ + virtual void begin(QTextStream* output) = 0; + /** End decoding. */ + virtual void end() = 0; + + /** + * Converts a line of terminal characters with associated properties into a text string + * and writes the string into an output QTextStream. + * + * @param characters An array of characters of length @p count. + * @param count The number of characters + * @param properties Additional properties which affect all characters in the line + */ + virtual void decodeLine(const Character* const characters, + int count, + LineProperty properties) = 0; +}; + +/** + * A terminal character decoder which produces plain text, ignoring colours and other appearance-related + * properties of the original characters. + */ +class PlainTextDecoder : public TerminalCharacterDecoder +{ +public: + PlainTextDecoder(); + + /** + * Set whether trailing whitespace at the end of lines should be included + * in the output. + * Defaults to true. + */ + void setTrailingWhitespace(bool enable); + /** + * Returns whether trailing whitespace at the end of lines is included + * in the output. + */ + bool trailingWhitespace() const; + /** + * Returns of character positions in the output stream + * at which new lines where added. Returns an empty if setTrackLinePositions() is false or if + * the output device is not a string. + */ + QList<int> linePositions() const; + /** Enables recording of character positions at which new lines are added. See linePositions() */ + void setRecordLinePositions(bool record); + + virtual void begin(QTextStream* output); + virtual void end(); + + virtual void decodeLine(const Character* const characters, + int count, + LineProperty properties); + + +private: + QTextStream* _output; + bool _includeTrailingWhitespace; + + bool _recordLinePositions; + QList<int> _linePositions; +}; + +/** + * A terminal character decoder which produces pretty HTML markup + */ +class HTMLDecoder : public TerminalCharacterDecoder +{ +public: + /** + * Constructs an HTML decoder using a default black-on-white color scheme. + */ + HTMLDecoder(); + + /** + * Sets the colour table which the decoder uses to produce the HTML colour codes in its + * output + */ + void setColorTable( const ColorEntry* table ); + + virtual void decodeLine(const Character* const characters, + int count, + LineProperty properties); + + virtual void begin(QTextStream* output); + virtual void end(); + +private: + void openSpan(QString& text , const QString& style); + void closeSpan(QString& text); + + QTextStream* _output; + const ColorEntry* _colorTable; + bool _innerSpanOpen; + quint8 _lastRendition; + CharacterColor _lastForeColor; + CharacterColor _lastBackColor; + +}; + +#endif
new file mode 100644 --- /dev/null +++ b/gui/src/TerminalDisplay.cpp @@ -0,0 +1,2975 @@ +/* + This file is part of Konsole, a terminal emulator for KDE. + + Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "TerminalDisplay.h" + +// Qt +#include <QtGui/QApplication> +#include <QtGui/QBoxLayout> +#include <QtGui/QClipboard> +#include <QtGui/QKeyEvent> +#include <QtCore/QEvent> +#include <QtCore/QTime> +#include <QtCore/QFile> +#include <QtGui/QGridLayout> +#include <QtGui/QLabel> +#include <QtGui/QLayout> +#include <QtGui/QPainter> +#include <QtGui/QPixmap> +#include <QtGui/QScrollBar> +#include <QtGui/QStyle> +#include <QtCore/QTimer> +#include <QtGui/QToolTip> +#include <QtCore/QTextStream> + +#include "Filter.h" +#include "konsole_wcwidth.h" +#include "ScreenWindow.h" +#include "TerminalCharacterDecoder.h" + +#ifndef loc +#define loc(X,Y) ((Y)*_columns+(X)) +#endif + +#define yMouseScroll 1 + +#define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "abcdefgjijklmnopqrstuvwxyz" \ + "0123456789./+@" + +const ColorEntry base_color_table[TABLE_COLORS] = +// The following are almost IBM standard color codes, with some slight +// gamma correction for the dim colors to compensate for bright X screens. +// It contains the 8 ansiterm/xterm colors in 2 intensities. +{ + // Fixme: could add faint colors here, also. + // normal + ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 1), // Dfore, Dback + ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0x18,0x18), 0), // Black, Red + ColorEntry(QColor(0x18,0xB2,0x18), 0), ColorEntry( QColor(0xB2,0x68,0x18), 0), // Green, Yellow + ColorEntry(QColor(0x18,0x18,0xB2), 0), ColorEntry( QColor(0xB2,0x18,0xB2), 0), // Blue, Magenta + ColorEntry(QColor(0x18,0xB2,0xB2), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 0), // Cyan, White + // intensiv + ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 1), + ColorEntry(QColor(0x68,0x68,0x68), 0), ColorEntry( QColor(0xFF,0x54,0x54), 0), + ColorEntry(QColor(0x54,0xFF,0x54), 0), ColorEntry( QColor(0xFF,0xFF,0x54), 0), + ColorEntry(QColor(0x54,0x54,0xFF), 0), ColorEntry( QColor(0xFF,0x54,0xFF), 0), + ColorEntry(QColor(0x54,0xFF,0xFF), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 0) +}; + +// scroll increment used when dragging selection at top/bottom of window. + +// static +bool TerminalDisplay::_antialiasText = true; +bool TerminalDisplay::HAVE_TRANSPARENCY = false; + +// we use this to force QPainter to display text in LTR mode +// more information can be found in: http://unicode.org/reports/tr9/ +const QChar LTR_OVERRIDE_CHAR( 0x202D ); + +/* ------------------------------------------------------------------------- */ +/* */ +/* Colors */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb) + + Code 0 1 2 3 4 5 6 7 + ----------- ------- ------- ------- ------- ------- ------- ------- ------- + ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White + IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White +*/ + +ScreenWindow* TerminalDisplay::screenWindow() const +{ + return _screenWindow; +} +void TerminalDisplay::setScreenWindow(ScreenWindow* window) +{ + // disconnect existing screen window if any + if ( _screenWindow ) + { + disconnect( _screenWindow , 0 , this , 0 ); + } + + _screenWindow = window; + + if ( window ) + { + +// TODO: Determine if this is an issue. +//#warning "The order here is not specified - does it matter whether updateImage or updateLineProperties comes first?" + connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateLineProperties()) ); + connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateImage()) ); + window->setWindowLines(_lines); + } +} + +const ColorEntry* TerminalDisplay::colorTable() const +{ + return _colorTable; +} +void TerminalDisplay::setBackgroundColor(const QColor& color) +{ + _colorTable[DEFAULT_BACK_COLOR].color = color; + QPalette p = palette(); + p.setColor( backgroundRole(), color ); + setPalette( p ); + + // Avoid propagating the palette change to the scroll bar + _scrollBar->setPalette( QApplication::palette() ); + + update(); +} +void TerminalDisplay::setForegroundColor(const QColor& color) +{ + _colorTable[DEFAULT_FORE_COLOR].color = color; + + update(); +} +void TerminalDisplay::setColorTable(const ColorEntry table[]) +{ + for (int i = 0; i < TABLE_COLORS; i++) + _colorTable[i] = table[i]; + + setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Font */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* + The VT100 has 32 special graphical characters. The usual vt100 extended + xterm fonts have these at 0x00..0x1f. + + QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals + come in here as proper unicode characters. + + We treat non-iso10646 fonts as VT100 extended and do the requiered mapping + from unicode to 0x00..0x1f. The remaining translation is then left to the + QCodec. +*/ + +static inline bool isLineChar(quint16 c) { return ((c & 0xFF80) == 0x2500);} +static inline bool isLineCharString(const QString& string) +{ + return (string.length() > 0) && (isLineChar(string.at(0).unicode())); +} + + +// assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i. + +unsigned short vt100_graphics[32] = +{ // 0/8 1/9 2/10 3/11 4/12 5/13 6/14 7/15 + 0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, + 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, + 0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534, + 0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7 +}; + +void TerminalDisplay::fontChange(const QFont&) +{ + QFontMetrics fm(font()); + _fontHeight = fm.height() + _lineSpacing; + + // waba TerminalDisplay 1.123: + // "Base character width on widest ASCII character. This prevents too wide + // characters in the presence of double wide (e.g. Japanese) characters." + // Get the width from representative normal width characters + _fontWidth = qRound((double)fm.width(REPCHAR)/(double)strlen(REPCHAR)); + + _fixedFont = true; + + int fw = fm.width(REPCHAR[0]); + for(unsigned int i=1; i< strlen(REPCHAR); i++) + { + if (fw != fm.width(REPCHAR[i])) + { + _fixedFont = false; + break; + } + } + + if (_fontWidth < 1) + _fontWidth=1; + + _fontAscent = fm.ascent(); + + emit changedFontMetricSignal( _fontHeight, _fontWidth ); + propagateSize(); + update(); +} + +void TerminalDisplay::setVTFont(const QFont& f) +{ + QFont font = f; + + QFontMetrics metrics(font); + + if ( !QFontInfo(font).fixedPitch() ) + { + //kWarning() << "Using an unsupported variable-width font in the terminal. This may produce display errors."; + } + + if ( metrics.height() < height() && metrics.maxWidth() < width() ) + { + // hint that text should be drawn without anti-aliasing. + // depending on the user's font configuration, this may not be respected + if (!_antialiasText) + font.setStyleStrategy( QFont::NoAntialias ); + + // experimental optimization. Konsole assumes that the terminal is using a + // mono-spaced font, in which case kerning information should have an effect. + // Disabling kerning saves some computation when rendering text. + font.setKerning(false); + + QWidget::setFont(font); + fontChange(font); + } +} + +void TerminalDisplay::setFont(const QFont &) +{ + // ignore font change request if not coming from konsole itself +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Constructor / Destructor */ +/* */ +/* ------------------------------------------------------------------------- */ + +TerminalDisplay::TerminalDisplay(QWidget *parent) +:QWidget(parent) +,_screenWindow(0) +,_allowBell(true) +,_gridLayout(0) +,_fontHeight(1) +,_fontWidth(1) +,_fontAscent(1) +,_boldIntense(true) +,_lines(1) +,_columns(1) +,_usedLines(1) +,_usedColumns(1) +,_contentHeight(1) +,_contentWidth(1) +,_image(0) +,_randomSeed(0) +,_resizing(false) +,_terminalSizeHint(false) +,_terminalSizeStartup(true) +,_bidiEnabled(false) +,_actSel(0) +,_wordSelectionMode(false) +,_lineSelectionMode(false) +,_preserveLineBreaks(false) +,_columnSelectionMode(false) +,_scrollbarLocation(NoScrollBar) +,_wordCharacters(":@-./_~") +,_bellMode(SystemBeepBell) +,_blinking(false) +,_hasBlinker(false) +,_cursorBlinking(false) +,_hasBlinkingCursor(false) +,_allowBlinkingText(true) +,_ctrlDrag(true) +,_tripleClickMode(SelectWholeLine) +,_isFixedSize(false) +,_possibleTripleClick(false) +,_resizeWidget(0) +,_resizeTimer(0) +,_flowControlWarningEnabled(false) +,_outputSuspendedLabel(0) +,_lineSpacing(0) +,_colorsInverted(false) +,_blendColor(qRgba(0,0,0,0xff)) +,_filterChain(new TerminalImageFilterChain()) +,_cursorShape(BlockCursor) +{ + // terminal applications are not designed with Right-To-Left in mind, + // so the layout is forced to Left-To-Right + setLayoutDirection(Qt::LeftToRight); + + // The offsets are not yet calculated. + // Do not calculate these too often to be more smoothly when resizing + // konsole in opaque mode. + _topMargin = DEFAULT_TOP_MARGIN; + _leftMargin = DEFAULT_LEFT_MARGIN; + + // create scroll bar for scrolling output up and down + // set the scroll bar's slider to occupy the whole area of the scroll bar initially + _scrollBar = new QScrollBar(this); + setScroll(0,0); + _scrollBar->setCursor( Qt::ArrowCursor ); + connect(_scrollBar, SIGNAL(valueChanged(int)), this, + SLOT(scrollBarPositionChanged(int))); + + // setup timers for blinking cursor and text + _blinkTimer = new QTimer(this); + connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent())); + _blinkCursorTimer = new QTimer(this); + connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent())); + + //KCursor::setAutoHideCursor( this, true ); + + setUsesMouse(true); + setColorTable(base_color_table); + setMouseTracking(true); + + // Enable drag and drop + setAcceptDrops(true); // attempt + dragInfo.state = diNone; + + setFocusPolicy( Qt::WheelFocus ); + + // enable input method support + setAttribute(Qt::WA_InputMethodEnabled, true); + + // this is an important optimization, it tells Qt + // that TerminalDisplay will handle repainting its entire area. + setAttribute(Qt::WA_OpaquePaintEvent); + + _gridLayout = new QGridLayout(this); + _gridLayout->setContentsMargins(0, 0, 0, 0); + + setLayout( _gridLayout ); + + new AutoScrollHandler(this); +} + +TerminalDisplay::~TerminalDisplay() +{ + disconnect(_blinkTimer); + disconnect(_blinkCursorTimer); + qApp->removeEventFilter( this ); + + delete[] _image; + + delete _gridLayout; + delete _outputSuspendedLabel; + delete _filterChain; +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Display Operations */ +/* */ +/* ------------------------------------------------------------------------- */ + +/** + A table for emulating the simple (single width) unicode drawing chars. + It represents the 250x - 257x glyphs. If it's zero, we can't use it. + if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered + 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit. + + Then, the pixels basically have the following interpretation: + _|||_ + -...- + -...- + -...- + _|||_ + +where _ = none + | = vertical line. + - = horizontal line. + */ + + +enum LineEncode +{ + TopL = (1<<1), + TopC = (1<<2), + TopR = (1<<3), + + LeftT = (1<<5), + Int11 = (1<<6), + Int12 = (1<<7), + Int13 = (1<<8), + RightT = (1<<9), + + LeftC = (1<<10), + Int21 = (1<<11), + Int22 = (1<<12), + Int23 = (1<<13), + RightC = (1<<14), + + LeftB = (1<<15), + Int31 = (1<<16), + Int32 = (1<<17), + Int33 = (1<<18), + RightB = (1<<19), + + BotL = (1<<21), + BotC = (1<<22), + BotR = (1<<23) +}; + +#include "LineFont.h" + +static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code) +{ + //Calculate cell midpoints, end points. + int cx = x + w/2; + int cy = y + h/2; + int ex = x + w - 1; + int ey = y + h - 1; + + quint32 toDraw = LineChars[code]; + + //Top _lines: + if (toDraw & TopL) + paint.drawLine(cx-1, y, cx-1, cy-2); + if (toDraw & TopC) + paint.drawLine(cx, y, cx, cy-2); + if (toDraw & TopR) + paint.drawLine(cx+1, y, cx+1, cy-2); + + //Bot _lines: + if (toDraw & BotL) + paint.drawLine(cx-1, cy+2, cx-1, ey); + if (toDraw & BotC) + paint.drawLine(cx, cy+2, cx, ey); + if (toDraw & BotR) + paint.drawLine(cx+1, cy+2, cx+1, ey); + + //Left _lines: + if (toDraw & LeftT) + paint.drawLine(x, cy-1, cx-2, cy-1); + if (toDraw & LeftC) + paint.drawLine(x, cy, cx-2, cy); + if (toDraw & LeftB) + paint.drawLine(x, cy+1, cx-2, cy+1); + + //Right _lines: + if (toDraw & RightT) + paint.drawLine(cx+2, cy-1, ex, cy-1); + if (toDraw & RightC) + paint.drawLine(cx+2, cy, ex, cy); + if (toDraw & RightB) + paint.drawLine(cx+2, cy+1, ex, cy+1); + + //Intersection points. + if (toDraw & Int11) + paint.drawPoint(cx-1, cy-1); + if (toDraw & Int12) + paint.drawPoint(cx, cy-1); + if (toDraw & Int13) + paint.drawPoint(cx+1, cy-1); + + if (toDraw & Int21) + paint.drawPoint(cx-1, cy); + if (toDraw & Int22) + paint.drawPoint(cx, cy); + if (toDraw & Int23) + paint.drawPoint(cx+1, cy); + + if (toDraw & Int31) + paint.drawPoint(cx-1, cy+1); + if (toDraw & Int32) + paint.drawPoint(cx, cy+1); + if (toDraw & Int33) + paint.drawPoint(cx+1, cy+1); + +} + +void TerminalDisplay::drawLineCharString( QPainter& painter, int x, int y, const QString& str, + const Character* attributes) +{ + const QPen& currentPen = painter.pen(); + + if ( (attributes->rendition & RE_BOLD) && _boldIntense ) + { + QPen boldPen(currentPen); + boldPen.setWidth(3); + painter.setPen( boldPen ); + } + + for (int i=0 ; i < str.length(); i++) + { + uchar code = str[i].cell(); + if (LineChars[code]) + drawLineChar(painter, x + (_fontWidth*i), y, _fontWidth, _fontHeight, code); + } + + painter.setPen( currentPen ); +} + +void TerminalDisplay::setKeyboardCursorShape(KeyboardCursorShape shape) +{ + _cursorShape = shape; +} +TerminalDisplay::KeyboardCursorShape TerminalDisplay::keyboardCursorShape() const +{ + return _cursorShape; +} +void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor& color) +{ + if (useForegroundColor) + _cursorColor = QColor(); // an invalid color means that + // the foreground color of the + // current character should + // be used + + else + _cursorColor = color; +} +QColor TerminalDisplay::keyboardCursorColor() const +{ + return _cursorColor; +} + +void TerminalDisplay::setOpacity(qreal opacity) +{ + QColor color(_blendColor); + color.setAlphaF(opacity); + + // enable automatic background filling to prevent the display + // flickering if there is no transparency + /*if ( color.alpha() == 255 ) + { + setAutoFillBackground(true); + } + else + { + setAutoFillBackground(false); + }*/ + + _blendColor = color.rgba(); +} + +void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting ) +{ + // the area of the widget showing the contents of the terminal display is drawn + // using the background color from the color scheme set with setColorTable() + // + // the area of the widget behind the scroll-bar is drawn using the background + // brush from the scroll-bar's palette, to give the effect of the scroll-bar + // being outside of the terminal display and visual consistency with other KDE + // applications. + // + QRect scrollBarArea = _scrollBar->isVisible() ? + rect.intersected(_scrollBar->geometry()) : + QRect(); + QRegion contentsRegion = QRegion(rect).subtracted(scrollBarArea); + QRect contentsRect = contentsRegion.boundingRect(); + + if ( HAVE_TRANSPARENCY && qAlpha(_blendColor) < 0xff && useOpacitySetting ) + { + QColor color(backgroundColor); + color.setAlpha(qAlpha(_blendColor)); + + painter.save(); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.fillRect(contentsRect, color); + painter.restore(); + } + else + painter.fillRect(contentsRect, backgroundColor); + + painter.fillRect(scrollBarArea,_scrollBar->palette().background()); +} + +void TerminalDisplay::drawCursor(QPainter& painter, + const QRect& rect, + const QColor& foregroundColor, + const QColor& /*backgroundColor*/, + bool& invertCharacterColor) +{ + QRect cursorRect = rect; + cursorRect.setHeight(_fontHeight - _lineSpacing - 1); + + if (!_cursorBlinking) + { + if ( _cursorColor.isValid() ) + painter.setPen(_cursorColor); + else + painter.setPen(foregroundColor); + + if ( _cursorShape == BlockCursor ) + { + // draw the cursor outline, adjusting the area so that + // it is draw entirely inside 'rect' + int penWidth = qMax(1,painter.pen().width()); + + painter.drawRect(cursorRect.adjusted(penWidth/2, + penWidth/2, + - penWidth/2 - penWidth%2, + - penWidth/2 - penWidth%2)); + if ( hasFocus() ) + { + painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor); + + if ( !_cursorColor.isValid() ) + { + // invert the colour used to draw the text to ensure that the character at + // the cursor position is readable + invertCharacterColor = true; + } + } + } + else if ( _cursorShape == UnderlineCursor ) + painter.drawLine(cursorRect.left(), + cursorRect.bottom(), + cursorRect.right(), + cursorRect.bottom()); + else if ( _cursorShape == IBeamCursor ) + painter.drawLine(cursorRect.left(), + cursorRect.top(), + cursorRect.left(), + cursorRect.bottom()); + + } +} + +void TerminalDisplay::drawCharacters(QPainter& painter, + const QRect& rect, + const QString& text, + const Character* style, + bool invertCharacterColor) +{ + // don't draw text which is currently blinking + if ( _blinking && (style->rendition & RE_BLINK) ) + return; + + // setup bold and underline + bool useBold; + ColorEntry::FontWeight weight = style->fontWeight(_colorTable); + if (weight == ColorEntry::UseCurrentFormat) + useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold(); + else + useBold = (weight == ColorEntry::Bold) ? true : false; + bool useUnderline = style->rendition & RE_UNDERLINE || font().underline(); + + QFont font = painter.font(); + if ( font.bold() != useBold + || font.underline() != useUnderline ) + { + font.setBold(useBold); + font.setUnderline(useUnderline); + painter.setFont(font); + } + + // setup pen + const CharacterColor& textColor = ( invertCharacterColor ? style->backgroundColor : style->foregroundColor ); + const QColor color = textColor.color(_colorTable); + QPen pen = painter.pen(); + if ( pen.color() != color ) + { + pen.setColor(color); + painter.setPen(color); + } + + // draw text + if ( isLineCharString(text) ) + drawLineCharString(painter,rect.x(),rect.y(),text,style); + else + { + // the drawText(rect,flags,string) overload is used here with null flags + // instead of drawText(rect,string) because the (rect,string) overload causes + // the application's default layout direction to be used instead of + // the widget-specific layout direction, which should always be + // Qt::LeftToRight for this widget + // This was discussed in: http://lists.kde.org/?t=120552223600002&r=1&w=2 + if (_bidiEnabled) + painter.drawText(rect,0,text); + else + painter.drawText(rect,0,LTR_OVERRIDE_CHAR+text); + } +} + +void TerminalDisplay::drawTextFragment(QPainter& painter , + const QRect& rect, + const QString& text, + const Character* style) +{ + painter.save(); + + // setup painter + const QColor foregroundColor = style->foregroundColor.color(_colorTable); + const QColor backgroundColor = style->backgroundColor.color(_colorTable); + + // draw background if different from the display's background color + if ( backgroundColor != palette().background().color() ) + drawBackground(painter,rect,backgroundColor, + false /* do not use transparency */); + + // draw cursor shape if the current character is the cursor + // this may alter the foreground and background colors + bool invertCharacterColor = false; + if ( style->rendition & RE_CURSOR ) + drawCursor(painter,rect,foregroundColor,backgroundColor,invertCharacterColor); + + // draw text + drawCharacters(painter,rect,text,style,invertCharacterColor); + + painter.restore(); +} + +void TerminalDisplay::setRandomSeed(uint randomSeed) { _randomSeed = randomSeed; } +uint TerminalDisplay::randomSeed() const { return _randomSeed; } + +#if 0 +/*! + Set XIM Position +*/ +void TerminalDisplay::setCursorPos(const int curx, const int cury) +{ + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + + int xpos, ypos; + ypos = _topMargin + tLy + _fontHeight*(cury-1) + _fontAscent; + xpos = _leftMargin + tLx + _fontWidth*curx; + _cursorLine = cury; + _cursorCol = curx; +} +#endif + +// scrolls the image by 'lines', down if lines > 0 or up otherwise. +// +// the terminal emulation keeps track of the scrolling of the character +// image as it receives input, and when the view is updated, it calls scrollImage() +// with the final scroll amount. this improves performance because scrolling the +// display is much cheaper than re-rendering all the text for the +// part of the image which has moved up or down. +// Instead only new lines have to be drawn +void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion) +{ + // if the flow control warning is enabled this will interfere with the + // scrolling optimizations and cause artifacts. the simple solution here + // is to just disable the optimization whilst it is visible + if ( _outputSuspendedLabel && _outputSuspendedLabel->isVisible() ) + return; + + // constrain the region to the display + // the bottom of the region is capped to the number of lines in the display's + // internal image - 2, so that the height of 'region' is strictly less + // than the height of the internal image. + QRect region = screenWindowRegion; + region.setBottom( qMin(region.bottom(),this->_lines-2) ); + + // return if there is nothing to do + if ( lines == 0 + || _image == 0 + || !region.isValid() + || (region.top() + abs(lines)) >= region.bottom() + || this->_lines <= region.height() ) return; + + // hide terminal size label to prevent it being scrolled + if (_resizeWidget && _resizeWidget->isVisible()) + _resizeWidget->hide(); + + // Note: With Qt 4.4 the left edge of the scrolled area must be at 0 + // to get the correct (newly exposed) part of the widget repainted. + // + // The right edge must be before the left edge of the scroll bar to + // avoid triggering a repaint of the entire widget, the distance is + // given by SCROLLBAR_CONTENT_GAP + // + // Set the QT_FLUSH_PAINT environment variable to '1' before starting the + // application to monitor repainting. + // + int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->width(); + const int SCROLLBAR_CONTENT_GAP = 1; + QRect scrollRect; + if ( _scrollbarLocation == ScrollBarLeft ) + { + scrollRect.setLeft(scrollBarWidth+SCROLLBAR_CONTENT_GAP); + scrollRect.setRight(width()); + } + else + { + scrollRect.setLeft(0); + scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP); + } + void* firstCharPos = &_image[ region.top() * this->_columns ]; + void* lastCharPos = &_image[ (region.top() + abs(lines)) * this->_columns ]; + + int top = _topMargin + (region.top() * _fontHeight); + int linesToMove = region.height() - abs(lines); + int bytesToMove = linesToMove * + this->_columns * + sizeof(Character); + + Q_ASSERT( linesToMove > 0 ); + Q_ASSERT( bytesToMove > 0 ); + + //scroll internal image + if ( lines > 0 ) + { + // check that the memory areas that we are going to move are valid + Q_ASSERT( (char*)lastCharPos + bytesToMove < + (char*)(_image + (this->_lines * this->_columns)) ); + + Q_ASSERT( (lines*this->_columns) < _imageSize ); + + //scroll internal image down + memmove( firstCharPos , lastCharPos , bytesToMove ); + + //set region of display to scroll + scrollRect.setTop(top); + } + else + { + // check that the memory areas that we are going to move are valid + Q_ASSERT( (char*)firstCharPos + bytesToMove < + (char*)(_image + (this->_lines * this->_columns)) ); + + //scroll internal image up + memmove( lastCharPos , firstCharPos , bytesToMove ); + + //set region of the display to scroll + scrollRect.setTop(top + abs(lines) * _fontHeight); + } + scrollRect.setHeight(linesToMove * _fontHeight ); + + Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty()); + + //scroll the display vertically to match internal _image + scroll( 0 , _fontHeight * (-lines) , scrollRect ); +} + +QRegion TerminalDisplay::hotSpotRegion() const +{ + QRegion region; + foreach( Filter::HotSpot* hotSpot , _filterChain->hotSpots() ) + { + QRect r; + if (hotSpot->startLine()==hotSpot->endLine()) { + r.setLeft(hotSpot->startColumn()); + r.setTop(hotSpot->startLine()); + r.setRight(hotSpot->endColumn()); + r.setBottom(hotSpot->endLine()); + region |= imageToWidget(r);; + } else { + r.setLeft(hotSpot->startColumn()); + r.setTop(hotSpot->startLine()); + r.setRight(_columns); + r.setBottom(hotSpot->startLine()); + region |= imageToWidget(r);; + for ( int line = hotSpot->startLine()+1 ; line < hotSpot->endLine() ; line++ ) { + r.setLeft(0); + r.setTop(line); + r.setRight(_columns); + r.setBottom(line); + region |= imageToWidget(r);; + } + r.setLeft(0); + r.setTop(hotSpot->endLine()); + r.setRight(hotSpot->endColumn()); + r.setBottom(hotSpot->endLine()); + region |= imageToWidget(r);; + } + } + return region; +} + +void TerminalDisplay::processFilters() +{ + if (!_screenWindow) + return; + + QRegion preUpdateHotSpots = hotSpotRegion(); + + // use _screenWindow->getImage() here rather than _image because + // other classes may call processFilters() when this display's + // ScreenWindow emits a scrolled() signal - which will happen before + // updateImage() is called on the display and therefore _image is + // out of date at this point + _filterChain->setImage( _screenWindow->getImage(), + _screenWindow->windowLines(), + _screenWindow->windowColumns(), + _screenWindow->getLineProperties() ); + _filterChain->process(); + + QRegion postUpdateHotSpots = hotSpotRegion(); + + update( preUpdateHotSpots | postUpdateHotSpots ); +} + +void TerminalDisplay::updateImage() +{ + if ( !_screenWindow ) + return; + + // optimization - scroll the existing image where possible and + // avoid expensive text drawing for parts of the image that + // can simply be moved up or down + scrollImage( _screenWindow->scrollCount() , + _screenWindow->scrollRegion() ); + _screenWindow->resetScrollCount(); + + if (!_image) { + // Create _image. + // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first. + updateImageSize(); + } + + Character* const newimg = _screenWindow->getImage(); + int lines = _screenWindow->windowLines(); + int columns = _screenWindow->windowColumns(); + + setScroll( _screenWindow->currentLine() , _screenWindow->lineCount() ); + + Q_ASSERT( this->_usedLines <= this->_lines ); + Q_ASSERT( this->_usedColumns <= this->_columns ); + + int y,x,len; + + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + _hasBlinker = false; + + CharacterColor cf; // undefined + CharacterColor _clipboard; // undefined + int cr = -1; // undefined + + const int linesToUpdate = qMin(this->_lines, qMax(0,lines )); + const int columnsToUpdate = qMin(this->_columns,qMax(0,columns)); + + QChar *disstrU = new QChar[columnsToUpdate]; + char *dirtyMask = new char[columnsToUpdate+2]; + QRegion dirtyRegion; + + // debugging variable, this records the number of lines that are found to + // be 'dirty' ( ie. have changed from the old _image to the new _image ) and + // which therefore need to be repainted + int dirtyLineCount = 0; + + for (y = 0; y < linesToUpdate; ++y) + { + const Character* currentLine = &_image[y*this->_columns]; + const Character* const newLine = &newimg[y*columns]; + + bool updateLine = false; + + // The dirty mask indicates which characters need repainting. We also + // mark surrounding neighbours dirty, in case the character exceeds + // its cell boundaries + memset(dirtyMask, 0, columnsToUpdate+2); + + for( x = 0 ; x < columnsToUpdate ; ++x) + { + if ( newLine[x] != currentLine[x] ) + { + dirtyMask[x] = true; + } + } + + if (!_resizing) // not while _resizing, we're expecting a paintEvent + for (x = 0; x < columnsToUpdate; ++x) + { + _hasBlinker |= (newLine[x].rendition & RE_BLINK); + + // Start drawing if this character or the next one differs. + // We also take the next one into account to handle the situation + // where characters exceed their cell width. + if (dirtyMask[x]) + { + quint16 c = newLine[x+0].character; + if ( !c ) + continue; + int p = 0; + disstrU[p++] = c; //fontMap(c); + bool lineDraw = isLineChar(c); + bool doubleWidth = (x+1 == columnsToUpdate) ? false : (newLine[x+1].character == 0); + cr = newLine[x].rendition; + _clipboard = newLine[x].backgroundColor; + if (newLine[x].foregroundColor != cf) cf = newLine[x].foregroundColor; + int lln = columnsToUpdate - x; + for (len = 1; len < lln; ++len) + { + const Character& ch = newLine[x+len]; + + if (!ch.character) + continue; // Skip trailing part of multi-col chars. + + bool nextIsDoubleWidth = (x+len+1 == columnsToUpdate) ? false : (newLine[x+len+1].character == 0); + + if ( ch.foregroundColor != cf || + ch.backgroundColor != _clipboard || + ch.rendition != cr || + !dirtyMask[x+len] || + isLineChar(c) != lineDraw || + nextIsDoubleWidth != doubleWidth ) + break; + + disstrU[p++] = c; //fontMap(c); + } + + QString unistr(disstrU, p); + + bool saveFixedFont = _fixedFont; + if (lineDraw) + _fixedFont = false; + if (doubleWidth) + _fixedFont = false; + + updateLine = true; + + _fixedFont = saveFixedFont; + x += len - 1; + } + + } + + //both the top and bottom halves of double height _lines must always be redrawn + //although both top and bottom halves contain the same characters, only + //the top one is actually + //drawn. + if (_lineProperties.count() > y) + updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT); + + // if the characters on the line are different in the old and the new _image + // then this line must be repainted. + if (updateLine) + { + dirtyLineCount++; + + // add the area occupied by this line to the region which needs to be + // repainted + QRect dirtyRect = QRect( _leftMargin+tLx , + _topMargin+tLy+_fontHeight*y , + _fontWidth * columnsToUpdate , + _fontHeight ); + + dirtyRegion |= dirtyRect; + } + + // replace the line of characters in the old _image with the + // current line of the new _image + memcpy((void*)currentLine,(const void*)newLine,columnsToUpdate*sizeof(Character)); + } + + // if the new _image is smaller than the previous _image, then ensure that the area + // outside the new _image is cleared + if ( linesToUpdate < _usedLines ) + { + dirtyRegion |= QRect( _leftMargin+tLx , + _topMargin+tLy+_fontHeight*linesToUpdate , + _fontWidth * this->_columns , + _fontHeight * (_usedLines-linesToUpdate) ); + } + _usedLines = linesToUpdate; + + if ( columnsToUpdate < _usedColumns ) + { + dirtyRegion |= QRect( _leftMargin+tLx+columnsToUpdate*_fontWidth , + _topMargin+tLy , + _fontWidth * (_usedColumns-columnsToUpdate) , + _fontHeight * this->_lines ); + } + _usedColumns = columnsToUpdate; + + dirtyRegion |= _inputMethodData.previousPreeditRect; + + // update the parts of the display which have changed + update(dirtyRegion); + + if ( _hasBlinker && !_blinkTimer->isActive()) _blinkTimer->start( TEXT_BLINK_DELAY ); + if (!_hasBlinker && _blinkTimer->isActive()) { _blinkTimer->stop(); _blinking = false; } + delete[] dirtyMask; + delete[] disstrU; + +} + +void TerminalDisplay::showResizeNotification() +{ + if (_terminalSizeHint && isVisible()) + { + if (_terminalSizeStartup) { + _terminalSizeStartup=false; + return; + } + if (!_resizeWidget) + { + _resizeWidget = new QLabel(QString("Size: XXX x XXX"), this); + _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width(QString("Size: XXX x XXX"))); + _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height()); + _resizeWidget->setAlignment(Qt::AlignCenter); + + _resizeWidget->setStyleSheet("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)"); + + _resizeTimer = new QTimer(this); + _resizeTimer->setSingleShot(true); + connect(_resizeTimer, SIGNAL(timeout()), _resizeWidget, SLOT(hide())); + } + QString sizeStr = QString("Size: %1 x %2").arg(_columns).arg(_lines); + _resizeWidget->setText(sizeStr); + _resizeWidget->move((width()-_resizeWidget->width())/2, + (height()-_resizeWidget->height())/2+20); + _resizeWidget->show(); + _resizeTimer->start(1000); + } +} + +void TerminalDisplay::setBlinkingCursor(bool blink) +{ + _hasBlinkingCursor=blink; + + if (blink && !_blinkCursorTimer->isActive()) + _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2); + + if (!blink && _blinkCursorTimer->isActive()) + { + _blinkCursorTimer->stop(); + if (_cursorBlinking) + blinkCursorEvent(); + else + _cursorBlinking = false; + } +} + +void TerminalDisplay::setBlinkingTextEnabled(bool blink) +{ + _allowBlinkingText = blink; + + if (blink && !_blinkTimer->isActive()) + _blinkTimer->start(TEXT_BLINK_DELAY); + + if (!blink && _blinkTimer->isActive()) + { + _blinkTimer->stop(); + _blinking = false; + } +} + +void TerminalDisplay::focusOutEvent(QFocusEvent*) +{ + // trigger a repaint of the cursor so that it is both visible (in case + // it was hidden during blinking) + // and drawn in a focused out state + _cursorBlinking = false; + updateCursor(); + + _blinkCursorTimer->stop(); + if (_blinking) + blinkEvent(); + + _blinkTimer->stop(); +} +void TerminalDisplay::focusInEvent(QFocusEvent*) +{ + if (_hasBlinkingCursor) + { + _blinkCursorTimer->start(); + } + updateCursor(); + + if (_hasBlinker) + _blinkTimer->start(); +} + +void TerminalDisplay::paintEvent( QPaintEvent* pe ) +{ + QPainter paint(this); + + foreach (const QRect &rect, (pe->region() & contentsRect()).rects()) + { + drawBackground(paint,rect,palette().background().color(), + true /* use opacity setting */); + drawContents(paint, rect); + } + drawInputMethodPreeditString(paint,preeditRect()); + paintFilters(paint); +} + +QPoint TerminalDisplay::cursorPosition() const +{ + if (_screenWindow) + return _screenWindow->cursorPosition(); + else + return QPoint(0,0); +} + +QRect TerminalDisplay::preeditRect() const +{ + const int preeditLength = string_width(_inputMethodData.preeditString); + + if ( preeditLength == 0 ) + return QRect(); + + return QRect(_leftMargin + _fontWidth*cursorPosition().x(), + _topMargin + _fontHeight*cursorPosition().y(), + _fontWidth*preeditLength, + _fontHeight); +} + +void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect) +{ + if ( _inputMethodData.preeditString.isEmpty() ) + return; + + const QPoint cursorPos = cursorPosition(); + + bool invertColors = false; + const QColor background = _colorTable[DEFAULT_BACK_COLOR].color; + const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color; + const Character* style = &_image[loc(cursorPos.x(),cursorPos.y())]; + + drawBackground(painter,rect,background,true); + drawCursor(painter,rect,foreground,background,invertColors); + drawCharacters(painter,rect,_inputMethodData.preeditString,style,invertColors); + + _inputMethodData.previousPreeditRect = rect; +} + +FilterChain* TerminalDisplay::filterChain() const +{ + return _filterChain; +} + +void TerminalDisplay::paintFilters(QPainter& painter) +{ + // get color of character under mouse and use it to draw + // lines for filters + QPoint cursorPos = mapFromGlobal(QCursor::pos()); + int cursorLine; + int cursorColumn; + int scrollBarWidth = (_scrollbarLocation == ScrollBarLeft) ? _scrollBar->width() : 0; + + getCharacterPosition( cursorPos , cursorLine , cursorColumn ); + Character cursorCharacter = _image[loc(cursorColumn,cursorLine)]; + + painter.setPen( QPen(cursorCharacter.foregroundColor.color(colorTable())) ); + + // iterate over hotspots identified by the display's currently active filters + // and draw appropriate visuals to indicate the presence of the hotspot + + QList<Filter::HotSpot*> spots = _filterChain->hotSpots(); + QListIterator<Filter::HotSpot*> iter(spots); + while (iter.hasNext()) + { + Filter::HotSpot* spot = iter.next(); + + QRegion region; + if ( spot->type() == Filter::HotSpot::Link ) { + QRect r; + if (spot->startLine()==spot->endLine()) { + r.setCoords( spot->startColumn()*_fontWidth + 1 + scrollBarWidth, + spot->startLine()*_fontHeight + 1, + (spot->endColumn()-1)*_fontWidth - 1 + scrollBarWidth, + (spot->endLine()+1)*_fontHeight - 1 ); + region |= r; + } else { + r.setCoords( spot->startColumn()*_fontWidth + 1 + scrollBarWidth, + spot->startLine()*_fontHeight + 1, + (_columns-1)*_fontWidth - 1 + scrollBarWidth, + (spot->startLine()+1)*_fontHeight - 1 ); + region |= r; + for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) { + r.setCoords( 0*_fontWidth + 1 + scrollBarWidth, + line*_fontHeight + 1, + (_columns-1)*_fontWidth - 1 + scrollBarWidth, + (line+1)*_fontHeight - 1 ); + region |= r; + } + r.setCoords( 0*_fontWidth + 1 + scrollBarWidth, + spot->endLine()*_fontHeight + 1, + (spot->endColumn()-1)*_fontWidth - 1 + scrollBarWidth, + (spot->endLine()+1)*_fontHeight - 1 ); + region |= r; + } + } + + for ( int line = spot->startLine() ; line <= spot->endLine() ; line++ ) + { + int startColumn = 0; + int endColumn = _columns-1; // TODO use number of _columns which are actually + // occupied on this line rather than the width of the + // display in _columns + + // ignore whitespace at the end of the lines + while ( QChar(_image[loc(endColumn,line)].character).isSpace() && endColumn > 0 ) + endColumn--; + + // increment here because the column which we want to set 'endColumn' to + // is the first whitespace character at the end of the line + endColumn++; + + if ( line == spot->startLine() ) + startColumn = spot->startColumn(); + if ( line == spot->endLine() ) + endColumn = spot->endColumn(); + + // subtract one pixel from + // the right and bottom so that + // we do not overdraw adjacent + // hotspots + // + // subtracting one pixel from all sides also prevents an edge case where + // moving the mouse outside a link could still leave it underlined + // because the check below for the position of the cursor + // finds it on the border of the target area + QRect r; + r.setCoords( startColumn*_fontWidth + 1 + scrollBarWidth, + line*_fontHeight + 1, + endColumn*_fontWidth - 1 + scrollBarWidth, + (line+1)*_fontHeight - 1 ); + // Underline link hotspots + if ( spot->type() == Filter::HotSpot::Link ) + { + QFontMetrics metrics(font()); + + // find the baseline (which is the invisible line that the characters in the font sit on, + // with some having tails dangling below) + int baseline = r.bottom() - metrics.descent(); + // find the position of the underline below that + int underlinePos = baseline + metrics.underlinePos(); + if ( region.contains( mapFromGlobal(QCursor::pos()) ) ){ + painter.drawLine( r.left() , underlinePos , + r.right() , underlinePos ); + } + } + // Marker hotspots simply have a transparent rectanglular shape + // drawn on top of them + else if ( spot->type() == Filter::HotSpot::Marker ) + { + //TODO - Do not use a hardcoded colour for this + painter.fillRect(r,QBrush(QColor(255,0,0,120))); + } + } + } +} +void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect) +{ + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + + int lux = qMin(_usedColumns-1, qMax(0,(rect.left() - tLx - _leftMargin ) / _fontWidth)); + int luy = qMin(_usedLines-1, qMax(0,(rect.top() - tLy - _topMargin ) / _fontHeight)); + int rlx = qMin(_usedColumns-1, qMax(0,(rect.right() - tLx - _leftMargin ) / _fontWidth)); + int rly = qMin(_usedLines-1, qMax(0,(rect.bottom() - tLy - _topMargin ) / _fontHeight)); + + const int bufferSize = _usedColumns; + QString unistr; + unistr.reserve(bufferSize); + for (int y = luy; y <= rly; y++) + { + quint16 c = _image[loc(lux,y)].character; + int x = lux; + if(!c && x) + x--; // Search for start of multi-column character + for (; x <= rlx; x++) + { + int len = 1; + int p = 0; + + // reset our buffer to the maximal size + unistr.resize(bufferSize); + QChar *disstrU = unistr.data(); + + // is this a single character or a sequence of characters ? + if ( _image[loc(x,y)].rendition & RE_EXTENDED_CHAR ) + { + // sequence of characters + ushort extendedCharLength = 0; + ushort* chars = ExtendedCharTable::instance + .lookupExtendedChar(_image[loc(x,y)].charSequence,extendedCharLength); + for ( int index = 0 ; index < extendedCharLength ; index++ ) + { + Q_ASSERT( p < bufferSize ); + disstrU[p++] = chars[index]; + } + } + else + { + // single character + c = _image[loc(x,y)].character; + if (c) + { + Q_ASSERT( p < bufferSize ); + disstrU[p++] = c; //fontMap(c); + } + } + + bool lineDraw = isLineChar(c); + bool doubleWidth = (_image[ qMin(loc(x,y)+1,_imageSize) ].character == 0); + CharacterColor currentForeground = _image[loc(x,y)].foregroundColor; + CharacterColor currentBackground = _image[loc(x,y)].backgroundColor; + quint8 currentRendition = _image[loc(x,y)].rendition; + + while (x+len <= rlx && + _image[loc(x+len,y)].foregroundColor == currentForeground && + _image[loc(x+len,y)].backgroundColor == currentBackground && + _image[loc(x+len,y)].rendition == currentRendition && + (_image[ qMin(loc(x+len,y)+1,_imageSize) ].character == 0) == doubleWidth && + isLineChar( c = _image[loc(x+len,y)].character) == lineDraw) // Assignment! + { + if (c) + disstrU[p++] = c; //fontMap(c); + if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition + len++; // Skip trailing part of multi-column character + len++; + } + if ((x+len < _usedColumns) && (!_image[loc(x+len,y)].character)) + len++; // Adjust for trailing part of multi-column character + + bool save__fixedFont = _fixedFont; + if (lineDraw) + _fixedFont = false; + if (doubleWidth) + _fixedFont = false; + unistr.resize(p); + + // Create a text scaling matrix for double width and double height lines. + QMatrix textScale; + + if (y < _lineProperties.size()) + { + if (_lineProperties[y] & LINE_DOUBLEWIDTH) + textScale.scale(2,1); + + if (_lineProperties[y] & LINE_DOUBLEHEIGHT) + textScale.scale(1,2); + } + + //Apply text scaling matrix. + paint.setWorldMatrix(textScale, true); + + //calculate the area in which the text will be drawn + QRect textArea = QRect( _leftMargin+tLx+_fontWidth*x , _topMargin+tLy+_fontHeight*y , _fontWidth*len , _fontHeight); + + //move the calculated area to take account of scaling applied to the painter. + //the position of the area from the origin (0,0) is scaled + //by the opposite of whatever + //transformation has been applied to the painter. this ensures that + //painting does actually start from textArea.topLeft() + //(instead of textArea.topLeft() * painter-scale) + textArea.moveTopLeft( textScale.inverted().map(textArea.topLeft()) ); + + //paint text fragment + drawTextFragment( paint, + textArea, + unistr, + &_image[loc(x,y)] ); //, + //0, + //!_isPrinting ); + + _fixedFont = save__fixedFont; + + //reset back to single-width, single-height _lines + paint.setWorldMatrix(textScale.inverted(), true); + + if (y < _lineProperties.size()-1) + { + //double-height _lines are represented by two adjacent _lines + //containing the same characters + //both _lines will have the LINE_DOUBLEHEIGHT attribute. + //If the current line has the LINE_DOUBLEHEIGHT attribute, + //we can therefore skip the next line + if (_lineProperties[y] & LINE_DOUBLEHEIGHT) + y++; + } + + x += len - 1; + } + } +} + +void TerminalDisplay::blinkEvent() +{ + if (!_allowBlinkingText) return; + + _blinking = !_blinking; + + //TODO: Optimize to only repaint the areas of the widget + // where there is blinking text + // rather than repainting the whole widget. + update(); +} + +QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const +{ + QRect result; + result.setLeft( _leftMargin + _fontWidth * imageArea.left() ); + result.setTop( _topMargin + _fontHeight * imageArea.top() ); + result.setWidth( _fontWidth * imageArea.width() ); + result.setHeight( _fontHeight * imageArea.height() ); + + return result; +} + +void TerminalDisplay::updateCursor() +{ + QRect cursorRect = imageToWidget( QRect(cursorPosition(),QSize(1,1)) ); + update(cursorRect); +} + +void TerminalDisplay::blinkCursorEvent() +{ + _cursorBlinking = !_cursorBlinking; + updateCursor(); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Resizing */ +/* */ +/* ------------------------------------------------------------------------- */ + +void TerminalDisplay::resizeEvent(QResizeEvent*) +{ + updateImageSize(); +} + +void TerminalDisplay::propagateSize() +{ + if (_isFixedSize) + { + setSize(_columns, _lines); + QWidget::setFixedSize(sizeHint()); + parentWidget()->adjustSize(); + parentWidget()->setFixedSize(parentWidget()->sizeHint()); + return; + } + if (_image) + updateImageSize(); +} + +void TerminalDisplay::updateImageSize() +{ + Character* oldimg = _image; + int oldlin = _lines; + int oldcol = _columns; + + makeImage(); + + // copy the old image to reduce flicker + int lines = qMin(oldlin,_lines); + int columns = qMin(oldcol,_columns); + + if (oldimg) + { + for (int line = 0; line < lines; line++) + { + memcpy((void*)&_image[_columns*line], + (void*)&oldimg[oldcol*line],columns*sizeof(Character)); + } + delete[] oldimg; + } + + if (_screenWindow) + _screenWindow->setWindowLines(_lines); + + _resizing = (oldlin!=_lines) || (oldcol!=_columns); + + if ( _resizing ) + { + showResizeNotification(); + emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent + } + + _resizing = false; +} + +//showEvent and hideEvent are reimplemented here so that it appears to other classes that the +//display has been resized when the display is hidden or shown. +// +//TODO: Perhaps it would be better to have separate signals for show and hide instead of using +//the same signal as the one for a content size change +void TerminalDisplay::showEvent(QShowEvent*) +{ + emit changedContentSizeSignal(_contentHeight,_contentWidth); +} +void TerminalDisplay::hideEvent(QHideEvent*) +{ + emit changedContentSizeSignal(_contentHeight,_contentWidth); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Scrollbar */ +/* */ +/* ------------------------------------------------------------------------- */ + +void TerminalDisplay::scrollBarPositionChanged(int) +{ + if ( !_screenWindow ) + return; + + _screenWindow->scrollTo( _scrollBar->value() ); + + // if the thumb has been moved to the bottom of the _scrollBar then set + // the display to automatically track new output, + // that is, scroll down automatically + // to how new _lines as they are added + const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum()); + _screenWindow->setTrackOutput( atEndOfOutput ); + + updateImage(); +} + +void TerminalDisplay::setScroll(int cursor, int slines) +{ + // update _scrollBar if the range or value has changed, + // otherwise return + // + // setting the range or value of a _scrollBar will always trigger + // a repaint, so it should be avoided if it is not necessary + if ( _scrollBar->minimum() == 0 && + _scrollBar->maximum() == (slines - _lines) && + _scrollBar->value() == cursor ) + { + return; + } + + disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int))); + _scrollBar->setRange(0,slines - _lines); + _scrollBar->setSingleStep(1); + _scrollBar->setPageStep(_lines); + _scrollBar->setValue(cursor); + connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int))); +} + +void TerminalDisplay::setScrollBarPosition(ScrollBarPosition position) +{ + if (_scrollbarLocation == position) + return; + + if ( position == NoScrollBar ) + _scrollBar->hide(); + else + _scrollBar->show(); + + _topMargin = _leftMargin = 1; + _scrollbarLocation = position; + + propagateSize(); + update(); +} + +void TerminalDisplay::mousePressEvent(QMouseEvent* ev) +{ + if ( _possibleTripleClick && (ev->button()==Qt::LeftButton) ) { + mouseTripleClickEvent(ev); + return; + } + + if ( !contentsRect().contains(ev->pos()) ) return; + + if ( !_screenWindow ) return; + + int charLine; + int charColumn; + getCharacterPosition(ev->pos(),charLine,charColumn); + QPoint pos = QPoint(charColumn,charLine); + + if ( ev->button() == Qt::LeftButton) + { + _lineSelectionMode = false; + _wordSelectionMode = false; + + emit isBusySelecting(true); // Keep it steady... + // Drag only when the Control key is hold + bool selected = false; + + // The receiver of the testIsSelected() signal will adjust + // 'selected' accordingly. + //emit testIsSelected(pos.x(), pos.y(), selected); + + selected = _screenWindow->isSelected(pos.x(),pos.y()); + + if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected ) { + // The user clicked inside selected text + dragInfo.state = diPending; + dragInfo.start = ev->pos(); + } + else { + // No reason to ever start a drag event + dragInfo.state = diNone; + + _preserveLineBreaks = !( ( ev->modifiers() & Qt::ControlModifier ) && !(ev->modifiers() & Qt::AltModifier) ); + _columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier); + + if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) + { + _screenWindow->clearSelection(); + + //emit clearSelectionSignal(); + pos.ry() += _scrollBar->value(); + _iPntSel = _pntSel = pos; + _actSel = 1; // left mouse button pressed but nothing selected yet. + + } + else + { + emit mouseSignal( 0, charColumn + 1, charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } + } + } + else if ( ev->button() == Qt::MidButton ) + { + if ( _mouseMarks || (!_mouseMarks && (ev->modifiers() & Qt::ShiftModifier)) ) + emitSelection(true,ev->modifiers() & Qt::ControlModifier); + else + emit mouseSignal( 1, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } + else if ( ev->button() == Qt::RightButton ) + { + if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) + emit configureRequest(ev->pos()); + else + emit mouseSignal( 2, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } +} + +QList<QAction*> TerminalDisplay::filterActions(const QPoint& position) +{ + int charLine, charColumn; + getCharacterPosition(position,charLine,charColumn); + + Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn); + + return spot ? spot->actions() : QList<QAction*>(); +} + +void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev) +{ + int charLine = 0; + int charColumn = 0; + int scrollBarWidth = (_scrollbarLocation == ScrollBarLeft) ? _scrollBar->width() : 0; + + getCharacterPosition(ev->pos(),charLine,charColumn); + + // handle filters + // change link hot-spot appearance on mouse-over + Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn); + if ( spot && spot->type() == Filter::HotSpot::Link) + { + QRegion previousHotspotArea = _mouseOverHotspotArea; + _mouseOverHotspotArea = QRegion(); + QRect r; + if (spot->startLine()==spot->endLine()) { + r.setCoords( spot->startColumn()*_fontWidth + scrollBarWidth, + spot->startLine()*_fontHeight, + spot->endColumn()*_fontWidth + scrollBarWidth, + (spot->endLine()+1)*_fontHeight - 1 ); + _mouseOverHotspotArea |= r; + } else { + r.setCoords( spot->startColumn()*_fontWidth + scrollBarWidth, + spot->startLine()*_fontHeight, + _columns*_fontWidth - 1 + scrollBarWidth, + (spot->startLine()+1)*_fontHeight ); + _mouseOverHotspotArea |= r; + for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) { + r.setCoords( 0*_fontWidth + scrollBarWidth, + line*_fontHeight, + _columns*_fontWidth + scrollBarWidth, + (line+1)*_fontHeight ); + _mouseOverHotspotArea |= r; + } + r.setCoords( 0*_fontWidth + scrollBarWidth, + spot->endLine()*_fontHeight, + spot->endColumn()*_fontWidth + scrollBarWidth, + (spot->endLine()+1)*_fontHeight ); + _mouseOverHotspotArea |= r; + } + // display tooltips when mousing over links + // TODO: Extend this to work with filter types other than links + const QString& tooltip = spot->tooltip(); + if ( !tooltip.isEmpty() ) + { + QToolTip::showText( mapToGlobal(ev->pos()) , tooltip , this , _mouseOverHotspotArea.boundingRect() ); + } + + update( _mouseOverHotspotArea | previousHotspotArea ); + } + else if ( !_mouseOverHotspotArea.isEmpty() ) + { + update( _mouseOverHotspotArea ); + // set hotspot area to an invalid rectangle + _mouseOverHotspotArea = QRegion(); + } + + // for auto-hiding the cursor, we need mouseTracking + if (ev->buttons() == Qt::NoButton ) return; + + // if the terminal is interested in mouse movements + // then emit a mouse movement signal, unless the shift + // key is being held down, which overrides this. + if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) + { + int button = 3; + if (ev->buttons() & Qt::LeftButton) + button = 0; + if (ev->buttons() & Qt::MidButton) + button = 1; + if (ev->buttons() & Qt::RightButton) + button = 2; + + + emit mouseSignal( button, + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum(), + 1 ); + + return; + } + + if (dragInfo.state == diPending) + { + // we had a mouse down, but haven't confirmed a drag yet + // if the mouse has moved sufficiently, we will confirm + + int distance = 10; //KGlobalSettings::dndEventDelay(); + if ( ev->x() > dragInfo.start.x() + distance || ev->x() < dragInfo.start.x() - distance || + ev->y() > dragInfo.start.y() + distance || ev->y() < dragInfo.start.y() - distance) + { + // we've left the drag square, we can start a real drag operation now + emit isBusySelecting(false); // Ok.. we can breath again. + + _screenWindow->clearSelection(); + doDrag(); + } + return; + } + else if (dragInfo.state == diDragging) + { + // this isn't technically needed because mouseMoveEvent is suppressed during + // Qt drag operations, replaced by dragMoveEvent + return; + } + + if (_actSel == 0) return; + + // don't extend selection while pasting + if (ev->buttons() & Qt::MidButton) return; + + extendSelection( ev->pos() ); +} + +void TerminalDisplay::extendSelection( const QPoint& position ) +{ + QPoint pos = position; + + if ( !_screenWindow ) + return; + + //if ( !contentsRect().contains(ev->pos()) ) return; + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + int scroll = _scrollBar->value(); + + // we're in the process of moving the mouse with the left button pressed + // the mouse cursor will kept caught within the bounds of the text in + // this widget. + + int linesBeyondWidget = 0; + + QRect textBounds(tLx + _leftMargin, + tLy + _topMargin, + _usedColumns*_fontWidth-1, + _usedLines*_fontHeight-1); + + // Adjust position within text area bounds. + QPoint oldpos = pos; + + pos.setX( qBound(textBounds.left(),pos.x(),textBounds.right()) ); + pos.setY( qBound(textBounds.top(),pos.y(),textBounds.bottom()) ); + + if ( oldpos.y() > textBounds.bottom() ) + { + linesBeyondWidget = (oldpos.y()-textBounds.bottom()) / _fontHeight; + _scrollBar->setValue(_scrollBar->value()+linesBeyondWidget+1); // scrollforward + } + if ( oldpos.y() < textBounds.top() ) + { + linesBeyondWidget = (textBounds.top()-oldpos.y()) / _fontHeight; + _scrollBar->setValue(_scrollBar->value()-linesBeyondWidget-1); // history + } + + int charColumn = 0; + int charLine = 0; + getCharacterPosition(pos,charLine,charColumn); + + QPoint here = QPoint(charColumn,charLine); //QPoint((pos.x()-tLx-_leftMargin+(_fontWidth/2))/_fontWidth,(pos.y()-tLy-_topMargin)/_fontHeight); + QPoint ohere; + QPoint _iPntSelCorr = _iPntSel; + _iPntSelCorr.ry() -= _scrollBar->value(); + QPoint _pntSelCorr = _pntSel; + _pntSelCorr.ry() -= _scrollBar->value(); + bool swapping = false; + + if ( _wordSelectionMode ) + { + // Extend to word boundaries + int i; + QChar selClass; + + bool left_not_right = ( here.y() < _iPntSelCorr.y() || + ( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) ); + bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() || + ( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) ); + swapping = left_not_right != old_left_not_right; + + // Find left (left_not_right ? from here : from start) + QPoint left = left_not_right ? here : _iPntSelCorr; + i = loc(left.x(),left.y()); + if (i>=0 && i<=_imageSize) { + selClass = charClass(_image[i].character); + while ( ((left.x()>0) || (left.y()>0 && (_lineProperties[left.y()-1] & LINE_WRAPPED) )) + && charClass(_image[i-1].character) == selClass ) + { i--; if (left.x()>0) left.rx()--; else {left.rx()=_usedColumns-1; left.ry()--;} } + } + + // Find left (left_not_right ? from start : from here) + QPoint right = left_not_right ? _iPntSelCorr : here; + i = loc(right.x(),right.y()); + if (i>=0 && i<=_imageSize) { + selClass = charClass(_image[i].character); + while( ((right.x()<_usedColumns-1) || (right.y()<_usedLines-1 && (_lineProperties[right.y()] & LINE_WRAPPED) )) + && charClass(_image[i+1].character) == selClass ) + { i++; if (right.x()<_usedColumns-1) right.rx()++; else {right.rx()=0; right.ry()++; } } + } + + // Pick which is start (ohere) and which is extension (here) + if ( left_not_right ) + { + here = left; ohere = right; + } + else + { + here = right; ohere = left; + } + ohere.rx()++; + } + + if ( _lineSelectionMode ) + { + // Extend to complete line + bool above_not_below = ( here.y() < _iPntSelCorr.y() ); + + QPoint above = above_not_below ? here : _iPntSelCorr; + QPoint below = above_not_below ? _iPntSelCorr : here; + + while (above.y()>0 && (_lineProperties[above.y()-1] & LINE_WRAPPED) ) + above.ry()--; + while (below.y()<_usedLines-1 && (_lineProperties[below.y()] & LINE_WRAPPED) ) + below.ry()++; + + above.setX(0); + below.setX(_usedColumns-1); + + // Pick which is start (ohere) and which is extension (here) + if ( above_not_below ) + { + here = above; ohere = below; + } + else + { + here = below; ohere = above; + } + + QPoint newSelBegin = QPoint( ohere.x(), ohere.y() ); + swapping = !(_tripleSelBegin==newSelBegin); + _tripleSelBegin = newSelBegin; + + ohere.rx()++; + } + + int offset = 0; + if ( !_wordSelectionMode && !_lineSelectionMode ) + { + int i; + QChar selClass; + + bool left_not_right = ( here.y() < _iPntSelCorr.y() || + ( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) ); + bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() || + ( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) ); + swapping = left_not_right != old_left_not_right; + + // Find left (left_not_right ? from here : from start) + QPoint left = left_not_right ? here : _iPntSelCorr; + + // Find left (left_not_right ? from start : from here) + QPoint right = left_not_right ? _iPntSelCorr : here; + if ( right.x() > 0 && !_columnSelectionMode ) + { + i = loc(right.x(),right.y()); + if (i>=0 && i<=_imageSize) { + selClass = charClass(_image[i-1].character); + /* if (selClass == ' ') + { + while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) && + !(_lineProperties[right.y()] & LINE_WRAPPED)) + { i++; right.rx()++; } + if (right.x() < _usedColumns-1) + right = left_not_right ? _iPntSelCorr : here; + else + right.rx()++; // will be balanced later because of offset=-1; + }*/ + } + } + + // Pick which is start (ohere) and which is extension (here) + if ( left_not_right ) + { + here = left; ohere = right; offset = 0; + } + else + { + here = right; ohere = left; offset = -1; + } + } + + if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) return; // not moved + + if (here == ohere) return; // It's not left, it's not right. + + if ( _actSel < 2 || swapping ) + { + if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode ) + { + _screenWindow->setSelectionStart( ohere.x() , ohere.y() , true ); + } + else + { + _screenWindow->setSelectionStart( ohere.x()-1-offset , ohere.y() , false ); + } + + } + + _actSel = 2; // within selection + _pntSel = here; + _pntSel.ry() += _scrollBar->value(); + + if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode ) + { + _screenWindow->setSelectionEnd( here.x() , here.y() ); + } + else + { + _screenWindow->setSelectionEnd( here.x()+offset , here.y() ); + } + +} + +void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev) +{ + if ( !_screenWindow ) + return; + + int charLine; + int charColumn; + getCharacterPosition(ev->pos(),charLine,charColumn); + + if ( ev->button() == Qt::LeftButton) + { + emit isBusySelecting(false); + if(dragInfo.state == diPending) + { + // We had a drag event pending but never confirmed. Kill selection + _screenWindow->clearSelection(); + //emit clearSelectionSignal(); + } + else + { + if ( _actSel > 1 ) + { + setSelection( _screenWindow->selectedText(_preserveLineBreaks) ); + } + + _actSel = 0; + + //FIXME: emits a release event even if the mouse is + // outside the range. The procedure used in `mouseMoveEvent' + // applies here, too. + + if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) + emit mouseSignal( 3, // release + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } + dragInfo.state = diNone; + } + + + if ( !_mouseMarks && + ((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier)) + || ev->button() == Qt::MidButton) ) + { + emit mouseSignal( 3, + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , + 0); + } +} + +void TerminalDisplay::getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const +{ + column = (widgetPoint.x() + _fontWidth/2 -contentsRect().left()-_leftMargin) / _fontWidth; + line = (widgetPoint.y()-contentsRect().top()-_topMargin) / _fontHeight; + + if ( line < 0 ) + line = 0; + if ( column < 0 ) + column = 0; + + if ( line >= _usedLines ) + line = _usedLines-1; + + // the column value returned can be equal to _usedColumns, which + // is the position just after the last character displayed in a line. + // + // this is required so that the user can select characters in the right-most + // column (or left-most for right-to-left input) + if ( column > _usedColumns ) + column = _usedColumns; +} + +void TerminalDisplay::updateLineProperties() +{ + if ( !_screenWindow ) + return; + + _lineProperties = _screenWindow->getLineProperties(); +} + +void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev) +{ + if ( ev->button() != Qt::LeftButton) return; + if ( !_screenWindow ) return; + + int charLine = 0; + int charColumn = 0; + + getCharacterPosition(ev->pos(),charLine,charColumn); + + QPoint pos(charColumn,charLine); + + // pass on double click as two clicks. + if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) + { + // Send just _ONE_ click event, since the first click of the double click + // was already sent by the click handler + emit mouseSignal( 0, + pos.x()+1, + pos.y()+1 +_scrollBar->value() -_scrollBar->maximum(), + 0 ); // left button + return; + } + + _screenWindow->clearSelection(); + QPoint bgnSel = pos; + QPoint endSel = pos; + int i = loc(bgnSel.x(),bgnSel.y()); + _iPntSel = bgnSel; + _iPntSel.ry() += _scrollBar->value(); + + _wordSelectionMode = true; + + // find word boundaries... + QChar selClass = charClass(_image[i].character); + { + // find the start of the word + int x = bgnSel.x(); + while ( ((x>0) || (bgnSel.y()>0 && (_lineProperties[bgnSel.y()-1] & LINE_WRAPPED) )) + && charClass(_image[i-1].character) == selClass ) + { + i--; + if (x>0) + x--; + else + { + x=_usedColumns-1; + bgnSel.ry()--; + } + } + + bgnSel.setX(x); + _screenWindow->setSelectionStart( bgnSel.x() , bgnSel.y() , false ); + + // find the end of the word + i = loc( endSel.x(), endSel.y() ); + x = endSel.x(); + while( ((x<_usedColumns-1) || (endSel.y()<_usedLines-1 && (_lineProperties[endSel.y()] & LINE_WRAPPED) )) + && charClass(_image[i+1].character) == selClass ) + { + i++; + if (x<_usedColumns-1) + x++; + else + { + x=0; + endSel.ry()++; + } + } + + endSel.setX(x); + + // In word selection mode don't select @ (64) if at end of word. + if ( ( QChar( _image[i].character ) == '@' ) && ( ( endSel.x() - bgnSel.x() ) > 0 ) ) + endSel.setX( x - 1 ); + + + _actSel = 2; // within selection + + _screenWindow->setSelectionEnd( endSel.x() , endSel.y() ); + + setSelection( _screenWindow->selectedText(_preserveLineBreaks) ); + } + + _possibleTripleClick=true; + + QTimer::singleShot(QApplication::doubleClickInterval(),this, + SLOT(tripleClickTimeout())); +} + +void TerminalDisplay::wheelEvent( QWheelEvent* ev ) +{ + if (ev->orientation() != Qt::Vertical) + return; + + // if the terminal program is not interested mouse events + // then send the event to the scrollbar if the slider has room to move + // or otherwise send simulated up / down key presses to the terminal program + // for the benefit of programs such as 'less' + if ( _mouseMarks ) + { + bool canScroll = _scrollBar->maximum() > 0; + if (canScroll) + _scrollBar->event(ev); + else + { + // assume that each Up / Down key event will cause the terminal application + // to scroll by one line. + // + // to get a reasonable scrolling speed, scroll by one line for every 5 degrees + // of mouse wheel rotation. Mouse wheels typically move in steps of 15 degrees, + // giving a scroll of 3 lines + int key = ev->delta() > 0 ? Qt::Key_Up : Qt::Key_Down; + + // QWheelEvent::delta() gives rotation in eighths of a degree + int wheelDegrees = ev->delta() / 8; + int linesToScroll = abs(wheelDegrees) / 5; + + QKeyEvent keyScrollEvent(QEvent::KeyPress,key,Qt::NoModifier); + + for (int i=0;i<linesToScroll;i++) + emit keyPressedSignal(&keyScrollEvent); + } + } + else + { + // terminal program wants notification of mouse activity + + int charLine; + int charColumn; + getCharacterPosition( ev->pos() , charLine , charColumn ); + + emit mouseSignal( ev->delta() > 0 ? 4 : 5, + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , + 0); + } +} + +void TerminalDisplay::tripleClickTimeout() +{ + _possibleTripleClick=false; +} + +void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev) +{ + if ( !_screenWindow ) return; + + int charLine; + int charColumn; + getCharacterPosition(ev->pos(),charLine,charColumn); + _iPntSel = QPoint(charColumn,charLine); + + _screenWindow->clearSelection(); + + _lineSelectionMode = true; + _wordSelectionMode = false; + + _actSel = 2; // within selection + emit isBusySelecting(true); // Keep it steady... + + while (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) ) + _iPntSel.ry()--; + + if (_tripleClickMode == SelectForwardsFromCursor) { + // find word boundary start + int i = loc(_iPntSel.x(),_iPntSel.y()); + QChar selClass = charClass(_image[i].character); + int x = _iPntSel.x(); + + while ( ((x>0) || + (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) ) + ) + && charClass(_image[i-1].character) == selClass ) + { + i--; + if (x>0) + x--; + else + { + x=_columns-1; + _iPntSel.ry()--; + } + } + + _screenWindow->setSelectionStart( x , _iPntSel.y() , false ); + _tripleSelBegin = QPoint( x, _iPntSel.y() ); + } + else if (_tripleClickMode == SelectWholeLine) { + _screenWindow->setSelectionStart( 0 , _iPntSel.y() , false ); + _tripleSelBegin = QPoint( 0, _iPntSel.y() ); + } + + while (_iPntSel.y()<_lines-1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED) ) + _iPntSel.ry()++; + + _screenWindow->setSelectionEnd( _columns - 1 , _iPntSel.y() ); + + setSelection(_screenWindow->selectedText(_preserveLineBreaks)); + + _iPntSel.ry() += _scrollBar->value(); +} + + +bool TerminalDisplay::focusNextPrevChild( bool next ) +{ + if (next) + return false; // This disables changing the active part in konqueror + // when pressing Tab + return QWidget::focusNextPrevChild( next ); +} + + +QChar TerminalDisplay::charClass(QChar qch) const +{ + if ( qch.isSpace() ) return ' '; + + if ( qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive ) ) + return 'a'; + + return qch; +} + +void TerminalDisplay::setWordCharacters(const QString& wc) +{ + _wordCharacters = wc; +} + +void TerminalDisplay::setUsesMouse(bool on) +{ + _mouseMarks = on; + setCursor( _mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor ); +} +bool TerminalDisplay::usesMouse() const +{ + return _mouseMarks; +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Clipboard */ +/* */ +/* ------------------------------------------------------------------------- */ + +#undef KeyPress + +void TerminalDisplay::emitSelection(bool useXselection,bool appendReturn) +{ + if ( !_screenWindow ) + return; + + // Paste Clipboard by simulating keypress events + QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection : + QClipboard::Clipboard); + if(appendReturn) + text.append("\r"); + if ( ! text.isEmpty() ) + { + text.replace('\n', '\r'); + QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text); + emit keyPressedSignal(&e); // expose as a big fat keypress event + + _screenWindow->clearSelection(); + } +} + +void TerminalDisplay::setSelection(const QString& t) +{ + QApplication::clipboard()->setText(t, QClipboard::Selection); +} + +void TerminalDisplay::copyClipboard() +{ + if ( !_screenWindow ) + return; + + QString text = _screenWindow->selectedText(_preserveLineBreaks); + if (!text.isEmpty()) + QApplication::clipboard()->setText(text); +} + +void TerminalDisplay::pasteClipboard() +{ + emitSelection(false,false); +} + +void TerminalDisplay::pasteSelection() +{ + emitSelection(true,false); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Keyboard */ +/* */ +/* ------------------------------------------------------------------------- */ + +void TerminalDisplay::setFlowControlWarningEnabled( bool enable ) +{ + _flowControlWarningEnabled = enable; + + // if the dialog is currently visible and the flow control warning has + // been disabled then hide the dialog + if (!enable) + outputSuspended(false); +} + +void TerminalDisplay::keyPressEvent( QKeyEvent* event ) +{ + bool emitKeyPressSignal = true; + + if(event->modifiers() == Qt::ControlModifier) + { + switch(event->key()) { + case Qt::Key_C: + copyClipboard(); + break; + case Qt::Key_V: + pasteClipboard(); + break; + }; + } else if ( event->modifiers() == Qt::ShiftModifier ) { + bool update = true; + + if ( event->key() == Qt::Key_PageUp ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollPages , -1 ); + } + else if ( event->key() == Qt::Key_PageDown ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollPages , 1 ); + } + else if ( event->key() == Qt::Key_Up ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollLines , -1 ); + } + else if ( event->key() == Qt::Key_Down ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollLines , 1 ); + } + else + update = false; + + if ( update ) + { + _screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() ); + + updateLineProperties(); + updateImage(); + + // do not send key press to terminal + emitKeyPressSignal = false; + } + } + + _actSel=0; // Key stroke implies a screen update, so TerminalDisplay won't + // know where the current selection is. + + if (_hasBlinkingCursor) + { + _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2); + if (_cursorBlinking) + blinkCursorEvent(); + else + _cursorBlinking = false; + } + + if ( emitKeyPressSignal ) + emit keyPressedSignal(event); + + event->accept(); +} + +void TerminalDisplay::inputMethodEvent( QInputMethodEvent* event ) +{ + QKeyEvent keyEvent(QEvent::KeyPress,0,Qt::NoModifier,event->commitString()); + emit keyPressedSignal(&keyEvent); + + _inputMethodData.preeditString = event->preeditString(); + update(preeditRect() | _inputMethodData.previousPreeditRect); + + event->accept(); +} +QVariant TerminalDisplay::inputMethodQuery( Qt::InputMethodQuery query ) const +{ + const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0,0); + switch ( query ) + { + case Qt::ImMicroFocus: + return imageToWidget(QRect(cursorPos.x(),cursorPos.y(),1,1)); + break; + case Qt::ImFont: + return font(); + break; + case Qt::ImCursorPosition: + // return the cursor position within the current line + return cursorPos.x(); + break; + case Qt::ImSurroundingText: + { + // return the text from the current line + QString lineText; + QTextStream stream(&lineText); + PlainTextDecoder decoder; + decoder.begin(&stream); + decoder.decodeLine(&_image[loc(0,cursorPos.y())],_usedColumns,_lineProperties[cursorPos.y()]); + decoder.end(); + return lineText; + } + break; + case Qt::ImCurrentSelection: + return QString(); + break; + default: + break; + } + + return QVariant(); +} + +bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent* keyEvent) +{ + int modifiers = keyEvent->modifiers(); + + // When a possible shortcut combination is pressed, + // emit the overrideShortcutCheck() signal to allow the host + // to decide whether the terminal should override it or not. + if (modifiers != Qt::NoModifier) + { + int modifierCount = 0; + unsigned int currentModifier = Qt::ShiftModifier; + + while (currentModifier <= Qt::KeypadModifier) + { + if (modifiers & currentModifier) + modifierCount++; + currentModifier <<= 1; + } + if (modifierCount < 2) + { + bool override = false; + emit overrideShortcutCheck(keyEvent,override); + if (override) + { + keyEvent->accept(); + return true; + } + } + } + + // Override any of the following shortcuts because + // they are needed by the terminal + int keyCode = keyEvent->key() | modifiers; + switch ( keyCode ) + { + // list is taken from the QLineEdit::event() code + case Qt::Key_Tab: + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + keyEvent->accept(); + return true; + } + return false; +} + +bool TerminalDisplay::event(QEvent* event) +{ + bool eventHandled = false; + switch (event->type()) + { + case QEvent::ShortcutOverride: + eventHandled = handleShortcutOverrideEvent((QKeyEvent*)event); + break; + case QEvent::PaletteChange: + case QEvent::ApplicationPaletteChange: + _scrollBar->setPalette( QApplication::palette() ); + break; + default: + break; + } + return eventHandled ? true : QWidget::event(event); +} + +void TerminalDisplay::setBellMode(int mode) +{ + _bellMode=mode; +} + +void TerminalDisplay::enableBell() +{ + _allowBell = true; +} + +void TerminalDisplay::bell(const QString& message) +{ + if (_bellMode==NoBell) return; + + //limit the rate at which bells can occur + //...mainly for sound effects where rapid bells in sequence + //produce a horrible noise + if ( _allowBell ) + { + _allowBell = false; + QTimer::singleShot(500,this,SLOT(enableBell())); + + if (_bellMode==SystemBeepBell) + { + // TODO: This will need added back in at some point + //KNotification::beep(); + } + else if (_bellMode==NotifyBell) + { + // TODO: This will need added back in at some point + //KNotification::event("BellVisible", message,QPixmap(),this); + } + else if (_bellMode==VisualBell) + { + swapColorTable(); + QTimer::singleShot(200,this,SLOT(swapColorTable())); + } + } +} + +void TerminalDisplay::swapColorTable() +{ + ColorEntry color = _colorTable[1]; + _colorTable[1]=_colorTable[0]; + _colorTable[0]= color; + _colorsInverted = !_colorsInverted; + update(); +} + +void TerminalDisplay::clearImage() +{ + // We initialize _image[_imageSize] too. See makeImage() + for (int i = 0; i <= _imageSize; i++) + { + _image[i].character = ' '; + _image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT, + DEFAULT_FORE_COLOR); + _image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT, + DEFAULT_BACK_COLOR); + _image[i].rendition = DEFAULT_RENDITION; + } +} + +void TerminalDisplay::calcGeometry() +{ + _scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height()); + switch(_scrollbarLocation) + { + case NoScrollBar : + _leftMargin = DEFAULT_LEFT_MARGIN; + _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN; + break; + case ScrollBarLeft : + _leftMargin = DEFAULT_LEFT_MARGIN + _scrollBar->width(); + _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width(); + _scrollBar->move(contentsRect().topLeft()); + break; + case ScrollBarRight: + _leftMargin = DEFAULT_LEFT_MARGIN; + _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width(); + _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width()-1,0)); + break; + } + + _topMargin = DEFAULT_TOP_MARGIN; + _contentHeight = contentsRect().height() - 2 * DEFAULT_TOP_MARGIN + /* mysterious */ 1; + + if (!_isFixedSize) + { + // ensure that display is always at least one column wide + _columns = qMax(1,_contentWidth / _fontWidth); + _usedColumns = qMin(_usedColumns,_columns); + + // ensure that display is always at least one line high + _lines = qMax(1,_contentHeight / _fontHeight); + _usedLines = qMin(_usedLines,_lines); + } +} + +void TerminalDisplay::makeImage() +{ + calcGeometry(); + + // confirm that array will be of non-zero size, since the painting code + // assumes a non-zero array length + Q_ASSERT( _lines > 0 && _columns > 0 ); + Q_ASSERT( _usedLines <= _lines && _usedColumns <= _columns ); + + _imageSize=_lines*_columns; + + // We over-commit one character so that we can be more relaxed in dealing with + // certain boundary conditions: _image[_imageSize] is a valid but unused position + _image = new Character[_imageSize+1]; + + clearImage(); +} + +// calculate the needed size, this must be synced with calcGeometry() +void TerminalDisplay::setSize(int columns, int lines) +{ + int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->sizeHint().width(); + int horizontalMargin = 2 * DEFAULT_LEFT_MARGIN; + int verticalMargin = 2 * DEFAULT_TOP_MARGIN; + + QSize newSize = QSize( horizontalMargin + scrollBarWidth + (columns * _fontWidth) , + verticalMargin + (lines * _fontHeight) ); + + if ( newSize != size() ) + { + _size = newSize; + updateGeometry(); + } +} + +void TerminalDisplay::setFixedSize(int cols, int lins) +{ + _isFixedSize = true; + + //ensure that display is at least one line by one column in size + _columns = qMax(1,cols); + _lines = qMax(1,lins); + _usedColumns = qMin(_usedColumns,_columns); + _usedLines = qMin(_usedLines,_lines); + + if (_image) + { + delete[] _image; + makeImage(); + } + setSize(cols, lins); + QWidget::setFixedSize(_size); +} + +QSize TerminalDisplay::sizeHint() const +{ + return _size; +} + + +/* --------------------------------------------------------------------- */ +/* */ +/* Drag & Drop */ +/* */ +/* --------------------------------------------------------------------- */ + +void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event) +{ + if (event->mimeData()->hasFormat("text/plain")) + event->acceptProposedAction(); +} + +void TerminalDisplay::dropEvent(QDropEvent* event) +{ + //KUrl::List urls = KUrl::List::fromMimeData(event->mimeData()); + + QString dropText; + /* + if (!urls.isEmpty()) + { + for ( int i = 0 ; i < urls.count() ; i++ ) + { + KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 ); + QString urlText; + + if (url.isLocalFile()) + urlText = url.path(); + else + urlText = url.url(); + + // in future it may be useful to be able to insert file names with drag-and-drop + // without quoting them (this only affects paths with spaces in) + urlText = KShell::quoteArg(urlText); + + dropText += urlText; + + if ( i != urls.count()-1 ) + dropText += ' '; + } + } + else + { + dropText = event->mimeData()->text(); + } + */ + + if(event->mimeData()->hasFormat("text/plain")) + { + emit sendStringToEmu(dropText.toLocal8Bit()); + } +} + +void TerminalDisplay::doDrag() +{ + dragInfo.state = diDragging; + dragInfo.dragObject = new QDrag(this); + QMimeData *mimeData = new QMimeData; + mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection)); + dragInfo.dragObject->setMimeData(mimeData); + dragInfo.dragObject->start(Qt::CopyAction); + // Don't delete the QTextDrag object. Qt will delete it when it's done with it. +} + +void TerminalDisplay::outputSuspended(bool suspended) +{ + //create the label when this function is first called + if (!_outputSuspendedLabel) + { + //This label includes a link to an English language website + //describing the 'flow control' (Xon/Xoff) feature found in almost + //all terminal emulators. + //If there isn't a suitable article available in the target language the link + //can simply be removed. + _outputSuspendedLabel = new QLabel( QString("<qt>Output has been " + "<a href=\"http://en.wikipedia.org/wiki/Flow_control\">suspended</a>" + " by pressing Ctrl+S." + " Press <b>Ctrl+Q</b> to resume.</qt>"), + this ); + + QPalette palette(_outputSuspendedLabel->palette()); + //KColorScheme::adjustBackground(palette,KColorScheme::NeutralBackground); + _outputSuspendedLabel->setPalette(palette); + _outputSuspendedLabel->setAutoFillBackground(true); + _outputSuspendedLabel->setBackgroundRole(QPalette::Base); + _outputSuspendedLabel->setFont(QApplication::font()); + _outputSuspendedLabel->setContentsMargins(5, 5, 5, 5); + + //enable activation of "Xon/Xoff" link in label + _outputSuspendedLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse | + Qt::LinksAccessibleByKeyboard); + _outputSuspendedLabel->setOpenExternalLinks(true); + _outputSuspendedLabel->setVisible(false); + + _gridLayout->addWidget(_outputSuspendedLabel); + _gridLayout->addItem( new QSpacerItem(0,0,QSizePolicy::Expanding, + QSizePolicy::Expanding), + 1,0); + + } + + _outputSuspendedLabel->setVisible(suspended); +} + +uint TerminalDisplay::lineSpacing() const +{ + return _lineSpacing; +} + +void TerminalDisplay::setLineSpacing(uint i) +{ + _lineSpacing = i; + setVTFont(font()); // Trigger an update. +} + +AutoScrollHandler::AutoScrollHandler(QWidget* parent) +: QObject(parent) +, _timerId(0) +{ + //parent->installEventFilter(this); +} +void AutoScrollHandler::timerEvent(QTimerEvent* event) +{ + if (event->timerId() != _timerId) + return; + + QMouseEvent mouseEvent( QEvent::MouseMove, + widget()->mapFromGlobal(QCursor::pos()), + Qt::NoButton, + Qt::LeftButton, + Qt::NoModifier); + + QApplication::sendEvent(widget(),&mouseEvent); +} +bool AutoScrollHandler::eventFilter(QObject* watched,QEvent* event) +{ + Q_ASSERT( watched == parent() ); + Q_UNUSED( watched ); + + QMouseEvent* mouseEvent = (QMouseEvent*)event; + switch (event->type()) + { + case QEvent::MouseMove: + { + bool mouseInWidget = widget()->rect().contains(mouseEvent->pos()); + + if (mouseInWidget) + { + if (_timerId) + killTimer(_timerId); + _timerId = 0; + } + else + { + if (!_timerId && (mouseEvent->buttons() & Qt::LeftButton)) + _timerId = startTimer(100); + } + break; + } + case QEvent::MouseButtonRelease: + if (_timerId && (mouseEvent->buttons() & ~Qt::LeftButton)) + { + killTimer(_timerId); + _timerId = 0; + } + break; + default: + break; + }; + + return false; +}
new file mode 100644 --- /dev/null +++ b/gui/src/TerminalDisplay.h @@ -0,0 +1,816 @@ +/* + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef TERMINALDISPLAY_H +#define TERMINALDISPLAY_H + +// Qt +#include <QtGui/QColor> +#include <QtCore/QPointer> +#include <QtGui/QWidget> + +// Konsole +#include "Filter.h" +#include "Character.h" + +class QDrag; +class QDragEnterEvent; +class QDropEvent; +class QLabel; +class QTimer; +class QEvent; +class QGridLayout; +class QKeyEvent; +class QScrollBar; +class QShowEvent; +class QHideEvent; +class QTimerEvent; +class QWidget; + +class KMenu; + +extern unsigned short vt100_graphics[32]; + +class ScreenWindow; + +/** + * A widget which displays output from a terminal emulation and sends input keypresses and mouse activity + * to the terminal. + * + * When the terminal emulation receives new output from the program running in the terminal, + * it will update the display by calling updateImage(). + * + * TODO More documentation + */ +class TerminalDisplay : public QWidget +{ + Q_OBJECT + +public: + /** Constructs a new terminal display widget with the specified parent. */ + TerminalDisplay(QWidget *parent=0); + virtual ~TerminalDisplay(); + + /** Returns the terminal color palette used by the display. */ + const ColorEntry* colorTable() const; + /** Sets the terminal color palette used by the display. */ + void setColorTable(const ColorEntry table[]); + /** + * Sets the seed used to generate random colors for the display + * (in color schemes that support them). + */ + void setRandomSeed(uint seed); + /** + * Returns the seed used to generate random colors for the display + * (in color schemes that support them). + */ + uint randomSeed() const; + + /** Sets the opacity of the terminal display. */ + void setOpacity(qreal opacity); + + /** + * This enum describes the location where the scroll bar is positioned in the display widget. + */ + enum ScrollBarPosition + { + /** Do not show the scroll bar. */ + NoScrollBar=0, + /** Show the scroll bar on the left side of the display. */ + ScrollBarLeft=1, + /** Show the scroll bar on the right side of the display. */ + ScrollBarRight=2 + }; + /** + * Specifies whether the terminal display has a vertical scroll bar, and if so whether it + * is shown on the left or right side of the display. + */ + void setScrollBarPosition(ScrollBarPosition position); + + /** + * Sets the current position and range of the display's scroll bar. + * + * @param cursor The position of the scroll bar's thumb. + * @param lines The maximum value of the scroll bar. + */ + void setScroll(int cursor, int lines); + + /** + * Returns the display's filter chain. When the image for the display is updated, + * the text is passed through each filter in the chain. Each filter can define + * hotspots which correspond to certain strings (such as URLs or particular words). + * Depending on the type of the hotspots created by the filter ( returned by Filter::Hotspot::type() ) + * the view will draw visual cues such as underlines on mouse-over for links or translucent + * rectangles for markers. + * + * To add a new filter to the view, call: + * viewWidget->filterChain()->addFilter( filterObject ); + */ + FilterChain* filterChain() const; + + /** + * Updates the filters in the display's filter chain. This will cause + * the hotspots to be updated to match the current image. + * + * WARNING: This function can be expensive depending on the + * image size and number of filters in the filterChain() + * + * TODO - This API does not really allow efficient usage. Revise it so + * that the processing can be done in a better way. + * + * eg: + * - Area of interest may be known ( eg. mouse cursor hovering + * over an area ) + */ + void processFilters(); + + /** + * Returns a list of menu actions created by the filters for the content + * at the given @p position. + */ + QList<QAction*> filterActions(const QPoint& position); + + /** Returns true if the cursor is set to blink or false otherwise. */ + bool blinkingCursor() { return _hasBlinkingCursor; } + /** Specifies whether or not the cursor blinks. */ + void setBlinkingCursor(bool blink); + + /** Specifies whether or not text can blink. */ + void setBlinkingTextEnabled(bool blink); + + void setCtrlDrag(bool enable) { _ctrlDrag=enable; } + bool ctrlDrag() { return _ctrlDrag; } + + /** + * This enum describes the methods for selecting text when + * the user triple-clicks within the display. + */ + enum TripleClickMode + { + /** Select the whole line underneath the cursor. */ + SelectWholeLine, + /** Select from the current cursor position to the end of the line. */ + SelectForwardsFromCursor + }; + /** Sets how the text is selected when the user triple clicks within the display. */ + void setTripleClickMode(TripleClickMode mode) { _tripleClickMode = mode; } + /** See setTripleClickSelectionMode() */ + TripleClickMode tripleClickMode() { return _tripleClickMode; } + + void setLineSpacing(uint); + uint lineSpacing() const; + + void emitSelection(bool useXselection,bool appendReturn); + + /** + * This enum describes the available shapes for the keyboard cursor. + * See setKeyboardCursorShape() + */ + enum KeyboardCursorShape + { + /** A rectangular block which covers the entire area of the cursor character. */ + BlockCursor, + /** + * A single flat line which occupies the space at the bottom of the cursor + * character's area. + */ + UnderlineCursor, + /** + * An cursor shaped like the capital letter 'I', similar to the IBeam + * cursor used in Qt/KDE text editors. + */ + IBeamCursor + }; + /** + * Sets the shape of the keyboard cursor. This is the cursor drawn + * at the position in the terminal where keyboard input will appear. + * + * In addition the terminal display widget also has a cursor for + * the mouse pointer, which can be set using the QWidget::setCursor() + * method. + * + * Defaults to BlockCursor + */ + void setKeyboardCursorShape(KeyboardCursorShape shape); + /** + * Returns the shape of the keyboard cursor. See setKeyboardCursorShape() + */ + KeyboardCursorShape keyboardCursorShape() const; + + /** + * Sets the color used to draw the keyboard cursor. + * + * The keyboard cursor defaults to using the foreground color of the character + * underneath it. + * + * @param useForegroundColor If true, the cursor color will change to match + * the foreground color of the character underneath it as it is moved, in this + * case, the @p color parameter is ignored and the color of the character + * under the cursor is inverted to ensure that it is still readable. + * @param color The color to use to draw the cursor. This is only taken into + * account if @p useForegroundColor is false. + */ + void setKeyboardCursorColor(bool useForegroundColor , const QColor& color); + + /** + * Returns the color of the keyboard cursor, or an invalid color if the keyboard + * cursor color is set to change according to the foreground color of the character + * underneath it. + */ + QColor keyboardCursorColor() const; + + /** + * Returns the number of lines of text which can be displayed in the widget. + * + * This will depend upon the height of the widget and the current font. + * See fontHeight() + */ + int lines() { return _lines; } + /** + * Returns the number of characters of text which can be displayed on + * each line in the widget. + * + * This will depend upon the width of the widget and the current font. + * See fontWidth() + */ + int columns() { return _columns; } + + /** + * Returns the height of the characters in the font used to draw the text in the display. + */ + int fontHeight() { return _fontHeight; } + /** + * Returns the width of the characters in the display. + * This assumes the use of a fixed-width font. + */ + int fontWidth() { return _fontWidth; } + + void setSize(int cols, int lins); + void setFixedSize(int cols, int lins); + + // reimplemented + QSize sizeHint() const; + + /** + * Sets which characters, in addition to letters and numbers, + * are regarded as being part of a word for the purposes + * of selecting words in the display by double clicking on them. + * + * The word boundaries occur at the first and last characters which + * are either a letter, number, or a character in @p wc + * + * @param wc An array of characters which are to be considered parts + * of a word ( in addition to letters and numbers ). + */ + void setWordCharacters(const QString& wc); + /** + * Returns the characters which are considered part of a word for the + * purpose of selecting words in the display with the mouse. + * + * @see setWordCharacters() + */ + QString wordCharacters() { return _wordCharacters; } + + /** + * Sets the type of effect used to alert the user when a 'bell' occurs in the + * terminal session. + * + * The terminal session can trigger the bell effect by calling bell() with + * the alert message. + */ + void setBellMode(int mode); + /** + * Returns the type of effect used to alert the user when a 'bell' occurs in + * the terminal session. + * + * See setBellMode() + */ + int bellMode() { return _bellMode; } + + /** + * This enum describes the different types of sounds and visual effects which + * can be used to alert the user when a 'bell' occurs in the terminal + * session. + */ + enum BellMode + { + /** A system beep. */ + SystemBeepBell=0, + /** + * KDE notification. This may play a sound, show a passive popup + * or perform some other action depending on the user's settings. + */ + NotifyBell=1, + /** A silent, visual bell (eg. inverting the display's colors briefly) */ + VisualBell=2, + /** No bell effects */ + NoBell=3 + }; + + void setSelection(const QString &t); + + /** + * Reimplemented. Has no effect. Use setVTFont() to change the font + * used to draw characters in the display. + */ + virtual void setFont(const QFont &); + + /** Returns the font used to draw characters in the display */ + QFont getVTFont() { return font(); } + + /** + * Sets the font used to draw the display. Has no effect if @p font + * is larger than the size of the display itself. + */ + void setVTFont(const QFont& font); + + /** + * Specified whether anti-aliasing of text in the terminal display + * is enabled or not. Defaults to enabled. + */ + static void setAntialias( bool antialias ) { _antialiasText = antialias; } + /** + * Returns true if anti-aliasing of text in the terminal is enabled. + */ + static bool antialias() { return _antialiasText; } + + /** + * Specifies whether characters with intense colors should be rendered + * as bold. Defaults to true. + */ + void setBoldIntense(bool value) { _boldIntense = value; } + /** + * Returns true if characters with intense colors are rendered in bold. + */ + bool getBoldIntense() { return _boldIntense; } + + /** + * Sets whether or not the current height and width of the + * terminal in lines and columns is displayed whilst the widget + * is being resized. + */ + void setTerminalSizeHint(bool on) { _terminalSizeHint=on; } + /** + * Returns whether or not the current height and width of + * the terminal in lines and columns is displayed whilst the widget + * is being resized. + */ + bool terminalSizeHint() { return _terminalSizeHint; } + /** + * Sets whether the terminal size display is shown briefly + * after the widget is first shown. + * + * See setTerminalSizeHint() , isTerminalSizeHint() + */ + void setTerminalSizeStartup(bool on) { _terminalSizeStartup=on; } + + /** + * Sets the status of the BiDi rendering inside the terminal display. + * Defaults to disabled. + */ + void setBidiEnabled(bool set) { _bidiEnabled=set; } + /** + * Returns the status of the BiDi rendering in this widget. + */ + bool isBidiEnabled() { return _bidiEnabled; } + + /** + * Sets the terminal screen section which is displayed in this widget. + * When updateImage() is called, the display fetches the latest character image from the + * the associated terminal screen window. + * + * In terms of the model-view paradigm, the ScreenWindow is the model which is rendered + * by the TerminalDisplay. + */ + void setScreenWindow( ScreenWindow* window ); + /** Returns the terminal screen section which is displayed in this widget. See setScreenWindow() */ + ScreenWindow* screenWindow() const; + + static bool HAVE_TRANSPARENCY; + +public slots: + + /** + * Causes the terminal display to fetch the latest character image from the associated + * terminal screen ( see setScreenWindow() ) and redraw the display. + */ + void updateImage(); + /** + * Causes the terminal display to fetch the latest line status flags from the + * associated terminal screen ( see setScreenWindow() ). + */ + void updateLineProperties(); + + /** Copies the selected text to the clipboard. */ + void copyClipboard(); + /** + * Pastes the content of the clipboard into the + * display. + */ + void pasteClipboard(); + /** + * Pastes the content of the selection into the + * display. + */ + void pasteSelection(); + + /** + * Changes whether the flow control warning box should be shown when the flow control + * stop key (Ctrl+S) are pressed. + */ + void setFlowControlWarningEnabled(bool enabled); + /** + * Returns true if the flow control warning box is enabled. + * See outputSuspended() and setFlowControlWarningEnabled() + */ + bool flowControlWarningEnabled() const + { return _flowControlWarningEnabled; } + + /** + * Causes the widget to display or hide a message informing the user that terminal + * output has been suspended (by using the flow control key combination Ctrl+S) + * + * @param suspended True if terminal output has been suspended and the warning message should + * be shown or false to indicate that terminal output has been resumed and that + * the warning message should disappear. + */ + void outputSuspended(bool suspended); + + /** + * Sets whether the program whoose output is being displayed in the view + * is interested in mouse events. + * + * If this is set to true, mouse signals will be emitted by the view when the user clicks, drags + * or otherwise moves the mouse inside the view. + * The user interaction needed to create selections will also change, and the user will be required + * to hold down the shift key to create a selection or perform other mouse activities inside the + * view area - since the program running in the terminal is being allowed to handle normal mouse + * events itself. + * + * @param usesMouse Set to true if the program running in the terminal is interested in mouse events + * or false otherwise. + */ + void setUsesMouse(bool usesMouse); + + /** See setUsesMouse() */ + bool usesMouse() const; + + /** + * Shows a notification that a bell event has occurred in the terminal. + * TODO: More documentation here + */ + void bell(const QString& message); + + /** + * Sets the background of the display to the specified color. + * @see setColorTable(), setForegroundColor() + */ + void setBackgroundColor(const QColor& color); + + /** + * Sets the text of the display to the specified color. + * @see setColorTable(), setBackgroundColor() + */ + void setForegroundColor(const QColor& color); + +signals: + + /** + * Emitted when the user presses a key whilst the terminal widget has focus. + */ + void keyPressedSignal(QKeyEvent *e); + + /** + * A mouse event occurred. + * @param button The mouse button (0 for left button, 1 for middle button, 2 for right button, 3 for release) + * @param column The character column where the event occurred + * @param line The character row where the event occurred + * @param eventType The type of event. 0 for a mouse press / release or 1 for mouse motion + */ + void mouseSignal(int button, int column, int line, int eventType); + void changedFontMetricSignal(int height, int width); + void changedContentSizeSignal(int height, int width); + + /** + * Emitted when the user right clicks on the display, or right-clicks with the Shift + * key held down if usesMouse() is true. + * + * This can be used to display a context menu. + */ + void configureRequest(const QPoint& position); + + /** + * When a shortcut which is also a valid terminal key sequence is pressed while + * the terminal widget has focus, this signal is emitted to allow the host to decide + * whether the shortcut should be overridden. + * When the shortcut is overridden, the key sequence will be sent to the terminal emulation instead + * and the action associated with the shortcut will not be triggered. + * + * @p override is set to false by default and the shortcut will be triggered as normal. + */ + void overrideShortcutCheck(QKeyEvent* keyEvent,bool& override); + + void isBusySelecting(bool); + void sendStringToEmu(const char*); + +protected: + virtual bool event( QEvent * ); + + virtual void paintEvent( QPaintEvent * ); + + virtual void showEvent(QShowEvent*); + virtual void hideEvent(QHideEvent*); + virtual void resizeEvent(QResizeEvent*); + + virtual void fontChange(const QFont &font); + virtual void focusInEvent(QFocusEvent* event); + virtual void focusOutEvent(QFocusEvent* event); + virtual void keyPressEvent(QKeyEvent* event); + virtual void mouseDoubleClickEvent(QMouseEvent* ev); + virtual void mousePressEvent( QMouseEvent* ); + virtual void mouseReleaseEvent( QMouseEvent* ); + virtual void mouseMoveEvent( QMouseEvent* ); + virtual void extendSelection( const QPoint& pos ); + virtual void wheelEvent( QWheelEvent* ); + + virtual bool focusNextPrevChild( bool next ); + + // drag and drop + virtual void dragEnterEvent(QDragEnterEvent* event); + virtual void dropEvent(QDropEvent* event); + void doDrag(); + enum DragState { diNone, diPending, diDragging }; + + struct _dragInfo { + DragState state; + QPoint start; + QDrag *dragObject; + } dragInfo; + + // classifies the 'ch' into one of three categories + // and returns a character to indicate which category it is in + // + // - A space (returns ' ') + // - Part of a word (returns 'a') + // - Other characters (returns the input character) + QChar charClass(QChar ch) const; + + void clearImage(); + + void mouseTripleClickEvent(QMouseEvent* ev); + + // reimplemented + virtual void inputMethodEvent ( QInputMethodEvent* event ); + virtual QVariant inputMethodQuery( Qt::InputMethodQuery query ) const; + +protected slots: + + void scrollBarPositionChanged(int value); + void blinkEvent(); + void blinkCursorEvent(); + + //Renables bell noises and visuals. Used to disable further bells for a short period of time + //after emitting the first in a sequence of bell events. + void enableBell(); + +private slots: + + void swapColorTable(); + void tripleClickTimeout(); // resets possibleTripleClick + +private: + + // -- Drawing helpers -- + + // divides the part of the display specified by 'rect' into + // fragments according to their colors and styles and calls + // drawTextFragment() to draw the fragments + void drawContents(QPainter &paint, const QRect &rect); + // draws a section of text, all the text in this section + // has a common color and style + void drawTextFragment(QPainter& painter, const QRect& rect, + const QString& text, const Character* style); + // draws the background for a text fragment + // if useOpacitySetting is true then the color's alpha value will be set to + // the display's transparency (set with setOpacity()), otherwise the background + // will be drawn fully opaque + void drawBackground(QPainter& painter, const QRect& rect, const QColor& color, + bool useOpacitySetting); + // draws the cursor character + void drawCursor(QPainter& painter, const QRect& rect , const QColor& foregroundColor, + const QColor& backgroundColor , bool& invertColors); + // draws the characters or line graphics in a text fragment + void drawCharacters(QPainter& painter, const QRect& rect, const QString& text, + const Character* style, bool invertCharacterColor); + // draws a string of line graphics + void drawLineCharString(QPainter& painter, int x, int y, + const QString& str, const Character* attributes); + + // draws the preedit string for input methods + void drawInputMethodPreeditString(QPainter& painter , const QRect& rect); + + // -- + + // maps an area in the character image to an area on the widget + QRect imageToWidget(const QRect& imageArea) const; + + // maps a point on the widget to the position ( ie. line and column ) + // of the character at that point. + void getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const; + + // the area where the preedit string for input methods will be draw + QRect preeditRect() const; + + // shows a notification window in the middle of the widget indicating the terminal's + // current size in columns and lines + void showResizeNotification(); + + // scrolls the image by a number of lines. + // 'lines' may be positive ( to scroll the image down ) + // or negative ( to scroll the image up ) + // 'region' is the part of the image to scroll - currently only + // the top, bottom and height of 'region' are taken into account, + // the left and right are ignored. + void scrollImage(int lines , const QRect& region); + + void calcGeometry(); + void propagateSize(); + void updateImageSize(); + void makeImage(); + + void paintFilters(QPainter& painter); + + // returns a region covering all of the areas of the widget which contain + // a hotspot + QRegion hotSpotRegion() const; + + // returns the position of the cursor in columns and lines + QPoint cursorPosition() const; + + // redraws the cursor + void updateCursor(); + + bool handleShortcutOverrideEvent(QKeyEvent* event); + + // the window onto the terminal screen which this display + // is currently showing. + QPointer<ScreenWindow> _screenWindow; + + bool _allowBell; + + QGridLayout* _gridLayout; + + bool _fixedFont; // has fixed pitch + int _fontHeight; // height + int _fontWidth; // width + int _fontAscent; // ascend + bool _boldIntense; // Whether intense colors should be rendered with bold font + + int _leftMargin; // offset + int _topMargin; // offset + + int _lines; // the number of lines that can be displayed in the widget + int _columns; // the number of columns that can be displayed in the widget + + int _usedLines; // the number of lines that are actually being used, this will be less + // than 'lines' if the character image provided with setImage() is smaller + // than the maximum image size which can be displayed + + int _usedColumns; // the number of columns that are actually being used, this will be less + // than 'columns' if the character image provided with setImage() is smaller + // than the maximum image size which can be displayed + + int _contentHeight; + int _contentWidth; + Character* _image; // [lines][columns] + // only the area [usedLines][usedColumns] in the image contains valid data + + int _imageSize; + QVector<LineProperty> _lineProperties; + + ColorEntry _colorTable[TABLE_COLORS]; + uint _randomSeed; + + bool _resizing; + bool _terminalSizeHint; + bool _terminalSizeStartup; + bool _bidiEnabled; + bool _mouseMarks; + + QPoint _iPntSel; // initial selection point + QPoint _pntSel; // current selection point + QPoint _tripleSelBegin; // help avoid flicker + int _actSel; // selection state + bool _wordSelectionMode; + bool _lineSelectionMode; + bool _preserveLineBreaks; + bool _columnSelectionMode; + + QClipboard* _clipboard; + QScrollBar* _scrollBar; + ScrollBarPosition _scrollbarLocation; + QString _wordCharacters; + int _bellMode; + + bool _blinking; // hide text in paintEvent + bool _hasBlinker; // has characters to blink + bool _cursorBlinking; // hide cursor in paintEvent + bool _hasBlinkingCursor; // has blinking cursor enabled + bool _allowBlinkingText; // allow text to blink + bool _ctrlDrag; // require Ctrl key for drag + TripleClickMode _tripleClickMode; + bool _isFixedSize; //Columns / lines are locked. + QTimer* _blinkTimer; // active when hasBlinker + QTimer* _blinkCursorTimer; // active when hasBlinkingCursor + + KMenu* _drop; + QString _dropText; + int _dndFileCount; + + bool _possibleTripleClick; // is set in mouseDoubleClickEvent and deleted + // after QApplication::doubleClickInterval() delay + + + QLabel* _resizeWidget; + QTimer* _resizeTimer; + + bool _flowControlWarningEnabled; + + //widgets related to the warning message that appears when the user presses Ctrl+S to suspend + //terminal output - informing them what has happened and how to resume output + QLabel* _outputSuspendedLabel; + + uint _lineSpacing; + + bool _colorsInverted; // true during visual bell + + QSize _size; + + QRgb _blendColor; + + // list of filters currently applied to the display. used for links and + // search highlight + TerminalImageFilterChain* _filterChain; + QRegion _mouseOverHotspotArea; + + KeyboardCursorShape _cursorShape; + + // custom cursor color. if this is invalid then the foreground + // color of the character under the cursor is used + QColor _cursorColor; + + + struct InputMethodData + { + QString preeditString; + QRect previousPreeditRect; + }; + InputMethodData _inputMethodData; + + static bool _antialiasText; // do we antialias or not + + //the delay in milliseconds between redrawing blinking text + static const int TEXT_BLINK_DELAY = 500; + static const int DEFAULT_LEFT_MARGIN = 1; + static const int DEFAULT_TOP_MARGIN = 1; + +public: + static void setTransparencyEnabled(bool enable) + { + HAVE_TRANSPARENCY = enable; + } +}; + +class AutoScrollHandler : public QObject +{ +Q_OBJECT + +public: + AutoScrollHandler(QWidget* parent); +protected: + virtual void timerEvent(QTimerEvent* event); + virtual bool eventFilter(QObject* watched,QEvent* event); +private: + QWidget* widget() const { return static_cast<QWidget*>(parent()); } + int _timerId; +}; + + +#endif // TERMINALDISPLAY_H
new file mode 100644 --- /dev/null +++ b/gui/src/VariablesDockWidget.cpp @@ -0,0 +1,168 @@ +#include "VariablesDockWidget.h" +#include <QHBoxLayout> +#include <QVBoxLayout> +#include <QPushButton> + +VariablesDockWidget::VariablesDockWidget(QWidget *parent) + : QDockWidget(parent) { + setObjectName("VariablesDockWidget"); + construct(); +} + +void VariablesDockWidget::construct() { + m_updateSemaphore = new QSemaphore(1); + QStringList headerLabels; + headerLabels << tr("Name") << tr("Type") << tr("Value"); + m_variablesTreeWidget = new QTreeWidget(this); + m_variablesTreeWidget->setHeaderHidden(false); + m_variablesTreeWidget->setHeaderLabels(headerLabels); + QVBoxLayout *layout = new QVBoxLayout(); + + setWindowTitle(tr("Workspace")); + setWidget(new QWidget()); + + layout->addWidget(m_variablesTreeWidget); + QWidget *buttonBar = new QWidget(this); + layout->addWidget(buttonBar); + + QHBoxLayout *buttonBarLayout = new QHBoxLayout(); + QPushButton *saveWorkspaceButton = new QPushButton(tr("Save"), buttonBar); + QPushButton *loadWorkspaceButton = new QPushButton(tr("Load"), buttonBar); + QPushButton *clearWorkspaceButton = new QPushButton(tr("Clear"), buttonBar); + buttonBarLayout->addWidget(saveWorkspaceButton); + buttonBarLayout->addWidget(loadWorkspaceButton); + buttonBarLayout->addWidget(clearWorkspaceButton); + buttonBarLayout->setMargin(2); + buttonBar->setLayout(buttonBarLayout); + + layout->setMargin(2); + widget()->setLayout(layout); + + connect(saveWorkspaceButton, SIGNAL(clicked()), this, SLOT(emitSaveWorkspace())); + connect(loadWorkspaceButton, SIGNAL(clicked()), this, SLOT(emitLoadWorkspace())); + connect(clearWorkspaceButton, SIGNAL(clicked()), this, SLOT(emitClearWorkspace())); + + QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem(); + treeWidgetItem->setData(0, 0, QString(tr("Local"))); + m_variablesTreeWidget->insertTopLevelItem(0, treeWidgetItem); + + treeWidgetItem = new QTreeWidgetItem(); + treeWidgetItem->setData(0, 0, QString(tr("Global"))); + m_variablesTreeWidget->insertTopLevelItem(1, treeWidgetItem); + + treeWidgetItem = new QTreeWidgetItem(); + treeWidgetItem->setData(0, 0, QString(tr("Persistent"))); + m_variablesTreeWidget->insertTopLevelItem(2, treeWidgetItem); + + treeWidgetItem = new QTreeWidgetItem(); + treeWidgetItem->setData(0, 0, QString(tr("Hidden"))); + m_variablesTreeWidget->insertTopLevelItem(3, treeWidgetItem); + + m_variablesTreeWidget->expandAll(); + m_variablesTreeWidget->setAlternatingRowColors(true); + m_variablesTreeWidget->setAnimated(true); +} + +void VariablesDockWidget::updateTreeEntry(QTreeWidgetItem *treeItem, SymbolRecord symbolRecord) { + treeItem->setData(0, 0, QString(symbolRecord.name().c_str())); + treeItem->setData(1, 0, QString(symbolRecord.varval().type_name().c_str())); + treeItem->setData(2, 0, OctaveLink::octaveValueAsQString(symbolRecord.varval())); +} + +void VariablesDockWidget::setVariablesList(QList<SymbolRecord> symbolTable) { + m_updateSemaphore->acquire(); + // Split the symbol table into its different scopes. + QList<SymbolRecord> localSymbolTable; + QList<SymbolRecord> globalSymbolTable; + QList<SymbolRecord> persistentSymbolTable; + QList<SymbolRecord> hiddenSymbolTable; + + foreach(SymbolRecord symbolRecord, symbolTable) { + // It's true that being global or hidden includes it's can mean it's also locally visible, + // but we want to distinguish that here. + if(symbolRecord.is_local() && !symbolRecord.is_global() && !symbolRecord.is_hidden()) { + localSymbolTable.append(symbolRecord); + } + + if(symbolRecord.is_global()) { + globalSymbolTable.append(symbolRecord); + } + + if(symbolRecord.is_persistent()) { + persistentSymbolTable.append(symbolRecord); + } + + if(symbolRecord.is_hidden()) { + hiddenSymbolTable.append(symbolRecord); + } + } + + updateScope(0, localSymbolTable); + updateScope(1, globalSymbolTable); + updateScope(2, persistentSymbolTable); + updateScope(3, hiddenSymbolTable); + m_updateSemaphore->release(); +} + +void VariablesDockWidget::updateScope(int topLevelItemIndex, QList<SymbolRecord> symbolTable) { + // This method may be a little bit confusing; variablesList is a complete list of all + // variables that are in the workspace currently. + QTreeWidgetItem *topLevelItem = m_variablesTreeWidget->topLevelItem(topLevelItemIndex); + + // First we check, if any variables that exist in the model tree have to be updated + // or created. So we walk the variablesList check against the tree. + foreach(SymbolRecord symbolRecord, symbolTable) { + int childCount = topLevelItem->childCount(); + bool alreadyExists = false; + QTreeWidgetItem *child; + + // Search for the corresponding item in the tree. If it has been found, child + // will contain the appropriate QTreeWidgetItem* pointing at it. + for(int i = 0; i < childCount; i++) { + child = topLevelItem->child(i); + if(child->data(0, 0).toString() == QString(symbolRecord.name().c_str())) { + alreadyExists = true; + break; + } + } + + // If it already exists, just update it. + if(alreadyExists) { + updateTreeEntry(child, symbolRecord); + } else { + // It does not exist, so create a new one and set the right values. + child = new QTreeWidgetItem(); + updateTreeEntry(child, symbolRecord); + topLevelItem->addChild(child); + } + } + + // Check the tree against the list for deleted variables. + for(int i = 0; i < topLevelItem->childCount(); i++) { + bool existsInVariableList = false; + QTreeWidgetItem *child = topLevelItem->child(i); + foreach(SymbolRecord symbolRecord, symbolTable) { + if(QString(symbolRecord.name().c_str()) == child->data(0, 0).toString()) { + existsInVariableList = true; + } + } + + if(!existsInVariableList) { + topLevelItem->removeChild(child); + delete child; + i--; + } + } +} + +void VariablesDockWidget::emitSaveWorkspace() { + emit saveWorkspace(); +} + +void VariablesDockWidget::emitLoadWorkspace() { + emit loadWorkspace(); +} + +void VariablesDockWidget::emitClearWorkspace() { + emit clearWorkspace(); +}
new file mode 100644 --- /dev/null +++ b/gui/src/VariablesDockWidget.h @@ -0,0 +1,34 @@ +#ifndef VARIABLESDOCKWIDGET_H +#define VARIABLESDOCKWIDGET_H + +#include <QDockWidget> +#include <QTreeWidget> +#include <QSemaphore> +#include "OctaveLink.h" + +class VariablesDockWidget : public QDockWidget +{ + Q_OBJECT +public: + VariablesDockWidget(QWidget *parent = 0); + void setVariablesList(QList<SymbolRecord> symbolTable); + +signals: + void saveWorkspace(); + void loadWorkspace(); + void clearWorkspace(); + +private slots: + void emitSaveWorkspace(); + void emitLoadWorkspace(); + void emitClearWorkspace(); + +private: + void construct(); + void updateTreeEntry(QTreeWidgetItem *treeItem, SymbolRecord symbolRecord); + void updateScope(int topLevelItemIndex, QList<SymbolRecord> symbolTable); + QTreeWidget *m_variablesTreeWidget; + QSemaphore *m_updateSemaphore; +}; + +#endif // VARIABLESDOCKWIDGET_H
new file mode 100644 --- /dev/null +++ b/gui/src/Vt102Emulation.cpp @@ -0,0 +1,1214 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight <robert.knight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "Vt102Emulation.h" + + +// this allows konsole to be compiled without XKB and XTEST extensions +// even though it might be available on a particular system. +#if defined(AVOID_XKB) + #undef HAVE_XKB +#endif + +#if defined(HAVE_XKB) + void scrolllock_set_off(); + void scrolllock_set_on(); +#endif + +// Standard +#include <stdio.h> +#include <unistd.h> +#include <assert.h> + +// Qt +#include <QtCore/QEvent> +#include <QtGui/QKeyEvent> +#include <QtCore/QByteRef> + +// Konsole +#include "KeyboardTranslator.h" +#include "Screen.h" + +Vt102Emulation::Vt102Emulation() + : Emulation(), + _titleUpdateTimer(new QTimer(this)) +{ + _titleUpdateTimer->setSingleShot(true); + QObject::connect(_titleUpdateTimer , SIGNAL(timeout()) , this , SLOT(updateTitle())); + + initTokenizer(); + reset(); +} + +Vt102Emulation::~Vt102Emulation() +{} + +void Vt102Emulation::clearEntireScreen() +{ + _currentScreen->clearEntireScreen(); + bufferedUpdate(); +} + +void Vt102Emulation::reset() +{ + resetTokenizer(); + resetModes(); + resetCharset(0); + _screen[0]->reset(); + resetCharset(1); + _screen[1]->reset(); + setCodec(LocaleCodec); + + bufferedUpdate(); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Processing the incoming byte stream */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* Incoming Bytes Event pipeline + + This section deals with decoding the incoming character stream. + Decoding means here, that the stream is first separated into `tokens' + which are then mapped to a `meaning' provided as operations by the + `Screen' class or by the emulation class itself. + + The pipeline proceeds as follows: + + - Tokenizing the ESC codes (onReceiveChar) + - VT100 code page translation of plain characters (applyCharset) + - Interpretation of ESC codes (processToken) + + The escape codes and their meaning are described in the + technical reference of this program. +*/ + +// Tokens ------------------------------------------------------------------ -- + +/* + Since the tokens are the central notion if this section, we've put them + in front. They provide the syntactical elements used to represent the + terminals operations as byte sequences. + + They are encodes here into a single machine word, so that we can later + switch over them easily. Depending on the token itself, additional + argument variables are filled with parameter values. + + The tokens are defined below: + + - CHR - Printable characters (32..255 but DEL (=127)) + - CTL - Control characters (0..31 but ESC (= 27), DEL) + - ESC - Escape codes of the form <ESC><CHR but `[]()+*#'> + - ESC_DE - Escape codes of the form <ESC><any of `()+*#%'> C + - CSI_PN - Escape codes of the form <ESC>'[' {Pn} ';' {Pn} C + - CSI_PS - Escape codes of the form <ESC>'[' {Pn} ';' ... C + - CSI_PR - Escape codes of the form <ESC>'[' '?' {Pn} ';' ... C + - CSI_PE - Escape codes of the form <ESC>'[' '!' {Pn} ';' ... C + - VT52 - VT52 escape codes + - <ESC><Chr> + - <ESC>'Y'{Pc}{Pc} + - XTE_HA - Xterm window/terminal attribute commands + of the form <ESC>`]' {Pn} `;' {Text} <BEL> + (Note that these are handled differently to the other formats) + + The last two forms allow list of arguments. Since the elements of + the lists are treated individually the same way, they are passed + as individual tokens to the interpretation. Further, because the + meaning of the parameters are names (althought represented as numbers), + they are includes within the token ('N'). + +*/ + +#define TY_CONSTRUCT(T,A,N) ( ((((int)N) & 0xffff) << 16) | ((((int)A) & 0xff) << 8) | (((int)T) & 0xff) ) + +#define TY_CHR( ) TY_CONSTRUCT(0,0,0) +#define TY_CTL(A ) TY_CONSTRUCT(1,A,0) +#define TY_ESC(A ) TY_CONSTRUCT(2,A,0) +#define TY_ESC_CS(A,B) TY_CONSTRUCT(3,A,B) +#define TY_ESC_DE(A ) TY_CONSTRUCT(4,A,0) +#define TY_CSI_PS(A,N) TY_CONSTRUCT(5,A,N) +#define TY_CSI_PN(A ) TY_CONSTRUCT(6,A,0) +#define TY_CSI_PR(A,N) TY_CONSTRUCT(7,A,N) + +#define TY_VT52(A) TY_CONSTRUCT(8,A,0) +#define TY_CSI_PG(A) TY_CONSTRUCT(9,A,0) +#define TY_CSI_PE(A) TY_CONSTRUCT(10,A,0) + +#define MAX_ARGUMENT 4096 + +// Tokenizer --------------------------------------------------------------- -- + +/* The tokenizer's state + + The state is represented by the buffer (tokenBuffer, tokenBufferPos), + and accompanied by decoded arguments kept in (argv,argc). + Note that they are kept internal in the tokenizer. +*/ + +void Vt102Emulation::resetTokenizer() +{ + tokenBufferPos = 0; + argc = 0; + argv[0] = 0; + argv[1] = 0; +} + +void Vt102Emulation::addDigit(int digit) +{ + if (argv[argc] < MAX_ARGUMENT) + argv[argc] = 10*argv[argc] + digit; +} + +void Vt102Emulation::addArgument() +{ + argc = qMin(argc+1,MAXARGS-1); + argv[argc] = 0; +} + +void Vt102Emulation::addToCurrentToken(int cc) +{ + tokenBuffer[tokenBufferPos] = cc; + tokenBufferPos = qMin(tokenBufferPos+1,MAX_TOKEN_LENGTH-1); +} + +// Character Class flags used while decoding + +#define CTL 1 // Control character +#define CHR 2 // Printable character +#define CPN 4 // TODO: Document me +#define DIG 8 // Digit +#define SCS 16 // TODO: Document me +#define GRP 32 // TODO: Document me +#define CPS 64 // Character which indicates end of window resize + // escape sequence '\e[8;<row>;<col>t' + +void Vt102Emulation::initTokenizer() +{ + int i; + quint8* s; + for(i = 0;i < 256; ++i) + charClass[i] = 0; + for(i = 0;i < 32; ++i) + charClass[i] |= CTL; + for(i = 32;i < 256; ++i) + charClass[i] |= CHR; + for(s = (quint8*)"@ABCDGHILMPSTXZcdfry"; *s; ++s) + charClass[*s] |= CPN; + // resize = \e[8;<row>;<col>t + for(s = (quint8*)"t"; *s; ++s) + charClass[*s] |= CPS; + for(s = (quint8*)"0123456789"; *s; ++s) + charClass[*s] |= DIG; + for(s = (quint8*)"()+*%"; *s; ++s) + charClass[*s] |= SCS; + for(s = (quint8*)"()+*#[]%"; *s; ++s) + charClass[*s] |= GRP; + + resetTokenizer(); +} + +/* Ok, here comes the nasty part of the decoder. + + Instead of keeping an explicit state, we deduce it from the + token scanned so far. It is then immediately combined with + the current character to form a scanning decision. + + This is done by the following defines. + + - P is the length of the token scanned so far. + - L (often P-1) is the position on which contents we base a decision. + - C is a character or a group of characters (taken from 'charClass'). + + - 'cc' is the current character + - 's' is a pointer to the start of the token buffer + - 'p' is the current position within the token buffer + + Note that they need to applied in proper order. +*/ + +#define lec(P,L,C) (p == (P) && s[(L)] == (C)) +#define lun( ) (p == 1 && cc >= 32 ) +#define les(P,L,C) (p == (P) && s[L] < 256 && (charClass[s[(L)]] & (C)) == (C)) +#define eec(C) (p >= 3 && cc == (C)) +#define ees(C) (p >= 3 && cc < 256 && (charClass[cc] & (C)) == (C)) +#define eps(C) (p >= 3 && s[2] != '?' && s[2] != '!' && s[2] != '>' && cc < 256 && (charClass[cc] & (C)) == (C)) +#define epp( ) (p >= 3 && s[2] == '?') +#define epe( ) (p >= 3 && s[2] == '!') +#define egt( ) (p >= 3 && s[2] == '>') +#define Xpe (tokenBufferPos >= 2 && tokenBuffer[1] == ']') +#define Xte (Xpe && cc == 7 ) +#define ces(C) (cc < 256 && (charClass[cc] & (C)) == (C) && !Xte) + +#define ESC 27 +#define CNTL(c) ((c)-'@') + +// process an incoming unicode character +void Vt102Emulation::receiveChar(int cc) +{ + if (cc == 127) + return; //VT100: ignore. + + if (ces(CTL)) + { + // DEC HACK ALERT! Control Characters are allowed *within* esc sequences in VT100 + // This means, they do neither a resetTokenizer() nor a pushToToken(). Some of them, do + // of course. Guess this originates from a weakly layered handling of the X-on + // X-off protocol, which comes really below this level. + if (cc == CNTL('X') || cc == CNTL('Z') || cc == ESC) + resetTokenizer(); //VT100: CAN or SUB + if (cc != ESC) + { + processToken(TY_CTL(cc+'@' ),0,0); + return; + } + } + // advance the state + addToCurrentToken(cc); + + int* s = tokenBuffer; + int p = tokenBufferPos; + + if (getMode(MODE_Ansi)) + { + if (lec(1,0,ESC)) { return; } + if (lec(1,0,ESC+128)) { s[0] = ESC; receiveChar('['); return; } + if (les(2,1,GRP)) { return; } + if (Xte ) { processWindowAttributeChange(); resetTokenizer(); return; } + if (Xpe ) { return; } + if (lec(3,2,'?')) { return; } + if (lec(3,2,'>')) { return; } + if (lec(3,2,'!')) { return; } + if (lun( )) { processToken( TY_CHR(), applyCharset(cc), 0); resetTokenizer(); return; } + if (lec(2,0,ESC)) { processToken( TY_ESC(s[1]), 0, 0); resetTokenizer(); return; } + if (les(3,1,SCS)) { processToken( TY_ESC_CS(s[1],s[2]), 0, 0); resetTokenizer(); return; } + if (lec(3,1,'#')) { processToken( TY_ESC_DE(s[2]), 0, 0); resetTokenizer(); return; } + if (eps( CPN)) { processToken( TY_CSI_PN(cc), argv[0],argv[1]); resetTokenizer(); return; } + + // resize = \e[8;<row>;<col>t + if (eps(CPS)) + { + processToken( TY_CSI_PS(cc, argv[0]), argv[1], argv[2]); + resetTokenizer(); + return; + } + + if (epe( )) { processToken( TY_CSI_PE(cc), 0, 0); resetTokenizer(); return; } + if (ees(DIG)) { addDigit(cc-'0'); return; } + if (eec(';')) { addArgument(); return; } + for (int i=0;i<=argc;i++) + { + if (epp()) + processToken( TY_CSI_PR(cc,argv[i]), 0, 0); + else if (egt()) + processToken( TY_CSI_PG(cc), 0, 0); // spec. case for ESC]>0c or ESC]>c + else if (cc == 'm' && argc - i >= 4 && (argv[i] == 38 || argv[i] == 48) && argv[i+1] == 2) + { + // ESC[ ... 48;2;<red>;<green>;<blue> ... m -or- ESC[ ... 38;2;<red>;<green>;<blue> ... m + i += 2; + processToken( TY_CSI_PS(cc, argv[i-2]), COLOR_SPACE_RGB, (argv[i] << 16) | (argv[i+1] << 8) | argv[i+2]); + i += 2; + } + else if (cc == 'm' && argc - i >= 2 && (argv[i] == 38 || argv[i] == 48) && argv[i+1] == 5) + { + // ESC[ ... 48;5;<index> ... m -or- ESC[ ... 38;5;<index> ... m + i += 2; + processToken( TY_CSI_PS(cc, argv[i-2]), COLOR_SPACE_256, argv[i]); + } + else + processToken( TY_CSI_PS(cc,argv[i]), 0, 0); + } + resetTokenizer(); + } + else + { + // VT52 Mode + if (lec(1,0,ESC)) + return; + if (les(1,0,CHR)) + { + processToken( TY_CHR(), s[0], 0); + resetTokenizer(); + return; + } + if (lec(2,1,'Y')) + return; + if (lec(3,1,'Y')) + return; + if (p < 4) + { + processToken( TY_VT52(s[1] ), 0, 0); + resetTokenizer(); + return; + } + processToken( TY_VT52(s[1]), s[2], s[3]); + resetTokenizer(); + return; + } +} +void Vt102Emulation::processWindowAttributeChange() +{ + // Describes the window or terminal session attribute to change + // See Session::UserTitleChange for possible values + int attributeToChange = 0; + int i; + for (i = 2; i < tokenBufferPos && + tokenBuffer[i] >= '0' && + tokenBuffer[i] <= '9'; i++) + { + attributeToChange = 10 * attributeToChange + (tokenBuffer[i]-'0'); + } + + if (tokenBuffer[i] != ';') + { + reportDecodingError(); + return; + } + + QString newValue; + newValue.reserve(tokenBufferPos-i-2); + for (int j = 0; j < tokenBufferPos-i-2; j++) + newValue[j] = tokenBuffer[i+1+j]; + + _pendingTitleUpdates[attributeToChange] = newValue; + _titleUpdateTimer->start(20); +} + +void Vt102Emulation::updateTitle() +{ + QListIterator<int> iter( _pendingTitleUpdates.keys() ); + while (iter.hasNext()) { + int arg = iter.next(); + emit titleChanged( arg , _pendingTitleUpdates[arg] ); + } + _pendingTitleUpdates.clear(); +} + +// Interpreting Codes --------------------------------------------------------- + +/* + Now that the incoming character stream is properly tokenized, + meaning is assigned to them. These are either operations of + the current _screen, or of the emulation class itself. + + The token to be interpreteted comes in as a machine word + possibly accompanied by two parameters. + + Likewise, the operations assigned to, come with up to two + arguments. One could consider to make up a proper table + from the function below. + + The technical reference manual provides more information + about this mapping. +*/ + +void Vt102Emulation::processToken(int token, int p, int q) +{ + switch (token) + { + + case TY_CHR( ) : _currentScreen->displayCharacter (p ); break; //UTF16 + + // 127 DEL : ignored on input + + case TY_CTL('@' ) : /* NUL: ignored */ break; + case TY_CTL('A' ) : /* SOH: ignored */ break; + case TY_CTL('B' ) : /* STX: ignored */ break; + case TY_CTL('C' ) : /* ETX: ignored */ break; + case TY_CTL('D' ) : /* EOT: ignored */ break; + case TY_CTL('E' ) : reportAnswerBack ( ); break; //VT100 + case TY_CTL('F' ) : /* ACK: ignored */ break; + case TY_CTL('G' ) : emit stateSet(NOTIFYBELL); + break; //VT100 + case TY_CTL('H' ) : _currentScreen->backspace ( ); break; //VT100 + case TY_CTL('I' ) : _currentScreen->tab ( ); break; //VT100 + case TY_CTL('J' ) : _currentScreen->newLine ( ); break; //VT100 + case TY_CTL('K' ) : _currentScreen->newLine ( ); break; //VT100 + case TY_CTL('L' ) : _currentScreen->newLine ( ); break; //VT100 + case TY_CTL('M' ) : _currentScreen->toStartOfLine ( ); break; //VT100 + + case TY_CTL('N' ) : useCharset ( 1); break; //VT100 + case TY_CTL('O' ) : useCharset ( 0); break; //VT100 + + case TY_CTL('P' ) : /* DLE: ignored */ break; + case TY_CTL('Q' ) : /* DC1: XON continue */ break; //VT100 + case TY_CTL('R' ) : /* DC2: ignored */ break; + case TY_CTL('S' ) : /* DC3: XOFF halt */ break; //VT100 + case TY_CTL('T' ) : /* DC4: ignored */ break; + case TY_CTL('U' ) : /* NAK: ignored */ break; + case TY_CTL('V' ) : /* SYN: ignored */ break; + case TY_CTL('W' ) : /* ETB: ignored */ break; + case TY_CTL('X' ) : _currentScreen->displayCharacter ( 0x2592); break; //VT100 + case TY_CTL('Y' ) : /* EM : ignored */ break; + case TY_CTL('Z' ) : _currentScreen->displayCharacter ( 0x2592); break; //VT100 + case TY_CTL('[' ) : /* ESC: cannot be seen here. */ break; + case TY_CTL('\\' ) : /* FS : ignored */ break; + case TY_CTL(']' ) : /* GS : ignored */ break; + case TY_CTL('^' ) : /* RS : ignored */ break; + case TY_CTL('_' ) : /* US : ignored */ break; + + case TY_ESC('D' ) : _currentScreen->index ( ); break; //VT100 + case TY_ESC('E' ) : _currentScreen->nextLine ( ); break; //VT100 + case TY_ESC('H' ) : _currentScreen->changeTabStop (true ); break; //VT100 + case TY_ESC('M' ) : _currentScreen->reverseIndex ( ); break; //VT100 + case TY_ESC('Z' ) : reportTerminalType ( ); break; + case TY_ESC('c' ) : reset ( ); break; + + case TY_ESC('n' ) : useCharset ( 2); break; + case TY_ESC('o' ) : useCharset ( 3); break; + case TY_ESC('7' ) : saveCursor ( ); break; + case TY_ESC('8' ) : restoreCursor ( ); break; + + case TY_ESC('=' ) : setMode (MODE_AppKeyPad); break; + case TY_ESC('>' ) : resetMode (MODE_AppKeyPad); break; + case TY_ESC('<' ) : setMode (MODE_Ansi ); break; //VT100 + + case TY_ESC_CS('(', '0') : setCharset (0, '0'); break; //VT100 + case TY_ESC_CS('(', 'A') : setCharset (0, 'A'); break; //VT100 + case TY_ESC_CS('(', 'B') : setCharset (0, 'B'); break; //VT100 + + case TY_ESC_CS(')', '0') : setCharset (1, '0'); break; //VT100 + case TY_ESC_CS(')', 'A') : setCharset (1, 'A'); break; //VT100 + case TY_ESC_CS(')', 'B') : setCharset (1, 'B'); break; //VT100 + + case TY_ESC_CS('*', '0') : setCharset (2, '0'); break; //VT100 + case TY_ESC_CS('*', 'A') : setCharset (2, 'A'); break; //VT100 + case TY_ESC_CS('*', 'B') : setCharset (2, 'B'); break; //VT100 + + case TY_ESC_CS('+', '0') : setCharset (3, '0'); break; //VT100 + case TY_ESC_CS('+', 'A') : setCharset (3, 'A'); break; //VT100 + case TY_ESC_CS('+', 'B') : setCharset (3, 'B'); break; //VT100 + + case TY_ESC_CS('%', 'G') : setCodec (Utf8Codec ); break; //LINUX + case TY_ESC_CS('%', '@') : setCodec (LocaleCodec ); break; //LINUX + + case TY_ESC_DE('3' ) : /* Double height line, top half */ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true ); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , true ); + break; + case TY_ESC_DE('4' ) : /* Double height line, bottom half */ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true ); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , true ); + break; + case TY_ESC_DE('5' ) : /* Single width, single height line*/ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , false); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , false); + break; + case TY_ESC_DE('6' ) : /* Double width, single height line*/ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , false); + break; + case TY_ESC_DE('8' ) : _currentScreen->helpAlign ( ); break; + +// resize = \e[8;<row>;<col>t + case TY_CSI_PS('t', 8) : setImageSize( q /* columns */, p /* lines */ ); break; + +// change tab text color : \e[28;<color>t color: 0-16,777,215 + case TY_CSI_PS('t', 28) : emit changeTabTextColorRequest ( p ); break; + + case TY_CSI_PS('K', 0) : _currentScreen->clearToEndOfLine ( ); break; + case TY_CSI_PS('K', 1) : _currentScreen->clearToBeginOfLine ( ); break; + case TY_CSI_PS('K', 2) : _currentScreen->clearEntireLine ( ); break; + case TY_CSI_PS('J', 0) : _currentScreen->clearToEndOfScreen ( ); break; + case TY_CSI_PS('J', 1) : _currentScreen->clearToBeginOfScreen ( ); break; + case TY_CSI_PS('J', 2) : _currentScreen->clearEntireScreen ( ); break; + case TY_CSI_PS('J', 3) : clearHistory(); break; + case TY_CSI_PS('g', 0) : _currentScreen->changeTabStop (false ); break; //VT100 + case TY_CSI_PS('g', 3) : _currentScreen->clearTabStops ( ); break; //VT100 + case TY_CSI_PS('h', 4) : _currentScreen-> setMode (MODE_Insert ); break; + case TY_CSI_PS('h', 20) : setMode (MODE_NewLine ); break; + case TY_CSI_PS('i', 0) : /* IGNORE: attached printer */ break; //VT100 + case TY_CSI_PS('l', 4) : _currentScreen-> resetMode (MODE_Insert ); break; + case TY_CSI_PS('l', 20) : resetMode (MODE_NewLine ); break; + case TY_CSI_PS('s', 0) : saveCursor ( ); break; + case TY_CSI_PS('u', 0) : restoreCursor ( ); break; + + case TY_CSI_PS('m', 0) : _currentScreen->setDefaultRendition ( ); break; + case TY_CSI_PS('m', 1) : _currentScreen-> setRendition (RE_BOLD ); break; //VT100 + case TY_CSI_PS('m', 4) : _currentScreen-> setRendition (RE_UNDERLINE); break; //VT100 + case TY_CSI_PS('m', 5) : _currentScreen-> setRendition (RE_BLINK ); break; //VT100 + case TY_CSI_PS('m', 7) : _currentScreen-> setRendition (RE_REVERSE ); break; + case TY_CSI_PS('m', 10) : /* IGNORED: mapping related */ break; //LINUX + case TY_CSI_PS('m', 11) : /* IGNORED: mapping related */ break; //LINUX + case TY_CSI_PS('m', 12) : /* IGNORED: mapping related */ break; //LINUX + case TY_CSI_PS('m', 22) : _currentScreen->resetRendition (RE_BOLD ); break; + case TY_CSI_PS('m', 24) : _currentScreen->resetRendition (RE_UNDERLINE); break; + case TY_CSI_PS('m', 25) : _currentScreen->resetRendition (RE_BLINK ); break; + case TY_CSI_PS('m', 27) : _currentScreen->resetRendition (RE_REVERSE ); break; + + case TY_CSI_PS('m', 30) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 0); break; + case TY_CSI_PS('m', 31) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 1); break; + case TY_CSI_PS('m', 32) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 2); break; + case TY_CSI_PS('m', 33) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 3); break; + case TY_CSI_PS('m', 34) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 4); break; + case TY_CSI_PS('m', 35) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 5); break; + case TY_CSI_PS('m', 36) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 6); break; + case TY_CSI_PS('m', 37) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 7); break; + + case TY_CSI_PS('m', 38) : _currentScreen->setForeColor (p, q); break; + + case TY_CSI_PS('m', 39) : _currentScreen->setForeColor (COLOR_SPACE_DEFAULT, 0); break; + + case TY_CSI_PS('m', 40) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 0); break; + case TY_CSI_PS('m', 41) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 1); break; + case TY_CSI_PS('m', 42) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 2); break; + case TY_CSI_PS('m', 43) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 3); break; + case TY_CSI_PS('m', 44) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 4); break; + case TY_CSI_PS('m', 45) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 5); break; + case TY_CSI_PS('m', 46) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 6); break; + case TY_CSI_PS('m', 47) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 7); break; + + case TY_CSI_PS('m', 48) : _currentScreen->setBackColor (p, q); break; + + case TY_CSI_PS('m', 49) : _currentScreen->setBackColor (COLOR_SPACE_DEFAULT, 1); break; + + case TY_CSI_PS('m', 90) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 8); break; + case TY_CSI_PS('m', 91) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 9); break; + case TY_CSI_PS('m', 92) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 10); break; + case TY_CSI_PS('m', 93) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 11); break; + case TY_CSI_PS('m', 94) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 12); break; + case TY_CSI_PS('m', 95) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 13); break; + case TY_CSI_PS('m', 96) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 14); break; + case TY_CSI_PS('m', 97) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 15); break; + + case TY_CSI_PS('m', 100) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 8); break; + case TY_CSI_PS('m', 101) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 9); break; + case TY_CSI_PS('m', 102) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 10); break; + case TY_CSI_PS('m', 103) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 11); break; + case TY_CSI_PS('m', 104) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 12); break; + case TY_CSI_PS('m', 105) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 13); break; + case TY_CSI_PS('m', 106) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 14); break; + case TY_CSI_PS('m', 107) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 15); break; + + case TY_CSI_PS('n', 5) : reportStatus ( ); break; + case TY_CSI_PS('n', 6) : reportCursorPosition ( ); break; + case TY_CSI_PS('q', 0) : /* IGNORED: LEDs off */ break; //VT100 + case TY_CSI_PS('q', 1) : /* IGNORED: LED1 on */ break; //VT100 + case TY_CSI_PS('q', 2) : /* IGNORED: LED2 on */ break; //VT100 + case TY_CSI_PS('q', 3) : /* IGNORED: LED3 on */ break; //VT100 + case TY_CSI_PS('q', 4) : /* IGNORED: LED4 on */ break; //VT100 + case TY_CSI_PS('x', 0) : reportTerminalParms ( 2); break; //VT100 + case TY_CSI_PS('x', 1) : reportTerminalParms ( 3); break; //VT100 + + case TY_CSI_PN('@' ) : _currentScreen->insertChars (p ); break; + case TY_CSI_PN('A' ) : _currentScreen->cursorUp (p ); break; //VT100 + case TY_CSI_PN('B' ) : _currentScreen->cursorDown (p ); break; //VT100 + case TY_CSI_PN('C' ) : _currentScreen->cursorRight (p ); break; //VT100 + case TY_CSI_PN('D' ) : _currentScreen->cursorLeft (p ); break; //VT100 + case TY_CSI_PN('G' ) : _currentScreen->setCursorX (p ); break; //LINUX + case TY_CSI_PN('H' ) : _currentScreen->setCursorYX (p, q); break; //VT100 + case TY_CSI_PN('I' ) : _currentScreen->tab (p ); break; + case TY_CSI_PN('L' ) : _currentScreen->insertLines (p ); break; + case TY_CSI_PN('M' ) : _currentScreen->deleteLines (p ); break; + case TY_CSI_PN('P' ) : _currentScreen->deleteChars (p ); break; + case TY_CSI_PN('S' ) : _currentScreen->scrollUp (p ); break; + case TY_CSI_PN('T' ) : _currentScreen->scrollDown (p ); break; + case TY_CSI_PN('X' ) : _currentScreen->eraseChars (p ); break; + case TY_CSI_PN('Z' ) : _currentScreen->backtab (p ); break; + case TY_CSI_PN('c' ) : reportTerminalType ( ); break; //VT100 + case TY_CSI_PN('d' ) : _currentScreen->setCursorY (p ); break; //LINUX + case TY_CSI_PN('f' ) : _currentScreen->setCursorYX (p, q); break; //VT100 + case TY_CSI_PN('r' ) : setMargins (p, q); break; //VT100 + case TY_CSI_PN('y' ) : /* IGNORED: Confidence test */ break; //VT100 + + case TY_CSI_PR('h', 1) : setMode (MODE_AppCuKeys); break; //VT100 + case TY_CSI_PR('l', 1) : resetMode (MODE_AppCuKeys); break; //VT100 + case TY_CSI_PR('s', 1) : saveMode (MODE_AppCuKeys); break; //FIXME + case TY_CSI_PR('r', 1) : restoreMode (MODE_AppCuKeys); break; //FIXME + + case TY_CSI_PR('l', 2) : resetMode (MODE_Ansi ); break; //VT100 + + case TY_CSI_PR('h', 3) : setMode (MODE_132Columns);break; //VT100 + case TY_CSI_PR('l', 3) : resetMode (MODE_132Columns);break; //VT100 + + case TY_CSI_PR('h', 4) : /* IGNORED: soft scrolling */ break; //VT100 + case TY_CSI_PR('l', 4) : /* IGNORED: soft scrolling */ break; //VT100 + + case TY_CSI_PR('h', 5) : _currentScreen-> setMode (MODE_Screen ); break; //VT100 + case TY_CSI_PR('l', 5) : _currentScreen-> resetMode (MODE_Screen ); break; //VT100 + + case TY_CSI_PR('h', 6) : _currentScreen-> setMode (MODE_Origin ); break; //VT100 + case TY_CSI_PR('l', 6) : _currentScreen-> resetMode (MODE_Origin ); break; //VT100 + case TY_CSI_PR('s', 6) : _currentScreen-> saveMode (MODE_Origin ); break; //FIXME + case TY_CSI_PR('r', 6) : _currentScreen->restoreMode (MODE_Origin ); break; //FIXME + + case TY_CSI_PR('h', 7) : _currentScreen-> setMode (MODE_Wrap ); break; //VT100 + case TY_CSI_PR('l', 7) : _currentScreen-> resetMode (MODE_Wrap ); break; //VT100 + case TY_CSI_PR('s', 7) : _currentScreen-> saveMode (MODE_Wrap ); break; //FIXME + case TY_CSI_PR('r', 7) : _currentScreen->restoreMode (MODE_Wrap ); break; //FIXME + + case TY_CSI_PR('h', 8) : /* IGNORED: autorepeat on */ break; //VT100 + case TY_CSI_PR('l', 8) : /* IGNORED: autorepeat off */ break; //VT100 + case TY_CSI_PR('s', 8) : /* IGNORED: autorepeat on */ break; //VT100 + case TY_CSI_PR('r', 8) : /* IGNORED: autorepeat off */ break; //VT100 + + case TY_CSI_PR('h', 9) : /* IGNORED: interlace */ break; //VT100 + case TY_CSI_PR('l', 9) : /* IGNORED: interlace */ break; //VT100 + case TY_CSI_PR('s', 9) : /* IGNORED: interlace */ break; //VT100 + case TY_CSI_PR('r', 9) : /* IGNORED: interlace */ break; //VT100 + + case TY_CSI_PR('h', 12) : /* IGNORED: Cursor blink */ break; //att610 + case TY_CSI_PR('l', 12) : /* IGNORED: Cursor blink */ break; //att610 + case TY_CSI_PR('s', 12) : /* IGNORED: Cursor blink */ break; //att610 + case TY_CSI_PR('r', 12) : /* IGNORED: Cursor blink */ break; //att610 + + case TY_CSI_PR('h', 25) : setMode (MODE_Cursor ); break; //VT100 + case TY_CSI_PR('l', 25) : resetMode (MODE_Cursor ); break; //VT100 + case TY_CSI_PR('s', 25) : saveMode (MODE_Cursor ); break; //VT100 + case TY_CSI_PR('r', 25) : restoreMode (MODE_Cursor ); break; //VT100 + + case TY_CSI_PR('h', 40) : setMode(MODE_Allow132Columns ); break; // XTERM + case TY_CSI_PR('l', 40) : resetMode(MODE_Allow132Columns ); break; // XTERM + + case TY_CSI_PR('h', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + case TY_CSI_PR('l', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + case TY_CSI_PR('s', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + case TY_CSI_PR('r', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + + case TY_CSI_PR('h', 47) : setMode (MODE_AppScreen); break; //VT100 + case TY_CSI_PR('l', 47) : resetMode (MODE_AppScreen); break; //VT100 + case TY_CSI_PR('s', 47) : saveMode (MODE_AppScreen); break; //XTERM + case TY_CSI_PR('r', 47) : restoreMode (MODE_AppScreen); break; //XTERM + + case TY_CSI_PR('h', 67) : /* IGNORED: DECBKM */ break; //XTERM + case TY_CSI_PR('l', 67) : /* IGNORED: DECBKM */ break; //XTERM + case TY_CSI_PR('s', 67) : /* IGNORED: DECBKM */ break; //XTERM + case TY_CSI_PR('r', 67) : /* IGNORED: DECBKM */ break; //XTERM + + // XTerm defines the following modes: + // SET_VT200_MOUSE 1000 + // SET_VT200_HIGHLIGHT_MOUSE 1001 + // SET_BTN_EVENT_MOUSE 1002 + // SET_ANY_EVENT_MOUSE 1003 + // + + //Note about mouse modes: + //There are four mouse modes which xterm-compatible terminals can support - 1000,1001,1002,1003 + //Konsole currently supports mode 1000 (basic mouse press and release) and mode 1002 (dragging the mouse). + //TODO: Implementation of mouse modes 1001 (something called hilight tracking) and + //1003 (a slight variation on dragging the mouse) + // + + case TY_CSI_PR('h', 1000) : setMode (MODE_Mouse1000); break; //XTERM + case TY_CSI_PR('l', 1000) : resetMode (MODE_Mouse1000); break; //XTERM + case TY_CSI_PR('s', 1000) : saveMode (MODE_Mouse1000); break; //XTERM + case TY_CSI_PR('r', 1000) : restoreMode (MODE_Mouse1000); break; //XTERM + + case TY_CSI_PR('h', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM + case TY_CSI_PR('l', 1001) : resetMode (MODE_Mouse1001); break; //XTERM + case TY_CSI_PR('s', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM + case TY_CSI_PR('r', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM + + case TY_CSI_PR('h', 1002) : setMode (MODE_Mouse1002); break; //XTERM + case TY_CSI_PR('l', 1002) : resetMode (MODE_Mouse1002); break; //XTERM + case TY_CSI_PR('s', 1002) : saveMode (MODE_Mouse1002); break; //XTERM + case TY_CSI_PR('r', 1002) : restoreMode (MODE_Mouse1002); break; //XTERM + + case TY_CSI_PR('h', 1003) : setMode (MODE_Mouse1003); break; //XTERM + case TY_CSI_PR('l', 1003) : resetMode (MODE_Mouse1003); break; //XTERM + case TY_CSI_PR('s', 1003) : saveMode (MODE_Mouse1003); break; //XTERM + case TY_CSI_PR('r', 1003) : restoreMode (MODE_Mouse1003); break; //XTERM + + case TY_CSI_PR('h', 1034) : /* IGNORED: 8bitinput activation */ break; //XTERM + + case TY_CSI_PR('h', 1047) : setMode (MODE_AppScreen); break; //XTERM + case TY_CSI_PR('l', 1047) : _screen[1]->clearEntireScreen(); resetMode(MODE_AppScreen); break; //XTERM + case TY_CSI_PR('s', 1047) : saveMode (MODE_AppScreen); break; //XTERM + case TY_CSI_PR('r', 1047) : restoreMode (MODE_AppScreen); break; //XTERM + + //FIXME: Unitoken: save translations + case TY_CSI_PR('h', 1048) : saveCursor ( ); break; //XTERM + case TY_CSI_PR('l', 1048) : restoreCursor ( ); break; //XTERM + case TY_CSI_PR('s', 1048) : saveCursor ( ); break; //XTERM + case TY_CSI_PR('r', 1048) : restoreCursor ( ); break; //XTERM + + //FIXME: every once new sequences like this pop up in xterm. + // Here's a guess of what they could mean. + case TY_CSI_PR('h', 1049) : saveCursor(); _screen[1]->clearEntireScreen(); setMode(MODE_AppScreen); break; //XTERM + case TY_CSI_PR('l', 1049) : resetMode(MODE_AppScreen); restoreCursor(); break; //XTERM + + //FIXME: weird DEC reset sequence + case TY_CSI_PE('p' ) : /* IGNORED: reset ( ) */ break; + + //FIXME: when changing between vt52 and ansi mode evtl do some resetting. + case TY_VT52('A' ) : _currentScreen->cursorUp ( 1); break; //VT52 + case TY_VT52('B' ) : _currentScreen->cursorDown ( 1); break; //VT52 + case TY_VT52('C' ) : _currentScreen->cursorRight ( 1); break; //VT52 + case TY_VT52('D' ) : _currentScreen->cursorLeft ( 1); break; //VT52 + + case TY_VT52('F' ) : setAndUseCharset (0, '0'); break; //VT52 + case TY_VT52('G' ) : setAndUseCharset (0, 'B'); break; //VT52 + + case TY_VT52('H' ) : _currentScreen->setCursorYX (1,1 ); break; //VT52 + case TY_VT52('I' ) : _currentScreen->reverseIndex ( ); break; //VT52 + case TY_VT52('J' ) : _currentScreen->clearToEndOfScreen ( ); break; //VT52 + case TY_VT52('K' ) : _currentScreen->clearToEndOfLine ( ); break; //VT52 + case TY_VT52('Y' ) : _currentScreen->setCursorYX (p-31,q-31 ); break; //VT52 + case TY_VT52('Z' ) : reportTerminalType ( ); break; //VT52 + case TY_VT52('<' ) : setMode (MODE_Ansi ); break; //VT52 + case TY_VT52('=' ) : setMode (MODE_AppKeyPad); break; //VT52 + case TY_VT52('>' ) : resetMode (MODE_AppKeyPad); break; //VT52 + + case TY_CSI_PG('c' ) : reportSecondaryAttributes( ); break; //VT100 + + default: + reportDecodingError(); + break; + }; +} + +void Vt102Emulation::clearScreenAndSetColumns(int columnCount) +{ + setImageSize(_currentScreen->getLines(),columnCount); + clearEntireScreen(); + setDefaultMargins(); + _currentScreen->setCursorYX(0,0); +} + +void Vt102Emulation::sendString(const char* s , int length) +{ + if ( length >= 0 ) + emit sendData(s,length); + else + emit sendData(s,strlen(s)); +} + +void Vt102Emulation::reportCursorPosition() +{ + char tmp[20]; + sprintf(tmp,"\033[%d;%dR",_currentScreen->getCursorY()+1,_currentScreen->getCursorX()+1); + sendString(tmp); +} + +void Vt102Emulation::reportTerminalType() +{ + // Primary device attribute response (Request was: ^[[0c or ^[[c (from TT321 Users Guide)) + // VT220: ^[[?63;1;2;3;6;7;8c (list deps on emul. capabilities) + // VT100: ^[[?1;2c + // VT101: ^[[?1;0c + // VT102: ^[[?6v + if (getMode(MODE_Ansi)) + sendString("\033[?1;2c"); // I'm a VT100 + else + sendString("\033/Z"); // I'm a VT52 +} + +void Vt102Emulation::reportSecondaryAttributes() +{ + // Seconday device attribute response (Request was: ^[[>0c or ^[[>c) + if (getMode(MODE_Ansi)) + sendString("\033[>0;115;0c"); // Why 115? ;) + else + sendString("\033/Z"); // FIXME I don't think VT52 knows about it but kept for + // konsoles backward compatibility. +} + +void Vt102Emulation::reportTerminalParms(int p) +// DECREPTPARM +{ + char tmp[100]; + sprintf(tmp,"\033[%d;1;1;112;112;1;0x",p); // not really true. + sendString(tmp); +} + +void Vt102Emulation::reportStatus() +{ + sendString("\033[0n"); //VT100. Device status report. 0 = Ready. +} + +void Vt102Emulation::reportAnswerBack() +{ + // FIXME - Test this with VTTEST + // This is really obsolete VT100 stuff. + const char* ANSWER_BACK = ""; + sendString(ANSWER_BACK); +} + +/*! + `cx',`cy' are 1-based. + `eventType' indicates the button pressed (0-2) + or a general mouse release (3). + + eventType represents the kind of mouse action that occurred: + 0 = Mouse button press or release + 1 = Mouse drag +*/ + +void Vt102Emulation::sendMouseEvent( int cb, int cx, int cy , int eventType ) +{ + if (cx < 1 || cy < 1) + return; + + // normal buttons are passed as 0x20 + button, + // mouse wheel (buttons 4,5) as 0x5c + button + if (cb >= 4) + cb += 0x3c; + + //Mouse motion handling + if ((getMode(MODE_Mouse1002) || getMode(MODE_Mouse1003)) && eventType == 1) + cb += 0x20; //add 32 to signify motion event + + char command[20]; + sprintf(command,"\033[M%c%c%c",cb+0x20,cx+0x20,cy+0x20); + sendString(command); +} + +void Vt102Emulation::sendText( const QString& text ) +{ + if (!text.isEmpty()) + { + QKeyEvent event(QEvent::KeyPress, + 0, + Qt::NoModifier, + text); + + sendKeyEvent(&event); // expose as a big fat keypress event + } +} +void Vt102Emulation::sendKeyEvent( QKeyEvent* event ) +{ + Qt::KeyboardModifiers modifiers = event->modifiers(); + KeyboardTranslator::States states = KeyboardTranslator::NoState; + + // get current states + if (getMode(MODE_NewLine) ) states |= KeyboardTranslator::NewLineState; + if (getMode(MODE_Ansi) ) states |= KeyboardTranslator::AnsiState; + if (getMode(MODE_AppCuKeys)) states |= KeyboardTranslator::CursorKeysState; + if (getMode(MODE_AppScreen)) states |= KeyboardTranslator::AlternateScreenState; + if (getMode(MODE_AppKeyPad) && (modifiers & Qt::KeypadModifier)) + states |= KeyboardTranslator::ApplicationKeypadState; + + // check flow control state + if (modifiers & Qt::ControlModifier) + { + if (event->key() == Qt::Key_S) + emit flowControlKeyPressed(true); + else if (event->key() == Qt::Key_Q) + emit flowControlKeyPressed(false); + } + + // lookup key binding + if ( _keyTranslator ) + { + KeyboardTranslator::Entry entry = _keyTranslator->findEntry( + event->key() , + modifiers, + states ); + + // send result to terminal + QByteArray textToSend; + + // special handling for the Alt (aka. Meta) modifier. pressing + // Alt+[Character] results in Esc+[Character] being sent + // (unless there is an entry defined for this particular combination + // in the keyboard modifier) + bool wantsAltModifier = entry.modifiers() & entry.modifierMask() & Qt::AltModifier; + bool wantsAnyModifier = entry.state() & + entry.stateMask() & KeyboardTranslator::AnyModifierState; + + if ( modifiers & Qt::AltModifier && !(wantsAltModifier || wantsAnyModifier) + && !event->text().isEmpty() ) + { + textToSend.prepend("\033"); + } + + if ( entry.command() != KeyboardTranslator::NoCommand ) + { + if (entry.command() & KeyboardTranslator::EraseCommand) + textToSend += eraseChar(); + + // TODO command handling + } + else if ( !entry.text().isEmpty() ) + { + textToSend += _codec->fromUnicode(entry.text(true,modifiers)); + } + else + textToSend += _codec->fromUnicode(event->text()); + + sendData( textToSend.constData() , textToSend.length() ); + } + else + { + // print an error message to the terminal if no key translator has been + // set + QString translatorError = QString("No keyboard translator available. " + "The information needed to convert key presses " + "into characters to send to the terminal " + "is missing."); + reset(); + receiveData( translatorError.toAscii().constData() , translatorError.count() ); + } +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* VT100 Charsets */ +/* */ +/* ------------------------------------------------------------------------- */ + +// Character Set Conversion ------------------------------------------------ -- + +/* + The processing contains a VT100 specific code translation layer. + It's still in use and mainly responsible for the line drawing graphics. + + These and some other glyphs are assigned to codes (0x5f-0xfe) + normally occupied by the latin letters. Since this codes also + appear within control sequences, the extra code conversion + does not permute with the tokenizer and is placed behind it + in the pipeline. It only applies to tokens, which represent + plain characters. + + This conversion it eventually continued in TerminalDisplay.C, since + it might involve VT100 enhanced fonts, which have these + particular glyphs allocated in (0x00-0x1f) in their code page. +*/ + +#define CHARSET _charset[_currentScreen==_screen[1]] + +// Apply current character map. + +unsigned short Vt102Emulation::applyCharset(unsigned short c) +{ + if (CHARSET.graphic && 0x5f <= c && c <= 0x7e) return vt100_graphics[c-0x5f]; + if (CHARSET.pound && c == '#' ) return 0xa3; //This mode is obsolete + return c; +} + +/* + "Charset" related part of the emulation state. + This configures the VT100 charset filter. + + While most operation work on the current _screen, + the following two are different. +*/ + +void Vt102Emulation::resetCharset(int scrno) +{ + _charset[scrno].cu_cs = 0; + strncpy(_charset[scrno].charset,"BBBB",4); + _charset[scrno].sa_graphic = false; + _charset[scrno].sa_pound = false; + _charset[scrno].graphic = false; + _charset[scrno].pound = false; +} + +void Vt102Emulation::setCharset(int n, int cs) // on both screens. +{ + _charset[0].charset[n&3] = cs; useCharset(_charset[0].cu_cs); + _charset[1].charset[n&3] = cs; useCharset(_charset[1].cu_cs); +} + +void Vt102Emulation::setAndUseCharset(int n, int cs) +{ + CHARSET.charset[n&3] = cs; + useCharset(n&3); +} + +void Vt102Emulation::useCharset(int n) +{ + CHARSET.cu_cs = n&3; + CHARSET.graphic = (CHARSET.charset[n&3] == '0'); + CHARSET.pound = (CHARSET.charset[n&3] == 'A'); //This mode is obsolete +} + +void Vt102Emulation::setDefaultMargins() +{ + _screen[0]->setDefaultMargins(); + _screen[1]->setDefaultMargins(); +} + +void Vt102Emulation::setMargins(int t, int b) +{ + _screen[0]->setMargins(t, b); + _screen[1]->setMargins(t, b); +} + +void Vt102Emulation::saveCursor() +{ + CHARSET.sa_graphic = CHARSET.graphic; + CHARSET.sa_pound = CHARSET.pound; //This mode is obsolete + // we are not clear about these + //sa_charset = charsets[cScreen->_charset]; + //sa_charset_num = cScreen->_charset; + _currentScreen->saveCursor(); +} + +void Vt102Emulation::restoreCursor() +{ + CHARSET.graphic = CHARSET.sa_graphic; + CHARSET.pound = CHARSET.sa_pound; //This mode is obsolete + _currentScreen->restoreCursor(); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Mode Operations */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* + Some of the emulations state is either added to the state of the screens. + + This causes some scoping problems, since different emulations choose to + located the mode either to the current _screen or to both. + + For strange reasons, the extend of the rendition attributes ranges over + all screens and not over the actual _screen. + + We decided on the precise precise extend, somehow. +*/ + +// "Mode" related part of the state. These are all booleans. + +void Vt102Emulation::resetModes() +{ + // MODE_Allow132Columns is not reset here + // to match Xterm's behaviour (see Xterm's VTReset() function) + + resetMode(MODE_132Columns); saveMode(MODE_132Columns); + resetMode(MODE_Mouse1000); saveMode(MODE_Mouse1000); + resetMode(MODE_Mouse1001); saveMode(MODE_Mouse1001); + resetMode(MODE_Mouse1002); saveMode(MODE_Mouse1002); + resetMode(MODE_Mouse1003); saveMode(MODE_Mouse1003); + + resetMode(MODE_AppScreen); saveMode(MODE_AppScreen); + resetMode(MODE_AppCuKeys); saveMode(MODE_AppCuKeys); + resetMode(MODE_AppKeyPad); saveMode(MODE_AppKeyPad); + resetMode(MODE_NewLine); + setMode(MODE_Ansi); +} + +void Vt102Emulation::setMode(int m) +{ + _currentModes.mode[m] = true; + switch (m) + { + case MODE_132Columns: + if (getMode(MODE_Allow132Columns)) + clearScreenAndSetColumns(132); + else + _currentModes.mode[m] = false; + break; + case MODE_Mouse1000: + case MODE_Mouse1001: + case MODE_Mouse1002: + case MODE_Mouse1003: + emit programUsesMouseChanged(false); + break; + + case MODE_AppScreen : _screen[1]->clearSelection(); + setScreen(1); + break; + } + if (m < MODES_SCREEN || m == MODE_NewLine) + { + _screen[0]->setMode(m); + _screen[1]->setMode(m); + } +} + +void Vt102Emulation::resetMode(int m) +{ + _currentModes.mode[m] = false; + switch (m) + { + case MODE_132Columns: + if (getMode(MODE_Allow132Columns)) + clearScreenAndSetColumns(80); + break; + case MODE_Mouse1000 : + case MODE_Mouse1001 : + case MODE_Mouse1002 : + case MODE_Mouse1003 : + emit programUsesMouseChanged(true); + break; + + case MODE_AppScreen : + _screen[0]->clearSelection(); + setScreen(0); + break; + } + if (m < MODES_SCREEN || m == MODE_NewLine) + { + _screen[0]->resetMode(m); + _screen[1]->resetMode(m); + } +} + +void Vt102Emulation::saveMode(int m) +{ + _savedModes.mode[m] = _currentModes.mode[m]; +} + +void Vt102Emulation::restoreMode(int m) +{ + if (_savedModes.mode[m]) + setMode(m); + else + resetMode(m); +} + +bool Vt102Emulation::getMode(int m) +{ + return _currentModes.mode[m]; +} + +char Vt102Emulation::eraseChar() const +{ + KeyboardTranslator::Entry entry = _keyTranslator->findEntry( + Qt::Key_Backspace, + 0, + 0); + if ( entry.text().count() > 0 ) + return entry.text()[0]; + else + return '\b'; +} + +// print contents of the scan buffer +static void hexdump(int* s, int len) +{ int i; + for (i = 0; i < len; i++) + { + if (s[i] == '\\') + printf("\\\\"); + else + if ((s[i]) > 32 && s[i] < 127) + printf("%c",s[i]); + else + printf("\\%04x(hex)",s[i]); + } +} + +void Vt102Emulation::reportDecodingError() +{ + if (tokenBufferPos == 0 || ( tokenBufferPos == 1 && (tokenBuffer[0] & 0xff) >= 32) ) + return; + printf("Undecodable sequence: "); + hexdump(tokenBuffer,tokenBufferPos); + printf("\n"); +} +
new file mode 100644 --- /dev/null +++ b/gui/src/Vt102Emulation.h @@ -0,0 +1,186 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight <robertknight@gmail.com> + Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef VT102EMULATION_H +#define VT102EMULATION_H + +// Standard Library +#include <stdio.h> + +// Qt +#include <QtGui/QKeyEvent> +#include <QtCore/QHash> +#include <QtCore/QTimer> + +// Konsole +#include "Emulation.h" +#include "Screen.h" + +#define MODE_AppScreen (MODES_SCREEN+0) // Mode #1 +#define MODE_AppCuKeys (MODES_SCREEN+1) // Application cursor keys (DECCKM) +#define MODE_AppKeyPad (MODES_SCREEN+2) // +#define MODE_Mouse1000 (MODES_SCREEN+3) // Send mouse X,Y position on press and release +#define MODE_Mouse1001 (MODES_SCREEN+4) // Use Hilight mouse tracking +#define MODE_Mouse1002 (MODES_SCREEN+5) // Use cell motion mouse tracking +#define MODE_Mouse1003 (MODES_SCREEN+6) // Use all motion mouse tracking +#define MODE_Ansi (MODES_SCREEN+7) // Use US Ascii for character sets G0-G3 (DECANM) +#define MODE_132Columns (MODES_SCREEN+8) // 80 <-> 132 column mode switch (DECCOLM) +#define MODE_Allow132Columns (MODES_SCREEN+9) // Allow DECCOLM mode +#define MODE_total (MODES_SCREEN+10) + +struct CharCodes +{ + // coding info + char charset[4]; // + int cu_cs; // actual charset. + bool graphic; // Some VT100 tricks + bool pound ; // Some VT100 tricks + bool sa_graphic; // saved graphic + bool sa_pound; // saved pound +}; + +/** + * Provides an xterm compatible terminal emulation based on the DEC VT102 terminal. + * A full description of this terminal can be found at http://vt100.net/docs/vt102-ug/ + * + * In addition, various additional xterm escape sequences are supported to provide + * features such as mouse input handling. + * See http://rtfm.etla.org/xterm/ctlseq.html for a description of xterm's escape + * sequences. + * + */ +class Vt102Emulation : public Emulation +{ +Q_OBJECT + +public: + /** Constructs a new emulation */ + Vt102Emulation(); + ~Vt102Emulation(); + + // reimplemented from Emulation + virtual void clearEntireScreen(); + virtual void reset(); + virtual char eraseChar() const; + +public slots: + // reimplemented from Emulation + virtual void sendString(const char*,int length = -1); + virtual void sendText(const QString& text); + virtual void sendKeyEvent(QKeyEvent*); + virtual void sendMouseEvent(int buttons, int column, int line, int eventType); + +protected: + // reimplemented from Emulation + virtual void setMode(int mode); + virtual void resetMode(int mode); + virtual void receiveChar(int cc); + +private slots: + //causes changeTitle() to be emitted for each (int,QString) pair in pendingTitleUpdates + //used to buffer multiple title updates + void updateTitle(); + +private: + unsigned short applyCharset(unsigned short c); + void setCharset(int n, int cs); + void useCharset(int n); + void setAndUseCharset(int n, int cs); + void saveCursor(); + void restoreCursor(); + void resetCharset(int scrno); + + void setMargins(int top, int bottom); + //set margins for all screens back to their defaults + void setDefaultMargins(); + + // returns true if 'mode' is set or false otherwise + bool getMode (int mode); + // saves the current boolean value of 'mode' + void saveMode (int mode); + // restores the boolean value of 'mode' + void restoreMode(int mode); + // resets all modes + // (except MODE_Allow132Columns) + void resetModes(); + + void resetTokenizer(); + #define MAX_TOKEN_LENGTH 80 + void addToCurrentToken(int cc); + int tokenBuffer[MAX_TOKEN_LENGTH]; //FIXME: overflow? + int tokenBufferPos; +#define MAXARGS 15 + void addDigit(int dig); + void addArgument(); + int argv[MAXARGS]; + int argc; + void initTokenizer(); + + // Set of flags for each of the ASCII characters which indicates + // what category they fall into (printable character, control, digit etc.) + // for the purposes of decoding terminal output + int charClass[256]; + + void reportDecodingError(); + + void processToken(int code, int p, int q); + void processWindowAttributeChange(); + + void reportTerminalType(); + void reportSecondaryAttributes(); + void reportStatus(); + void reportAnswerBack(); + void reportCursorPosition(); + void reportTerminalParms(int p); + + void onScrollLock(); + void scrollLock(const bool lock); + + // clears the screen and resizes it to the specified + // number of columns + void clearScreenAndSetColumns(int columnCount); + + CharCodes _charset[2]; + + class TerminalState + { + public: + // Initializes all modes to false + TerminalState() + { memset(&mode,false,MODE_total * sizeof(bool)); } + + bool mode[MODE_total]; + }; + + TerminalState _currentModes; + TerminalState _savedModes; + + //hash table and timer for buffering calls to the session instance + //to update the name of the session + //or window title. + //these calls occur when certain escape sequences are seen in the + //output from the terminal + QHash<int,QString> _pendingTitleUpdates; + QTimer* _titleUpdateTimer; +}; + +#endif // VT102EMULATION_H
new file mode 100644 --- /dev/null +++ b/gui/src/konsole_export.h @@ -0,0 +1,67 @@ +/* + This file is part of the KDE project + Copyright (C) 2009 Patrick Spendrin <ps_ml@gmx.de> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef KONSOLE_EXPORT_H +#define KONSOLE_EXPORT_H + +/* needed for KDE_EXPORT macros */ +//#include <kdemacros.h> +#include <QtCore/qglobal.h> +#define KDE_EXPORT +#define KDE_IMPORT + +#ifndef KONSOLEPRIVATE_EXPORT +# if defined(MAKE_KONSOLEPRIVATE_LIB) + /* We are building this library */ +# define KONSOLEPRIVATE_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define KONSOLEPRIVATE_EXPORT KDE_IMPORT +# endif +#endif + +#include <iostream> +//#define kWarning(x) std::cout + +#include <stdio.h> + +//#define i18n +inline QString i18n(char *buff,...) +{ + char msg[2048]; + va_list arglist; + + va_start(arglist,buff); + + snprintf(msg,2048,buff, arglist); + + va_end(arglist); + + return QString(msg); +} + +#define i18nc + + +//#define KDE_fseek ::fseek +//#define KDE_lseek ::lseek + + +#endif
new file mode 100644 --- /dev/null +++ b/gui/src/konsole_wcwidth.cpp @@ -0,0 +1,216 @@ +/* $XFree86: xc/programs/xterm/wcwidth.character,v 1.3 2001/07/29 22:08:16 tsi Exp $ */ +/* + * This is an implementation of wcwidth() and wcswidth() as defined in + * "The Single UNIX Specification, Version 2, The Open Group, 1997" + * <http://www.UNIX-systems.org/online.html> + * + * Markus Kuhn -- 2001-01-12 -- public domain + */ + +#include "konsole_wcwidth.h" + +struct interval { + unsigned short first; + unsigned short last; +}; + +/* auxiliary function for binary search in interval table */ +static int bisearch(quint16 ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * FullWidth (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that quint16 characters are encoded + * in ISO 10646. + */ + +int konsole_wcwidth(quint16 ucs) +{ + /* sorted list of non-overlapping intervals of non-spacing characters */ + static const struct interval combining[] = { + { 0x0300, 0x034E }, { 0x0360, 0x0362 }, { 0x0483, 0x0486 }, + { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 }, + { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C4 }, { 0x064B, 0x0655 }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x0901, 0x0902 }, { 0x093C, 0x093C }, + { 0x0941, 0x0948 }, { 0x094D, 0x094D }, { 0x0951, 0x0954 }, + { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, + { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, + { 0x0A02, 0x0A02 }, { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, + { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, + { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, + { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0B01, 0x0B01 }, + { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, + { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, + { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, + { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, { 0x0DCA, 0x0DCA }, + { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 }, + { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, + { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD }, + { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, + { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, + { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, { 0x0F99, 0x0FBC }, + { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1032 }, + { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, { 0x1058, 0x1059 }, + { 0x1160, 0x11FF }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 }, + { 0x17C9, 0x17D3 }, { 0x180B, 0x180E }, { 0x18A9, 0x18A9 }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x206A, 0x206F }, + { 0x20D0, 0x20E3 }, { 0x302A, 0x302F }, { 0x3099, 0x309A }, + { 0xFB1E, 0xFB1E }, { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, + { 0xFFF9, 0xFFFB } + }; + + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + (ucs >= 0x2e80 && ucs <= 0xa4cf && (ucs & ~0x0011) != 0x300a && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff5f) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) /* do not compare UINT16 with 0x20000 || + (ucs >= 0x20000 && ucs <= 0x2ffff) */)); +} + +#if 0 +/* + * The following function is the same as konsole_wcwidth(), except that + * spacing characters in the East Asian Ambiguous (A) category as + * defined in Unicode Technical Report #11 have a column width of 2. + * This experimental variant might be useful for users of CJK legacy + * encodings who want to migrate to UCS. It is not otherwise + * recommended for general use. + */ +int konsole_wcwidth_cjk(quint16 ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AD, 0x00AD }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014A }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, { 0x02CD, 0x02CD }, + { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, { 0x02DD, 0x02DD }, + { 0x0391, 0x03A1 }, { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, + { 0x03C3, 0x03C9 }, { 0x0401, 0x0401 }, { 0x0410, 0x044F }, + { 0x0451, 0x0451 }, { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, + { 0x2018, 0x2019 }, { 0x201C, 0x201D }, { 0x2020, 0x2021 }, + { 0x2025, 0x2027 }, { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, + { 0x2035, 0x2035 }, { 0x203B, 0x203B }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2121, 0x2122 }, { 0x2126, 0x2126 }, + { 0x212B, 0x212B }, { 0x2154, 0x2155 }, { 0x215B, 0x215B }, + { 0x215E, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21D2, 0x21D2 }, { 0x21D4, 0x21D4 }, + { 0x2200, 0x2200 }, { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, + { 0x220B, 0x220B }, { 0x220F, 0x220F }, { 0x2211, 0x2211 }, + { 0x2215, 0x2215 }, { 0x221A, 0x221A }, { 0x221D, 0x2220 }, + { 0x2223, 0x2223 }, { 0x2225, 0x2225 }, { 0x2227, 0x222C }, + { 0x222E, 0x222E }, { 0x2234, 0x2237 }, { 0x223C, 0x223D }, + { 0x2248, 0x2248 }, { 0x224C, 0x224C }, { 0x2252, 0x2252 }, + { 0x2260, 0x2261 }, { 0x2264, 0x2267 }, { 0x226A, 0x226B }, + { 0x226E, 0x226F }, { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, + { 0x2295, 0x2295 }, { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, + { 0x22BF, 0x22BF }, { 0x2312, 0x2312 }, { 0x2460, 0x24BF }, + { 0x24D0, 0x24E9 }, { 0x2500, 0x254B }, { 0x2550, 0x2574 }, + { 0x2580, 0x258F }, { 0x2592, 0x2595 }, { 0x25A0, 0x25A1 }, + { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, { 0x25B6, 0x25B7 }, + { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, { 0x25C6, 0x25C8 }, + { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, { 0x25E2, 0x25E5 }, + { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, { 0x2609, 0x2609 }, + { 0x260E, 0x260F }, { 0x261C, 0x261C }, { 0x261E, 0x261E }, + { 0x2640, 0x2640 }, { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, + { 0x2663, 0x2665 }, { 0x2667, 0x266A }, { 0x266C, 0x266D }, + { 0x266F, 0x266F }, { 0x300A, 0x300B }, { 0x301A, 0x301B }, + { 0xE000, 0xF8FF }, { 0xFFFD, 0xFFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return konsole_wcwidth(ucs); +} +#endif + +// single byte char: +1, multi byte char: +2 +int string_width( const QString &txt ) +{ + int w = 0; + for ( int i = 0; i < txt.length(); ++i ) + w += konsole_wcwidth( txt[ i ].unicode() ); + return w; +}
new file mode 100644 --- /dev/null +++ b/gui/src/konsole_wcwidth.h @@ -0,0 +1,24 @@ +/* $XFree86: xc/programs/xterm/wcwidth.h,v 1.2 2001/06/18 19:09:27 dickey Exp $ */ + +/* Markus Kuhn -- 2001-01-12 -- public domain */ +/* Adaptions for KDE by Waldo Bastian <bastian@kde.org> */ +/* + Rewritten for QT4 by e_k <e_k at users.sourceforge.net> +*/ + + +#ifndef _KONSOLE_WCWIDTH_H_ +#define _KONSOLE_WCWIDTH_H_ + +// Qt +#include <QtCore/QBool> +#include <QtCore/QString> + +int konsole_wcwidth(quint16 ucs); +#if 0 +int konsole_wcwidth_cjk(Q_UINT16 ucs); +#endif + +int string_width( const QString &txt ); + +#endif
new file mode 100644 --- /dev/null +++ b/gui/src/kprocess.cpp @@ -0,0 +1,345 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kprocess_p.h" + +#include <qfile.h> + +#ifdef Q_OS_WIN +# include <windows.h> +#else +# include <unistd.h> +# include <errno.h> +#endif + +#ifndef Q_OS_WIN +# define STD_OUTPUT_HANDLE 1 +# define STD_ERROR_HANDLE 2 +#endif + +#ifdef _WIN32_WCE +#include <stdio.h> +#endif + +void KProcessPrivate::writeAll(const QByteArray &buf, int fd) +{ +#ifdef Q_OS_WIN +#ifndef _WIN32_WCE + HANDLE h = GetStdHandle(fd); + if (h) { + DWORD wr; + WriteFile(h, buf.data(), buf.size(), &wr, 0); + } +#else + fwrite(buf.data(), 1, buf.size(), (FILE*)fd); +#endif +#else + int off = 0; + do { + int ret = ::write(fd, buf.data() + off, buf.size() - off); + if (ret < 0) { + if (errno != EINTR) + return; + } else { + off += ret; + } + } while (off < buf.size()); +#endif +} + +void KProcessPrivate::forwardStd(KProcess::ProcessChannel good, int fd) +{ + Q_Q(KProcess); + + QProcess::ProcessChannel oc = q->readChannel(); + q->setReadChannel(good); + writeAll(q->readAll(), fd); + q->setReadChannel(oc); +} + +void KProcessPrivate::_k_forwardStdout() +{ +#ifndef _WIN32_WCE + forwardStd(KProcess::StandardOutput, STD_OUTPUT_HANDLE); +#else + forwardStd(KProcess::StandardOutput, (int)stdout); +#endif +} + +void KProcessPrivate::_k_forwardStderr() +{ +#ifndef _WIN32_WCE + forwardStd(KProcess::StandardError, STD_ERROR_HANDLE); +#else + forwardStd(KProcess::StandardError, (int)stderr); +#endif +} + +///////////////////////////// +// public member functions // +///////////////////////////// + +KProcess::KProcess(QObject *parent) : + QProcess(parent), + d_ptr(new KProcessPrivate) +{ + d_ptr->q_ptr = this; + setOutputChannelMode(ForwardedChannels); +} + +KProcess::KProcess(KProcessPrivate *d, QObject *parent) : + QProcess(parent), + d_ptr(d) +{ + d_ptr->q_ptr = this; + setOutputChannelMode(ForwardedChannels); +} + +KProcess::~KProcess() +{ + delete d_ptr; +} + +void KProcess::setOutputChannelMode(OutputChannelMode mode) +{ + Q_D(KProcess); + + d->outputChannelMode = mode; + disconnect(this, SIGNAL(readyReadStandardOutput())); + disconnect(this, SIGNAL(readyReadStandardError())); + switch (mode) { + case OnlyStdoutChannel: + connect(this, SIGNAL(readyReadStandardError()), SLOT(_k_forwardStderr())); + break; + case OnlyStderrChannel: + connect(this, SIGNAL(readyReadStandardOutput()), SLOT(_k_forwardStdout())); + break; + default: + QProcess::setProcessChannelMode((ProcessChannelMode)mode); + return; + } + QProcess::setProcessChannelMode(QProcess::SeparateChannels); +} + +KProcess::OutputChannelMode KProcess::outputChannelMode() const +{ + Q_D(const KProcess); + + return d->outputChannelMode; +} + +void KProcess::setNextOpenMode(QIODevice::OpenMode mode) +{ + Q_D(KProcess); + + d->openMode = mode; +} + +#define DUMMYENV "_KPROCESS_DUMMY_=" + +void KProcess::clearEnvironment() +{ + setEnvironment(QStringList() << QString::fromLatin1(DUMMYENV)); +} + +void KProcess::setEnv(const QString &name, const QString &value, bool overwrite) +{ + QStringList env = environment(); + if (env.isEmpty()) { + env = systemEnvironment(); + env.removeAll(QString::fromLatin1(DUMMYENV)); + } + QString fname(name); + fname.append(QLatin1Char('=')); + for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) + if ((*it).startsWith(fname)) { + if (overwrite) { + *it = fname.append(value); + setEnvironment(env); + } + return; + } + env.append(fname.append(value)); + setEnvironment(env); +} + +void KProcess::unsetEnv(const QString &name) +{ + QStringList env = environment(); + if (env.isEmpty()) { + env = systemEnvironment(); + env.removeAll(QString::fromLatin1(DUMMYENV)); + } + QString fname(name); + fname.append(QLatin1Char('=')); + for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) + if ((*it).startsWith(fname)) { + env.erase(it); + if (env.isEmpty()) + env.append(QString::fromLatin1(DUMMYENV)); + setEnvironment(env); + return; + } +} + +void KProcess::setProgram(const QString &exe, const QStringList &args) +{ + Q_D(KProcess); + + d->prog = exe; + d->args = args; +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +void KProcess::setProgram(const QStringList &argv) +{ + Q_D(KProcess); + + Q_ASSERT( !argv.isEmpty() ); + d->args = argv; + d->prog = d->args.takeFirst(); +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +KProcess &KProcess::operator<<(const QString &arg) +{ + Q_D(KProcess); + + if (d->prog.isEmpty()) + d->prog = arg; + else + d->args << arg; + return *this; +} + +KProcess &KProcess::operator<<(const QStringList &args) +{ + Q_D(KProcess); + + if (d->prog.isEmpty()) + setProgram(args); + else + d->args << args; + return *this; +} + +void KProcess::clearProgram() +{ + Q_D(KProcess); + + d->prog.clear(); + d->args.clear(); +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +void KProcess::setShellCommand(const QString &cmd) +{ + Q_D(KProcess); + d->args.clear(); + d->prog = QString::fromLatin1("/bin/sh"); + d->args << QString::fromLatin1("-c") << cmd; +} + +QStringList KProcess::program() const +{ + Q_D(const KProcess); + + QStringList argv = d->args; + argv.prepend(d->prog); + return argv; +} + +void KProcess::start() +{ + Q_D(KProcess); + + QProcess::start(d->prog, d->args, d->openMode); +} + +int KProcess::execute(int msecs) +{ + start(); + if (!waitForFinished(msecs)) { + kill(); + waitForFinished(-1); + return -2; + } + return (exitStatus() == QProcess::NormalExit) ? exitCode() : -1; +} + +// static +int KProcess::execute(const QString &exe, const QStringList &args, int msecs) +{ + KProcess p; + p.setProgram(exe, args); + return p.execute(msecs); +} + +// static +int KProcess::execute(const QStringList &argv, int msecs) +{ + KProcess p; + p.setProgram(argv); + return p.execute(msecs); +} + +int KProcess::startDetached() +{ + Q_D(KProcess); + + qint64 pid; + if (!QProcess::startDetached(d->prog, d->args, workingDirectory(), &pid)) + return 0; + return (int) pid; +} + +// static +int KProcess::startDetached(const QString &exe, const QStringList &args) +{ + qint64 pid; + if (!QProcess::startDetached(exe, args, QString(), &pid)) + return 0; + return (int) pid; +} + +// static +int KProcess::startDetached(const QStringList &argv) +{ + QStringList args = argv; + QString prog = args.takeFirst(); + return startDetached(prog, args); +} + +int KProcess::pid() const +{ +#ifdef Q_OS_UNIX + return (int) QProcess::pid(); +#else + return QProcess::pid() ? QProcess::pid()->dwProcessId : 0; +#endif +} +
new file mode 100644 --- /dev/null +++ b/gui/src/kprocess.h @@ -0,0 +1,342 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPROCESS_H +#define KPROCESS_H + +#include <QtCore/QProcess> +class KProcess; +class KProcessPrivate; + + +/** + * \class KProcess kprocess.h <KProcess> + * + * Child process invocation, monitoring and control. + * + * This class extends QProcess by some useful functionality, overrides + * some defaults with saner values and wraps parts of the API into a more + * accessible one. + * This is the preferred way of spawning child processes in KDE; don't + * use QProcess directly. + * + * @author Oswald Buddenhagen <ossi@kde.org> + **/ +class KProcess : public QProcess +{ + Q_OBJECT + Q_DECLARE_PRIVATE(KProcess) + +public: + + /** + * Modes in which the output channels can be opened. + */ + enum OutputChannelMode { + SeparateChannels = QProcess::SeparateChannels, + /**< Standard output and standard error are handled by KProcess + as separate channels */ + MergedChannels = QProcess::MergedChannels, + /**< Standard output and standard error are handled by KProcess + as one channel */ + ForwardedChannels = QProcess::ForwardedChannels, + /**< Both standard output and standard error are forwarded + to the parent process' respective channel */ + OnlyStdoutChannel, + /**< Only standard output is handled; standard error is forwarded */ + OnlyStderrChannel /**< Only standard error is handled; standard output is forwarded */ + }; + + /** + * Constructor + */ + explicit KProcess(QObject *parent = 0); + + /** + * Destructor + */ + virtual ~KProcess(); + + /** + * Set how to handle the output channels of the child process. + * + * The default is ForwardedChannels, which is unlike in QProcess. + * Do not request more than you actually handle, as this output is + * simply lost otherwise. + * + * This function must be called before starting the process. + * + * @param mode the output channel handling mode + */ + void setOutputChannelMode(OutputChannelMode mode); + + /** + * Query how the output channels of the child process are handled. + * + * @return the output channel handling mode + */ + OutputChannelMode outputChannelMode() const; + + /** + * Set the QIODevice open mode the process will be opened in. + * + * This function must be called before starting the process, obviously. + * + * @param mode the open mode. Note that this mode is automatically + * "reduced" according to the channel modes and redirections. + * The default is QIODevice::ReadWrite. + */ + void setNextOpenMode(QIODevice::OpenMode mode); + + /** + * Adds the variable @p name to the process' environment. + * + * This function must be called before starting the process. + * + * @param name the name of the environment variable + * @param value the new value for the environment variable + * @param overwrite if @c false and the environment variable is already + * set, the old value will be preserved + */ + void setEnv(const QString &name, const QString &value, bool overwrite = true); + + /** + * Removes the variable @p name from the process' environment. + * + * This function must be called before starting the process. + * + * @param name the name of the environment variable + */ + void unsetEnv(const QString &name); + + /** + * Empties the process' environment. + * + * Note that LD_LIBRARY_PATH/DYLD_LIBRARY_PATH is automatically added + * on *NIX. + * + * This function must be called before starting the process. + */ + void clearEnvironment(); + + /** + * Set the program and the command line arguments. + * + * This function must be called before starting the process, obviously. + * + * @param exe the program to execute + * @param args the command line arguments for the program, + * one per list element + */ + void setProgram(const QString &exe, const QStringList &args = QStringList()); + + /** + * @overload + * + * @param argv the program to execute and the command line arguments + * for the program, one per list element + */ + void setProgram(const QStringList &argv); + + /** + * Append an element to the command line argument list for this process. + * + * If no executable is set yet, it will be set instead. + * + * For example, doing an "ls -l /usr/local/bin" can be achieved by: + * \code + * KProcess p; + * p << "ls" << "-l" << "/usr/local/bin"; + * ... + * \endcode + * + * This function must be called before starting the process, obviously. + * + * @param arg the argument to add + * @return a reference to this KProcess + */ + KProcess &operator<<(const QString& arg); + + /** + * @overload + * + * @param args the arguments to add + * @return a reference to this KProcess + */ + KProcess &operator<<(const QStringList& args); + + /** + * Clear the program and command line argument list. + */ + void clearProgram(); + + /** + * Set a command to execute through a shell (a POSIX sh on *NIX + * and cmd.exe on Windows). + * + * Using this for anything but user-supplied commands is usually a bad + * idea, as the command's syntax depends on the platform. + * Redirections including pipes, etc. are better handled by the + * respective functions provided by QProcess. + * + * If KProcess determines that the command does not really need a + * shell, it will trasparently execute it without one for performance + * reasons. + * + * This function must be called before starting the process, obviously. + * + * @param cmd the command to execute through a shell. + * The caller must make sure that all filenames etc. are properly + * quoted when passed as argument. Failure to do so often results in + * serious security holes. See KShell::quoteArg(). + */ + void setShellCommand(const QString &cmd); + + /** + * Obtain the currently set program and arguments. + * + * @return a list, the first element being the program, the remaining ones + * being command line arguments to the program. + */ + QStringList program() const; + + /** + * Start the process. + * + * @see QProcess::start(const QString &, const QStringList &, OpenMode) + */ + void start(); + + /** + * Start the process, wait for it to finish, and return the exit code. + * + * This method is roughly equivalent to the sequence: + * <code> + * start(); + * waitForFinished(msecs); + * return exitCode(); + * </code> + * + * Unlike the other execute() variants this method is not static, + * so the process can be parametrized properly and talked to. + * + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + int execute(int msecs = -1); + + /** + * @overload + * + * @param exe the program to execute + * @param args the command line arguments for the program, + * one per list element + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + static int execute(const QString &exe, const QStringList &args = QStringList(), int msecs = -1); + + /** + * @overload + * + * @param argv the program to execute and the command line arguments + * for the program, one per list element + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + static int execute(const QStringList &argv, int msecs = -1); + + /** + * Start the process and detach from it. See QProcess::startDetached() + * for details. + * + * Unlike the other startDetached() variants this method is not static, + * so the process can be parametrized properly. + * @note Currently, only the setProgram()/setShellCommand() and + * setWorkingDirectory() parametrizations are supported. + * + * The KProcess object may be re-used immediately after calling this + * function. + * + * @return the PID of the started process or 0 on error + */ + int startDetached(); + + /** + * @overload + * + * @param exe the program to start + * @param args the command line arguments for the program, + * one per list element + * @return the PID of the started process or 0 on error + */ + static int startDetached(const QString &exe, const QStringList &args = QStringList()); + + /** + * @overload + * + * @param argv the program to start and the command line arguments + * for the program, one per list element + * @return the PID of the started process or 0 on error + */ + static int startDetached(const QStringList &argv); + + /** + * Obtain the process' ID as known to the system. + * + * Unlike with QProcess::pid(), this is a real PID also on Windows. + * + * This function can be called only while the process is running. + * It cannot be applied to detached processes. + * + * @return the process ID + */ + int pid() const; + +protected: + /** + * @internal + */ + KProcess(KProcessPrivate *d, QObject *parent); + + /** + * @internal + */ + KProcessPrivate * const d_ptr; + +private: + // hide those + using QProcess::setReadChannelMode; + using QProcess::readChannelMode; + using QProcess::setProcessChannelMode; + using QProcess::processChannelMode; + + Q_PRIVATE_SLOT(d_func(), void _k_forwardStdout()) + Q_PRIVATE_SLOT(d_func(), void _k_forwardStderr()) +}; + +#include "kprocess_p.h" + +#endif +
new file mode 100644 --- /dev/null +++ b/gui/src/kprocess_p.h @@ -0,0 +1,49 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPROCESS_P_H +#define KPROCESS_P_H +class KProcess; +class KProcessPrivate; + +#include "kprocess.h" +class KProcessPrivate { + Q_DECLARE_PUBLIC(KProcess) +protected: + KProcessPrivate() : + openMode(QIODevice::ReadWrite) + { + } + void writeAll(const QByteArray &buf, int fd); + void forwardStd(KProcess::ProcessChannel good, int fd); + void _k_forwardStdout(); + void _k_forwardStderr(); + + QString prog; + QStringList args; + KProcess::OutputChannelMode outputChannelMode; + QIODevice::OpenMode openMode; + + KProcess *q_ptr; +}; + + +#endif
new file mode 100644 --- /dev/null +++ b/gui/src/kpty.cpp @@ -0,0 +1,710 @@ +/* + + This file is part of the KDE libraries + Copyright (C) 2002 Waldo Bastian <bastian@kde.org> + Copyright (C) 2002-2003,2007-2008 Oswald Buddenhagen <ossi@kde.org> + Copyright (C) 2010 KDE e.V. <kde-ev-board@kde.org> + Author Adriaan de Groot <groot@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kpty_p.h" + +#ifdef __sgi +#define __svr4__ +#endif + +#ifdef __osf__ +#define _OSF_SOURCE +#include <float.h> +#endif + +#ifdef _AIX +#define _ALL_SOURCE +#endif + +// __USE_XOPEN isn't defined by default in ICC +// (needed for ptsname(), grantpt() and unlockpt()) +#ifdef __INTEL_COMPILER +# ifndef __USE_XOPEN +# define __USE_XOPEN +# endif +#endif + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/param.h> + +#include <errno.h> +#include <fcntl.h> +#include <time.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <grp.h> + +#if defined(HAVE_PTY_H) +# include <pty.h> +#endif + +#ifdef HAVE_LIBUTIL_H +# include <libutil.h> +#elif defined(HAVE_UTIL_H) +# include <util.h> +#endif + +#define HAVE_UTMPX +#define _UTMPX_COMPAT + +#ifdef HAVE_UTEMPTER +extern "C" { +# include <utempter.h> +} +#else +# include <utmp.h> +# ifdef HAVE_UTMPX +# include <utmpx.h> +# endif +# if !defined(_PATH_UTMPX) && defined(_UTMPX_FILE) +# define _PATH_UTMPX _UTMPX_FILE +# endif +# if !defined(_PATH_WTMPX) && defined(_WTMPX_FILE) +# define _PATH_WTMPX _WTMPX_FILE +# endif +#endif + +/* for HP-UX (some versions) the extern C is needed, and for other + platforms it doesn't hurt */ +extern "C" { +#include <termios.h> +#if defined(HAVE_TERMIO_H) +# include <termio.h> // struct winsize on some systems +#endif +} + +#if defined (_HPUX_SOURCE) +# define _TERMIOS_INCLUDED +# include <bsdtty.h> +#endif + +#ifdef HAVE_SYS_STROPTS_H +# include <sys/stropts.h> // Defines I_PUSH +# define _NEW_TTY_CTRL +#endif + +#if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) || defined (__bsdi__) || defined(__APPLE__) || defined (__DragonFly__) +# define _tcgetattr(fd, ttmode) ioctl(fd, TIOCGETA, (char *)ttmode) +#else +# if defined(_HPUX_SOURCE) || defined(__Lynx__) || defined (__CYGWIN__) || defined(__sun) +# define _tcgetattr(fd, ttmode) tcgetattr(fd, ttmode) +# else +# define _tcgetattr(fd, ttmode) ioctl(fd, TCGETS, (char *)ttmode) +# endif +#endif + +#if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) || defined (__bsdi__) || defined(__APPLE__) || defined (__DragonFly__) +# define _tcsetattr(fd, ttmode) ioctl(fd, TIOCSETA, (char *)ttmode) +#else +# if defined(_HPUX_SOURCE) || defined(__CYGWIN__) || defined(__sun) +# define _tcsetattr(fd, ttmode) tcsetattr(fd, TCSANOW, ttmode) +# else +# define _tcsetattr(fd, ttmode) ioctl(fd, TCSETS, (char *)ttmode) +# endif +#endif + +#include <QtCore/Q_PID> + +#define TTY_GROUP "tty" + +#ifndef PATH_MAX +# ifdef MAXPATHLEN +# define PATH_MAX MAXPATHLEN +# else +# define PATH_MAX 1024 +# endif +#endif + +/////////////////////// +// private functions // +/////////////////////// + +////////////////// +// private data // +////////////////// + +KPtyPrivate::KPtyPrivate(KPty* parent) : + masterFd(-1), slaveFd(-1), ownMaster(true), q_ptr(parent) +{ +} + +KPtyPrivate::~KPtyPrivate() +{ +} + +#ifndef HAVE_OPENPTY +bool KPtyPrivate::chownpty(bool grant) +{ + return !QProcess::execute(KStandardDirs::findExe("kgrantpty"), + QStringList() << (grant?"--grant":"--revoke") << QString::number(masterFd)); +} +#endif + +///////////////////////////// +// public member functions // +///////////////////////////// + +KPty::KPty() : + d_ptr(new KPtyPrivate(this)) +{ +} + +KPty::KPty(KPtyPrivate *d) : + d_ptr(d) +{ + d_ptr->q_ptr = this; +} + +KPty::~KPty() +{ + close(); + delete d_ptr; +} + +bool KPty::open() +{ + Q_D(KPty); + + if (d->masterFd >= 0) + return true; + + d->ownMaster = true; + + QByteArray ptyName; + + // Find a master pty that we can open //////////////////////////////// + + // Because not all the pty animals are created equal, they want to + // be opened by several different methods. + + // We try, as we know them, one by one. + +#ifdef HAVE_OPENPTY + + char ptsn[PATH_MAX]; + if (::openpty( &d->masterFd, &d->slaveFd, ptsn, 0, 0)) + { + d->masterFd = -1; + d->slaveFd = -1; + //kWarning(175) << "Can't open a pseudo teletype"; + return false; + } + d->ttyName = ptsn; + +#else + +#ifdef HAVE__GETPTY // irix + + char *ptsn = _getpty(&d->masterFd, O_RDWR|O_NOCTTY, S_IRUSR|S_IWUSR, 0); + if (ptsn) { + d->ttyName = ptsn; + goto grantedpt; + } + +#elif defined(HAVE_PTSNAME) || defined(TIOCGPTN) + +#ifdef HAVE_POSIX_OPENPT + d->masterFd = ::posix_openpt(O_RDWR|O_NOCTTY); +#elif defined(HAVE_GETPT) + d->masterFd = ::getpt(); +#elif defined(PTM_DEVICE) + //d->masterFd = KDE_open(PTM_DEVICE, O_RDWR|O_NOCTTY); +d->masterFd = ::open(PTM_DEVICE, O_RDWR|O_NOCTTY); +#else +# error No method to open a PTY master detected. +#endif + if (d->masterFd >= 0) + { +#ifdef HAVE_PTSNAME + char *ptsn = ptsname(d->masterFd); + if (ptsn) { + d->ttyName = ptsn; +#else + int ptyno; + if (!ioctl(d->masterFd, TIOCGPTN, &ptyno)) { + char buf[32]; + sprintf(buf, "/dev/pts/%d", ptyno); + d->ttyName = buf; +#endif +#ifdef HAVE_GRANTPT + if (!grantpt(d->masterFd)) + goto grantedpt; +#else + goto gotpty; +#endif + } + ::close(d->masterFd); + d->masterFd = -1; + } +#endif // HAVE_PTSNAME || TIOCGPTN + + // Linux device names, FIXME: Trouble on other systems? + for (const char* s3 = "pqrstuvwxyzabcde"; *s3; s3++) + { + for (const char* s4 = "0123456789abcdef"; *s4; s4++) + { + ptyName = QString().sprintf("/dev/pty%c%c", *s3, *s4).toAscii(); + d->ttyName = QString().sprintf("/dev/tty%c%c", *s3, *s4).toAscii(); + + d->masterFd = ::open(ptyName.data(), O_RDWR); + if (d->masterFd >= 0) + { +#ifdef Q_OS_SOLARIS + /* Need to check the process group of the pty. + * If it exists, then the slave pty is in use, + * and we need to get another one. + */ + int pgrp_rtn; + if (ioctl(d->masterFd, TIOCGPGRP, &pgrp_rtn) == 0 || errno != EIO) { + ::close(d->masterFd); + d->masterFd = -1; + continue; + } +#endif /* Q_OS_SOLARIS */ + if (!access(d->ttyName.data(),R_OK|W_OK)) // checks availability based on permission bits + { + if (!geteuid()) + { + struct group* p = getgrnam(TTY_GROUP); + if (!p) + p = getgrnam("wheel"); + gid_t gid = p ? p->gr_gid : getgid (); + + chown(d->ttyName.data(), getuid(), gid); + chmod(d->ttyName.data(), S_IRUSR|S_IWUSR|S_IWGRP); + } + goto gotpty; + } + ::close(d->masterFd); + d->masterFd = -1; + } + } + } + + //kWarning(175) << "Can't open a pseudo teletype"; + return false; + + gotpty: + KDE_struct_stat st; + if (KDE_stat(d->ttyName.data(), &st)) + return false; // this just cannot happen ... *cough* Yeah right, I just + // had it happen when pty #349 was allocated. I guess + // there was some sort of leak? I only had a few open. + if (((st.st_uid != getuid()) || + (st.st_mode & (S_IRGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH))) && + !d->chownpty(true)) + { + + /*kWarning(175) + << "chownpty failed for device " << ptyName << "::" << d->ttyName + << "\nThis means the communication can be eavesdropped." << endl; +*/ +} + + grantedpt: + +#ifdef HAVE_REVOKE + revoke(d->ttyName.data()); +#endif + +#ifdef HAVE_UNLOCKPT + unlockpt(d->masterFd); +#elif defined(TIOCSPTLCK) + int flag = 0; + ioctl(d->masterFd, TIOCSPTLCK, &flag); +#endif + + d->slaveFd = ::open(d->ttyName.data(), O_RDWR | O_NOCTTY); + if (d->slaveFd < 0) + { + //kWarning(175) << "Can't open slave pseudo teletype"; + ::close(d->masterFd); + d->masterFd = -1; + return false; + } + +#if (defined(__svr4__) || defined(__sgi__) || defined(Q_OS_SOLARIS)) + // Solaris uses STREAMS for terminal handling. It is possible + // for the pty handling modules to be left off the stream; in that + // case push them on. ioctl(fd, I_FIND, ...) is documented to return + // 1 if the module is on the stream already. + { + static const char *pt = "ptem"; + static const char *ld = "ldterm"; + if (ioctl(d->slaveFd, I_FIND, pt) == 0) + ioctl(d->slaveFd, I_PUSH, pt); + if (ioctl(d->slaveFd, I_FIND, ld) == 0) + ioctl(d->slaveFd, I_PUSH, ld); + } +#endif + +#endif /* HAVE_OPENPTY */ + + fcntl(d->masterFd, F_SETFD, FD_CLOEXEC); + fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC); + + return true; +} + +bool KPty::open(int fd) +{ +#if !defined(HAVE_PTSNAME) && !defined(TIOCGPTN) + //kWarning(175) << "Unsupported attempt to open pty with fd" << fd; + return false; +#else + Q_D(KPty); + + if (d->masterFd >= 0) { + //kWarning(175) << "Attempting to open an already open pty"; + return false; + } + + d->ownMaster = false; + +# ifdef HAVE_PTSNAME + char *ptsn = ptsname(fd); + if (ptsn) { + d->ttyName = ptsn; +# else + int ptyno; + if (!ioctl(fd, TIOCGPTN, &ptyno)) { + char buf[32]; + sprintf(buf, "/dev/pts/%d", ptyno); + d->ttyName = buf; +# endif + } else { + //kWarning(175) << "Failed to determine pty slave device for fd" << fd; + return false; + } + + d->masterFd = fd; + if (!openSlave()) { + d->masterFd = -1; + return false; + } + + return true; +#endif +} + +void KPty::closeSlave() +{ + Q_D(KPty); + + if (d->slaveFd < 0) + return; + ::close(d->slaveFd); + d->slaveFd = -1; +} + +bool KPty::openSlave() +{ + Q_D(KPty); + + if (d->slaveFd >= 0) + return true; + if (d->masterFd < 0) { + //kWarning(175) << "Attempting to open pty slave while master is closed"; + return false; + } + d->slaveFd = ::open(d->ttyName.data(), O_RDWR | O_NOCTTY); + if (d->slaveFd < 0) { + //kWarning(175) << "Can't open slave pseudo teletype"; + return false; + } + fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC); + return true; +} + +void KPty::close() +{ + Q_D(KPty); + + if (d->masterFd < 0) + return; + closeSlave(); + if (d->ownMaster) { +#ifndef HAVE_OPENPTY + // don't bother resetting unix98 pty, it will go away after closing master anyway. + if (memcmp(d->ttyName.data(), "/dev/pts/", 9)) { + if (!geteuid()) { + struct stat st; + if (!stat(d->ttyName.data(), &st)) { + chown(d->ttyName.data(), 0, st.st_gid == getgid() ? 0 : -1); + chmod(d->ttyName.data(), S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); + } + } else { + fcntl(d->masterFd, F_SETFD, 0); + d->chownpty(false); + } + } +#endif + ::close(d->masterFd); + } + d->masterFd = -1; +} + +void KPty::setCTty() +{ + Q_D(KPty); + + // Setup job control ////////////////////////////////// + + // Become session leader, process group leader, + // and get rid of the old controlling terminal. + setsid(); + + // make our slave pty the new controlling terminal. +#ifdef TIOCSCTTY + ioctl(d->slaveFd, TIOCSCTTY, 0); +#else + // __svr4__ hack: the first tty opened after setsid() becomes controlling tty + ::close(open(d->ttyName, O_WRONLY, 0)); +#endif + + // make our new process group the foreground group on the pty + int pgrp = getpid(); +#if defined(_POSIX_VERSION) || defined(__svr4__) + tcsetpgrp(d->slaveFd, pgrp); +#elif defined(TIOCSPGRP) + ioctl(d->slaveFd, TIOCSPGRP, (char *)&pgrp); +#endif +} + +void KPty::login(const char *user, const char *remotehost) +{ +#ifdef HAVE_UTEMPTER + Q_D(KPty); + + addToUtmp(d->ttyName, remotehost, d->masterFd); + Q_UNUSED(user); +#else +# ifdef HAVE_UTMPX + struct utmpx l_struct; +# else + struct utmp l_struct; +# endif + memset(&l_struct, 0, sizeof(l_struct)); + // note: strncpy without terminators _is_ correct here. man 4 utmp + + if (user) + strncpy(l_struct.ut_name, user, sizeof(l_struct.ut_name)); + + if (remotehost) { + strncpy(l_struct.ut_host, remotehost, sizeof(l_struct.ut_host)); +# ifdef HAVE_STRUCT_UTMP_UT_SYSLEN + l_struct.ut_syslen = qMin(strlen(remotehost), sizeof(l_struct.ut_host)); +# endif + } + +# ifndef __GLIBC__ + Q_D(KPty); + const char *str_ptr = d->ttyName.data(); + if (!memcmp(str_ptr, "/dev/", 5)) + str_ptr += 5; + strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line)); +# ifdef HAVE_STRUCT_UTMP_UT_ID + strncpy(l_struct.ut_id, + str_ptr + strlen(str_ptr) - sizeof(l_struct.ut_id), + sizeof(l_struct.ut_id)); +# endif +# endif + +# ifdef HAVE_UTMPX + //gettimeofday(&l_struct.ut_tv, 0); + gettimeofday((struct timeval *)&l_struct.ut_tv, 0); +# else + l_struct.ut_time = time(0); +# endif + +# ifdef HAVE_LOGIN +# ifdef HAVE_LOGINX + ::loginx(&l_struct); +# else + ::login(&l_struct); +# endif +# else +# ifdef HAVE_STRUCT_UTMP_UT_TYPE + l_struct.ut_type = USER_PROCESS; +# endif +# ifdef HAVE_STRUCT_UTMP_UT_PID + l_struct.ut_pid = getpid(); +# ifdef HAVE_STRUCT_UTMP_UT_SESSION + l_struct.ut_session = getsid(0); +# endif +# endif +# ifdef HAVE_UTMPX + utmpxname(_PATH_UTMPX); + setutxent(); + pututxline(&l_struct); + endutxent(); + //updwtmpx(_PATH_WTMPX, &l_struct); +# else + utmpname(_PATH_UTMP); + setutent(); + pututline(&l_struct); + endutent(); + updwtmp(_PATH_WTMP, &l_struct); +# endif +# endif +#endif +} + +void KPty::logout() +{ +#ifdef HAVE_UTEMPTER + Q_D(KPty); + + removeLineFromUtmp(d->ttyName, d->masterFd); +#else + Q_D(KPty); + + const char *str_ptr = d->ttyName.data(); + if (!memcmp(str_ptr, "/dev/", 5)) + str_ptr += 5; +# ifdef __GLIBC__ + else { + const char *sl_ptr = strrchr(str_ptr, '/'); + if (sl_ptr) + str_ptr = sl_ptr + 1; + } +# endif +# ifdef HAVE_LOGIN +# ifdef HAVE_LOGINX + ::logoutx(str_ptr, 0, DEAD_PROCESS); +# else + ::logout(str_ptr); +# endif +# else +# ifdef HAVE_UTMPX + struct utmpx l_struct, *ut; +# else + struct utmp l_struct, *ut; +# endif + memset(&l_struct, 0, sizeof(l_struct)); + + strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line)); + +# ifdef HAVE_UTMPX + utmpxname(_PATH_UTMPX); + setutxent(); + if ((ut = getutxline(&l_struct))) { +# else + utmpname(_PATH_UTMP); + setutent(); + if ((ut = getutline(&l_struct))) { +# endif + memset(ut->ut_name, 0, sizeof(*ut->ut_name)); + memset(ut->ut_host, 0, sizeof(*ut->ut_host)); +# ifdef HAVE_STRUCT_UTMP_UT_SYSLEN + ut->ut_syslen = 0; +# endif +# ifdef HAVE_STRUCT_UTMP_UT_TYPE + ut->ut_type = DEAD_PROCESS; +# endif +# ifdef HAVE_UTMPX + //gettimeofday(&(ut->ut_tv), 0); + gettimeofday((struct timeval *)&(ut->ut_tv), 0); + pututxline(ut); + } + endutxent(); +# else + ut->ut_time = time(0); + pututline(ut); + } + endutent(); +# endif +# endif +#endif +} + +bool KPty::tcGetAttr(struct ::termios *ttmode) const +{ + Q_D(const KPty); + +#ifdef Q_OS_SOLARIS + if (_tcgetattr(d->slaveFd, ttmode) == 0) return true; +#endif + return _tcgetattr(d->masterFd, ttmode) == 0; +} + +bool KPty::tcSetAttr(struct ::termios *ttmode) +{ + Q_D(KPty); + +#ifdef Q_OS_SOLARIS + if (_tcsetattr(d->slaveFd, ttmode) == 0) return true; +#endif + return _tcsetattr(d->masterFd, ttmode) == 0; +} + +bool KPty::setWinSize(int lines, int columns) +{ + Q_D(KPty); + + struct winsize winSize; + memset(&winSize, 0, sizeof(winSize)); + winSize.ws_row = (unsigned short)lines; + winSize.ws_col = (unsigned short)columns; + return ioctl(d->masterFd, TIOCSWINSZ, (char *)&winSize) == 0; +} + +bool KPty::setEcho(bool echo) +{ + struct ::termios ttmode; + if (!tcGetAttr(&ttmode)) + return false; + if (!echo) + ttmode.c_lflag &= ~ECHO; + else + ttmode.c_lflag |= ECHO; + return tcSetAttr(&ttmode); +} + +const char *KPty::ttyName() const +{ + Q_D(const KPty); + + return d->ttyName.data(); +} + +int KPty::masterFd() const +{ + Q_D(const KPty); + + return d->masterFd; +} + +int KPty::slaveFd() const +{ + Q_D(const KPty); + + return d->slaveFd; +}
new file mode 100644 --- /dev/null +++ b/gui/src/kpty.h @@ -0,0 +1,204 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2003,2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kpty_h +#define kpty_h +#include <QtCore/qglobal.h> + +struct KPtyPrivate; +struct termios; + +/** + * Provides primitives for opening & closing a pseudo TTY pair, assigning the + * controlling TTY, utmp registration and setting various terminal attributes. + */ +class KPty { + Q_DECLARE_PRIVATE(KPty) + +public: + + /** + * Constructor + */ + KPty(); + + /** + * Destructor: + * + * If the pty is still open, it will be closed. Note, however, that + * an utmp registration is @em not undone. + */ + ~KPty(); + + /** + * Create a pty master/slave pair. + * + * @return true if a pty pair was successfully opened + */ + bool open(); + + /** + * Open using an existing pty master. + * + * @param fd an open pty master file descriptor. + * The ownership of the fd remains with the caller; + * it will not be automatically closed at any point. + * @return true if a pty pair was successfully opened + */ + bool open(int fd); + + /** + * Close the pty master/slave pair. + */ + void close(); + + /** + * Close the pty slave descriptor. + * + * When creating the pty, KPty also opens the slave and keeps it open. + * Consequently the master will never receive an EOF notification. + * Usually this is the desired behavior, as a closed pty slave can be + * reopened any time - unlike a pipe or socket. However, in some cases + * pipe-alike behavior might be desired. + * + * After this function was called, slaveFd() and setCTty() cannot be + * used. + */ + void closeSlave(); + + /** + * Open the pty slave descriptor. + * + * This undoes the effect of closeSlave(). + * + * @return true if the pty slave was successfully opened + */ + bool openSlave(); + + /** + * Creates a new session and process group and makes this pty the + * controlling tty. + */ + void setCTty(); + + /** + * Creates an utmp entry for the tty. + * This function must be called after calling setCTty and + * making this pty the stdin. + * @param user the user to be logged on + * @param remotehost the host from which the login is coming. This is + * @em not the local host. For remote logins it should be the hostname + * of the client. For local logins from inside an X session it should + * be the name of the X display. Otherwise it should be empty. + */ + void login(const char *user = 0, const char *remotehost = 0); + + /** + * Removes the utmp entry for this tty. + */ + void logout(); + + /** + * Wrapper around tcgetattr(3). + * + * This function can be used only while the PTY is open. + * You will need an #include <termios.h> to do anything useful + * with it. + * + * @param ttmode a pointer to a termios structure. + * Note: when declaring ttmode, @c struct @c ::termios must be used - + * without the '::' some version of HP-UX thinks, this declares + * the struct in your class, in your method. + * @return @c true on success, false otherwise + */ + bool tcGetAttr(struct ::termios *ttmode) const; + + /** + * Wrapper around tcsetattr(3) with mode TCSANOW. + * + * This function can be used only while the PTY is open. + * + * @param ttmode a pointer to a termios structure. + * @return @c true on success, false otherwise. Note that success means + * that @em at @em least @em one attribute could be set. + */ + bool tcSetAttr(struct ::termios *ttmode); + + /** + * Change the logical (screen) size of the pty. + * The default is 24 lines by 80 columns. + * + * This function can be used only while the PTY is open. + * + * @param lines the number of rows + * @param columns the number of columns + * @return @c true on success, false otherwise + */ + bool setWinSize(int lines, int columns); + + /** + * Set whether the pty should echo input. + * + * Echo is on by default. + * If the output of automatically fed (non-interactive) PTY clients + * needs to be parsed, disabling echo often makes it much simpler. + * + * This function can be used only while the PTY is open. + * + * @param echo true if input should be echoed. + * @return @c true on success, false otherwise + */ + bool setEcho(bool echo); + + /** + * @return the name of the slave pty device. + * + * This function should be called only while the pty is open. + */ + const char *ttyName() const; + + /** + * @return the file descriptor of the master pty + * + * This function should be called only while the pty is open. + */ + int masterFd() const; + + /** + * @return the file descriptor of the slave pty + * + * This function should be called only while the pty slave is open. + */ + int slaveFd() const; + +protected: + /** + * @internal + */ + KPty(KPtyPrivate *d); + + /** + * @internal + */ + KPtyPrivate * const d_ptr; +}; + +#endif +
new file mode 100644 --- /dev/null +++ b/gui/src/kpty_export.h @@ -0,0 +1,46 @@ +/* This file is part of the KDE project + Copyright (C) 2007 David Faure <faure@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPTY_EXPORT_H +#define KPTY_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +//#include <kdemacros.h> +#include <QtCore/qglobal.h> +#define KDE_EXPORT +#define KDE_IMPORT + +#ifndef KPTY_EXPORT +# if defined(KDELIBS_STATIC_LIBS) + /* No export/import for static libraries */ +# define KPTY_EXPORT +# elif defined(MAKE_KDECORE_LIB) + /* We are building this library */ +# define KPTY_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define KPTY_EXPORT KDE_IMPORT +# endif +#endif + +# ifndef KPTY_EXPORT_DEPRECATED +# define KPTY_EXPORT_DEPRECATED KDE_DEPRECATED KPTY_EXPORT +# endif + +#endif
new file mode 100644 --- /dev/null +++ b/gui/src/kpty_p.h @@ -0,0 +1,58 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2003,2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kpty_p_h +#define kpty_p_h + +#include "kpty.h" + +#if defined(Q_OS_MAC) +#define HAVE_UTIL_H +#define HAVE_UTMPX +#define _UTMPX_COMPAT +#define HAVE_PTSNAME +#define HAVE_SYS_TIME_H +#else +#define HAVE_PTY_H +#endif + +#define HAVE_OPENPTY + +#include <QtCore/QByteArray> + +struct KPtyPrivate { + Q_DECLARE_PUBLIC(KPty) + + KPtyPrivate(KPty* parent); + virtual ~KPtyPrivate(); +#ifndef HAVE_OPENPTY + bool chownpty(bool grant); +#endif + + int masterFd; + int slaveFd; + bool ownMaster:1; + + QByteArray ttyName; + + KPty *q_ptr; +}; + +#endif
new file mode 100644 --- /dev/null +++ b/gui/src/kptydevice.cpp @@ -0,0 +1,413 @@ +/* + + This file is part of the KDE libraries + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + Copyright (C) 2010 KDE e.V. <kde-ev-board@kde.org> + Author Adriaan de Groot <groot@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kptydevice.h" +#include "kpty_p.h" +#define i18n + +#include <QtCore/QSocketNotifier> + +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <termios.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#if defined(Q_OS_FREEBSD) || defined(Q_OS_MAC) + // "the other end's output queue size" - kinda braindead, huh? +# define PTY_BYTES_AVAILABLE TIOCOUTQ +#elif defined(TIOCINQ) + // "our end's input queue size" +# define PTY_BYTES_AVAILABLE TIOCINQ +#else + // likewise. more generic ioctl (theoretically) +# define PTY_BYTES_AVAILABLE FIONREAD +#endif + +////////////////// +// private data // +////////////////// + +// Lifted from Qt. I don't think they would mind. ;) +// Re-lift again from Qt whenever a proper replacement for pthread_once appears +static void qt_ignore_sigpipe() +{ + static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0); + if (atom.testAndSetRelaxed(0, 1)) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &noaction, 0); + } +} + +#define NO_INTR(ret,func) do { ret = func; } while (ret < 0 && errno == EINTR) + +bool KPtyDevicePrivate::_k_canRead() +{ + Q_Q(KPtyDevice); + qint64 readBytes = 0; + +#ifdef Q_OS_IRIX // this should use a config define, but how to check it? + size_t available; +#else + int available; +#endif + if (!::ioctl(q->masterFd(), PTY_BYTES_AVAILABLE, (char *) &available)) { +#ifdef Q_OS_SOLARIS + // A Pty is a STREAMS module, and those can be activated + // with 0 bytes available. This happens either when ^C is + // pressed, or when an application does an explicit write(a,b,0) + // which happens in experiments fairly often. When 0 bytes are + // available, you must read those 0 bytes to clear the STREAMS + // module, but we don't want to hit the !readBytes case further down. + if (!available) { + char c; + // Read the 0-byte STREAMS message + NO_INTR(readBytes, read(q->masterFd(), &c, 0)); + // Should return 0 bytes read; -1 is error + if (readBytes < 0) { + readNotifier->setEnabled(false); + emit q->readEof(); + return false; + } + return true; + } +#endif + + char *ptr = readBuffer.reserve(available); +#ifdef Q_OS_SOLARIS + // Even if available > 0, it is possible for read() + // to return 0 on Solaris, due to 0-byte writes in the stream. + // Ignore them and keep reading until we hit *some* data. + // In Solaris it is possible to have 15 bytes available + // and to (say) get 0, 0, 6, 0 and 9 bytes in subsequent reads. + // Because the stream is set to O_NONBLOCK in finishOpen(), + // an EOF read will return -1. + readBytes = 0; + while (!readBytes) +#endif + // Useless block braces except in Solaris + { + NO_INTR(readBytes, read(q->masterFd(), ptr, available)); + } + if (readBytes < 0) { + readBuffer.unreserve(available); + q->setErrorString(i18n("Error reading from PTY")); + return false; + } + readBuffer.unreserve(available - readBytes); // *should* be a no-op + } + + if (!readBytes) { + readNotifier->setEnabled(false); + emit q->readEof(); + return false; + } else { + if (!emittedReadyRead) { + emittedReadyRead = true; + emit q->readyRead(); + emittedReadyRead = false; + } + return true; + } +} + +bool KPtyDevicePrivate::_k_canWrite() +{ + Q_Q(KPtyDevice); + + writeNotifier->setEnabled(false); + if (writeBuffer.isEmpty()) + return false; + + qt_ignore_sigpipe(); + int wroteBytes; + NO_INTR(wroteBytes, + write(q->masterFd(), + writeBuffer.readPointer(), writeBuffer.readSize())); + if (wroteBytes < 0) { + q->setErrorString(i18n("Error writing to PTY")); + return false; + } + writeBuffer.free(wroteBytes); + + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(wroteBytes); + emittedBytesWritten = false; + } + + if (!writeBuffer.isEmpty()) + writeNotifier->setEnabled(true); + return true; +} + +#ifndef timeradd +// Lifted from GLIBC +# define timeradd(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ + if ((result)->tv_usec >= 1000000) { \ + ++(result)->tv_sec; \ + (result)->tv_usec -= 1000000; \ + } \ + } while (0) +# define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +bool KPtyDevicePrivate::doWait(int msecs, bool reading) +{ + Q_Q(KPtyDevice); +#ifndef __linux__ + struct timeval etv; +#endif + struct timeval tv, *tvp; + + if (msecs < 0) + tvp = 0; + else { + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs % 1000) * 1000; +#ifndef __linux__ + gettimeofday(&etv, 0); + timeradd(&tv, &etv, &etv); +#endif + tvp = &tv; + } + + while (reading ? readNotifier->isEnabled() : !writeBuffer.isEmpty()) { + fd_set rfds; + fd_set wfds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + if (readNotifier->isEnabled()) + FD_SET(q->masterFd(), &rfds); + if (!writeBuffer.isEmpty()) + FD_SET(q->masterFd(), &wfds); + +#ifndef __linux__ + if (tvp) { + gettimeofday(&tv, 0); + timersub(&etv, &tv, &tv); + if (tv.tv_sec < 0) + tv.tv_sec = tv.tv_usec = 0; + } +#endif + + switch (select(q->masterFd() + 1, &rfds, &wfds, 0, tvp)) { + case -1: + if (errno == EINTR) + break; + return false; + case 0: + q->setErrorString(i18n("PTY operation timed out")); + return false; + default: + if (FD_ISSET(q->masterFd(), &rfds)) { + bool canRead = _k_canRead(); + if (reading && canRead) + return true; + } + if (FD_ISSET(q->masterFd(), &wfds)) { + bool canWrite = _k_canWrite(); + if (!reading) + return canWrite; + } + break; + } + } + return false; +} + +void KPtyDevicePrivate::finishOpen(QIODevice::OpenMode mode) +{ + Q_Q(KPtyDevice); + + q->QIODevice::open(mode); + fcntl(q->masterFd(), F_SETFL, O_NONBLOCK); + readBuffer.clear(); + readNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Read, q); + writeNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Write, q); + QObject::connect(readNotifier, SIGNAL(activated(int)), q, SLOT(_k_canRead())); + QObject::connect(writeNotifier, SIGNAL(activated(int)), q, SLOT(_k_canWrite())); + readNotifier->setEnabled(true); +} + +///////////////////////////// +// public member functions // +///////////////////////////// + +KPtyDevice::KPtyDevice(QObject *parent) : + QIODevice(parent), + KPty(new KPtyDevicePrivate(this)) +{ +} + +KPtyDevice::~KPtyDevice() +{ + close(); +} + +bool KPtyDevice::open(OpenMode mode) +{ + Q_D(KPtyDevice); + + if (masterFd() >= 0) + return true; + + if (!KPty::open()) { + setErrorString(i18n("Error opening PTY")); + return false; + } + + d->finishOpen(mode); + + return true; +} + +bool KPtyDevice::open(int fd, OpenMode mode) +{ + Q_D(KPtyDevice); + + if (!KPty::open(fd)) { + setErrorString(i18n("Error opening PTY")); + return false; + } + + d->finishOpen(mode); + + return true; +} + +void KPtyDevice::close() +{ + Q_D(KPtyDevice); + + if (masterFd() < 0) + return; + + delete d->readNotifier; + delete d->writeNotifier; + + QIODevice::close(); + + KPty::close(); +} + +bool KPtyDevice::isSequential() const +{ + return true; +} + +bool KPtyDevice::canReadLine() const +{ + Q_D(const KPtyDevice); + return QIODevice::canReadLine() || d->readBuffer.canReadLine(); +} + +bool KPtyDevice::atEnd() const +{ + Q_D(const KPtyDevice); + return QIODevice::atEnd() && d->readBuffer.isEmpty(); +} + +qint64 KPtyDevice::bytesAvailable() const +{ + Q_D(const KPtyDevice); + return QIODevice::bytesAvailable() + d->readBuffer.size(); +} + +qint64 KPtyDevice::bytesToWrite() const +{ + Q_D(const KPtyDevice); + return d->writeBuffer.size(); +} + +bool KPtyDevice::waitForReadyRead(int msecs) +{ + Q_D(KPtyDevice); + return d->doWait(msecs, true); +} + +bool KPtyDevice::waitForBytesWritten(int msecs) +{ + Q_D(KPtyDevice); + return d->doWait(msecs, false); +} + +void KPtyDevice::setSuspended(bool suspended) +{ + Q_D(KPtyDevice); + d->readNotifier->setEnabled(!suspended); +} + +bool KPtyDevice::isSuspended() const +{ + Q_D(const KPtyDevice); + return !d->readNotifier->isEnabled(); +} + +// protected +qint64 KPtyDevice::readData(char *data, qint64 maxlen) +{ + Q_D(KPtyDevice); + return d->readBuffer.read(data, (int)qMin<qint64>(maxlen, KMAXINT)); +} + +// protected +qint64 KPtyDevice::readLineData(char *data, qint64 maxlen) +{ + Q_D(KPtyDevice); + return d->readBuffer.readLine(data, (int)qMin<qint64>(maxlen, KMAXINT)); +} + +// protected +qint64 KPtyDevice::writeData(const char *data, qint64 len) +{ + Q_D(KPtyDevice); + Q_ASSERT(len <= KMAXINT); + + d->writeBuffer.write(data, len); + d->writeNotifier->setEnabled(true); + return len; +} +
new file mode 100644 --- /dev/null +++ b/gui/src/kptydevice.h @@ -0,0 +1,353 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kptydev_h +#define kptydev_h + +struct KPtyPrivate; +struct KPtyDevicePrivate; + +#include "kpty.h" +#include "kpty_p.h" +#include <QtCore/QIODevice> +#include <QSocketNotifier> + +#define Q_DECLARE_PRIVATE_MI(Class, SuperClass) \ + inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(SuperClass::d_ptr); } \ + inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(SuperClass::d_ptr); } \ + friend class Class##Private; + +/** + * Encapsulates KPty into a QIODevice, so it can be used with Q*Stream, etc. + */ +class KPtyDevice : public QIODevice, public KPty { //krazy:exclude=dpointer (via macro) + Q_OBJECT + Q_DECLARE_PRIVATE_MI(KPtyDevice, KPty) + +public: + + /** + * Constructor + */ + KPtyDevice(QObject *parent = 0); + + /** + * Destructor: + * + * If the pty is still open, it will be closed. Note, however, that + * an utmp registration is @em not undone. + */ + virtual ~KPtyDevice(); + + /** + * Create a pty master/slave pair. + * + * @return true if a pty pair was successfully opened + */ + virtual bool open(OpenMode mode = ReadWrite | Unbuffered); + + /** + * Open using an existing pty master. The ownership of the fd + * remains with the caller, i.e., close() will not close the fd. + * + * This is useful if you wish to attach a secondary "controller" to an + * existing pty device such as a terminal widget. + * Note that you will need to use setSuspended() on both devices to + * control which one gets the incoming data from the pty. + * + * @param fd an open pty master file descriptor. + * @param mode the device mode to open the pty with. + * @return true if a pty pair was successfully opened + */ + bool open(int fd, OpenMode mode = ReadWrite | Unbuffered); + + /** + * Close the pty master/slave pair. + */ + virtual void close(); + + /** + * Sets whether the KPtyDevice monitors the pty for incoming data. + * + * When the KPtyDevice is suspended, it will no longer attempt to buffer + * data that becomes available from the pty and it will not emit any + * signals. + * + * Do not use on closed ptys. + * After a call to open(), the pty is not suspended. If you need to + * ensure that no data is read, call this function before the main loop + * is entered again (i.e., immediately after opening the pty). + */ + void setSuspended(bool suspended); + + /** + * Returns true if the KPtyDevice is not monitoring the pty for incoming + * data. + * + * Do not use on closed ptys. + * + * See setSuspended() + */ + bool isSuspended() const; + + /** + * @return always true + */ + virtual bool isSequential() const; + + /** + * @reimp + */ + bool canReadLine() const; + + /** + * @reimp + */ + bool atEnd() const; + + /** + * @reimp + */ + qint64 bytesAvailable() const; + + /** + * @reimp + */ + qint64 bytesToWrite() const; + + bool waitForBytesWritten(int msecs = -1); + bool waitForReadyRead(int msecs = -1); + + +Q_SIGNALS: + /** + * Emitted when EOF is read from the PTY. + * + * Data may still remain in the buffers. + */ + void readEof(); + +protected: + virtual qint64 readData(char *data, qint64 maxSize); + virtual qint64 readLineData(char *data, qint64 maxSize); + virtual qint64 writeData(const char *data, qint64 maxSize); + +private: + Q_PRIVATE_SLOT(d_func(), bool _k_canRead()) + Q_PRIVATE_SLOT(d_func(), bool _k_canWrite()) +}; + +#define KMAXINT ((int)(~0U >> 1)) + +///////////////////////////////////////////////////// +// Helper. Remove when QRingBuffer becomes public. // +///////////////////////////////////////////////////// + +#include <QtCore/qbytearray.h> +#include <QtCore/qlinkedlist.h> + +#define CHUNKSIZE 4096 + +class KRingBuffer +{ +public: + KRingBuffer() + { + clear(); + } + + void clear() + { + buffers.clear(); + QByteArray tmp; + tmp.resize(CHUNKSIZE); + buffers << tmp; + head = tail = 0; + totalSize = 0; + } + + inline bool isEmpty() const + { + return buffers.count() == 1 && !tail; + } + + inline int size() const + { + return totalSize; + } + + inline int readSize() const + { + return (buffers.count() == 1 ? tail : buffers.first().size()) - head; + } + + inline const char *readPointer() const + { + Q_ASSERT(totalSize > 0); + return buffers.first().constData() + head; + } + + void free(int bytes) + { + totalSize -= bytes; + Q_ASSERT(totalSize >= 0); + + forever { + int nbs = readSize(); + + if (bytes < nbs) { + head += bytes; + if (head == tail && buffers.count() == 1) { + buffers.first().resize(CHUNKSIZE); + head = tail = 0; + } + break; + } + + bytes -= nbs; + if (buffers.count() == 1) { + buffers.first().resize(CHUNKSIZE); + head = tail = 0; + break; + } + + buffers.removeFirst(); + head = 0; + } + } + + char *reserve(int bytes) + { + totalSize += bytes; + + char *ptr; + if (tail + bytes <= buffers.last().size()) { + ptr = buffers.last().data() + tail; + tail += bytes; + } else { + buffers.last().resize(tail); + QByteArray tmp; + tmp.resize(qMax(CHUNKSIZE, bytes)); + ptr = tmp.data(); + buffers << tmp; + tail = bytes; + } + return ptr; + } + + // release a trailing part of the last reservation + inline void unreserve(int bytes) + { + totalSize -= bytes; + tail -= bytes; + } + + inline void write(const char *data, int len) + { + memcpy(reserve(len), data, len); + } + + // Find the first occurrence of c and return the index after it. + // If c is not found until maxLength, maxLength is returned, provided + // it is smaller than the buffer size. Otherwise -1 is returned. + int indexAfter(char c, int maxLength = KMAXINT) const + { + int index = 0; + int start = head; + QLinkedList<QByteArray>::ConstIterator it = buffers.begin(); + forever { + if (!maxLength) + return index; + if (index == size()) + return -1; + const QByteArray &buf = *it; + ++it; + int len = qMin((it == buffers.end() ? tail : buf.size()) - start, + maxLength); + const char *ptr = buf.data() + start; + if (const char *rptr = (const char *)memchr(ptr, c, len)) + return index + (rptr - ptr) + 1; + index += len; + maxLength -= len; + start = 0; + } + } + + inline int lineSize(int maxLength = KMAXINT) const + { + return indexAfter('\n', maxLength); + } + + inline bool canReadLine() const + { + return lineSize() != -1; + } + + int read(char *data, int maxLength) + { + int bytesToRead = qMin(size(), maxLength); + int readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readPointer(); + int bs = qMin(bytesToRead - readSoFar, readSize()); + memcpy(data + readSoFar, ptr, bs); + readSoFar += bs; + free(bs); + } + return readSoFar; + } + + int readLine(char *data, int maxLength) + { + return read(data, lineSize(qMin(maxLength, size()))); + } + +private: + QLinkedList<QByteArray> buffers; + int head, tail; + int totalSize; +}; + +struct KPtyDevicePrivate : public KPtyPrivate { + Q_DECLARE_PUBLIC(KPtyDevice) + + KPtyDevicePrivate(KPty* parent) : + KPtyPrivate(parent), + emittedReadyRead(false), emittedBytesWritten(false), + readNotifier(0), writeNotifier(0) + { + } + + bool _k_canRead(); + bool _k_canWrite(); + + bool doWait(int msecs, bool reading); + void finishOpen(QIODevice::OpenMode mode); + + bool emittedReadyRead; + bool emittedBytesWritten; + QSocketNotifier *readNotifier; + QSocketNotifier *writeNotifier; + KRingBuffer readBuffer; + KRingBuffer writeBuffer; +}; + +#endif +
new file mode 100644 --- /dev/null +++ b/gui/src/kptyprocess.cpp @@ -0,0 +1,119 @@ +/* + + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "kptyprocess.h" +#include "kprocess.h" + +#include "kptydevice.h" + +#include <stdlib.h> +#include <unistd.h> + +////////////////// +// private data // +////////////////// + +KPtyProcess::KPtyProcess(QObject *parent) : + KProcess(new KPtyProcessPrivate, parent) +{ + Q_D(KPtyProcess); + + d->pty = new KPtyDevice(this); + d->pty->open(); + connect(this, SIGNAL(stateChanged(QProcess::ProcessState)), + SLOT(_k_onStateChanged(QProcess::ProcessState))); +} + +KPtyProcess::KPtyProcess(int ptyMasterFd, QObject *parent) : + KProcess(new KPtyProcessPrivate, parent) +{ + Q_D(KPtyProcess); + + d->pty = new KPtyDevice(this); + d->pty->open(ptyMasterFd); + connect(this, SIGNAL(stateChanged(QProcess::ProcessState)), + SLOT(_k_onStateChanged(QProcess::ProcessState))); +} + +KPtyProcess::~KPtyProcess() +{ + Q_D(KPtyProcess); + + if (state() != QProcess::NotRunning && d->addUtmp) { + d->pty->logout(); + disconnect(SIGNAL(stateChanged(QProcess::ProcessState)), + this, SLOT(_k_onStateChanged(QProcess::ProcessState))); + } + delete d->pty; +} + +void KPtyProcess::setPtyChannels(PtyChannels channels) +{ + Q_D(KPtyProcess); + + d->ptyChannels = channels; +} + +KPtyProcess::PtyChannels KPtyProcess::ptyChannels() const +{ + Q_D(const KPtyProcess); + + return d->ptyChannels; +} + +void KPtyProcess::setUseUtmp(bool value) +{ + Q_D(KPtyProcess); + + d->addUtmp = value; +} + +bool KPtyProcess::isUseUtmp() const +{ + Q_D(const KPtyProcess); + + return d->addUtmp; +} + +KPtyDevice *KPtyProcess::pty() const +{ + Q_D(const KPtyProcess); + + return d->pty; +} + +void KPtyProcess::setupChildProcess() +{ + Q_D(KPtyProcess); + + d->pty->setCTty(); + if (d->addUtmp) + d->pty->login(getenv("USER"), getenv("DISPLAY")); + //d->pty->login(KUser(KUser::UseRealUserID).loginName().toLocal8Bit().data(), qgetenv("DISPLAY")); + if (d->ptyChannels & StdinChannel) + dup2(d->pty->slaveFd(), 0); + if (d->ptyChannels & StdoutChannel) + dup2(d->pty->slaveFd(), 1); + if (d->ptyChannels & StderrChannel) + dup2(d->pty->slaveFd(), 2); + + KProcess::setupChildProcess(); +}
new file mode 100644 --- /dev/null +++ b/gui/src/kptyprocess.h @@ -0,0 +1,157 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPTYPROCESS_H +#define KPTYPROCESS_H + +#include "kprocess.h" +#include "kprocess_p.h" +#include "kptydevice.h" + +class KPtyDevice; +class KPtyProcess; +struct KPtyProcessPrivate; + +/** + * This class extends KProcess by support for PTYs (pseudo TTYs). + * + * The PTY is opened as soon as the class is instantiated. Verify that + * it was opened successfully by checking that pty()->masterFd() is not -1. + * + * The PTY is always made the process' controlling TTY. + * Utmp registration and connecting the stdio handles to the PTY are optional. + * + * No attempt to integrate with QProcess' waitFor*() functions was made, + * for it is impossible. Note that execute() does not work with the PTY, too. + * Use the PTY device's waitFor*() functions or use it asynchronously. + * + * @author Oswald Buddenhagen <ossi@kde.org> + */ +class KPtyProcess : public KProcess +{ + Q_OBJECT + Q_DECLARE_PRIVATE(KPtyProcess) + +public: + enum PtyChannelFlag { + NoChannels = 0, /**< The PTY is not connected to any channel. */ + StdinChannel = 1, /**< Connect PTY to stdin. */ + StdoutChannel = 2, /**< Connect PTY to stdout. */ + StderrChannel = 4, /**< Connect PTY to stderr. */ + AllOutputChannels = 6, /**< Connect PTY to all output channels. */ + AllChannels = 7 /**< Connect PTY to all channels. */ + }; + + Q_DECLARE_FLAGS(PtyChannels, PtyChannelFlag) + + /** + * Constructor + */ + explicit KPtyProcess(QObject *parent = 0); + + /** + * Construct a process using an open pty master. + * + * @param ptyMasterFd an open pty master file descriptor. + * The process does not take ownership of the descriptor; + * it will not be automatically closed at any point. + */ + KPtyProcess(int ptyMasterFd, QObject *parent = 0); + + /** + * Destructor + */ + virtual ~KPtyProcess(); + + /** + * Set to which channels the PTY should be assigned. + * + * This function must be called before starting the process. + * + * @param channels the output channel handling mode + */ + void setPtyChannels(PtyChannels channels); + + /** + * Query to which channels the PTY is assigned. + * + * @return the output channel handling mode + */ + PtyChannels ptyChannels() const; + + /** + * Set whether to register the process as a TTY login in utmp. + * + * Utmp is disabled by default. + * It should enabled for interactively fed processes, like terminal + * emulations. + * + * This function must be called before starting the process. + * + * @param value whether to register in utmp. + */ + void setUseUtmp(bool value); + + /** + * Get whether to register the process as a TTY login in utmp. + * + * @return whether to register in utmp + */ + bool isUseUtmp() const; + + /** + * Get the PTY device of this process. + * + * @return the PTY device + */ + KPtyDevice *pty() const; + +protected: + /** + * @reimp + */ + virtual void setupChildProcess(); + +private: + Q_PRIVATE_SLOT(d_func(), void _k_onStateChanged(QProcess::ProcessState)) +}; + +struct KPtyProcessPrivate : KProcessPrivate { + KPtyProcessPrivate() : + ptyChannels(KPtyProcess::NoChannels), + addUtmp(false) + { + } + + void _k_onStateChanged(QProcess::ProcessState newState) + { + if (newState == QProcess::NotRunning && addUtmp) + pty->logout(); + } + + KPtyDevice *pty; + KPtyProcess::PtyChannels ptyChannels; + bool addUtmp : 1; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(KPtyProcess::PtyChannels) + +#endif