diff --git a/src/libs/CMakeLists.txt b/src/libs/CMakeLists.txt index ef261231f4d..4cb8e8d925f 100644 --- a/src/libs/CMakeLists.txt +++ b/src/libs/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(3rdparty) +add_subdirectory(advanceddockingsystem) add_subdirectory(aggregation) add_subdirectory(extensionsystem) add_subdirectory(utils) diff --git a/src/libs/advanceddockingsystem/CMakeLists.txt b/src/libs/advanceddockingsystem/CMakeLists.txt new file mode 100644 index 00000000000..ba5deced265 --- /dev/null +++ b/src/libs/advanceddockingsystem/CMakeLists.txt @@ -0,0 +1,31 @@ +add_qtc_library(AdvancedDockingSystem + DEPENDS Qt5::Widgets Qt5::Core Qt5::Gui Utils + SOURCES + ads_globals.cpp ads_globals.h + dockareatabbar.cpp dockareatabbar.h + dockareatitlebar.cpp dockareatitlebar.h + dockareawidget.cpp dockareawidget.h + dockcomponentsfactory.cpp dockcomponentsfactory.h + dockcontainerwidget.cpp dockcontainerwidget.h + dockingstatereader.cpp dockingstatereader.h + dockmanager.cpp dockmanager.h + dockoverlay.cpp dockoverlay.h + docksplitter.cpp docksplitter.h + dockwidget.cpp dockwidget.h + dockwidgettab.cpp dockwidgettab.h + elidinglabel.cpp elidinglabel.h + floatingdockcontainer.cpp floatingdockcontainer.h + floatingdragpreview.cpp floatingdragpreview.h + iconprovider.cpp iconprovider.h + workspacedialog.cpp workspacedialog.h + workspacemodel.cpp workspacemodel.h + workspaceview.cpp workspaceview.h + workspacedialog.ui + resources.qrc +) + +extend_qtc_target(AdvancedDockingSystem + INCLUDES linux + SOURCES + linux/floatingwidgettitlebar.cpp linux/floatingwidgettitlebar.h +) diff --git a/src/libs/advanceddockingsystem/LICENSE.LGPLv21 b/src/libs/advanceddockingsystem/LICENSE.LGPLv21 new file mode 100644 index 00000000000..dfcab5e29b7 --- /dev/null +++ b/src/libs/advanceddockingsystem/LICENSE.LGPLv21 @@ -0,0 +1,514 @@ + GNU LESSER GENERAL PUBLIC LICENSE + + The Qt Toolkit is Copyright (C) 2015 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + You may use, distribute and copy the Qt Toolkit under the terms of + GNU Lesser General Public License version 2.1, which is displayed below. + +------------------------------------------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/src/libs/advanceddockingsystem/ads_globals.cpp b/src/libs/advanceddockingsystem/ads_globals.cpp new file mode 100644 index 00000000000..36df8ed869c --- /dev/null +++ b/src/libs/advanceddockingsystem/ads_globals.cpp @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "ads_globals.h" + +#include "dockmanager.h" +#include "docksplitter.h" +#include "iconprovider.h" + +#include +#include +#include + +namespace ADS { + +namespace internal { + +void replaceSplitterWidget(QSplitter *splitter, QWidget *from, QWidget *to) +{ + int index = splitter->indexOf(from); + from->setParent(nullptr); + splitter->insertWidget(index, to); +} + +DockInsertParam dockAreaInsertParameters(DockWidgetArea area) +{ + switch (area) { + case TopDockWidgetArea: + return DockInsertParam(Qt::Vertical, false); + case RightDockWidgetArea: + return DockInsertParam(Qt::Horizontal, true); + case CenterDockWidgetArea: + case BottomDockWidgetArea: + return DockInsertParam(Qt::Vertical, true); + case LeftDockWidgetArea: + return DockInsertParam(Qt::Horizontal, false); + default: + DockInsertParam(Qt::Vertical, false); + } + + return DockInsertParam(Qt::Vertical, false); +} + +QPixmap createTransparentPixmap(const QPixmap &source, qreal opacity) +{ + QPixmap transparentPixmap(source.size()); + transparentPixmap.fill(Qt::transparent); + QPainter painter(&transparentPixmap); + painter.setOpacity(opacity); + painter.drawPixmap(0, 0, source); + return transparentPixmap; +} + +void hideEmptyParentSplitters(DockSplitter *splitter) +{ + while (splitter && splitter->isVisible()) { + if (!splitter->hasVisibleContent()) { + splitter->hide(); + } + splitter = internal::findParent(splitter); + } +} + +void setButtonIcon(QAbstractButton* button, + QStyle::StandardPixmap standarPixmap, + ADS::eIcon customIconId) +{ + // First we try to use custom icons if available + QIcon icon = DockManager::iconProvider().customIcon(customIconId); + if (!icon.isNull()) { + button->setIcon(icon); + return; + } + + if (Utils::HostOsInfo::isLinuxHost()) { + button->setIcon(button->style()->standardIcon(standarPixmap)); + } else { + // The standard icons does not look good on high DPI screens so we create + // our own "standard" icon here. + QPixmap normalPixmap = button->style()->standardPixmap(standarPixmap, nullptr, button); + icon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), QIcon::Disabled); + icon.addPixmap(normalPixmap, QIcon::Normal); + button->setIcon(icon); + } +} + +} // namespace internal +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/ads_globals.h b/src/libs/advanceddockingsystem/ads_globals.h new file mode 100644 index 00000000000..a81cbe57d02 --- /dev/null +++ b/src/libs/advanceddockingsystem/ads_globals.h @@ -0,0 +1,232 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +class QAbstractButton; + +#ifndef ADS_STATIC +#ifdef ADVANCEDDOCKINGSYSTEM_LIBRARY +#define ADS_EXPORT Q_DECL_EXPORT +#else +#define ADS_EXPORT Q_DECL_IMPORT +#endif +#else +#define ADS_EXPORT +#endif + +//#define ADS_DEBUG_PRINT + +// Define ADS_DEBUG_PRINT to enable a lot of debug output +#ifdef ADS_DEBUG_PRINT +#define ADS_PRINT(s) qDebug() << s +#else +#define ADS_PRINT(s) +#endif + +// Set ADS_DEBUG_LEVEL to enable additional debug output and to enable layout +// dumps to qDebug and std::cout after layout changes +#define ADS_DEBUG_LEVEL 0 + +class QSplitter; + +namespace ADS { + +enum eStateFileVersion { InitialVerison = 0, Version1 = 1, CurrentVersion = Version1 }; + +class DockSplitter; + +enum DockWidgetArea { + NoDockWidgetArea = 0x00, + LeftDockWidgetArea = 0x01, + RightDockWidgetArea = 0x02, + TopDockWidgetArea = 0x04, + BottomDockWidgetArea = 0x08, + CenterDockWidgetArea = 0x10, + + InvalidDockWidgetArea = NoDockWidgetArea, + OuterDockAreas = TopDockWidgetArea | LeftDockWidgetArea | RightDockWidgetArea + | BottomDockWidgetArea, + AllDockAreas = OuterDockAreas | CenterDockWidgetArea +}; +Q_DECLARE_FLAGS(DockWidgetAreas, DockWidgetArea) + +enum eTitleBarButton { TitleBarButtonTabsMenu, TitleBarButtonUndock, TitleBarButtonClose }; + +/** + * The different dragging states + */ +enum eDragState { + DraggingInactive, //!< DraggingInactive + DraggingMousePressed, //!< DraggingMousePressed + DraggingTab, //!< DraggingTab + DraggingFloatingWidget //!< DraggingFloatingWidget +}; + +/** + * The different icons used in the UI + */ +enum eIcon { + TabCloseIcon, //!< TabCloseIcon + DockAreaMenuIcon, //!< DockAreaMenuIcon + DockAreaUndockIcon, //!< DockAreaUndockIcon + DockAreaCloseIcon, //!< DockAreaCloseIcon + + IconCount, //!< just a delimiter for range checks +}; + +/** + * For bitwise combination of dock wdget features + */ +enum eBitwiseOperator +{ + BitwiseAnd, + BitwiseOr +}; + +namespace internal { +const bool restoreTesting = true; +const bool restore = false; +const char *const closedProperty = "close"; +const char *const dirtyProperty = "dirty"; + +/** + * Replace the from widget in the given splitter with the To widget + */ +void replaceSplitterWidget(QSplitter *splitter, QWidget *from, QWidget *to); + +/** + * This function walks the splitter tree upwards to hides all splitters + * that do not have visible content + */ +void hideEmptyParentSplitters(DockSplitter *firstParentSplitter); + +/** + * Convenience class for QPair to provide better naming than first and + * second + */ +class DockInsertParam : public QPair +{ +public: + using QPair::QPair; + Qt::Orientation orientation() const { return this->first; } + bool append() const { return this->second; } + int insertOffset() const { return append() ? 1 : 0; } +}; + +/** + * Returns the insertion parameters for the given dock area + */ +DockInsertParam dockAreaInsertParameters(DockWidgetArea area); + +/** + * Searches for the parent widget of the given type. + * Returns the parent widget of the given widget or 0 if the widget is not + * child of any widget of type T + * + * It is not safe to use this function in in DockWidget because only + * the current dock widget has a parent. All dock widgets that are not the + * current dock widget in a dock area have no parent. + */ +template +T findParent(const QWidget *widget) +{ + QWidget *parentWidget = widget->parentWidget(); + while (parentWidget) { + T parentImpl = qobject_cast(parentWidget); + if (parentImpl) { + return parentImpl; + } + parentWidget = parentWidget->parentWidget(); + } + return 0; +} + +/** + * Creates a semi transparent pixmap from the given pixmap Source. + * The Opacity parameter defines the opacity from completely transparent (0.0) + * to completely opaque (1.0) + */ +QPixmap createTransparentPixmap(const QPixmap &source, qreal opacity); + +/** + * Helper function for settings flags in a QFlags instance. + */ +template +void setFlag(T &flags, typename T::enum_type flag, bool on = true) +{ + flags.setFlag(flag, on); +} + +/** + * Helper function for settings tooltips without cluttering the code with + * tests for preprocessor macros + */ +template +void setToolTip(QObjectPtr obj, const QString &tip) +{ +#ifndef QT_NO_TOOLTIP + obj->setToolTip(tip); +#else + Q_UNUSED(obj); + Q_UNUSED(tip); +#endif +} + +/** + * Helper function to set the icon of a certain button. + * Use this function to set the icons for the dock area and dock widget buttons. + * The function first uses the CustomIconId to get an icon from the + * IconProvider. You can register your custom icons with the icon provider, if + * you do not want to use the default buttons and if you do not want to use + * stylesheets. + * If the IconProvider does not return a valid icon (icon is null), the function + * fetches the given standard pixmap from the QStyle. + * param[in] Button The button whose icons are to be set + * param[in] StandardPixmap The standard pixmap to be used for the button + * param[in] CustomIconId The identifier for the custom icon. + */ +void setButtonIcon(QAbstractButton *button, QStyle::StandardPixmap standarPixmap, + ADS::eIcon CustomIconId); + +} // namespace internal +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/advanceddockingsystem-lib.pri b/src/libs/advanceddockingsystem/advanceddockingsystem-lib.pri new file mode 100644 index 00000000000..57526219853 --- /dev/null +++ b/src/libs/advanceddockingsystem/advanceddockingsystem-lib.pri @@ -0,0 +1,58 @@ +shared { + DEFINES += ADVANCEDDOCKINGSYSTEM_LIBRARY +} else { + DEFINES += BUILD_ADVANCEDDOCKINGSYSTEM_STATIC_LIB +} + +## Input +RESOURCES += \ + resources.qrc + +HEADERS += \ + ads_globals.h \ + dockareatabbar.h \ + dockareatitlebar.h \ + dockareawidget.h \ + dockcomponentsfactory.h \ + dockcontainerwidget.h \ + dockingstatereader.h \ + dockmanager.h \ + dockoverlay.h \ + docksplitter.h \ + dockwidget.h \ + dockwidgettab.h \ + elidinglabel.h \ + floatingdockcontainer.h \ + floatingdragpreview.h \ + iconprovider.h \ + workspacedialog.h \ + workspacemodel.h \ + workspaceview.h + +SOURCES += \ + ads_globals.cpp \ + dockareatabbar.cpp \ + dockareatitlebar.cpp \ + dockareawidget.cpp \ + dockcomponentsfactory.cpp \ + dockcontainerwidget.cpp \ + dockingstatereader.cpp \ + dockmanager.cpp \ + dockoverlay.cpp \ + docksplitter.cpp \ + dockwidget.cpp \ + dockwidgettab.cpp \ + elidinglabel.cpp \ + floatingdockcontainer.cpp \ + floatingdragpreview.cpp \ + iconprovider.cpp \ + workspacedialog.cpp \ + workspacemodel.cpp \ + workspaceview.cpp + +FORMS += \ + workspacedialog.ui + +include(linux/linux.pri) + +DISTFILES += advanceddockingsystem.pri diff --git a/src/libs/advanceddockingsystem/advanceddockingsystem.pro b/src/libs/advanceddockingsystem/advanceddockingsystem.pro new file mode 100644 index 00000000000..30b4a3a77e8 --- /dev/null +++ b/src/libs/advanceddockingsystem/advanceddockingsystem.pro @@ -0,0 +1,6 @@ +unix:QMAKE_CXXFLAGS_DEBUG += -O3 + +INCLUDEPATH += $$PWD $$PWD/linux + +include(../../qtcreatorlibrary.pri) +include(advanceddockingsystem-lib.pri) diff --git a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs new file mode 100644 index 00000000000..876ea534f6f --- /dev/null +++ b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs @@ -0,0 +1,48 @@ +import qbs 1.0 + +QtcLibrary { + name: "AdvancedDockingSystem" + + cpp.optimization: "fast" + cpp.defines: base.concat("ADVANCEDDOCKINGSYSTEM_LIBRARY") + cpp.includePaths: base.concat([".", linux.prefix]) + + Depends { name: "Qt"; submodules: ["widgets", "core", "gui"] } + Depends { name: "Utils" } + + Group { + name: "General" + files: [ + "ads_globals.cpp", "ads_globals.h", + "dockareaareatabbar.cpp", "dockareatabbar.h", + "dockareatitlebar.cpp", "dockareatitlebar.h", + "dockareawidget.cpp", "dockareawidget.h", + "dockcomponentsfactory.cpp", "dockcomponentsfactory.h", + "dockcontainerwidget.cpp", "dockcontainerwidget.h", + "dockingstatereader.cpp", "dockingstatereader.h", + "dockmanager.cpp", "dockmanager.h", + "dockoverlay.cpp", "dockoverlay.h", + "docksplitter.cpp", "docksplitter.h", + "dockwidget.cpp", "dockwidget.h", + "dockwidgettab.cpp", "dockwidgettab.h", + "elidinglabel.cpp", "elidinglabel.h", + "floatingdockcontainer.cpp", "floatingdockcontainer.h", + "floatingdragpreview.cpp", "floatingdragpreview.h", + "iconprovider.cpp", "iconprovider.h", + "workspacedialog.cpp", "workspacedialog.h", + "workspacemodel.cpp", "workspacemodel.h", + "workspaceview.cpp", "workspaceview.h", + "workspacedialog.ui", + "resources.qrc" + ] + } + + Group { + name: "Linux" + id: linux + prefix: "linux/" + files: [ + "floatingwidgettitlebar.cpp", "floatingwidgettitlebar.h" + ] + } +} diff --git a/src/libs/advanceddockingsystem/advanceddockingsystem_dependencies.pri b/src/libs/advanceddockingsystem/advanceddockingsystem_dependencies.pri new file mode 100644 index 00000000000..df4563c7936 --- /dev/null +++ b/src/libs/advanceddockingsystem/advanceddockingsystem_dependencies.pri @@ -0,0 +1,3 @@ +QTC_LIB_NAME = AdvancedDockingSystem +QTC_LIB_DEPENDS += utils +INCLUDEPATH *= $$IDE_SOURCE_TREE/src/libs/advanceddockingsystem diff --git a/src/libs/advanceddockingsystem/dockareatabbar.cpp b/src/libs/advanceddockingsystem/dockareatabbar.cpp new file mode 100644 index 00000000000..84cf3d9cdcc --- /dev/null +++ b/src/libs/advanceddockingsystem/dockareatabbar.cpp @@ -0,0 +1,402 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dockareatabbar.h" + +#include "dockareawidget.h" +#include "dockmanager.h" +#include "dockoverlay.h" +#include "dockwidget.h" +#include "dockwidgettab.h" +#include "floatingdockcontainer.h" +#include "floatingdragpreview.h" + +#include +#include +#include +#include +#include +#include + +#include + +static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg) + +namespace ADS +{ + /** + * Private data class of DockAreaTabBar class (pimpl) + */ + struct DockAreaTabBarPrivate + { + DockAreaTabBar *q; + DockAreaWidget *m_dockArea; + QWidget *m_tabsContainerWidget; + QBoxLayout *m_tabsLayout; + int m_currentIndex = -1; + + /** + * Private data constructor + */ + DockAreaTabBarPrivate(DockAreaTabBar *parent); + + /** + * Update tabs after current index changed or when tabs are removed. + * The function reassigns the stylesheet to update the tabs + */ + void updateTabs(); + + /** + * Convenience function to access first tab + */ + DockWidgetTab *firstTab() const {return q->tab(0);} + + /** + * Convenience function to access last tab + */ + DockWidgetTab *lastTab() const {return q->tab(q->count() - 1);} + }; + // struct DockAreaTabBarPrivate + + DockAreaTabBarPrivate::DockAreaTabBarPrivate(DockAreaTabBar *parent) + : q(parent) + {} + + void DockAreaTabBarPrivate::updateTabs() + { + // Set active TAB and update all other tabs to be inactive + for (int i = 0; i < q->count(); ++i) { + auto tabWidget = q->tab(i); + if (!tabWidget) + continue; + + if (i == m_currentIndex) { + tabWidget->show(); + tabWidget->setActiveTab(true); + q->ensureWidgetVisible(tabWidget); + } else { + tabWidget->setActiveTab(false); + } + } + } + + DockAreaTabBar::DockAreaTabBar(DockAreaWidget *parent) + : QScrollArea(parent) + , d(new DockAreaTabBarPrivate(this)) + { + d->m_dockArea = parent; + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setFrameStyle(QFrame::NoFrame); + setWidgetResizable(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + d->m_tabsContainerWidget = new QWidget(); + d->m_tabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + d->m_tabsContainerWidget->setObjectName("tabsContainerWidget"); + d->m_tabsLayout = new QBoxLayout(QBoxLayout::LeftToRight); + d->m_tabsLayout->setContentsMargins(0, 0, 0, 0); + d->m_tabsLayout->setSpacing(0); + d->m_tabsLayout->addStretch(1); + d->m_tabsContainerWidget->setLayout(d->m_tabsLayout); + setWidget(d->m_tabsContainerWidget); + } + + DockAreaTabBar::~DockAreaTabBar() { delete d; } + + void DockAreaTabBar::wheelEvent(QWheelEvent *event) + { + event->accept(); + const int direction = event->angleDelta().y(); + if (direction < 0) { + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20); + } else { + horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20); + } + } + + void DockAreaTabBar::setCurrentIndex(int index) + { + if (index == d->m_currentIndex) + return; + + if (index < -1 || index > (count() - 1)) { + qWarning() << Q_FUNC_INFO << "Invalid index" << index; + return; + } + + emit currentChanging(index); + d->m_currentIndex = index; + d->updateTabs(); + updateGeometry(); + emit currentChanged(index); + } + + int DockAreaTabBar::count() const + { + // The tab bar contains a stretch item as last item + return d->m_tabsLayout->count() - 1; + } + + void DockAreaTabBar::insertTab(int index, DockWidgetTab *dockWidgetTab) + { + d->m_tabsLayout->insertWidget(index, dockWidgetTab); + connect(dockWidgetTab, &DockWidgetTab::clicked, this, &DockAreaTabBar::onTabClicked); + connect(dockWidgetTab, + &DockWidgetTab::closeRequested, + this, + &DockAreaTabBar::onTabCloseRequested); + connect(dockWidgetTab, + &DockWidgetTab::closeOtherTabsRequested, + this, + &DockAreaTabBar::onCloseOtherTabsRequested); + connect(dockWidgetTab, &DockWidgetTab::moved, this, &DockAreaTabBar::onTabWidgetMoved); + connect(dockWidgetTab, + &DockWidgetTab::elidedChanged, + this, + &DockAreaTabBar::elidedChanged); + dockWidgetTab->installEventFilter(this); + emit tabInserted(index); + if (index <= d->m_currentIndex || d->m_currentIndex == -1) { + setCurrentIndex(d->m_currentIndex + 1); + } + updateGeometry(); + } + + void DockAreaTabBar::removeTab(DockWidgetTab *dockWidgetTab) + { + if (!count()) + return; + + qCInfo(adsLog) << Q_FUNC_INFO; + int newCurrentIndex = currentIndex(); + int removeIndex = d->m_tabsLayout->indexOf(dockWidgetTab); + if (count() == 1) + newCurrentIndex = -1; + + if (newCurrentIndex > removeIndex) { + newCurrentIndex--; + } else if (newCurrentIndex == removeIndex) { + newCurrentIndex = -1; + // First we walk to the right to search for the next visible tab + for (int i = (removeIndex + 1); i < count(); ++i) { + if (tab(i)->isVisibleTo(this)) { + newCurrentIndex = i - 1; + break; + } + } + + // If there is no visible tab right to this tab then we walk to + // the left to find a visible tab + if (newCurrentIndex < 0) { + for (int i = (removeIndex - 1); i >= 0; --i) { + if (tab(i)->isVisibleTo(this)) { + newCurrentIndex = i; + break; + } + } + } + } + + emit removingTab(removeIndex); + d->m_tabsLayout->removeWidget(dockWidgetTab); + dockWidgetTab->disconnect(this); + dockWidgetTab->removeEventFilter(this); + qCInfo(adsLog) << "NewCurrentIndex " << newCurrentIndex; + if (newCurrentIndex != d->m_currentIndex) { + setCurrentIndex(newCurrentIndex); + } else { + d->updateTabs(); + } + updateGeometry(); + } + + int DockAreaTabBar::currentIndex() const { return d->m_currentIndex; } + + DockWidgetTab *DockAreaTabBar::currentTab() const + { + if (d->m_currentIndex < 0) { + return nullptr; + } else { + return qobject_cast( + d->m_tabsLayout->itemAt(d->m_currentIndex)->widget()); + } + } + + void DockAreaTabBar::onTabClicked() + { + DockWidgetTab *tab = qobject_cast(sender()); + if (!tab) + return; + + int index = d->m_tabsLayout->indexOf(tab); + if (index < 0) + return; + + setCurrentIndex(index); + emit tabBarClicked(index); + } + + void DockAreaTabBar::onTabCloseRequested() + { + DockWidgetTab *tab = qobject_cast(sender()); + int index = d->m_tabsLayout->indexOf(tab); + closeTab(index); + } + + void DockAreaTabBar::onCloseOtherTabsRequested() + { + auto senderTab = qobject_cast(sender()); + for (int i = 0; i < count(); ++i) { + auto currentTab = tab(i); + if (currentTab->isClosable() && !currentTab->isHidden() && currentTab != senderTab) { + // If the dock widget is deleted with the closeTab() call, its tab it will no longer + // be in the layout, and thus the index needs to be updated to not skip any tabs + int offset = currentTab->dockWidget()->features().testFlag( + DockWidget::DockWidgetDeleteOnClose) + ? 1 + : 0; + closeTab(i); + // If the the dock widget blocks closing, i.e. if the flag + // CustomCloseHandling is set, and the dock widget is still open, + // then we do not need to correct the index + if (currentTab->dockWidget()->isClosed()) { + i -= offset; + } + } + } + } + + DockWidgetTab *DockAreaTabBar::tab(int index) const + { + if (index >= count() || index < 0) + return nullptr; + + return qobject_cast(d->m_tabsLayout->itemAt(index)->widget()); + } + + void DockAreaTabBar::onTabWidgetMoved(const QPoint &globalPosition) + { + DockWidgetTab *movingTab = qobject_cast(sender()); + if (!movingTab) + return; + + int fromIndex = d->m_tabsLayout->indexOf(movingTab); + auto mousePos = mapFromGlobal(globalPosition); + mousePos.rx() = qMax(d->firstTab()->geometry().left(), mousePos.x()); + mousePos.rx() = qMin(d->lastTab()->geometry().right(), mousePos.x()); + int toIndex = -1; + // Find tab under mouse + for (int i = 0; i < count(); ++i) { + DockWidgetTab *dropTab = tab(i); + if (dropTab == movingTab || !dropTab->isVisibleTo(this) + || !dropTab->geometry().contains(mousePos)) + continue; + + toIndex = d->m_tabsLayout->indexOf(dropTab); + if (toIndex == fromIndex) + toIndex = -1; + + break; + } + + if (toIndex > -1) { + d->m_tabsLayout->removeWidget(movingTab); + d->m_tabsLayout->insertWidget(toIndex, movingTab); + qCInfo(adsLog) << "tabMoved from" << fromIndex << "to" << toIndex; + emit tabMoved(fromIndex, toIndex); + setCurrentIndex(toIndex); + } else { + // Ensure that the moved tab is reset to its start position + d->m_tabsLayout->update(); + } + } + + void DockAreaTabBar::closeTab(int index) + { + if (index < 0 || index >= count()) + return; + + auto dockWidgetTab = tab(index); + if (dockWidgetTab->isHidden()) + return; + + emit tabCloseRequested(index); + } + + bool DockAreaTabBar::eventFilter(QObject *watched, QEvent *event) + { + bool result = Super::eventFilter(watched, event); + DockWidgetTab *dockWidgetTab = qobject_cast(watched); + if (!dockWidgetTab) + return result; + + switch (event->type()) { + case QEvent::Hide: + emit tabClosed(d->m_tabsLayout->indexOf(dockWidgetTab)); + updateGeometry(); + break; + case QEvent::Show: + emit tabOpened(d->m_tabsLayout->indexOf(dockWidgetTab)); + updateGeometry(); + break; + default: + break; + } + + return result; + } + + bool DockAreaTabBar::isTabOpen(int index) const + { + if (index < 0 || index >= count()) + return false; + + return !tab(index)->isHidden(); + } + + QSize DockAreaTabBar::minimumSizeHint() const + { + QSize size = sizeHint(); + size.setWidth(10); + return size; + } + + QSize DockAreaTabBar::sizeHint() const + { + return d->m_tabsContainerWidget->sizeHint(); + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareatabbar.h b/src/libs/advanceddockingsystem/dockareatabbar.h new file mode 100644 index 00000000000..991286c45ee --- /dev/null +++ b/src/libs/advanceddockingsystem/dockareatabbar.h @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" + +#include + +namespace ADS { + +class DockAreaWidget; +class DockWidgetTab; +struct DockAreaTabBarPrivate; +class DockAreaTitleBar; +class FloatingDockContainer; +class AbstractFloatingWidget; + +/** + * Custom tabbar implementation for tab area that is shown on top of a + * dock area widget. + * The tabbar displays the tab widgets of the contained dock widgets. + * We cannot use QTabBar here because it does a lot of fancy animations + * that will crash the application if a tab is removed while the animation + * has not finished. And we need to remove a tab, if the user drags a + * a dock widget out of a group of tabbed widgets + */ +class ADS_EXPORT DockAreaTabBar : public QScrollArea +{ + Q_OBJECT +private: + DockAreaTabBarPrivate *d; ///< private data (pimpl) + friend struct DockAreaTabBarPrivate; + friend class DockAreaTitleBar; + + void onTabClicked(); + void onTabCloseRequested(); + void onCloseOtherTabsRequested(); + void onTabWidgetMoved(const QPoint &globalPos); + +protected: + virtual void wheelEvent(QWheelEvent *event) override; + +public: + using Super = QScrollArea; + + /** + * Default Constructor + */ + DockAreaTabBar(DockAreaWidget *parent); + + /** + * Virtual Destructor + */ + virtual ~DockAreaTabBar() override; + + /** + * Inserts the given dock widget tab at the given position. + * Inserting a new tab at an index less than or equal to the current index + * will increment the current index, but keep the current tab. + */ + void insertTab(int Index, DockWidgetTab *tab); + + /** + * Removes the given DockWidgetTab from the tabbar + */ + void removeTab(DockWidgetTab *tab); + + /** + * Returns the number of tabs in this tabbar + */ + int count() const; + + /** + * Returns the current index or -1 if no tab is selected + */ + int currentIndex() const; + + /** + * Returns the current tab or a nullptr if no tab is selected. + */ + DockWidgetTab *currentTab() const; + + /** + * Returns the tab with the given index + */ + DockWidgetTab *tab(int index) const; + + /** + * Filters the tab widget events + */ + virtual bool eventFilter(QObject *watched, QEvent *event) override; + + /** + * This function returns true if the tab is open, that means if it is + * visible to the user. If the function returns false, the tab is + * closed + */ + bool isTabOpen(int index) const; + + /** + * Overrides the minimumSizeHint() function of QScrollArea + * The minimumSizeHint() is bigger than the sizeHint () for the scroll + * area because even if the scrollbars are invisible, the required speace + * is reserved in the minimumSizeHint(). This override simply returns + * sizeHint(); + */ + virtual QSize minimumSizeHint() const override; + + /** + * The function provides a sizeHint that matches the height of the + * internal viewport. + */ + virtual QSize sizeHint() const override; + + /** + * This property sets the index of the tab bar's visible tab + */ + void setCurrentIndex(int index); + + /** + * This function will close the tab given in Index param. + * Closing a tab means, the tab will be hidden, it will not be removed + */ + void closeTab(int index); + +signals: + /** + * This signal is emitted when the tab bar's current tab is about to be changed. The new + * current has the given index, or -1 if there isn't a new one. + */ + void currentChanging(int index); + + /** + * This signal is emitted when the tab bar's current tab changes. The new + * current has the given index, or -1 if there isn't a new one + */ + void currentChanged(int index); + + /** + * This signal is emitted when user clicks on a tab + */ + void tabBarClicked(int index); + + /** + * This signal is emitted when the close button on a tab is clicked. + * The index is the index that should be closed. + */ + void tabCloseRequested(int index); + + /** + * This signal is emitted if a tab has been closed + */ + void tabClosed(int index); + + /** + * This signal is emitted if a tab has been opened. + * A tab is opened if it has been made visible + */ + void tabOpened(int index); + + /** + * This signal is emitted when the tab has moved the tab at index position + * from to index position to. + */ + void tabMoved(int from, int to); + + /** + * This signal is emitted, just before the tab with the given index is + * removed + */ + void removingTab(int index); + + /** + * This signal is emitted if a tab has been inserted + */ + void tabInserted(int index); + + /** + * This signal is emitted when a tab title elide state has been changed + */ + void elidedChanged(bool elided); +}; // class DockAreaTabBar + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.cpp b/src/libs/advanceddockingsystem/dockareatitlebar.cpp new file mode 100644 index 00000000000..a35aafbc874 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockareatitlebar.cpp @@ -0,0 +1,577 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dockareatitlebar.h" + +#include "ads_globals.h" +#include "dockareatabbar.h" +#include "dockareawidget.h" +#include "dockmanager.h" +#include "dockoverlay.h" +#include "dockwidget.h" +#include "dockwidgettab.h" +#include "floatingdockcontainer.h" +#include "floatingdragpreview.h" +#include "iconprovider.h" +#include "dockcomponentsfactory.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg) + +namespace ADS +{ + /** + * Private data class of DockAreaTitleBar class (pimpl) + */ + struct DockAreaTitleBarPrivate + { + DockAreaTitleBar *q; + QPointer m_tabsMenuButton; + QPointer m_undockButton; + QPointer m_closeButton; + QBoxLayout *m_layout; + DockAreaWidget *m_dockArea; + DockAreaTabBar *m_tabBar; + bool m_menuOutdated = true; + QMenu *m_tabsMenu; + QList m_dockWidgetActionsButtons; + + QPoint m_dragStartMousePos; + eDragState m_dragState = DraggingInactive; + AbstractFloatingWidget *m_floatingWidget = nullptr; + + /** + * Private data constructor + */ + DockAreaTitleBarPrivate(DockAreaTitleBar *parent); + + /** + * Creates the title bar close and menu buttons + */ + void createButtons(); + + /** + * Creates the internal TabBar + */ + void createTabBar(); + + /** + * Convenience function for DockManager access + */ + DockManager *dockManager() const { return m_dockArea->dockManager(); } + + /** + * Returns true if the given config flag is set + */ + static bool testConfigFlag(DockManager::eConfigFlag flag) + { + return DockManager::configFlags().testFlag(flag); + } + + /** + * Test function for current drag state + */ + bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; } + + + /** + * Starts floating + */ + void startFloating(const QPoint &offset); + + /** + * Makes the dock area floating + */ + AbstractFloatingWidget *makeAreaFloating(const QPoint &offset, eDragState dragState); + }; // struct DockAreaTitleBarPrivate + + + DockAreaTitleBarPrivate::DockAreaTitleBarPrivate(DockAreaTitleBar *parent) + : q(parent) + {} + + void DockAreaTitleBarPrivate::createButtons() + { + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + // Tabs menu button + m_tabsMenuButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasTabsMenuButton)); + m_tabsMenuButton->setObjectName("tabsMenuButton"); + m_tabsMenuButton->setAutoRaise(true); + m_tabsMenuButton->setPopupMode(QToolButton::InstantPopup); + internal::setButtonIcon(m_tabsMenuButton, + QStyle::SP_TitleBarUnshadeButton, + ADS::DockAreaMenuIcon); + QMenu *tabsMenu = new QMenu(m_tabsMenuButton); +#ifndef QT_NO_TOOLTIP + tabsMenu->setToolTipsVisible(true); +#endif + QObject::connect(tabsMenu, &QMenu::aboutToShow, q, &DockAreaTitleBar::onTabsMenuAboutToShow); + m_tabsMenuButton->setMenu(tabsMenu); + internal::setToolTip(m_tabsMenuButton, QObject::tr("List All Tabs")); + m_tabsMenuButton->setSizePolicy(sizePolicy); + m_layout->addWidget(m_tabsMenuButton, 0); + QObject::connect(m_tabsMenuButton->menu(), + &QMenu::triggered, + q, + &DockAreaTitleBar::onTabsMenuActionTriggered); + + // Undock button + m_undockButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasUndockButton)); + m_undockButton->setObjectName("undockButton"); + m_undockButton->setAutoRaise(true); + internal::setToolTip(m_undockButton, QObject::tr("Detach Group")); + internal::setButtonIcon(m_undockButton, + QStyle::SP_TitleBarNormalButton, + ADS::DockAreaUndockIcon); + m_undockButton->setSizePolicy(sizePolicy); + m_layout->addWidget(m_undockButton, 0); + QObject::connect(m_undockButton, + &QToolButton::clicked, + q, + &DockAreaTitleBar::onUndockButtonClicked); + + // Close button + m_closeButton = new TitleBarButton(testConfigFlag(DockManager::DockAreaHasCloseButton)); + m_closeButton->setObjectName("closeButton"); + m_closeButton->setAutoRaise(true); + internal::setButtonIcon(m_closeButton, + QStyle::SP_TitleBarCloseButton, + ADS::DockAreaCloseIcon); + if (testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) { + internal::setToolTip(m_closeButton, QObject::tr("Close Active Tab")); + } else { + internal::setToolTip(m_closeButton, QObject::tr("Close Group")); + } + m_closeButton->setSizePolicy(sizePolicy); + m_closeButton->setIconSize(QSize(16, 16)); + m_layout->addWidget(m_closeButton, 0); + QObject::connect(m_closeButton, + &QToolButton::clicked, + q, + &DockAreaTitleBar::onCloseButtonClicked); + } + + void DockAreaTitleBarPrivate::createTabBar() + { + m_tabBar = componentsFactory()->createDockAreaTabBar(m_dockArea); + m_tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + m_layout->addWidget(m_tabBar); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabClosed, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabOpened, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabInserted, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::removingTab, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabMoved, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + QObject::connect(m_tabBar, + &DockAreaTabBar::currentChanged, + q, + &DockAreaTitleBar::onCurrentTabChanged); + QObject::connect(m_tabBar, + &DockAreaTabBar::tabBarClicked, + q, + &DockAreaTitleBar::tabBarClicked); + QObject::connect(m_tabBar, + &DockAreaTabBar::elidedChanged, + q, + &DockAreaTitleBar::markTabsMenuOutdated); + } + + AbstractFloatingWidget *DockAreaTitleBarPrivate::makeAreaFloating(const QPoint &offset, + eDragState dragState) + { + QSize size = m_dockArea->size(); + m_dragState = dragState; + bool opaqueUndocking = DockManager::configFlags().testFlag(DockManager::OpaqueUndocking) + || (DraggingFloatingWidget != dragState); + FloatingDockContainer *floatingDockContainer = nullptr; + AbstractFloatingWidget *floatingWidget; + if (opaqueUndocking) { + floatingWidget = floatingDockContainer = new FloatingDockContainer(m_dockArea); + } else { + auto w = new FloatingDragPreview(m_dockArea); + QObject::connect(w, &FloatingDragPreview::draggingCanceled, [=]() { + m_dragState = DraggingInactive; + }); + floatingWidget = w; + } + + floatingWidget->startFloating(offset, size, dragState, nullptr); + if (floatingDockContainer) { + auto topLevelDockWidget = floatingDockContainer->topLevelDockWidget(); + if (topLevelDockWidget) { + topLevelDockWidget->emitTopLevelChanged(true); + } + } + + return floatingWidget; + } + + void DockAreaTitleBarPrivate::startFloating(const QPoint &offset) + { + m_floatingWidget = makeAreaFloating(offset, DraggingFloatingWidget); + } + + TitleBarButton::TitleBarButton(bool visible, QWidget *parent) + : TitleBarButtonType(parent), + m_visible(visible), + m_hideWhenDisabled(DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaHideDisabledButtons)) + {} + + void TitleBarButton::setVisible(bool visible) + { + // 'visible' can stay 'true' if and only if this button is configured to generaly visible: + visible = visible && m_visible; + + // 'visible' can stay 'true' unless: this button is configured to be invisible when it + // is disabled and it is currently disabled: + if (visible && m_hideWhenDisabled) { + visible = isEnabled(); + } + + Super::setVisible(visible); + } + + bool TitleBarButton::event(QEvent *event) + { + if (QEvent::EnabledChange == event->type() && m_hideWhenDisabled) { + // force setVisible() call + // Calling setVisible() directly here doesn't work well when button is expected to be shown first time + QMetaObject::invokeMethod(this, "setVisible", Qt::QueuedConnection, Q_ARG(bool, isEnabled())); + } + + return Super::event(event); + } + + SpacerWidget::SpacerWidget(QWidget *parent) + : QWidget(parent) + { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setStyleSheet("border: none; background: none;"); + } + + DockAreaTitleBar::DockAreaTitleBar(DockAreaWidget *parent) + : QFrame(parent) + , d(new DockAreaTitleBarPrivate(this)) + { + d->m_dockArea = parent; + + setObjectName("dockAreaTitleBar"); + d->m_layout = new QBoxLayout(QBoxLayout::LeftToRight); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + d->createTabBar(); + d->m_layout->addWidget(new SpacerWidget(this)); + d->createButtons(); + } + + DockAreaTitleBar::~DockAreaTitleBar() { + if (!d->m_closeButton.isNull()) + delete d->m_closeButton; + + if (!d->m_tabsMenuButton.isNull()) + delete d->m_tabsMenuButton; + + if (!d->m_undockButton.isNull()) + delete d->m_undockButton; + + delete d; + } + + DockAreaTabBar *DockAreaTitleBar::tabBar() const { return d->m_tabBar; } + + void DockAreaTitleBar::markTabsMenuOutdated() { + if (DockAreaTitleBarPrivate::testConfigFlag(DockManager::DockAreaDynamicTabsMenuButtonVisibility)) { + bool hasElidedTabTitle = false; + for (int i = 0; i < d->m_tabBar->count(); ++i) { + if (!d->m_tabBar->isTabOpen(i)) + continue; + + DockWidgetTab* tab = d->m_tabBar->tab(i); + if (tab->isTitleElided()) { + hasElidedTabTitle = true; + break; + } + } + bool visible = (hasElidedTabTitle && (d->m_tabBar->count() > 1)); + QMetaObject::invokeMethod(d->m_tabsMenuButton, "setVisible", Qt::QueuedConnection, Q_ARG(bool, visible)); + } + d->m_menuOutdated = true; + } + + void DockAreaTitleBar::onTabsMenuAboutToShow() + { + if (!d->m_menuOutdated) { + return; + } + + QMenu *menu = d->m_tabsMenuButton->menu(); + menu->clear(); + for (int i = 0; i < d->m_tabBar->count(); ++i) { + if (!d->m_tabBar->isTabOpen(i)) + continue; + + auto tab = d->m_tabBar->tab(i); + QAction *action = menu->addAction(tab->icon(), tab->text()); + internal::setToolTip(action, tab->toolTip()); + action->setData(i); + } + + d->m_menuOutdated = false; + } + + void DockAreaTitleBar::onCloseButtonClicked() + { + qCInfo(adsLog) << Q_FUNC_INFO; + if (d->testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) { + d->m_tabBar->closeTab(d->m_tabBar->currentIndex()); + } else { + d->m_dockArea->closeArea(); + } + } + + void DockAreaTitleBar::onUndockButtonClicked() + { + if (d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) { + d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive); + } + } + + void DockAreaTitleBar::onTabsMenuActionTriggered(QAction *action) + { + int index = action->data().toInt(); + d->m_tabBar->setCurrentIndex(index); + emit tabBarClicked(index); + } + + void DockAreaTitleBar::updateDockWidgetActionsButtons() + { + DockWidget* dockWidget = d->m_tabBar->currentTab()->dockWidget(); + if (!d->m_dockWidgetActionsButtons.isEmpty()) { + for (auto button : d->m_dockWidgetActionsButtons) { + d->m_layout->removeWidget(button); + delete button; + } + d->m_dockWidgetActionsButtons.clear(); + } + + auto actions = dockWidget->titleBarActions(); + if (actions.isEmpty()) + return; + + int insertIndex = indexOf(d->m_tabsMenuButton); + for (auto action : actions) { + auto button = new TitleBarButton(true, this); + button->setDefaultAction(action); + button->setAutoRaise(true); + button->setPopupMode(QToolButton::InstantPopup); + button->setObjectName(action->objectName()); + d->m_layout->insertWidget(insertIndex++, button, 0); + d->m_dockWidgetActionsButtons.append(button); + } + } + + void DockAreaTitleBar::onCurrentTabChanged(int index) + { + if (index < 0) + return; + + if (d->testConfigFlag(DockManager::DockAreaCloseButtonClosesTab)) { + DockWidget *dockWidget = d->m_tabBar->tab(index)->dockWidget(); + d->m_closeButton->setEnabled( + dockWidget->features().testFlag(DockWidget::DockWidgetClosable)); + } + + updateDockWidgetActionsButtons(); + } + + QAbstractButton *DockAreaTitleBar::button(eTitleBarButton which) const + { + switch (which) { + case TitleBarButtonTabsMenu: + return d->m_tabsMenuButton; + case TitleBarButtonUndock: + return d->m_undockButton; + case TitleBarButtonClose: + return d->m_closeButton; + } + return nullptr; + } + + void DockAreaTitleBar::setVisible(bool visible) + { + Super::setVisible(visible); + markTabsMenuOutdated(); + } + + + void DockAreaTitleBar::mousePressEvent(QMouseEvent *event) + { + if (event->button() == Qt::LeftButton) { + event->accept(); + d->m_dragStartMousePos = event->pos(); + d->m_dragState = DraggingMousePressed; + return; + } + Super::mousePressEvent(event); + } + + void DockAreaTitleBar::mouseReleaseEvent(QMouseEvent *event) + { + if (event->button() == Qt::LeftButton) { + qCInfo(adsLog) << Q_FUNC_INFO; + event->accept(); + auto CurrentDragState = d->m_dragState; + d->m_dragStartMousePos = QPoint(); + d->m_dragState = DraggingInactive; + if (DraggingFloatingWidget == CurrentDragState) + d->m_floatingWidget->finishDragging(); + + return; + } + Super::mouseReleaseEvent(event); + } + + void DockAreaTitleBar::mouseMoveEvent(QMouseEvent *event) + { + Super::mouseMoveEvent(event); + if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { + d->m_dragState = DraggingInactive; + return; + } + + // move floating window + if (d->isDraggingState(DraggingFloatingWidget)) { + d->m_floatingWidget->moveFloating(); + return; + } + + // If this is the last dock area in a dock container it does not make + // sense to move it to a new floating widget and leave this one empty + if (d->m_dockArea->dockContainer()->isFloating() + && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1) { + return; + } + + // If one single dock widget in this area is not floatable then the whole + // area is not floatable + // If we do non opaque undocking, then we can create the floating drag + // preview if the dock widget is movable + auto features = d->m_dockArea->features(); + if (!features.testFlag(DockWidget::DockWidgetFloatable) + && !(features.testFlag(DockWidget::DockWidgetMovable) + && !DockManager::testConfigFlag(DockManager::OpaqueUndocking))) { + return; + } + + int dragDistance = (d->m_dragStartMousePos - event->pos()).manhattanLength(); + if (dragDistance >= DockManager::startDragDistance()) { + qCInfo(adsLog) << "TabsScrollArea::startFloating"; + d->startFloating(d->m_dragStartMousePos); + auto overlay = d->m_dockArea->dockManager()->containerOverlay(); + overlay->setAllowedAreas(OuterDockAreas); + } + + return; + } + + void DockAreaTitleBar::mouseDoubleClickEvent(QMouseEvent *event) + { + // If this is the last dock area in a dock container it does not make + // sense to move it to a new floating widget and leave this one empty + if (d->m_dockArea->dockContainer()->isFloating() + && d->m_dockArea->dockContainer()->dockAreaCount() == 1) + return; + + if (!d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) + return; + + d->makeAreaFloating(event->pos(), DraggingInactive); + } + + void DockAreaTitleBar::contextMenuEvent(QContextMenuEvent *event) + { + event->accept(); + if (d->isDraggingState(DraggingFloatingWidget)) + return; + + QMenu menu(this); + auto action = menu.addAction(tr("Detach Area"), + this, + &DockAreaTitleBar::onUndockButtonClicked); + action->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetFloatable)); + menu.addSeparator(); + action = menu.addAction(tr("Close Area"), this, &DockAreaTitleBar::onCloseButtonClicked); + action->setEnabled(d->m_dockArea->features().testFlag(DockWidget::DockWidgetClosable)); + menu.addAction(tr("Close Other Areas"), d->m_dockArea, &DockAreaWidget::closeOtherAreas); + menu.exec(event->globalPos()); + } + + void DockAreaTitleBar::insertWidget(int index, QWidget *widget) + { + d->m_layout->insertWidget(index, widget); + } + + int DockAreaTitleBar::indexOf(QWidget *widget) const + { + return d->m_layout->indexOf(widget); + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.h b/src/libs/advanceddockingsystem/dockareatitlebar.h new file mode 100644 index 00000000000..3562d057dad --- /dev/null +++ b/src/libs/advanceddockingsystem/dockareatitlebar.h @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" + +#include +#include + +class QAbstractButton; + +namespace ADS { + +class DockAreaTabBar; +class DockAreaWidget; +struct DockAreaTitleBarPrivate; + +using TitleBarButtonType = QToolButton; + +/** + * Title bar button of a dock area that customizes TitleBarButtonType appearance/behavior + * according to various config flags such as: + * DockManager::DockAreaHas_xxx_Button - if set to 'false' keeps the button always invisible + * DockManager::DockAreaHideDisabledButtons - if set to 'true' hides button when it is disabled + */ +class TitleBarButton : public TitleBarButtonType +{ + Q_OBJECT + bool m_visible = true; + bool m_hideWhenDisabled = false; +public: + using Super = TitleBarButtonType; + TitleBarButton(bool visible = true, QWidget *parent = nullptr); + + /** + * Adjust this visibility change request with our internal settings: + */ + virtual void setVisible(bool visible) override; + +protected: + /** + * Handle EnabledChanged signal to set button invisible if the configured + */ + bool event(QEvent *event) override; +}; + +/** + * This spacer widget is here because of the following problem. + * The dock area title bar handles mouse dragging and moving the floating widget. + * The problem is, that if the title bar becomes invisible, i.e. if the dock + * area contains only one single dock widget and the dock area is moved + * into a floating widget, then mouse events are not handled anymore and dragging + * of the floating widget stops. + */ +class SpacerWidget : public QWidget +{ + Q_OBJECT +public: + SpacerWidget(QWidget *parent = nullptr); + virtual QSize sizeHint() const override {return QSize(0, 0);} + virtual QSize minimumSizeHint() const override {return QSize(0, 0);} +}; + +/** + * Title bar of a dock area. + * The title bar contains a tabbar with all tabs for a dock widget group and + * with a tabs menu button, a undock button and a close button. + */ +class ADS_EXPORT DockAreaTitleBar : public QFrame +{ + Q_OBJECT +private: + DockAreaTitleBarPrivate *d; ///< private data (pimpl) + friend struct DockAreaTitleBarPrivate; + + void onTabsMenuAboutToShow(); + void onCloseButtonClicked(); + void onUndockButtonClicked(); + void onTabsMenuActionTriggered(QAction *action); + void onCurrentTabChanged(int index); + +protected: + /** + * Stores mouse position to detect dragging + */ + virtual void mousePressEvent(QMouseEvent *event) override; + + /** + * Stores mouse position to detect dragging + */ + virtual void mouseReleaseEvent(QMouseEvent *event) override; + + /** + * Starts floating the complete docking area including all dock widgets, + * if it is not the last dock area in a floating widget + */ + virtual void mouseMoveEvent(QMouseEvent *event) override; + + /** + * Double clicking the title bar also starts floating of the complete area + */ + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + + /** + * Show context menu + */ + virtual void contextMenuEvent(QContextMenuEvent *event) override; + +public: + /** + * Call this slot to tell the title bar that it should update the tabs menu + * the next time it is shown. + */ + void markTabsMenuOutdated(); + + using Super = QFrame; + /** + * Default Constructor + */ + DockAreaTitleBar(DockAreaWidget *parent); + + /** + * Virtual Destructor + */ + virtual ~DockAreaTitleBar() override; + + /** + * Returns the pointer to the tabBar() + */ + DockAreaTabBar *tabBar() const; + + /** + * Returns the button corresponding to the given title bar button identifier + */ + QAbstractButton *button(eTitleBarButton which) const; + + /** + * Updates the visibility of the dock widget actions in the title bar + */ + void updateDockWidgetActionsButtons(); + + /** + * Marks the tabs menu outdated before it calls its base class + * implementation + */ + virtual void setVisible(bool visible) override; + + /** + * Inserts a custom widget at position index into this title bar. + * If index is negative, the widget is added at the end. + * You can use this function to insert custom widgets into the title bar. + */ + void insertWidget(int index, QWidget *widget); + + /** + * Searches for widget widget in this title bar. + * You can use this function, to get the position of the default + * widget in the tile bar. + * \code + * int tabBarIndex = TitleBar->indexOf(TitleBar->tabBar()); + * int closeButtonIndex = TitleBar->indexOf(TitleBar->button(TitleBarButtonClose)); + * \endcode + */ + int indexOf(QWidget *widget) const; + +signals: + /** + * This signal is emitted if a tab in the tab bar is clicked by the user + * or if the user clicks on a tab item in the title bar tab menu. + */ + void tabBarClicked(int index); +}; // class DockAreaTitleBar + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareawidget.cpp b/src/libs/advanceddockingsystem/dockareawidget.cpp new file mode 100644 index 00000000000..32370dea6a9 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockareawidget.cpp @@ -0,0 +1,686 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dockareawidget.h" + +#include "dockareatabbar.h" +#include "dockareatitlebar.h" +#include "dockcomponentsfactory.h" +#include "dockcontainerwidget.h" +#include "dockmanager.h" +#include "dockoverlay.h" +#include "docksplitter.h" +#include "dockwidget.h" +#include "dockwidgettab.h" +#include "floatingdockcontainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg) + +namespace ADS +{ + static const char *const INDEX_PROPERTY = "index"; + static const char *const ACTION_PROPERTY = "action"; + + /** + * Internal dock area layout mimics stack layout but only inserts the current + * widget into the internal QLayout object. + * \warning Only the current widget has a parent. All other widgets + * do not have a parent. That means, a widget that is in this layout may + * return nullptr for its parent() function if it is not the current widget. + */ + class DockAreaLayout + { + private: + QBoxLayout *m_parentLayout; + QList m_widgets; + int m_currentIndex = -1; + QWidget *m_currentWidget = nullptr; + + public: + /** + * Creates an instance with the given parent layout + */ + DockAreaLayout(QBoxLayout *parentLayout) + : m_parentLayout(parentLayout) + {} + + /** + * Returns the number of widgets in this layout + */ + int count() const { return m_widgets.count(); } + + /** + * Inserts the widget at the given index position into the internal widget + * list + */ + void insertWidget(int index, QWidget *widget) + { + widget->setParent(nullptr); + if (index < 0) { + index = m_widgets.count(); + } + m_widgets.insert(index, widget); + if (m_currentIndex < 0) { + setCurrentIndex(index); + } else { + if (index <= m_currentIndex) { + ++m_currentIndex; + } + } + } + + /** + * Removes the given widget from the layout + */ + void removeWidget(QWidget *widget) + { + if (currentWidget() == widget) { + auto layoutItem = m_parentLayout->takeAt(1); + if (layoutItem) { + layoutItem->widget()->setParent(nullptr); + } + m_currentWidget = nullptr; + m_currentIndex = -1; + } + m_widgets.removeOne(widget); + } + + /** + * Returns the current selected widget + */ + QWidget *currentWidget() const { return m_currentWidget; } + + /** + * Activates the widget with the give index. + */ + void setCurrentIndex(int index) + { + QWidget *prev = currentWidget(); + QWidget *next = widget(index); + if (!next || (next == prev && !m_currentWidget)) { + return; + } + + bool reenableUpdates = false; + QWidget *parent = m_parentLayout->parentWidget(); + + if (parent && parent->updatesEnabled()) { + reenableUpdates = true; + parent->setUpdatesEnabled(false); + } + + // TODO + auto layoutItem = m_parentLayout->takeAt(1); + if (layoutItem) { + layoutItem->widget()->setParent(nullptr); + } + + m_parentLayout->addWidget(next); + if (prev) { + prev->hide(); + } + m_currentIndex = index; + m_currentWidget = next; + + if (reenableUpdates) { + parent->setUpdatesEnabled(true); + } + } + + /** + * Returns the index of the current active widget + */ + int currentIndex() const { return m_currentIndex; } + + /** + * Returns true if there are no widgets in the layout + */ + bool isEmpty() const { return m_widgets.empty(); } + + /** + * Returns the index of the given widget + */ + int indexOf(QWidget *widget) const { return m_widgets.indexOf(widget); } + + /** + * Returns the widget for the given index + */ + QWidget *widget(int index) const + { + return (index < m_widgets.size()) ? m_widgets.at(index) : nullptr; + } + + /** + * Returns the geometry of the current active widget + */ + QRect geometry() const { return m_widgets.empty() ? QRect() : currentWidget()->geometry(); } + }; + + /** + * Private data class of DockAreaWidget class (pimpl) + */ + struct DockAreaWidgetPrivate + { + DockAreaWidget *q = nullptr; + QBoxLayout *m_layout = nullptr; + DockAreaLayout *m_contentsLayout = nullptr; + DockAreaTitleBar *m_titleBar = nullptr; + DockManager *m_dockManager = nullptr; + bool m_updateTitleBarButtons = false; + DockWidgetAreas m_allowedAreas = AllDockAreas; + + /** + * Private data constructor + */ + DockAreaWidgetPrivate(DockAreaWidget *parent); + + /** + * Creates the layout for top area with tabs and close button + */ + void createTitleBar(); + + /** + * Returns the dock widget with the given index + */ + DockWidget *dockWidgetAt(int index) + { + return qobject_cast(m_contentsLayout->widget(index)); + } + + /** + * Convenience function to ease title widget access by index + */ + DockWidgetTab *tabWidgetAt(int index) { return dockWidgetAt(index)->tabWidget(); } + + /** + * Returns the tab action of the given dock widget + */ + QAction *dockWidgetTabAction(DockWidget *dockWidget) const + { + return qvariant_cast(dockWidget->property(ACTION_PROPERTY)); + } + + /** + * Returns the index of the given dock widget + */ + int dockWidgetIndex(DockWidget *dockWidget) const + { + return dockWidget->property(INDEX_PROPERTY).toInt(); + } + + /** + * Convenience function for tabbar access + */ + DockAreaTabBar *tabBar() const { return m_titleBar->tabBar(); } + + /** + * Udpates the enable state of the close and detach button + */ + void updateTitleBarButtonStates(); + }; + // struct DockAreaWidgetPrivate + + DockAreaWidgetPrivate::DockAreaWidgetPrivate(DockAreaWidget *parent) + : q(parent) + {} + + void DockAreaWidgetPrivate::createTitleBar() + { + m_titleBar = componentsFactory()->createDockAreaTitleBar(q); + m_layout->addWidget(m_titleBar); + QObject::connect(tabBar(), + &DockAreaTabBar::tabCloseRequested, + q, + &DockAreaWidget::onTabCloseRequested); + QObject::connect(m_titleBar, + &DockAreaTitleBar::tabBarClicked, + q, + &DockAreaWidget::setCurrentIndex); + QObject::connect(tabBar(), &DockAreaTabBar::tabMoved, q, &DockAreaWidget::reorderDockWidget); + } + + void DockAreaWidgetPrivate::updateTitleBarButtonStates() + { + if (q->isHidden()) { + m_updateTitleBarButtons = true; + return; + } + + m_titleBar->button(TitleBarButtonClose) + ->setEnabled(q->features().testFlag(DockWidget::DockWidgetClosable)); + m_titleBar->button(TitleBarButtonUndock) + ->setEnabled(q->features().testFlag(DockWidget::DockWidgetFloatable)); + m_titleBar->updateDockWidgetActionsButtons(); + m_updateTitleBarButtons = false; + } + + DockAreaWidget::DockAreaWidget(DockManager *dockManager, DockContainerWidget *parent) + : QFrame(parent) + , d(new DockAreaWidgetPrivate(this)) + { + d->m_dockManager = dockManager; + d->m_layout = new QBoxLayout(QBoxLayout::TopToBottom); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + + d->createTitleBar(); + d->m_contentsLayout = new DockAreaLayout(d->m_layout); + if (d->m_dockManager) { + emit d->m_dockManager->dockAreaCreated(this); + } + } + + DockAreaWidget::~DockAreaWidget() + { + qCInfo(adsLog) << Q_FUNC_INFO; + delete d->m_contentsLayout; + delete d; + } + + DockManager *DockAreaWidget::dockManager() const { return d->m_dockManager; } + + DockContainerWidget *DockAreaWidget::dockContainer() const + { + return internal::findParent(this); + } + + void DockAreaWidget::addDockWidget(DockWidget *dockWidget) + { + insertDockWidget(d->m_contentsLayout->count(), dockWidget); + } + + void DockAreaWidget::insertDockWidget(int index, DockWidget *dockWidget, bool activate) + { + d->m_contentsLayout->insertWidget(index, dockWidget); + dockWidget->tabWidget()->setDockAreaWidget(this); + auto tabWidget = dockWidget->tabWidget(); + // Inserting the tab will change the current index which in turn will + // make the tab widget visible in the slot + d->tabBar()->blockSignals(true); + d->tabBar()->insertTab(index, tabWidget); + d->tabBar()->blockSignals(false); + tabWidget->setVisible(!dockWidget->isClosed()); + dockWidget->setProperty(INDEX_PROPERTY, index); + if (activate) { + setCurrentIndex(index); + } + dockWidget->setDockArea(this); + d->updateTitleBarButtonStates(); + } + + void DockAreaWidget::removeDockWidget(DockWidget *dockWidget) + { + qCInfo(adsLog) << Q_FUNC_INFO; + auto nextOpen = nextOpenDockWidget(dockWidget); + + d->m_contentsLayout->removeWidget(dockWidget); + auto tabWidget = dockWidget->tabWidget(); + tabWidget->hide(); + d->tabBar()->removeTab(tabWidget); + DockContainerWidget *dockContainerWidget = dockContainer(); + if (nextOpen) { + setCurrentDockWidget(nextOpen); + } else if (d->m_contentsLayout->isEmpty() && dockContainerWidget->dockAreaCount() > 1) { + qCInfo(adsLog) << "Dock Area empty"; + dockContainerWidget->removeDockArea(this); + this->deleteLater(); + } else { + // if contents layout is not empty but there are no more open dock + // widgets, then we need to hide the dock area because it does not + // contain any visible content + hideAreaWithNoVisibleContent(); + } + + d->updateTitleBarButtonStates(); + updateTitleBarVisibility(); + auto topLevelDockWidget = dockContainerWidget->topLevelDockWidget(); + if (topLevelDockWidget) { + topLevelDockWidget->emitTopLevelChanged(true); + } + +#if (ADS_DEBUG_LEVEL > 0) + dockContainerWidget->dumpLayout(); +#endif + } + + void DockAreaWidget::hideAreaWithNoVisibleContent() + { + this->toggleView(false); + + // Hide empty parent splitters + auto splitter = internal::findParent(this); + internal::hideEmptyParentSplitters(splitter); + + //Hide empty floating widget + DockContainerWidget *container = this->dockContainer(); + if (!container->isFloating()) { + return; + } + + updateTitleBarVisibility(); + auto topLevelWidget = container->topLevelDockWidget(); + auto floatingWidget = container->floatingWidget(); + if (topLevelWidget) { + floatingWidget->updateWindowTitle(); + DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); + } else if (container->openedDockAreas().isEmpty()) { + floatingWidget->hide(); + } + } + + void DockAreaWidget::onTabCloseRequested(int index) + { + qCInfo(adsLog) << Q_FUNC_INFO << "index" << index; + auto *currentDockWidget = dockWidget(index); + if (currentDockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { + currentDockWidget->closeDockWidgetInternal(); + } else { + currentDockWidget->toggleView(false); + } + } + + DockWidget *DockAreaWidget::currentDockWidget() const + { + int currentIdx = currentIndex(); + if (currentIdx < 0) { + return nullptr; + } + + return dockWidget(currentIdx); + } + + void DockAreaWidget::setCurrentDockWidget(DockWidget *dockWidget) + { + if (dockManager()->isRestoringState()) { + return; + } + + internalSetCurrentDockWidget(dockWidget); + } + + void DockAreaWidget::internalSetCurrentDockWidget(DockWidget *dockWidget) + { + int index = indexOf(dockWidget); + if (index < 0) { + return; + } + + setCurrentIndex(index); + } + + void DockAreaWidget::setCurrentIndex(int index) + { + auto currentTabBar = d->tabBar(); + if (index < 0 || index > (currentTabBar->count() - 1)) { + qWarning() << Q_FUNC_INFO << "Invalid index" << index; + return; + } + + auto cw = d->m_contentsLayout->currentWidget(); + auto nw = d->m_contentsLayout->widget(index); + if (cw == nw && !nw->isHidden()) { + return; + } + + emit currentChanging(index); + currentTabBar->setCurrentIndex(index); + d->m_contentsLayout->setCurrentIndex(index); + d->m_contentsLayout->currentWidget()->show(); + emit currentChanged(index); + } + + int DockAreaWidget::currentIndex() const { return d->m_contentsLayout->currentIndex(); } + + QRect DockAreaWidget::titleBarGeometry() const { return d->m_titleBar->geometry(); } + + QRect DockAreaWidget::contentAreaGeometry() const { return d->m_contentsLayout->geometry(); } + + int DockAreaWidget::indexOf(DockWidget *dockWidget) + { + return d->m_contentsLayout->indexOf(dockWidget); + } + + QList DockAreaWidget::dockWidgets() const + { + QList dockWidgetList; + for (int i = 0; i < d->m_contentsLayout->count(); ++i) { + dockWidgetList.append(dockWidget(i)); + } + return dockWidgetList; + } + + int DockAreaWidget::openDockWidgetsCount() const + { + int count = 0; + for (int i = 0; i < d->m_contentsLayout->count(); ++i) { + if (!dockWidget(i)->isClosed()) { + ++count; + } + } + return count; + } + + QList DockAreaWidget::openedDockWidgets() const + { + QList dockWidgetList; + for (int i = 0; i < d->m_contentsLayout->count(); ++i) { + DockWidget *currentDockWidget = dockWidget(i); + if (!currentDockWidget->isClosed()) { + dockWidgetList.append(dockWidget(i)); + } + } + return dockWidgetList; + } + + int DockAreaWidget::indexOfFirstOpenDockWidget() const + { + for (int i = 0; i < d->m_contentsLayout->count(); ++i) { + if (!dockWidget(i)->isClosed()) { + return i; + } + } + + return -1; + } + + int DockAreaWidget::dockWidgetsCount() const { return d->m_contentsLayout->count(); } + + DockWidget *DockAreaWidget::dockWidget(int index) const + { + return qobject_cast(d->m_contentsLayout->widget(index)); + } + + void DockAreaWidget::reorderDockWidget(int fromIndex, int toIndex) + { + qCInfo(adsLog) << Q_FUNC_INFO; + if (fromIndex >= d->m_contentsLayout->count() || fromIndex < 0 + || toIndex >= d->m_contentsLayout->count() || toIndex < 0 || fromIndex == toIndex) { + qCInfo(adsLog) << "Invalid index for tab movement" << fromIndex << toIndex; + return; + } + + auto widget = d->m_contentsLayout->widget(fromIndex); + d->m_contentsLayout->removeWidget(widget); + d->m_contentsLayout->insertWidget(toIndex, widget); + setCurrentIndex(toIndex); + } + + void DockAreaWidget::toggleDockWidgetView(DockWidget *dockWidget, bool open) + { + Q_UNUSED(dockWidget) + Q_UNUSED(open) + updateTitleBarVisibility(); + } + + void DockAreaWidget::updateTitleBarVisibility() + { + DockContainerWidget *container = dockContainer(); + if (!container) { + return; + } + + if (DockManager::configFlags().testFlag(DockManager::AlwaysShowTabs)) { + return; + } + + if (d->m_titleBar) { + d->m_titleBar->setVisible(!container->isFloating() || !container->hasTopLevelDockWidget()); + } + } + + void DockAreaWidget::markTitleBarMenuOutdated() + { + if (d->m_titleBar) { + d->m_titleBar->markTabsMenuOutdated(); + } + } + + void DockAreaWidget::saveState(QXmlStreamWriter &stream) const + { + stream.writeStartElement("area"); + stream.writeAttribute("tabs", QString::number(d->m_contentsLayout->count())); + auto localDockWidget = currentDockWidget(); + QString name = localDockWidget ? localDockWidget->objectName() : ""; + stream.writeAttribute("current", name); + qCInfo(adsLog) << Q_FUNC_INFO << "TabCount: " << d->m_contentsLayout->count() + << " Current: " << name; + for (int i = 0; i < d->m_contentsLayout->count(); ++i) { + dockWidget(i)->saveState(stream); + } + stream.writeEndElement(); + } + + DockWidget *DockAreaWidget::nextOpenDockWidget(DockWidget *dockWidget) const + { + auto openDockWidgets = openedDockWidgets(); + if (openDockWidgets.count() > 1 + || (openDockWidgets.count() == 1 && openDockWidgets[0] != dockWidget)) { + DockWidget *nextDockWidget; + if (openDockWidgets.last() == dockWidget) { + nextDockWidget = openDockWidgets[openDockWidgets.count() - 2]; + } else { + int nextIndex = openDockWidgets.indexOf(dockWidget) + 1; + nextDockWidget = openDockWidgets[nextIndex]; + } + + return nextDockWidget; + } else { + return nullptr; + } + } + + DockWidget::DockWidgetFeatures DockAreaWidget::features(eBitwiseOperator mode) const + { + if (BitwiseAnd == mode) { + DockWidget::DockWidgetFeatures features(DockWidget::AllDockWidgetFeatures); + for (const auto dockWidget : dockWidgets()) { + features &= dockWidget->features(); + } + return features; + } else { + DockWidget::DockWidgetFeatures features(DockWidget::NoDockWidgetFeatures); + for (const auto dockWidget : dockWidgets()) { + features |= dockWidget->features(); + } + return features; + } + } + + void DockAreaWidget::toggleView(bool open) + { + setVisible(open); + + emit viewToggled(open); + } + + void DockAreaWidget::setVisible(bool visible) + { + Super::setVisible(visible); + if (d->m_updateTitleBarButtons) { + d->updateTitleBarButtonStates(); + } + } + + void DockAreaWidget::setAllowedAreas(DockWidgetAreas areas) + { + d->m_allowedAreas = areas; + } + + DockWidgetAreas DockAreaWidget::allowedAreas() const + { + return d->m_allowedAreas; + } + + QAbstractButton *DockAreaWidget::titleBarButton(eTitleBarButton which) const + { + return d->m_titleBar->button(which); + } + + void DockAreaWidget::closeArea() + { + // If there is only one single dock widget and this widget has the + // DeleteOnClose feature, then we delete the dock widget now + auto openDockWidgets = openedDockWidgets(); + if (openDockWidgets.count() == 1 + && openDockWidgets[0]->features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { + openDockWidgets[0]->closeDockWidgetInternal(); + } else { + for (auto dockWidget : openedDockWidgets()) { + dockWidget->toggleView(false); + } + } + } + + void DockAreaWidget::closeOtherAreas() { dockContainer()->closeOtherAreas(this); } + + DockAreaTitleBar *DockAreaWidget::titleBar() const { return d->m_titleBar; } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockareawidget.h b/src/libs/advanceddockingsystem/dockareawidget.h new file mode 100644 index 00000000000..9949ef16822 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockareawidget.h @@ -0,0 +1,319 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" +#include "dockwidget.h" + +#include + +class QXmlStreamWriter; +class QAbstractButton; + +namespace ADS { + +struct DockAreaWidgetPrivate; +class DockManager; +class DockContainerWidget; +class DockContainerWidgetPrivate; +class DockAreaTitleBar; + +/** + * DockAreaWidget manages multiple instances of DockWidgets. + * It displays a title tab, which is clickable and will switch to + * the contents associated to the title when clicked. + */ +class ADS_EXPORT DockAreaWidget : public QFrame +{ + Q_OBJECT +private: + DockAreaWidgetPrivate *d; ///< private data (pimpl) + friend struct DockAreaWidgetPrivate; + friend class DockContainerWidget; + friend class DockContainerWidgetPrivate; + friend class DockWidgetTab; + friend struct DockWidgetPrivate; + friend class DockWidget; + friend struct DockManagerPrivate; + friend class DockManager; + + void onTabCloseRequested(int index); + + /** + * Reorder the index position of DockWidget at fromIndx to toIndex + * if a tab in the tabbar is dragged from one index to another one + */ + void reorderDockWidget(int fromIndex, int toIndex); + +protected: + /** + * Inserts a dock widget into dock area. + * All dockwidgets in the dock area tabified in a stacked layout with tabs. + * The index indicates the index of the new dockwidget in the tabbar and + * in the stacked layout. If the Activate parameter is true, the new + * DockWidget will be the active one in the stacked layout + */ + void insertDockWidget(int index, DockWidget *dockWidget, bool activate = true); + + /** + * Add a new dock widget to dock area. + * All dockwidgets in the dock area tabified in a stacked layout with tabs + */ + void addDockWidget(DockWidget *dockWidget); + + /** + * Removes the given dock widget from the dock area + */ + void removeDockWidget(DockWidget *dockWidget); + + /** + * Called from dock widget if it is opened or closed + */ + void toggleDockWidgetView(DockWidget *dockWidget, bool open); + + /** + * This is a helper function to get the next open dock widget to activate + * if the given DockWidget will be closed or removed. + * The function returns the next widget that should be activated or + * nullptr in case there are no more open widgets in this area. + */ + DockWidget *nextOpenDockWidget(DockWidget *dockWidget) const; + + /** + * Returns the index of the given DockWidget in the internal layout + */ + int indexOf(DockWidget *dockWidget); + + /** + * Call this function, if you already know, that the dock does not + * contain any visible content (any open dock widgets). + */ + void hideAreaWithNoVisibleContent(); + + /** + * Updates the dock area layout and components visibility + */ + void updateTitleBarVisibility(); + + /** + * This is the internal private function for setting the current widget. + * This function is called by the public setCurrentDockWidget() function + * and by the dock manager when restoring the state + */ + void internalSetCurrentDockWidget(DockWidget *dockWidget); + + /** + * Marks tabs menu to update + */ + void markTitleBarMenuOutdated(); + + void toggleView(bool open); + +public: + using Super = QFrame; + + /** + * Default Constructor + */ + DockAreaWidget(DockManager *dockManager, DockContainerWidget *parent); + + /** + * Virtual Destructor + */ + virtual ~DockAreaWidget() override; + + /** + * Returns the dock manager object this dock area belongs to + */ + DockManager *dockManager() const; + + /** + * Returns the dock container widget this dock area widget belongs to or 0 + * if there is no + */ + DockContainerWidget *dockContainer() const; + + /** + * Returns the rectangle of the title area + */ + QRect titleBarGeometry() const; + + /** + * Returns the rectangle of the content + */ + QRect contentAreaGeometry() const; + + /** + * Returns the number of dock widgets in this area + */ + int dockWidgetsCount() const; + + /** + * Returns a list of all dock widgets in this dock area. + * This list contains open and closed dock widgets. + */ + QList dockWidgets() const; + + /** + * Returns the number of open dock widgets in this area + */ + int openDockWidgetsCount() const; + + /** + * Returns a list of dock widgets that are not closed. + */ + QList openedDockWidgets() const; + + /** + * Returns a dock widget by its index + */ + DockWidget *dockWidget(int indexOf) const; + + /** + * Returns the index of the current active dock widget or -1 if there + * are is no active dock widget (ie.e if all dock widgets are closed) + */ + int currentIndex() const; + + /** + * Returns the index of the first open dock widgets in the list of + * dock widgets. + * This function is here for performance reasons. Normally it would + * be possible to take the first dock widget from the list returned by + * openedDockWidgets() function. But that function enumerates all + * dock widgets while this functions stops after the first open dock widget. + * If there are no open dock widgets, the function returns -1. + */ + int indexOfFirstOpenDockWidget() const; + + /** + * Returns the current active dock widget or a nullptr if there is no + * active dock widget (i.e. if all dock widgets are closed) + */ + DockWidget *currentDockWidget() const; + + /** + * Shows the tab with the given dock widget + */ + void setCurrentDockWidget(DockWidget *dockWidget); + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter &stream) const; + + /** + * This functions returns the dock widget features of all dock widget in + * this area. + * A bitwise and is used to combine the flags of all dock widgets. That + * means, if only one single dock widget does not support a certain flag, + * the whole dock are does not support the flag. I.e. if one single + * dock widget in this area is not closable, the whole dock are is not + * closable. + */ + DockWidget::DockWidgetFeatures features(eBitwiseOperator mode = BitwiseAnd) const; + + /** + * Returns the title bar button corresponding to the given title bar + * button identifier + */ + QAbstractButton *titleBarButton(eTitleBarButton which) const; + + /** + * Update the close button if visibility changed + */ + virtual void setVisible(bool visible) override; + + /** + * Configures the areas of this particular dock area that are allowed for docking + */ + void setAllowedAreas(DockWidgetAreas areas); + + /** + * Returns flags with all allowed drop areas of this particular dock area + */ + DockWidgetAreas allowedAreas() const; + + /** + * Returns the title bar of this dock area + */ + DockAreaTitleBar *titleBar() const; + + /** + * This activates the tab for the given tab index. + * If the dock widget for the given tab is not visible, the this function + * call will make it visible. + */ + void setCurrentIndex(int indexOf); + + /** + * Closes the dock area and all dock widgets in this area + */ + void closeArea(); + + /** + * This function closes all other areas except of this area + */ + void closeOtherAreas(); + +signals: + /** + * This signal is emitted when user clicks on a tab at an index. + */ + void tabBarClicked(int indexOf); + + /** + * This signal is emitted when the tab bar's current tab is about to be changed. The new + * current has the given index, or -1 if there isn't a new one. + * @param index + */ + void currentChanging(int indexOf); + + /** + * This signal is emitted when the tab bar's current tab changes. The new + * current has the given index, or -1 if there isn't a new one + * @param index + */ + void currentChanged(int indexOf); + + /** + * This signal is emitted if the visibility of this dock area is toggled + * via toggle view function + */ + void viewToggled(bool open); +}; // class DockAreaWidget + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp b/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp new file mode 100644 index 00000000000..1b5a0933c2a --- /dev/null +++ b/src/libs/advanceddockingsystem/dockcomponentsfactory.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dockcomponentsfactory.h" + +#include "dockwidgettab.h" +#include "dockareatabbar.h" +#include "dockareatitlebar.h" +#include "dockwidget.h" +#include "dockareawidget.h" + +#include + +namespace ADS +{ + static std::unique_ptr g_defaultFactory(new DockComponentsFactory()); + + DockWidgetTab *DockComponentsFactory::createDockWidgetTab(DockWidget *dockWidget) const + { + return new DockWidgetTab(dockWidget); + } + + DockAreaTabBar *DockComponentsFactory::createDockAreaTabBar(DockAreaWidget *dockArea) const + { + return new DockAreaTabBar(dockArea); + } + + DockAreaTitleBar *DockComponentsFactory::createDockAreaTitleBar(DockAreaWidget *dockArea) const + { + return new DockAreaTitleBar(dockArea); + } + + const DockComponentsFactory *DockComponentsFactory::factory() + { + return g_defaultFactory.get(); + } + + void DockComponentsFactory::setFactory(DockComponentsFactory *factory) + { + g_defaultFactory.reset(factory); + } + + void DockComponentsFactory::resetDefaultFactory() + { + g_defaultFactory.reset(new DockComponentsFactory()); + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockcomponentsfactory.h b/src/libs/advanceddockingsystem/dockcomponentsfactory.h new file mode 100644 index 00000000000..8e1019a9312 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockcomponentsfactory.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" + +namespace ADS { + +class DockWidgetTab; +class DockAreaTitleBar; +class DockAreaTabBar; +class DockAreaWidget; +class DockWidget; + +/** + * Factory for creation of certain GUI elements for the docking framework. + * A default unique instance provided by DockComponentsFactory is used for + * creation of all supported components. To inject your custom components, + * you can create your own derived dock components factory and register + * it via setDefaultFactory() function. + * \code + * CDockComponentsFactory::setDefaultFactory(new MyComponentsFactory())); + * \endcode + */ +class ADS_EXPORT DockComponentsFactory +{ +public: + /** + * Force virtual destructor + */ + virtual ~DockComponentsFactory() {} + + /** + * This default implementation just creates a dock widget tab with + * new DockWidgetTab(dockWidget). + */ + virtual DockWidgetTab *createDockWidgetTab(DockWidget *dockWidget) const; + + /** + * This default implementation just creates a dock area tab bar with + * new DockAreaTabBar(dockArea). + */ + virtual DockAreaTabBar *createDockAreaTabBar(DockAreaWidget *dockArea) const; + + /** + * This default implementation just creates a dock area title bar with + * new DockAreaTitleBar(dockArea). + */ + virtual DockAreaTitleBar *createDockAreaTitleBar(DockAreaWidget *dockArea) const; + + /** + * Returns the default components factory + */ + static const DockComponentsFactory *factory(); + + /** + * Sets a new default factory for creation of GUI elements. + * This function takes ownership of the given Factory. + */ + static void setFactory(DockComponentsFactory* factory); + + /** + * Resets the current factory to the + */ + static void resetDefaultFactory(); +}; + +/** + * Convenience function to ease factory instance access + */ +inline const DockComponentsFactory *componentsFactory() +{ + return DockComponentsFactory::factory(); +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp new file mode 100644 index 00000000000..769d09e4ac6 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp @@ -0,0 +1,1459 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dockcontainerwidget.h" + +#include "ads_globals.h" +#include "dockareawidget.h" +#include "dockingstatereader.h" +#include "dockmanager.h" +#include "dockoverlay.h" +#include "docksplitter.h" +#include "dockwidget.h" +#include "floatingdockcontainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg) + +namespace ADS +{ + static unsigned int zOrderCounter = 0; + + enum eDropMode { + DropModeIntoArea, ///< drop widget into a dock area + DropModeIntoContainer, ///< drop into container + DropModeInvalid ///< invalid mode - do not drop + }; + + /** + * Converts dock area ID to an index for array access + */ + static int areaIdToIndex(DockWidgetArea area) + { + switch (area) { + case LeftDockWidgetArea: + return 0; + case RightDockWidgetArea: + return 1; + case TopDockWidgetArea: + return 2; + case BottomDockWidgetArea: + return 3; + case CenterDockWidgetArea: + return 4; + default: + return 4; + } + } + + /** + * Helper function to ease insertion of dock area into splitter + */ + static void insertWidgetIntoSplitter(QSplitter *splitter, QWidget *widget, bool append) + { + if (append) { + splitter->addWidget(widget); + } else { + splitter->insertWidget(0, widget); + } + } + + /** + * Private data class of DockContainerWidget class (pimpl) + */ + class DockContainerWidgetPrivate + { + public: + DockContainerWidget *q; + QPointer m_dockManager; + unsigned int m_zOrderIndex = 0; + QList m_dockAreas; + QGridLayout *m_layout = nullptr; + QSplitter *m_rootSplitter = nullptr; + bool m_isFloating = false; + DockAreaWidget *m_lastAddedAreaCache[5]; + int m_visibleDockAreaCount = -1; + DockAreaWidget *m_topLevelDockArea = nullptr; + + /** + * Private data constructor + */ + DockContainerWidgetPrivate(DockContainerWidget *parent); + + /** + * Adds dock widget to container and returns the dock area that contains + * the inserted dock widget + */ + DockAreaWidget *dockWidgetIntoContainer(DockWidgetArea area, DockWidget *dockWidget); + + /** + * Adds dock widget to a existing DockWidgetArea + */ + DockAreaWidget *dockWidgetIntoDockArea(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *targetDockArea); + + /** + * Add dock area to this container + */ + void addDockArea(DockAreaWidget *newDockWidget, DockWidgetArea area = CenterDockWidgetArea); + + /** + * Drop floating widget into container + */ + void dropIntoContainer(FloatingDockContainer *floatingWidget, DockWidgetArea area); + + /** + * Drop floating widget into dock area + */ + void dropIntoSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea, + DockWidgetArea area); + + /** + * Moves the dock widget or dock area given in Widget parameter to a + * new dock widget area + */ + void moveToNewSection(QWidget *widget, DockAreaWidget *targetArea, DockWidgetArea area); + + /** + * Moves the dock widget or dock area given in Widget parameter to a + * a dock area in container + */ + void moveToContainer(QWidget *widget, DockWidgetArea area); + + /** + * Creates a new tab for a widget dropped into the center of a section + */ + void dropIntoCenterOfSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea); + + /** + * Creates a new tab for a widget dropped into the center of a section + */ + void moveIntoCenterOfSection(QWidget *widget, DockAreaWidget *targetArea); + + /** + * Adds new dock areas to the internal dock area list + */ + void addDockAreasToList(const QList newDockAreas); + + /** + * Wrapper function for DockAreas append, that ensures that dock area signals + * are properly connected to dock container slots + */ + void appendDockAreas(const QList newDockAreas); + + /** + * Save state of child nodes + */ + void saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget); + + /** + * Restore state of child nodes. + * \param[in] Stream The data stream that contains the serialized state + * \param[out] CreatedWidget The widget created from parsed data or 0 if + * the parsed widget was an empty splitter + * \param[in] Testing If Testing is true, only the stream data is + * parsed without modifiying anything. + */ + bool restoreChildNodes(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing); + + /** + * Restores a splitter. + * \see restoreChildNodes() for details + */ + bool restoreSplitter(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); + + /** + * Restores a dock area. + * \see restoreChildNodes() for details + */ + bool restoreDockArea(DockingStateReader &stateReader, QWidget *&createdWidget, bool testing); + + /** + * Helper function for recursive dumping of layout + */ + void dumpRecursive(int level, QWidget *widget) const; + + /** + * Calculate the drop mode from the given target position + */ + eDropMode getDropMode(const QPoint &targetPosition); + + /** + * Initializes the visible dock area count variable if it is not initialized + * yet + */ + void initVisibleDockAreaCount() + { + if (m_visibleDockAreaCount > -1) { + return; + } + + m_visibleDockAreaCount = 0; + for (auto dockArea : m_dockAreas) { + m_visibleDockAreaCount += dockArea->isHidden() ? 0 : 1; + } + } + + /** + * Access function for the visible dock area counter + */ + int visibleDockAreaCount() + { + // Lazy initialization - we initialize the VisibleDockAreaCount variable + // on first use + initVisibleDockAreaCount(); + return m_visibleDockAreaCount; + } + + /** + * The visible dock area count changes, if dock areas are remove, added or + * when its view is toggled + */ + void onVisibleDockAreaCountChanged(); + + void emitDockAreasRemoved() + { + onVisibleDockAreaCountChanged(); + emit q->dockAreasRemoved(); + } + + void emitDockAreasAdded() + { + onVisibleDockAreaCountChanged(); + emit q->dockAreasAdded(); + } + + /** + * Helper function for creation of new splitter + */ + DockSplitter *createSplitter(Qt::Orientation orientation, QWidget *parent = nullptr) + { + auto *splitter = new DockSplitter(orientation, parent); + splitter->setOpaqueResize( + DockManager::configFlags().testFlag(DockManager::OpaqueSplitterResize)); + splitter->setChildrenCollapsible(false); + return splitter; + } + + void onDockAreaViewToggled(bool visible) + { + DockAreaWidget *dockArea = qobject_cast(q->sender()); + m_visibleDockAreaCount += visible ? 1 : -1; + onVisibleDockAreaCountChanged(); + emit q->dockAreaViewToggled(dockArea, visible); + } + }; // struct DockContainerWidgetPrivate + + DockContainerWidgetPrivate::DockContainerWidgetPrivate(DockContainerWidget *parent) + : q(parent) + { + std::fill(std::begin(m_lastAddedAreaCache), std::end(m_lastAddedAreaCache), nullptr); + } + + eDropMode DockContainerWidgetPrivate::getDropMode(const QPoint &targetPosition) + { + DockAreaWidget *dockArea = q->dockAreaAt(targetPosition); + auto dropArea = InvalidDockWidgetArea; + auto containerDropArea = m_dockManager->containerOverlay()->dropAreaUnderCursor(); + + if (dockArea) { + auto dropOverlay = m_dockManager->dockAreaOverlay(); + dropOverlay->setAllowedAreas(dockArea->allowedAreas()); + dropArea = dropOverlay->showOverlay(dockArea); + if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) { + dropArea = InvalidDockWidgetArea; + } + + if (dropArea != InvalidDockWidgetArea) { + qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; + return DropModeIntoArea; + } + } + + // mouse is over container + if (InvalidDockWidgetArea == dropArea) { + dropArea = containerDropArea; + qCInfo(adsLog) << "Container Drop Content: " << dropArea; + if (dropArea != InvalidDockWidgetArea) { + return DropModeIntoContainer; + } + } + + return DropModeInvalid; + } + + void DockContainerWidgetPrivate::onVisibleDockAreaCountChanged() + { + auto topLevelDockArea = q->topLevelDockArea(); + + if (topLevelDockArea) { + this->m_topLevelDockArea = topLevelDockArea; + topLevelDockArea->titleBarButton(TitleBarButtonUndock) + ->setVisible(false || !q->isFloating()); + topLevelDockArea->titleBarButton(TitleBarButtonClose) + ->setVisible(false || !q->isFloating()); + } else if (this->m_topLevelDockArea) { + this->m_topLevelDockArea->titleBarButton(TitleBarButtonUndock)->setVisible(true); + this->m_topLevelDockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); + this->m_topLevelDockArea = nullptr; + } + } + + void DockContainerWidgetPrivate::dropIntoContainer(FloatingDockContainer *floatingWidget, + DockWidgetArea area) + { + auto insertParam = internal::dockAreaInsertParameters(area); + DockContainerWidget *floatingDockContainer = floatingWidget->dockContainer(); + auto newDockAreas = floatingDockContainer + ->findChildren(QString(), + Qt::FindChildrenRecursively); + QSplitter *splitter = m_rootSplitter; + + if (m_dockAreas.count() <= 1) { + splitter->setOrientation(insertParam.orientation()); + } else if (splitter->orientation() != insertParam.orientation()) { + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); + newSplitter->addWidget(splitter); + splitter = newSplitter; + delete layoutItem; + } + + // Now we can insert the floating widget content into this container + auto floatingSplitter = floatingDockContainer->rootSplitter(); + if (floatingSplitter->count() == 1) { + insertWidgetIntoSplitter(splitter, floatingSplitter->widget(0), insertParam.append()); + } else if (floatingSplitter->orientation() == insertParam.orientation()) { + while (floatingSplitter->count()) { + insertWidgetIntoSplitter(splitter, + floatingSplitter->widget(0), + insertParam.append()); + } + } else { + insertWidgetIntoSplitter(splitter, floatingSplitter, insertParam.append()); + } + + m_rootSplitter = splitter; + addDockAreasToList(newDockAreas); + + // If we dropped the floating widget into the main dock container that does + // not contain any dock widgets, then splitter is invisible and we need to + // show it to display the docked widgets + if (!splitter->isVisible()) { + splitter->show(); + } + q->dumpLayout(); + } + + void DockContainerWidgetPrivate::dropIntoCenterOfSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea) + { + DockContainerWidget *floatingContainer = floatingWidget->dockContainer(); + auto newDockWidgets = floatingContainer->dockWidgets(); + auto topLevelDockArea = floatingContainer->topLevelDockArea(); + int newCurrentIndex = -1; + + // If the floating widget contains only one single dock are, then the + // current dock widget of the dock area will also be the future current + // dock widget in the drop area. + if (topLevelDockArea) { + newCurrentIndex = topLevelDockArea->currentIndex(); + } + + for (int i = 0; i < newDockWidgets.count(); ++i) { + DockWidget *dockWidget = newDockWidgets[i]; + targetArea->insertDockWidget(i, dockWidget, false); + // If the floating widget contains multiple visible dock areas, then we + // simply pick the first visible open dock widget and make it + // the current one. + if (newCurrentIndex < 0 && !dockWidget->isClosed()) { + newCurrentIndex = i; + } + } + targetArea->setCurrentIndex(newCurrentIndex); + targetArea->updateTitleBarVisibility(); + return; + } + + void DockContainerWidgetPrivate::dropIntoSection(FloatingDockContainer *floatingWidget, + DockAreaWidget *targetArea, + DockWidgetArea area) + { + // Dropping into center means all dock widgets in the dropped floating + // widget will become tabs of the drop area + if (CenterDockWidgetArea == area) { + dropIntoCenterOfSection(floatingWidget, targetArea); + return; + } + + auto insertParam = internal::dockAreaInsertParameters(area); + auto newDockAreas = floatingWidget->dockContainer() + ->findChildren(QString(), + Qt::FindChildrenRecursively); + QSplitter *targetAreaSplitter = internal::findParent(targetArea); + + if (!targetAreaSplitter) { + QSplitter *splitter = createSplitter(insertParam.orientation()); + m_layout->replaceWidget(targetArea, splitter); + splitter->addWidget(targetArea); + targetAreaSplitter = splitter; + } + int areaIndex = targetAreaSplitter->indexOf(targetArea); + auto widget = floatingWidget->dockContainer() + ->findChild(QString(), Qt::FindDirectChildrenOnly); + auto floatingSplitter = qobject_cast(widget); + + if (targetAreaSplitter->orientation() == insertParam.orientation()) { + auto sizes = targetAreaSplitter->sizes(); + int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) + ? targetArea->width() + : targetArea->height(); + bool adjustSplitterSizes = true; + if ((floatingSplitter->orientation() != insertParam.orientation()) + && floatingSplitter->count() > 1) { + targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), widget); + } else { + adjustSplitterSizes = (floatingSplitter->count() == 1); + int insertIndex = areaIndex + insertParam.insertOffset(); + while (floatingSplitter->count()) { + targetAreaSplitter->insertWidget(insertIndex++, floatingSplitter->widget(0)); + } + } + + if (adjustSplitterSizes) { + int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2; + sizes[areaIndex] = size; + sizes.insert(areaIndex, size); + targetAreaSplitter->setSizes(sizes); + } + } else { + QList newSplitterSizes; + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) + ? targetArea->width() + : targetArea->height(); + bool adjustSplitterSizes = true; + if ((floatingSplitter->orientation() != insertParam.orientation()) + && floatingSplitter->count() > 1) { + newSplitter->addWidget(widget); + } else { + adjustSplitterSizes = (floatingSplitter->count() == 1); + while (floatingSplitter->count()) { + newSplitter->addWidget(floatingSplitter->widget(0)); + } + } + + // Save the sizes before insertion and restore it later to prevent + // shrinking of existing area + auto sizes = targetAreaSplitter->sizes(); + insertWidgetIntoSplitter(newSplitter, targetArea, !insertParam.append()); + if (adjustSplitterSizes) { + int size = targetAreaSize / 2; + newSplitter->setSizes({size, size}); + } + targetAreaSplitter->insertWidget(areaIndex, newSplitter); + targetAreaSplitter->setSizes(sizes); + } + + addDockAreasToList(newDockAreas); + q->dumpLayout(); + } + + void DockContainerWidgetPrivate::moveIntoCenterOfSection(QWidget *widget, + DockAreaWidget *targetArea) + { + auto droppedDockWidget = qobject_cast(widget); + auto droppedArea = qobject_cast(widget); + + if (droppedDockWidget) { + DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); + if (oldDockArea) { + oldDockArea->removeDockWidget(droppedDockWidget); + } + targetArea->insertDockWidget(0, droppedDockWidget, true); + } else { + QList newDockWidgets = droppedArea->dockWidgets(); + int newCurrentIndex = droppedArea->currentIndex(); + for (int i = 0; i < newDockWidgets.count(); ++i) { + DockWidget *dockWidget = newDockWidgets[i]; + targetArea->insertDockWidget(i, dockWidget, false); + } + targetArea->setCurrentIndex(newCurrentIndex); + droppedArea->dockContainer()->removeDockArea(droppedArea); + droppedArea->deleteLater(); + } + + targetArea->updateTitleBarVisibility(); + return; + } + + void DockContainerWidgetPrivate::moveToNewSection(QWidget *widget, + DockAreaWidget *targetArea, + DockWidgetArea area) + { + // Dropping into center means all dock widgets in the dropped floating + // widget will become tabs of the drop area + if (CenterDockWidgetArea == area) { + moveIntoCenterOfSection(widget, targetArea); + return; + } + + DockWidget *droppedDockWidget = qobject_cast(widget); + DockAreaWidget *droppedDockArea = qobject_cast(widget); + DockAreaWidget *newDockArea; + if (droppedDockWidget) { + newDockArea = new DockAreaWidget(m_dockManager, q); + DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); + if (oldDockArea) { + oldDockArea->removeDockWidget(droppedDockWidget); + } + newDockArea->addDockWidget(droppedDockWidget); + } else { + droppedDockArea->dockContainer()->removeDockArea(droppedDockArea); + newDockArea = droppedDockArea; + } + + auto insertParam = internal::dockAreaInsertParameters(area); + QSplitter *targetAreaSplitter = internal::findParent(targetArea); + int areaIndex = targetAreaSplitter->indexOf(targetArea); + auto sizes = targetAreaSplitter->sizes(); + if (targetAreaSplitter->orientation() == insertParam.orientation()) { + int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) + ? targetArea->width() + : targetArea->height(); + targetAreaSplitter->insertWidget(areaIndex + insertParam.insertOffset(), newDockArea); + int size = (targetAreaSize - targetAreaSplitter->handleWidth()) / 2; + sizes[areaIndex] = size; + sizes.insert(areaIndex, size); + } else { + auto sizes = targetAreaSplitter->sizes(); + int targetAreaSize = (insertParam.orientation() == Qt::Horizontal) + ? targetArea->width() + : targetArea->height(); + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + newSplitter->addWidget(targetArea); + insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); + int size = targetAreaSize / 2; + newSplitter->setSizes({size, size}); + targetAreaSplitter->insertWidget(areaIndex, newSplitter); + } + targetAreaSplitter->setSizes(sizes); + + addDockAreasToList({newDockArea}); + } + + void DockContainerWidgetPrivate::moveToContainer(QWidget *widget, DockWidgetArea area) + { + DockWidget *droppedDockWidget = qobject_cast(widget); + DockAreaWidget *droppedDockArea = qobject_cast(widget); + DockAreaWidget *newDockArea; + + if (droppedDockWidget) { + newDockArea = new DockAreaWidget(m_dockManager, q); + DockAreaWidget *oldDockArea = droppedDockWidget->dockAreaWidget(); + if (oldDockArea) { + oldDockArea->removeDockWidget(droppedDockWidget); + } + newDockArea->addDockWidget(droppedDockWidget); + } else { + droppedDockArea->dockContainer()->removeDockArea(droppedDockArea); + newDockArea = droppedDockArea; + } + + addDockArea(newDockArea, area); + m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea; + } + + void DockContainerWidgetPrivate::addDockAreasToList(const QList newDockAreas) + { + int countBefore = m_dockAreas.count(); + int newAreaCount = newDockAreas.count(); + appendDockAreas(newDockAreas); + // If the user dropped a floating widget that contains only one single + // visible dock area, then its title bar button TitleBarButtonUndock is + // likely hidden. We need to ensure, that it is visible + for (auto dockArea : newDockAreas) { + dockArea->titleBarButton(TitleBarButtonUndock)->setVisible(true); + dockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); + } + + // We need to ensure, that the dock area title bar is visible. The title bar + // is invisible, if the dock are is a single dock area in a floating widget. + if (1 == countBefore) { + m_dockAreas.at(0)->updateTitleBarVisibility(); + } + + if (1 == newAreaCount) { + m_dockAreas.last()->updateTitleBarVisibility(); + } + + emitDockAreasAdded(); + } + + void DockContainerWidgetPrivate::appendDockAreas(const QList newDockAreas) + { + m_dockAreas.append(newDockAreas); + for (auto dockArea : newDockAreas) { + QObject::connect(dockArea, + &DockAreaWidget::viewToggled, + q, + std::bind(&DockContainerWidgetPrivate::onDockAreaViewToggled, + this, + std::placeholders::_1)); + } + } + + void DockContainerWidgetPrivate::saveChildNodesState(QXmlStreamWriter &stream, QWidget *widget) + { + QSplitter *splitter = qobject_cast(widget); + if (splitter) { + stream.writeStartElement("splitter"); + stream.writeAttribute("orientation", + QVariant::fromValue(splitter->orientation()).toString()); + stream.writeAttribute("count", QString::number(splitter->count())); + qCInfo(adsLog) << "NodeSplitter orient: " << splitter->orientation() + << " WidgetCont: " << splitter->count(); + for (int i = 0; i < splitter->count(); ++i) { + saveChildNodesState(stream, splitter->widget(i)); + } + + stream.writeStartElement("sizes"); + QStringList sizes; + for (auto size : splitter->sizes()) { + sizes.append(QString::number(size)); + } + stream.writeCharacters(sizes.join(" ")); + stream.writeEndElement(); + stream.writeEndElement(); + } else { + DockAreaWidget *dockArea = qobject_cast(widget); + if (dockArea) { + dockArea->saveState(stream); + } + } + } + + bool DockContainerWidgetPrivate::restoreSplitter(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) + { + QVariant orientationVar = QVariant(stateReader.attributes().value("orientation").toString()); + + // Check if the orientation string is convertable + if (!orientationVar.canConvert()) { + return false; + } + Qt::Orientation orientation = orientationVar.value(); + + bool ok; + int widgetCount = stateReader.attributes().value("count").toInt(&ok); + if (!ok) { + return false; + } + qCInfo(adsLog) << "Restore NodeSplitter Orientation: " << orientation + << " WidgetCount: " << widgetCount; + QSplitter *splitter = nullptr; + if (!testing) { + splitter = createSplitter(orientation); + } + bool visible = false; + QList sizes; + while (stateReader.readNextStartElement()) { + QWidget *childNode = nullptr; + bool result = true; + if (stateReader.name() == "splitter") { + result = restoreSplitter(stateReader, childNode, testing); + } else if (stateReader.name() == "area") { + result = restoreDockArea(stateReader, childNode, testing); + } else if (stateReader.name() == "sizes") { + QString size = stateReader.readElementText().trimmed(); + qCInfo(adsLog) << "Size: " << size; + QTextStream textStream(&size); + while (!textStream.atEnd()) { + int value; + textStream >> value; + sizes.append(value); + } + } else { + stateReader.skipCurrentElement(); + } + + if (!result) { + return false; + } + + if (testing || !childNode) { + continue; + } + + qCInfo(adsLog) << "ChildNode isVisible " << childNode->isVisible() << " isVisibleTo " + << childNode->isVisibleTo(splitter); + splitter->addWidget(childNode); + visible |= childNode->isVisibleTo(splitter); + } + + if (sizes.count() != widgetCount) { + return false; + } + + if (!testing) { + if (!splitter->count()) { + delete splitter; + splitter = nullptr; + } else { + splitter->setSizes(sizes); + splitter->setVisible(visible); + } + createdWidget = splitter; + } else { + createdWidget = nullptr; + } + + return true; + } + + bool DockContainerWidgetPrivate::restoreDockArea(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) + { + QString currentDockWidget = stateReader.attributes().value("current").toString(); + +#ifdef ADS_DEBUG_PRINT + bool ok; + int tabs = stateReader.attributes().value("tabs").toInt(&ok); + if (!ok) { + return false; + } + qCInfo(adsLog) << "Restore NodeDockArea Tabs: " << tabs + << " Current: " << currentDockWidget; +#endif + + DockAreaWidget *dockArea = nullptr; + if (!testing) { + dockArea = new DockAreaWidget(m_dockManager, q); + } + + while (stateReader.readNextStartElement()) { + if (stateReader.name() != "widget") { + continue; + } + + auto objectName = stateReader.attributes().value("name"); + if (objectName.isEmpty()) { + qCInfo(adsLog) << "Error: Empty name!"; + return false; + } + + QVariant closedVar = QVariant(stateReader.attributes().value("closed").toString()); + if (!closedVar.canConvert()) { + return false; + } + bool closed = closedVar.value(); + + stateReader.skipCurrentElement(); + DockWidget *dockWidget = m_dockManager->findDockWidget(objectName.toString()); + if (!dockWidget || testing) { + continue; + } + + qCInfo(adsLog) << "Dock Widget found - parent " << dockWidget->parent(); + // We hide the DockArea here to prevent the short display (the flashing) + // of the dock areas during application startup + dockArea->hide(); + dockArea->addDockWidget(dockWidget); + dockWidget->setToggleViewActionChecked(!closed); + dockWidget->setClosedState(closed); + dockWidget->setProperty(internal::closedProperty, closed); + dockWidget->setProperty(internal::dirtyProperty, false); + } + + if (testing) { + return true; + } + + if (!dockArea->dockWidgetsCount()) { + delete dockArea; + dockArea = nullptr; + } else { + dockArea->setProperty("currentDockWidget", currentDockWidget); + appendDockAreas({dockArea}); + } + + createdWidget = dockArea; + return true; + } + + bool DockContainerWidgetPrivate::restoreChildNodes(DockingStateReader &stateReader, + QWidget *&createdWidget, + bool testing) + { + bool result = true; + while (stateReader.readNextStartElement()) { + if (stateReader.name() == "splitter") { + result = restoreSplitter(stateReader, createdWidget, testing); + qCInfo(adsLog) << "Splitter"; + } else if (stateReader.name() == "area") { + result = restoreDockArea(stateReader, createdWidget, testing); + qCInfo(adsLog) << "DockAreaWidget"; + } else { + stateReader.skipCurrentElement(); + qCInfo(adsLog) << "Unknown element" << stateReader.name(); + } + } + + return result; + } + + DockAreaWidget *DockContainerWidgetPrivate::dockWidgetIntoContainer(DockWidgetArea area, + DockWidget *dockWidget) + { + DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q); + newDockArea->addDockWidget(dockWidget); + addDockArea(newDockArea, area); + newDockArea->updateTitleBarVisibility(); + m_lastAddedAreaCache[areaIdToIndex(area)] = newDockArea; + return newDockArea; + } + + void DockContainerWidgetPrivate::addDockArea(DockAreaWidget *newDockArea, DockWidgetArea area) + { + auto insertParam = internal::dockAreaInsertParameters(area); + // As long as we have only one dock area in the splitter we can adjust its orientation + if (m_dockAreas.count() <= 1) { + m_rootSplitter->setOrientation(insertParam.orientation()); + } + + QSplitter *splitter = m_rootSplitter; + if (splitter->orientation() == insertParam.orientation()) { + insertWidgetIntoSplitter(splitter, newDockArea, insertParam.append()); + } else { + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + if (insertParam.append()) { + QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); + newSplitter->addWidget(splitter); + newSplitter->addWidget(newDockArea); + delete layoutItem; + } else { + newSplitter->addWidget(newDockArea); + QLayoutItem *layoutItem = m_layout->replaceWidget(splitter, newSplitter); + newSplitter->addWidget(splitter); + delete layoutItem; + } + m_rootSplitter = newSplitter; + } + + addDockAreasToList({newDockArea}); + } + + void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget *widget) const + { +#if defined(QT_DEBUG) + QSplitter *splitter = qobject_cast(widget); + QByteArray buf; + buf.fill(' ', level * 4); + if (splitter) { +#ifdef ADS_DEBUG_PRINT + qDebug("%sSplitter %s v: %s c: %s", + buf.data(), + (splitter->orientation() == Qt::Vertical) ? "--" : "|", + splitter->isHidden() ? " " : "v", + QString::number(splitter->count()).toStdString().c_str()); + std::cout << buf.data() << "Splitter " + << ((splitter->orientation() == Qt::Vertical) ? "--" : "|") << " " + << (splitter->isHidden() ? " " : "v") << " " + << QString::number(splitter->count()).toStdString() << std::endl; +#endif + for (int i = 0; i < splitter->count(); ++i) { + dumpRecursive(level + 1, splitter->widget(i)); + } + } else { + DockAreaWidget *dockArea = qobject_cast(widget); + if (!dockArea) { + return; + } +#ifdef ADS_DEBUG_PRINT + qDebug("%sDockArea", buf.data()); + std::cout << buf.data() << (dockArea->isHidden() ? " " : "v") + << (dockArea->openDockWidgetsCount() > 0 ? " " : "c") << " DockArea" + << std::endl; + buf.fill(' ', (level + 1) * 4); + for (int i = 0; i < dockArea->dockWidgetsCount(); ++i) { + std::cout << buf.data() << (i == dockArea->currentIndex() ? "*" : " "); + DockWidget *dockWidget = dockArea->dockWidget(i); + std::cout << (dockWidget->isHidden() ? " " : "v"); + std::cout << (dockWidget->isClosed() ? "c" : " ") << " "; + std::cout << dockWidget->windowTitle().toStdString() << std::endl; + } +#endif + } +#else + Q_UNUSED(level) + Q_UNUSED(widget) +#endif + } + + DockAreaWidget *DockContainerWidgetPrivate::dockWidgetIntoDockArea(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget + *targetDockArea) + { + if (CenterDockWidgetArea == area) { + targetDockArea->addDockWidget(dockWidget); + targetDockArea->updateTitleBarVisibility(); + return targetDockArea; + } + + DockAreaWidget *newDockArea = new DockAreaWidget(m_dockManager, q); + newDockArea->addDockWidget(dockWidget); + auto insertParam = internal::dockAreaInsertParameters(area); + + QSplitter *targetAreaSplitter = internal::findParent(targetDockArea); + int index = targetAreaSplitter->indexOf(targetDockArea); + if (targetAreaSplitter->orientation() == insertParam.orientation()) { + qCInfo(adsLog) << "TargetAreaSplitter->orientation() == InsertParam.orientation()"; + targetAreaSplitter->insertWidget(index + insertParam.insertOffset(), newDockArea); + } else { + qCInfo(adsLog) << "TargetAreaSplitter->orientation() != InsertParam.orientation()"; + QSplitter *newSplitter = createSplitter(insertParam.orientation()); + newSplitter->addWidget(targetDockArea); + insertWidgetIntoSplitter(newSplitter, newDockArea, insertParam.append()); + targetAreaSplitter->insertWidget(index, newSplitter); + } + + appendDockAreas({newDockArea}); + emitDockAreasAdded(); + return newDockArea; + } + + DockContainerWidget::DockContainerWidget(DockManager *dockManager, QWidget *parent) + : QFrame(parent) + , d(new DockContainerWidgetPrivate(this)) + { + d->m_dockManager = dockManager; + d->m_isFloating = floatingWidget() != nullptr; + + d->m_layout = new QGridLayout(); + d->m_layout->setContentsMargins(0, 1, 0, 1); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + + // The function d->createSplitter() accesses the config flags from dock + // manager which in turn requires a properly constructed dock manager. + // If this dock container is the dock manager, then it is not properly + // constructed yet because this base class constructor is called before + // the constructor of the DockManager private class + if (dockManager != this) { + d->m_dockManager->registerDockContainer(this); + createRootSplitter(); + } + } + + DockContainerWidget::~DockContainerWidget() + { + if (d->m_dockManager) { + d->m_dockManager->removeDockContainer(this); + } + delete d; + } + + DockAreaWidget *DockContainerWidget::addDockWidget(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *dockAreaWidget) + { + DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); + if (oldDockArea) { + oldDockArea->removeDockWidget(dockWidget); + } + + dockWidget->setDockManager(d->m_dockManager); + if (dockAreaWidget) { + return d->dockWidgetIntoDockArea(area, dockWidget, dockAreaWidget); + } else { + return d->dockWidgetIntoContainer(area, dockWidget); + } + } + + void DockContainerWidget::removeDockWidget(DockWidget * dockWidget) + { + DockAreaWidget *area = dockWidget->dockAreaWidget(); + if (area) { + area->removeDockWidget(dockWidget); + } + } + + unsigned int DockContainerWidget::zOrderIndex() const { return d->m_zOrderIndex; } + + bool DockContainerWidget::isInFrontOf(DockContainerWidget *other) const + { + return this->zOrderIndex() > other->zOrderIndex(); + } + + bool DockContainerWidget::event(QEvent *event) + { + bool result = QWidget::event(event); + if (event->type() == QEvent::WindowActivate) { + d->m_zOrderIndex = ++zOrderCounter; + } else if (event->type() == QEvent::Show && !d->m_zOrderIndex) { + d->m_zOrderIndex = ++zOrderCounter; + } + + return result; + } + + void DockContainerWidget::addDockArea(DockAreaWidget *dockAreaWidget, DockWidgetArea area) + { + DockContainerWidget *container = dockAreaWidget->dockContainer(); + if (container && container != this) { + container->removeDockArea(dockAreaWidget); + } + + d->addDockArea(dockAreaWidget, area); + } + + void DockContainerWidget::removeDockArea(DockAreaWidget *area) + { + qCInfo(adsLog) << Q_FUNC_INFO; + area->disconnect(this); + d->m_dockAreas.removeAll(area); + DockSplitter *splitter = internal::findParent(area); + + // Remove area from parent splitter and recursively hide tree of parent + // splitters if it has no visible content + area->setParent(nullptr); + internal::hideEmptyParentSplitters(splitter); + + // Remove this area from cached areas + const auto &cache = d->m_lastAddedAreaCache; + if (auto p = std::find(cache, cache + sizeof(cache) / sizeof(cache[0]), area)) { + d->m_lastAddedAreaCache[std::distance(cache, p)] = nullptr; + } + + // If splitter has more than 1 widgets, we are finished and can leave + if (splitter->count() > 1) { + emitAndExit(); + return; + } + + // If this is the RootSplitter we need to remove empty splitters to + // avoid too many empty splitters + if (splitter == d->m_rootSplitter) { + qCInfo(adsLog) << "Removed from RootSplitter"; + // If splitter is empty, we are finished + if (!splitter->count()) { + splitter->hide(); + emitAndExit(); + return; + } + + QWidget *widget = splitter->widget(0); + QSplitter *childSplitter = qobject_cast(widget); + // If the one and only content widget of the splitter is not a splitter + // then we are finished + if (!childSplitter) { + emitAndExit(); + return; + } + + // We replace the superfluous RootSplitter with the ChildSplitter + childSplitter->setParent(nullptr); + QLayoutItem *layoutItem = d->m_layout->replaceWidget(splitter, childSplitter); + d->m_rootSplitter = childSplitter; + delete layoutItem; + qCInfo(adsLog) << "RootSplitter replaced by child splitter"; + } else if (splitter->count() == 1) { + qCInfo(adsLog) << "Replacing splitter with content"; + QSplitter *parentSplitter = internal::findParent(splitter); + auto sizes = parentSplitter->sizes(); + QWidget *widget = splitter->widget(0); + widget->setParent(this); + internal::replaceSplitterWidget(parentSplitter, splitter, widget); + parentSplitter->setSizes(sizes); + } + + delete splitter; + } + + void DockContainerWidget::emitAndExit() const + { + DockWidget *topLevelWidget = topLevelDockWidget(); + + // Updated the title bar visibility of the dock widget if there is only + // one single visible dock widget + DockWidget::emitTopLevelEventForWidget(topLevelWidget, true); + dumpLayout(); + d->emitDockAreasRemoved(); + } + + DockAreaWidget *DockContainerWidget::dockAreaAt(const QPoint &globalPosition) const + { + for (auto dockArea : d->m_dockAreas) { + if (dockArea->isVisible() + && dockArea->rect().contains(dockArea->mapFromGlobal(globalPosition))) { + return dockArea; + } + } + + return nullptr; + } + + DockAreaWidget *DockContainerWidget::dockArea(int index) const + { + return (index < dockAreaCount()) ? d->m_dockAreas[index] : nullptr; + } + + bool DockContainerWidget::isFloating() const { return d->m_isFloating; } + + int DockContainerWidget::dockAreaCount() const { return d->m_dockAreas.count(); } + + int DockContainerWidget::visibleDockAreaCount() const + { + int result = 0; + for (auto dockArea : d->m_dockAreas) { + result += dockArea->isHidden() ? 0 : 1; + } + + return result; + + // TODO Cache or precalculate this to speed it up because it is used during + // movement of floating widget + //return d->visibleDockAreaCount(); + } + + void DockContainerWidget::dropFloatingWidget(FloatingDockContainer *floatingWidget, + const QPoint &targetPosition) + { + qCInfo(adsLog) << Q_FUNC_INFO; + DockWidget *singleDroppedDockWidget = floatingWidget->topLevelDockWidget(); + DockWidget *singleDockWidget = topLevelDockWidget(); + DockAreaWidget *dockArea = dockAreaAt(targetPosition); + auto dropArea = InvalidDockWidgetArea; + auto containerDropArea = d->m_dockManager->containerOverlay()->dropAreaUnderCursor(); + bool dropped = false; + + if (dockArea) { + auto dropOverlay = d->m_dockManager->dockAreaOverlay(); + dropOverlay->setAllowedAreas(dockArea->allowedAreas()); + dropArea = dropOverlay->showOverlay(dockArea); + if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) { + dropArea = InvalidDockWidgetArea; + } + + if (dropArea != InvalidDockWidgetArea) { + qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; + d->dropIntoSection(floatingWidget, dockArea, dropArea); + dropped = true; + } + } + + // mouse is over container + if (InvalidDockWidgetArea == dropArea) { + dropArea = containerDropArea; + qCInfo(adsLog) << "Container Drop Content: " << dropArea; + if (dropArea != InvalidDockWidgetArea) { + d->dropIntoContainer(floatingWidget, dropArea); + dropped = true; + } + } + + if (dropped) { + floatingWidget->deleteLater(); + + // If we dropped a floating widget with only one single dock widget, then we + // drop a top level widget that changes from floating to docked now + DockWidget::emitTopLevelEventForWidget(singleDroppedDockWidget, false); + + // If there was a top level widget before the drop, then it is not top + // level widget anymore + DockWidget::emitTopLevelEventForWidget(singleDockWidget, false); + } + } + + void DockContainerWidget::dropWidget(QWidget *widget, const QPoint &targetPosition) + { + qCInfo(adsLog) << Q_FUNC_INFO; + DockWidget *singleDockWidget = topLevelDockWidget(); + DockAreaWidget *dockArea = dockAreaAt(targetPosition); + auto dropArea = InvalidDockWidgetArea; + auto containerDropArea = d->m_dockManager->containerOverlay()->dropAreaUnderCursor(); + + if (dockArea) { + auto dropOverlay = d->m_dockManager->dockAreaOverlay(); + dropOverlay->setAllowedAreas(dockArea->allowedAreas()); + dropArea = dropOverlay->showOverlay(dockArea); + if (containerDropArea != InvalidDockWidgetArea && containerDropArea != dropArea) { + dropArea = InvalidDockWidgetArea; + } + + if (dropArea != InvalidDockWidgetArea) { + qCInfo(adsLog) << "Dock Area Drop Content: " << dropArea; + d->moveToNewSection(widget, dockArea, dropArea); + } + } + + // mouse is over container + if (InvalidDockWidgetArea == dropArea) { + dropArea = containerDropArea; + qCInfo(adsLog) << "Container Drop Content: " << dropArea; + if (dropArea != InvalidDockWidgetArea) { + d->moveToContainer(widget, dropArea); + } + } + + // If there was a top level widget before the drop, then it is not top + // level widget anymore + DockWidget::emitTopLevelEventForWidget(singleDockWidget, false); + } + + QList DockContainerWidget::openedDockAreas() const + { + QList result; + for (auto dockArea : d->m_dockAreas) { + if (!dockArea->isHidden()) { + result.append(dockArea); + } + } + + return result; + } + + void DockContainerWidget::saveState(QXmlStreamWriter &stream) const + { + qCInfo(adsLog) << Q_FUNC_INFO << "isFloating " << isFloating(); + + stream.writeStartElement("container"); + stream.writeAttribute("floating", QVariant::fromValue(isFloating()).toString()); + if (isFloating()) { + FloatingDockContainer *floatingDockContainer = floatingWidget(); + QByteArray geometry = floatingDockContainer->saveGeometry(); + stream.writeTextElement("geometry", QString::fromLatin1(geometry.toBase64())); + } + d->saveChildNodesState(stream, d->m_rootSplitter); + stream.writeEndElement(); + } + + bool DockContainerWidget::restoreState(DockingStateReader &stateReader, bool testing) + { + QVariant floatingVar = QVariant(stateReader.attributes().value("floating").toString()); + if (!floatingVar.canConvert()) { + return false; + } + bool isFloating = floatingVar.value(); + qCInfo(adsLog) << "Restore DockContainerWidget Floating" << isFloating; + + QWidget *newRootSplitter{}; + if (!testing) { + d->m_visibleDockAreaCount = -1; // invalidate the dock area count + d->m_dockAreas.clear(); + std::fill(std::begin(d->m_lastAddedAreaCache), + std::end(d->m_lastAddedAreaCache), + nullptr); + } + + if (isFloating) { + qCInfo(adsLog) << "Restore floating widget"; + if (!stateReader.readNextStartElement() || stateReader.name() != "geometry") { + return false; + } + + QByteArray geometryString = stateReader + .readElementText( + DockingStateReader::ErrorOnUnexpectedElement) + .toLocal8Bit(); + QByteArray geometry = QByteArray::fromBase64(geometryString); + if (geometry.isEmpty()) { + return false; + } + + if (!testing) { + FloatingDockContainer *floatingDockContainer = floatingWidget(); + floatingDockContainer->restoreGeometry(geometry); + } + } + + if (!d->restoreChildNodes(stateReader, newRootSplitter, testing)) { + return false; + } + + if (testing) { + return true; + } + + // If the root splitter is empty, rostoreChildNodes returns a 0 pointer + // and we need to create a new empty root splitter + if (!newRootSplitter) { + newRootSplitter = d->createSplitter(Qt::Horizontal); + } + + d->m_layout->replaceWidget(d->m_rootSplitter, newRootSplitter); + QSplitter *oldRoot = d->m_rootSplitter; + d->m_rootSplitter = qobject_cast(newRootSplitter); + oldRoot->deleteLater(); + + return true; + } + + QSplitter *DockContainerWidget::rootSplitter() const { return d->m_rootSplitter; } + + void DockContainerWidget::createRootSplitter() + { + if (d->m_rootSplitter) { + return; + } + d->m_rootSplitter = d->createSplitter(Qt::Horizontal); + d->m_layout->addWidget(d->m_rootSplitter); + } + + void DockContainerWidget::dumpLayout() const + { +#if (ADS_DEBUG_LEVEL > 0) + qDebug("\n\nDumping layout --------------------------"); + std::cout << "\n\nDumping layout --------------------------" << std::endl; + d->dumpRecursive(0, d->m_rootSplitter); + qDebug("--------------------------\n\n"); + std::cout << "--------------------------\n\n" << std::endl; +#endif + } + + DockAreaWidget *DockContainerWidget::lastAddedDockAreaWidget(DockWidgetArea area) const + { + return d->m_lastAddedAreaCache[areaIdToIndex(area)]; + } + + bool DockContainerWidget::hasTopLevelDockWidget() const + { + if (!isFloating()) { + return false; + } + + auto dockAreas = openedDockAreas(); + if (dockAreas.count() != 1) { + return false; + } + + return dockAreas[0]->openDockWidgetsCount() == 1; + } + + DockWidget *DockContainerWidget::topLevelDockWidget() const + { + auto dockArea = topLevelDockArea(); + if (!dockArea) { + return nullptr; + } + + auto dockWidgets = dockArea->openedDockWidgets(); + if (dockWidgets.count() != 1) { + return nullptr; + } + + return dockWidgets[0]; + } + + DockAreaWidget *DockContainerWidget::topLevelDockArea() const + { + if (!isFloating()) { + return nullptr; + } + + auto dockAreas = openedDockAreas(); + if (dockAreas.count() != 1) { + return nullptr; + } + + return dockAreas[0]; + } + + QList DockContainerWidget::dockWidgets() const + { + QList result; + for (const auto dockArea : d->m_dockAreas) { + result.append(dockArea->dockWidgets()); + } + + return result; + } + + DockWidget::DockWidgetFeatures DockContainerWidget::features() const + { + DockWidget::DockWidgetFeatures features(DockWidget::AllDockWidgetFeatures); + for (const auto dockArea : d->m_dockAreas) { + features &= dockArea->features(); + } + + return features; + } + + FloatingDockContainer *DockContainerWidget::floatingWidget() const + { + return internal::findParent(this); + } + + void DockContainerWidget::closeOtherAreas(DockAreaWidget *keepOpenArea) + { + for (const auto dockArea : d->m_dockAreas) { + if (dockArea == keepOpenArea) { + continue; + } + + if (!dockArea->features(BitwiseAnd).testFlag(DockWidget::DockWidgetClosable)) { + continue; + } + + // We do not close areas with widgets with custom close handling + if (dockArea->features(BitwiseOr).testFlag(DockWidget::CustomCloseHandling)) { + continue; + } + + dockArea->closeArea(); + } + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.h b/src/libs/advanceddockingsystem/dockcontainerwidget.h new file mode 100644 index 00000000000..49e37d375ee --- /dev/null +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.h @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" +#include "dockwidget.h" + +#include + +class QXmlStreamWriter; + +namespace ADS { + +class DockContainerWidgetPrivate; +class DockAreaWidget; +class DockWidget; +class DockManager; +struct DockManagerPrivate; +class FloatingDockContainer; +struct FloatingDockContainerPrivate; +class FloatingDragPreview; +struct FloatingDragPreviewPrivate; +class DockingStateReader; + +/** + * Container that manages a number of dock areas with single dock widgets + * or tabyfied dock widgets in each area. + * Each window that support docking has a DockContainerWidget. That means + * the main application window and all floating windows contain + * a DockContainerWidget. + */ +class ADS_EXPORT DockContainerWidget : public QFrame +{ + Q_OBJECT +private: + DockContainerWidgetPrivate *d; ///< private data (pimpl) + friend class DockContainerWidgetPrivate; + friend class DockManager; + friend struct DockManagerPrivate; + friend class DockAreaWidget; + friend struct DockAreaWidgetPrivate; + friend class FloatingDockContainer; + friend struct FloatingDockContainerPrivate; + friend class DockWidget; + friend class FloatingDragPreview; + friend struct FloatingDragPreviewPrivate; + +protected: + /** + * Handles activation events to update zOrderIndex + */ + virtual bool event(QEvent *event) override; + +public: // TODO temporary + /** + * Access function for the internal root splitter + */ + QSplitter *rootSplitter() const; + +protected: + /** + * Helper function for creation of the root splitter + */ + void createRootSplitter(); + + /** + * Drop floating widget into the container + */ + void dropFloatingWidget(FloatingDockContainer *floatingWidget, const QPoint &targetPos); + + /** + * Drop a dock area or a dock widget given in widget parameter + */ + void dropWidget(QWidget *widget, const QPoint &targetPos); + + /** + * Adds the given dock area to this container widget + */ + void addDockArea(DockAreaWidget *dockAreaWidget, DockWidgetArea area = CenterDockWidgetArea); + + /** + * Removes the given dock area from this container + */ + void removeDockArea(DockAreaWidget *area); + + /** + * This function replaces the goto construct. Still need to write a good description. + */ + void emitAndExit() const; // TODO rename + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter &stream) const; + + /** + * Restores the state from given stream. + * If Testing is true, the function only parses the data from the given + * stream but does not restore anything. You can use this check for + * faulty files before you start restoring the state + */ + bool restoreState(DockingStateReader &stream, bool testing); + + /** + * This function returns the last added dock area widget for the given + * area identifier or 0 if no dock area widget has been added for the given + * area + */ + DockAreaWidget *lastAddedDockAreaWidget(DockWidgetArea area) const; + + /** + * If hasSingleVisibleDockWidget() returns true, this function returns the + * one and only visible dock widget. Otherwise it returns a nullptr. + */ + DockWidget *topLevelDockWidget() const; + + /** + * Returns the top level dock area. + */ + DockAreaWidget *topLevelDockArea() const; + + /** + * This function returns a list of all dock widgets in this floating widget. + * It may be possible, depending on the implementation, that dock widgets, + * that are not visible to the user have no parent widget. Therefore simply + * calling findChildren() would not work here. Therefore this function + * iterates over all dock areas and creates a list that contains all + * dock widgets returned from all dock areas. + */ + QList dockWidgets() const; + +public: + /** + * Default Constructor + */ + DockContainerWidget(DockManager *dockManager, QWidget *parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~DockContainerWidget() override; + + /** + * Adds dockwidget into the given area. + * If DockAreaWidget is not null, then the area parameter indicates the area + * into the DockAreaWidget. If DockAreaWidget is null, the Dockwidget will + * be dropped into the container. + * \return Returns the dock area widget that contains the new DockWidget + */ + DockAreaWidget *addDockWidget(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *dockAreaWidget = nullptr); + + /** + * Removes dockwidget + */ + void removeDockWidget(DockWidget *dockWidget); + + /** + * Returns the current zOrderIndex + */ + virtual unsigned int zOrderIndex() const; + + /** + * This function returns true if this container widgets z order index is + * higher than the index of the container widget given in Other parameter + */ + bool isInFrontOf(DockContainerWidget *other) const; + + /** + * Returns the dock area at the given global position or 0 if there is no + * dock area at this position + */ + DockAreaWidget *dockAreaAt(const QPoint &globalPos) const; + + /** + * Returns the dock area at the given Index or 0 if the index is out of + * range + */ + DockAreaWidget *dockArea(int index) const; + + /** + * Returns the list of dock areas that are not closed + * If all dock widgets in a dock area are closed, the dock area will be closed + */ + QList openedDockAreas() const; + + /** + * This function returns true if this dock area has only one single + * visible dock widget. + * A top level widget is a real floating widget. Only the isFloating() + * function of top level widgets may returns true. + */ + bool hasTopLevelDockWidget() const; + + /** + * Returns the number of dock areas in this container + */ + int dockAreaCount() const; + + /** + * Returns the number of visible dock areas + */ + int visibleDockAreaCount() const; + + /** + * This function returns true, if this container is in a floating widget + */ + bool isFloating() const; + + /** + * Dumps the layout for debugging purposes + */ + void dumpLayout() const; + + /** + * This functions returns the dock widget features of all dock widget in + * this container. + * A bitwise and is used to combine the flags of all dock widgets. That + * means, if only dock widget does not support a certain flag, the whole + * dock are does not support the flag. + */ + DockWidget::DockWidgetFeatures features() const; + + /** + * If this dock container is in a floating widget, this function returns + * the floating widget. + * Else, it returns a nullptr. + */ + FloatingDockContainer *floatingWidget() const; + + /** + * Call this function to close all dock areas except the KeepOpenArea + */ + void closeOtherAreas(DockAreaWidget *keepOpenArea); + +signals: + /** + * This signal is emitted if one or multiple dock areas has been added to + * the internal list of dock areas. + * If multiple dock areas are inserted, this signal is emitted only once + */ + void dockAreasAdded(); + + /** + * This signal is emitted if one or multiple dock areas has been removed + */ + void dockAreasRemoved(); + + /** + * This signal is emitted if a dock area is opened or closed via + * toggleView() function + */ + void dockAreaViewToggled(DockAreaWidget *dockArea, bool open); +}; // class DockContainerWidget + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockingstatereader.cpp b/src/libs/advanceddockingsystem/dockingstatereader.cpp new file mode 100644 index 00000000000..3802a608502 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockingstatereader.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dockingstatereader.h" + +namespace ADS { + +void DockingStateReader::setFileVersion(int fileVersion) +{ + m_fileVersion = fileVersion; +} + +int DockingStateReader::fileVersion() const +{ + return m_fileVersion; +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockingstatereader.h b/src/libs/advanceddockingsystem/dockingstatereader.h new file mode 100644 index 00000000000..00141496edb --- /dev/null +++ b/src/libs/advanceddockingsystem/dockingstatereader.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace ADS { + +/** + * Extends QXmlStreamReader with file version information + */ +class DockingStateReader : public QXmlStreamReader +{ +private: + int m_fileVersion; + +public: + using QXmlStreamReader::QXmlStreamReader; + + /** + * Set the file version for this state reader + */ + void setFileVersion(int fileVersion); + + /** + * Returns the file version set via setFileVersion + */ + int fileVersion() const; +}; + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockmanager.cpp b/src/libs/advanceddockingsystem/dockmanager.cpp new file mode 100644 index 00000000000..18d9e45e0fb --- /dev/null +++ b/src/libs/advanceddockingsystem/dockmanager.cpp @@ -0,0 +1,819 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dockmanager.h" + +#include "ads_globals.h" +#include "dockareawidget.h" +#include "dockingstatereader.h" +#include "dockoverlay.h" +#include "dockwidget.h" +#include "dockwidgettab.h" +#include "floatingdockcontainer.h" +#include "iconprovider.h" + +#include "workspacedialog.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg) + +namespace ADS +{ + static DockManager::ConfigFlags g_staticConfigFlags = DockManager::DefaultNonOpaqueConfig; + + /** + * Private data class of DockManager class (pimpl) + */ + struct DockManagerPrivate + { + DockManager *q; + QList m_floatingWidgets; + QList m_containers; + DockOverlay *m_containerOverlay; + DockOverlay *m_dockAreaOverlay; + QMap m_dockWidgetsMap; + bool m_restoringState = false; + QVector m_uninitializedFloatingWidgets; + + QString m_workspaceName; + bool m_workspaceListDirty = true; + QStringList m_workspaces; + QHash m_workspaceDateTimes; + QString m_workspaceToRestoreAtStartup; + bool m_autorestoreLastWorkspace; // This option is set in the Workspace Manager! + QSettings *m_settings; + + /** + * Private data constructor + */ + DockManagerPrivate(DockManager *parent); + + /** + * Checks if the given data stream is a valid docking system state + * file. + */ + bool checkFormat(const QByteArray &state, int version); + + /** + * Restores the state + */ + bool restoreStateFromXml(const QByteArray &state, + int version, + bool testing = internal::restore); + + /** + * Restore state + */ + bool restoreState(const QByteArray &state, int version); + + void restoreDockWidgetsOpenState(); + void restoreDockAreasIndices(); + void emitTopLevelEvents(); + + void hideFloatingWidgets() + { + // Hide updates of floating widgets from user + for (auto floatingWidget : m_floatingWidgets) { // TODO qAsConst() + floatingWidget->hide(); + } + } + + void markDockWidgetsDirty() + { + for (auto dockWidget : m_dockWidgetsMap) { // TODO qAsConst() + dockWidget->setProperty("dirty", true); + } + } + + /** + * Restores the container with the given index + */ + bool restoreContainer(int index, DockingStateReader &stream, bool testing); + + void workspaceLoadingProgress(); + }; + // struct DockManagerPrivate + + DockManagerPrivate::DockManagerPrivate(DockManager *parent) + : q(parent) + {} + + bool DockManagerPrivate::restoreContainer(int index, DockingStateReader &stream, bool testing) + { + if (testing) { + index = 0; + } + + bool result = false; + if (index >= m_containers.count()) { + FloatingDockContainer *floatingWidget = new FloatingDockContainer(q); + result = floatingWidget->restoreState(stream, testing); + } else { + qCInfo(adsLog) << "d->m_containers[i]->restoreState "; + auto container = m_containers[index]; + if (container->isFloating()) { + result = container->floatingWidget()->restoreState(stream, testing); + } else { + result = container->restoreState(stream, testing); + } + } + + return result; + } + + bool DockManagerPrivate::checkFormat(const QByteArray &state, int version) + { + return restoreStateFromXml(state, version, internal::restoreTesting); + } + + bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int version, bool testing) + { + Q_UNUSED(version) // TODO version is not needed, why is it in here in the first place? + + if (state.isEmpty()) { + return false; + } + DockingStateReader stateReader(state); + stateReader.readNextStartElement(); + if (stateReader.name() != "QtAdvancedDockingSystem") { + return false; + } + qCInfo(adsLog) << stateReader.attributes().value("version"); + bool ok; + int v = stateReader.attributes().value("version").toInt(&ok); + if (!ok || v > CurrentVersion) { + return false; + } + + stateReader.setFileVersion(v); + bool result = true; +#ifdef ADS_DEBUG_PRINT + int dockContainers = stateReader.attributes().value("containers").toInt(); + qCInfo(adsLog) << dockContainers; +#endif + int dockContainerCount = 0; + while (stateReader.readNextStartElement()) { + if (stateReader.name() == "container") { + result = restoreContainer(dockContainerCount, stateReader, testing); + if (!result) { + break; + } + dockContainerCount++; + } + } + + if (!testing) { + // Delete remaining empty floating widgets + int floatingWidgetIndex = dockContainerCount - 1; + int deleteCount = m_floatingWidgets.count() - floatingWidgetIndex; + for (int i = 0; i < deleteCount; ++i) { + m_floatingWidgets[floatingWidgetIndex + i]->deleteLater(); + q->removeDockContainer(m_floatingWidgets[floatingWidgetIndex + i]->dockContainer()); + } + } + + return result; + } + + void DockManagerPrivate::restoreDockWidgetsOpenState() + { + // All dock widgets, that have not been processed in the restore state + // function are invisible to the user now and have no assigned dock area + // They do not belong to any dock container, until the user toggles the + // toggle view action the next time + for (auto dockWidget : m_dockWidgetsMap) { + if (dockWidget->property(internal::dirtyProperty).toBool()) { + dockWidget->flagAsUnassigned(); + emit dockWidget->viewToggled(false); + } else { + dockWidget->toggleViewInternal( + !dockWidget->property(internal::closedProperty).toBool()); + } + } + } + + void DockManagerPrivate::restoreDockAreasIndices() + { + // Now all dock areas are properly restored and we setup the index of + // The dock areas because the previous toggleView() action has changed + // the dock area index + int count = 0; + for (auto dockContainer : m_containers) { + count++; + for (int i = 0; i < dockContainer->dockAreaCount(); ++i) { + DockAreaWidget *dockArea = dockContainer->dockArea(i); + QString dockWidgetName = dockArea->property("currentDockWidget").toString(); + DockWidget *dockWidget = nullptr; + if (!dockWidgetName.isEmpty()) { + dockWidget = q->findDockWidget(dockWidgetName); + } + + if (!dockWidget || dockWidget->isClosed()) { + int index = dockArea->indexOfFirstOpenDockWidget(); + if (index < 0) { + continue; + } + dockArea->setCurrentIndex(index); + } else { + dockArea->internalSetCurrentDockWidget(dockWidget); + } + } + } + } + + void DockManagerPrivate::emitTopLevelEvents() + { + // Finally we need to send the topLevelChanged() signals for all dock + // widgets if top level changed + for (auto dockContainer : m_containers) { + DockWidget *topLevelDockWidget = dockContainer->topLevelDockWidget(); + if (topLevelDockWidget) { + topLevelDockWidget->emitTopLevelChanged(true); + } else { + for (int i = 0; i < dockContainer->dockAreaCount(); ++i) { + auto dockArea = dockContainer->dockArea(i); + for (auto dockWidget : dockArea->dockWidgets()) { + dockWidget->emitTopLevelChanged(false); + } + } + } + } + } + + bool DockManagerPrivate::restoreState(const QByteArray &state, int version) + { + QByteArray currentState = state.startsWith("m_workspaceListDirty = true; + }); + + createRootSplitter(); + QMainWindow *mainWindow = qobject_cast(parent); + if (mainWindow) { + mainWindow->setCentralWidget(this); + } + + d->m_dockAreaOverlay = new DockOverlay(this, DockOverlay::ModeDockAreaOverlay); + d->m_containerOverlay = new DockOverlay(this, DockOverlay::ModeContainerOverlay); + d->m_containers.append(this); + //d->loadStylesheet(); + } + + DockManager::~DockManager() + { + // If the factory default workspace is still loaded, create a default workspace just in case + // the layout changed as there is no tracking of layout changes. + if (isFactoryDefaultWorkspace(d->m_workspaceName) + && !isDefaultWorkspace(d->m_workspaceName)) { + createWorkspace(Constants::DEFAULT_NAME); + openWorkspace(Constants::DEFAULT_NAME); + } + + emit aboutToUnloadWorkspace(d->m_workspaceName); + save(); + + for (auto floatingWidget : d->m_floatingWidgets) { + delete floatingWidget; + } + delete d; + } + + DockManager::ConfigFlags DockManager::configFlags() { return g_staticConfigFlags; } + + void DockManager::setConfigFlags(const ConfigFlags flags) { g_staticConfigFlags = flags; } + + void DockManager::setConfigFlag(eConfigFlag flag, bool on) + { + internal::setFlag(g_staticConfigFlags, flag, on); + } + + bool DockManager::testConfigFlag(eConfigFlag flag) + { + return configFlags().testFlag(flag); + } + + IconProvider &DockManager::iconProvider() + { + static IconProvider instance; + return instance; + } + + int DockManager::startDragDistance() + { + return static_cast(QApplication::startDragDistance() * 1.5); + } + + void DockManager::setSettings(QSettings *settings) { d->m_settings = settings; } + + DockAreaWidget *DockManager::addDockWidget(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *dockAreaWidget) + { + d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); + return DockContainerWidget::addDockWidget(area, dockWidget, dockAreaWidget); + } + + DockAreaWidget *DockManager::addDockWidgetTab(DockWidgetArea area, DockWidget *dockWidget) + { + DockAreaWidget *areaWidget = lastAddedDockAreaWidget(area); + if (areaWidget) { + return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, areaWidget); + } else if (!openedDockAreas().isEmpty()) { + return addDockWidget(area, dockWidget, openedDockAreas().last()); + } else { + return addDockWidget(area, dockWidget, nullptr); + } + } + + DockAreaWidget *DockManager::addDockWidgetTabToArea(DockWidget *dockWidget, + DockAreaWidget *dockAreaWidget) + { + return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, dockAreaWidget); + } + + FloatingDockContainer *DockManager::addDockWidgetFloating(DockWidget *dockWidget) + { + d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); + DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); + if (oldDockArea) { + oldDockArea->removeDockWidget(dockWidget); + } + + dockWidget->setDockManager(this); + FloatingDockContainer *floatingWidget = new FloatingDockContainer(dockWidget); + floatingWidget->resize(dockWidget->size()); + if (isVisible()) { + floatingWidget->show(); + } else { + d->m_uninitializedFloatingWidgets.append(floatingWidget); + } + return floatingWidget; + } + + void DockManager::registerFloatingWidget(FloatingDockContainer *floatingWidget) + { + d->m_floatingWidgets.append(floatingWidget); + emit floatingWidgetCreated(floatingWidget); + qCInfo(adsLog) << "d->FloatingWidgets.count() " << d->m_floatingWidgets.count(); + } + + void DockManager::removeFloatingWidget(FloatingDockContainer *floatingWidget) + { + d->m_floatingWidgets.removeAll(floatingWidget); + } + + void DockManager::registerDockContainer(DockContainerWidget *dockContainer) + { + d->m_containers.append(dockContainer); + } + + void DockManager::removeDockContainer(DockContainerWidget *dockContainer) + { + if (this != dockContainer) { + d->m_containers.removeAll(dockContainer); + } + } + + DockOverlay *DockManager::containerOverlay() const { return d->m_containerOverlay; } + + DockOverlay *DockManager::dockAreaOverlay() const { return d->m_dockAreaOverlay; } + + const QList DockManager::dockContainers() const + { + return d->m_containers; + } + + const QList DockManager::floatingWidgets() const + { + return d->m_floatingWidgets; + } + + unsigned int DockManager::zOrderIndex() const { return 0; } + + QByteArray DockManager::saveState(int version) const + { + QByteArray xmlData; + QXmlStreamWriter stream(&xmlData); + auto configFlags = DockManager::configFlags(); + stream.setAutoFormatting(configFlags.testFlag(XmlAutoFormattingEnabled)); + stream.writeStartDocument(); + stream.writeStartElement("QtAdvancedDockingSystem"); + stream.writeAttribute("version", QString::number(version)); + stream.writeAttribute("containers", QString::number(d->m_containers.count())); + for (auto container : d->m_containers) { + container->saveState(stream); + } + + stream.writeEndElement(); + stream.writeEndDocument(); + return xmlData; + } + + bool DockManager::restoreState(const QByteArray &state, int version) + { + // Prevent multiple calls as long as state is not restore. This may + // happen, if QApplication::processEvents() is called somewhere + if (d->m_restoringState) { + return false; + } + + // We hide the complete dock manager here. Restoring the state means + // that DockWidgets are removed from the DockArea internal stack layout + // which in turn means, that each time a widget is removed the stack + // will show and raise the next available widget which in turn + // triggers show events for the dock widgets. To avoid this we hide the + // dock manager. Because there will be no processing of application + // events until this function is finished, the user will not see this + // hiding + bool isHidden = this->isHidden(); + if (!isHidden) { + hide(); + } + d->m_restoringState = true; + emit restoringState(); + bool result = d->restoreState(state, version); + d->m_restoringState = false; + emit stateRestored(); + if (!isHidden) { + show(); + } + + return result; + } + + void DockManager::showEvent(QShowEvent *event) + { + Super::showEvent(event); + if (d->m_uninitializedFloatingWidgets.empty()) { + return; + } + + for (auto floatingWidget : d->m_uninitializedFloatingWidgets) { + floatingWidget->show(); + } + d->m_uninitializedFloatingWidgets.clear(); + } + + DockWidget *DockManager::findDockWidget(const QString &objectName) const + { + return d->m_dockWidgetsMap.value(objectName, nullptr); + } + + void DockManager::removeDockWidget(DockWidget *dockWidget) + { + emit dockWidgetAboutToBeRemoved(dockWidget); + d->m_dockWidgetsMap.remove(dockWidget->objectName()); + DockContainerWidget::removeDockWidget(dockWidget); + emit dockWidgetRemoved(dockWidget); + } + + QMap DockManager::dockWidgetsMap() const { return d->m_dockWidgetsMap; } + + bool DockManager::isRestoringState() const { return d->m_restoringState; } + + void DockManager::showWorkspaceMananger() + { + // Save current workspace + save(); + + WorkspaceDialog workspaceDialog(this, parentWidget()); + workspaceDialog.setAutoLoadWorkspace(autoRestorLastWorkspace()); + workspaceDialog.exec(); + + QTC_ASSERT(d->m_settings, return ); + d->m_settings->setValue(Constants::AUTO_RESTORE_WORKSPACE_SETTINGS_KEY, + workspaceDialog.autoLoadWorkspace()); + } + + bool DockManager::isFactoryDefaultWorkspace(const QString &workspace) const + { + return workspace == QLatin1String(Constants::FACTORY_DEFAULT_NAME); + } + + bool DockManager::isDefaultWorkspace(const QString &workspace) const + { + return workspace == QLatin1String(Constants::DEFAULT_NAME); + } + + bool DockManager::save() + { + if (isFactoryDefaultWorkspace(activeWorkspace())) + return true; + + emit aboutToSaveWorkspace(); + + bool result = write(saveState(), parentWidget()); + if (result) { + d->m_workspaceDateTimes.insert(activeWorkspace(), QDateTime::currentDateTime()); + } else { + QMessageBox::warning(parentWidget(), + tr("Cannot Save Session"), + tr("Could not save session to file %1") + .arg(workspaceNameToFileName(d->m_workspaceName) + .toUserOutput())); + } + + return result; + } + + QString DockManager::activeWorkspace() const { return d->m_workspaceName; } + + QString DockManager::lastWorkspace() const + { + QTC_ASSERT(d->m_settings, return {}); + return d->m_settings->value(Constants::STARTUP_WORKSPACE_SETTINGS_KEY).toString(); + } + + bool DockManager::autoRestorLastWorkspace() const + { + QTC_ASSERT(d->m_settings, return false); + return d->m_settings->value(Constants::AUTO_RESTORE_WORKSPACE_SETTINGS_KEY).toBool(); + } + + const QString m_dirName = QLatin1String("workspaces"); + const QString m_fileExt = QLatin1String(".wrk"); // TODO + + QStringList DockManager::workspaces() + { + if (d->m_workspaces.isEmpty() || d->m_workspaceListDirty) { + auto tmp = QSet::fromList(d->m_workspaces); + + QTC_ASSERT(d->m_settings, return {}); + QDir workspaceDir(QFileInfo(d->m_settings->fileName()).path() + QLatin1Char('/') + + m_dirName); + QFileInfoList workspaceFiles + = workspaceDir.entryInfoList(QStringList() << QLatin1String("*.wrk"), + QDir::NoFilter, + QDir::Time); // TODO Choose different extension + for (const QFileInfo &fileInfo : workspaceFiles) { + QString filename = fileInfo.completeBaseName(); + filename.replace("_", " "); + d->m_workspaceDateTimes.insert(filename, fileInfo.lastModified()); + //if (name != QLatin1String(Constants::DEFAULT_NAME)) + tmp.insert(filename); + } + //d->m_workspaces.prepend(QLatin1String(Constants::DEFAULT_NAME)); + + d->m_workspaceListDirty = false; + d->m_workspaces = tmp.toList(); + } + return d->m_workspaces; + } + + QDateTime DockManager::workspaceDateTime(const QString &workspace) const + { + return d->m_workspaceDateTimes.value(workspace); + } + + Utils::FilePath DockManager::workspaceNameToFileName(const QString &workspaceName) const + { + QTC_ASSERT(d->m_settings, return {}); + QString workspaceNameCopy = workspaceName; + return Utils::FilePath::fromString( + QFileInfo(d->m_settings->fileName()).path() + QLatin1Char('/') + m_dirName + + QLatin1Char('/') + workspaceNameCopy.replace(" ", "_") + QLatin1String(".wrk")); + } + + /** + * Creates \a workspace, but does not actually create the file. + */ + bool DockManager::createWorkspace(const QString &workspace) + { + if (workspaces().contains(workspace)) + return false; + d->m_workspaces.insert(1, workspace); + d->m_workspaceDateTimes.insert(workspace, QDateTime::currentDateTime()); + + emit workspaceListChanged(); + + return true; + } + + bool DockManager::openWorkspace(const QString &workspace) + { + // Do nothing if we have that workspace already loaded, exception if the + // workspace is the default virgin workspace we still want to be able to + // load the default workspace. + if (workspace == d->m_workspaceName) // && !isFactoryDefaultWorkspace(workspace)) + return true; + + if (!workspaces().contains(workspace)) + return false; + + // Check if the currently active workspace isn't empty and try to save it + if (!d->m_workspaceName.isEmpty()) { + // Allow everyone to set something in the workspace and before saving + emit aboutToUnloadWorkspace(d->m_workspaceName); + if (!save()) { + return false; + } + } + + // Try loading the file + QByteArray data; + Utils::FilePath fileName = workspaceNameToFileName(workspace); + if (fileName.exists()) { + QFile file(fileName.toString()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::warning(parentWidget(), + tr("Cannot Restore Workspace"), + tr("Could not restore workspace %1") + .arg(fileName.toUserOutput())); + return false; + } + data = file.readAll(); + file.close(); + } + + emit openingWorkspace(workspace); + // If data was loaded from file try to restore its state + if (!data.isNull() && !restoreState(data)) { + return false; + } + d->m_workspaceName = workspace; + emit workspaceLoaded(workspace); + + return true; + } + + /** + * \brief Shows a dialog asking the user to confirm deleting the workspace \p workspace + */ + bool DockManager::confirmWorkspaceDelete(const QStringList &workspace) + { + const QString title = workspace.size() == 1 ? tr("Delete Workspace") + : tr("Delete Workspaces"); + const QString question = workspace.size() == 1 + ? tr("Delete workspace %1?").arg(workspace.first()) + : tr("Delete these workspaces?\n %1") + .arg(workspace.join("\n ")); + return QMessageBox::question(parentWidget(), + title, + question, + QMessageBox::Yes | QMessageBox::No) + == QMessageBox::Yes; + } + + /** + * Deletes \a workspace name from workspace list and the file from disk. + */ + bool DockManager::deleteWorkspace(const QString &workspace) + { + // Remove workspace from internal list + if (!d->m_workspaces.contains(workspace)) + return false; + d->m_workspaces.removeOne(workspace); + + emit workspacesRemoved(); + emit workspaceListChanged(); + + // Remove corresponding workspace file + QFile fi(workspaceNameToFileName(workspace).toString()); + if (fi.exists()) + return fi.remove(); + + return false; // TODO If we allow temporary workspaces without writing them to file + // directly, this needs to be true otherwise in all those cases it will return false. + } + + void DockManager::deleteWorkspaces(const QStringList &workspaces) + { + for (const QString &workspace : workspaces) + deleteWorkspace(workspace); + } + + bool DockManager::cloneWorkspace(const QString &original, const QString &clone) + { + if (!d->m_workspaces.contains(original)) + return false; + + QFile fi(workspaceNameToFileName(original).toString()); + // If the file does not exist, we can still clone + if (!fi.exists() || fi.copy(workspaceNameToFileName(clone).toString())) { + d->m_workspaces.insert(1, clone); + d->m_workspaceDateTimes + .insert(clone, workspaceNameToFileName(clone).toFileInfo().lastModified()); + return true; + } + return false; + } + + bool DockManager::renameWorkspace(const QString &original, const QString &newName) + { + if (!cloneWorkspace(original, newName)) + return false; + if (original == activeWorkspace()) + openWorkspace(newName); + return deleteWorkspace(original); + } + + bool DockManager::write(const QByteArray &data, QString *errorString) const + { + Utils::FilePath fileName = workspaceNameToFileName(activeWorkspace()); + + QDir tmp; + tmp.mkpath(fileName.toFileInfo().path()); + Utils::FileSaver fileSaver(fileName.toString(), QIODevice::Text); + if (!fileSaver.hasError()) { + fileSaver.write(data); + } + bool ok = fileSaver.finalize(); + + if (!ok && errorString) { + *errorString = fileSaver.errorString(); + } + + return ok; + } + +#ifdef QT_GUI_LIB + bool DockManager::write(const QByteArray &data, QWidget *parent) const + { + QString errorString; + const bool success = write(data, &errorString); + if (!success) + QMessageBox::critical(parent, + QCoreApplication::translate("Utils::FileSaverBase", "File Error"), + errorString); + return success; + } +#endif // QT_GUI_LIB + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockmanager.h b/src/libs/advanceddockingsystem/dockmanager.h new file mode 100644 index 00000000000..c5236911d75 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockmanager.h @@ -0,0 +1,478 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" +#include "dockcontainerwidget.h" +#include "dockwidget.h" +#include "floatingdockcontainer.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QSettings; +class QMenu; + +namespace ADS { + +namespace Constants { +const char FACTORY_DEFAULT_NAME[] = "factorydefault"; +const char DEFAULT_NAME[] = "default"; +const char STARTUP_WORKSPACE_SETTINGS_KEY[] = "QML/Designer/StartupWorkspace"; +const char AUTO_RESTORE_WORKSPACE_SETTINGS_KEY[] = "QML/Designer/AutoRestoreLastWorkspace"; +} // namespace Constants + +struct DockManagerPrivate; +class FloatingDockContainer; +struct FloatingDockContainerPrivate; +class DockComponentsFactory; +class DockContainerWidget; +class DockContainerWidgetPrivate; +class DockOverlay; +class DockAreaTabBar; +class DockWidgetTab; +struct DockWidgetTabPrivate; +struct DockAreaWidgetPrivate; +class IconProvider; + +/** + * The central dock manager that maintains the complete docking system. + * With the configuration flags you can globally control the functionality + * of the docking system. The dock manager uses an internal stylesheet to + * style its components like splitters, tabs and buttons. If you want to + * disable this stylesheet because your application uses its own, + * just call the function for settings the stylesheet with an empty + * string. + * \code + * dockManager->setStyleSheet(""); + * \endcode + **/ +class ADS_EXPORT DockManager : public DockContainerWidget +{ + Q_OBJECT +private: + DockManagerPrivate *d; ///< private data (pimpl) + friend struct DockManagerPrivate; + friend class FloatingDockContainer; + friend struct FloatingDockContainerPrivate; + friend class DockContainerWidget; + friend class DockContainerWidgetPrivate; + friend class DockAreaTabBar; + friend class DockWidgetTab; + friend struct DockAreaWidgetPrivate; + friend struct DockWidgetTabPrivate; + friend class FloatingDragPreview; + friend struct FloatingDragPreviewPrivate; + friend class DockAreaTitleBar; + +protected: + /** + * Registers the given floating widget in the internal list of + * floating widgets + */ + void registerFloatingWidget(FloatingDockContainer *floatingWidget); + + /** + * Remove the given floating widget from the list of registered floating + * widgets + */ + void removeFloatingWidget(FloatingDockContainer *floatingWidget); + + /** + * Registers the given dock container widget + */ + void registerDockContainer(DockContainerWidget *dockContainer); + + /** + * Remove dock container from the internal list of registered dock + * containers + */ + void removeDockContainer(DockContainerWidget *dockContainer); + + /** + * Overlay for containers + */ + DockOverlay *containerOverlay() const; + + /** + * Overlay for dock areas + */ + DockOverlay *dockAreaOverlay() const; + + /** + * Show the floating widgets that has been created floating + */ + virtual void showEvent(QShowEvent *event) override; + +public: + using Super = DockContainerWidget; + + /** + * These global configuration flags configure some global dock manager + * settings. + */ + enum eConfigFlag { + ActiveTabHasCloseButton + = 0x0001, //!< If this flag is set, the active tab in a tab area has a close button + DockAreaHasCloseButton + = 0x0002, //!< If the flag is set each dock area has a close button + DockAreaCloseButtonClosesTab + = 0x0004, //!< If the flag is set, the dock area close button closes the active tab, if not set, it closes the complete dock area + OpaqueSplitterResize + = 0x0008, //!< See QSplitter::setOpaqueResize() documentation + XmlAutoFormattingEnabled + = 0x0010, //!< If enabled, the XML writer automatically adds line-breaks and indentation to empty sections between elements (ignorable whitespace). + XmlCompressionEnabled + = 0x0020, //!< If enabled, the XML output will be compressed and is not human readable anymore + TabCloseButtonIsToolButton + = 0x0040, //! If enabled the tab close buttons will be QToolButtons instead of QPushButtons - disabled by default + AllTabsHaveCloseButton + = 0x0080, //!< if this flag is set, then all tabs that are closable show a close button + RetainTabSizeWhenCloseButtonHidden + = 0x0100, //!< if this flag is set, the space for the close button is reserved even if the close button is not visible + OpaqueUndocking + = 0x0200, ///< If enabled, the widgets are immediately undocked into floating widgets, if disabled, only a draw preview is undocked and the real undocking is deferred until the mouse is released + DragPreviewIsDynamic + = 0x0400, ///< If opaque undocking is disabled, this flag defines the behavior of the drag preview window, if this flag is enabled, the preview will be adjusted dynamically to the drop area + DragPreviewShowsContentPixmap + = 0x0800, ///< If opaque undocking is disabled, the created drag preview window shows a copy of the content of the dock widget / dock are that is dragged + DragPreviewHasWindowFrame + = 0x1000, ///< If opaque undocking is disabled, then this flag configures if the drag preview is frameless or looks like a real window + AlwaysShowTabs + = 0x2000, ///< If this option is enabled, the tab of a dock widget is always displayed - even if it is the only visible dock widget in a floating widget. + DockAreaHasUndockButton + = 0x4000, //!< If the flag is set each dock area has an undock button + DockAreaHasTabsMenuButton + = 0x8000, //!< If the flag is set each dock area has a tabs menu button + DockAreaHideDisabledButtons + = 0x10000, //!< If the flag is set disabled dock area buttons will not appear on the tollbar at all (enabling them will bring them back) + DockAreaDynamicTabsMenuButtonVisibility + = 0x20000, //!< If the flag is set dock area will disable a tabs menu button when there is only one tab in the area + FloatingContainerHasWidgetTitle + = 0x40000, + FloatingContainerHasWidgetIcon + = 0x80000, + + DefaultDockAreaButtons = DockAreaHasCloseButton + | DockAreaHasUndockButton + | DockAreaHasTabsMenuButton,///< default configuration of dock area title bar buttons + + DefaultBaseConfig = DefaultDockAreaButtons + | ActiveTabHasCloseButton + | XmlCompressionEnabled + | FloatingContainerHasWidgetTitle,///< default base configuration settings + + DefaultOpaqueConfig = DefaultBaseConfig + | OpaqueSplitterResize + | OpaqueUndocking, ///< the default configuration with opaque operations - this may cause issues if ActiveX or Qt 3D windows are involved + + DefaultNonOpaqueConfig = DefaultBaseConfig + | DragPreviewShowsContentPixmap, ///< the default configuration for non opaque operations + + NonOpaqueWithWindowFrame = DefaultNonOpaqueConfig + | DragPreviewHasWindowFrame ///< the default configuration for non opaque operations that show a real window with frame + }; + Q_DECLARE_FLAGS(ConfigFlags, eConfigFlag) + + /** + * Default Constructor. + * If the given parent is a QMainWindow, the dock manager sets itself as the + * central widget. + * Before you create any dock widgets, you should properly setup the + * configuration flags via setConfigFlags(). + */ + DockManager(QWidget *parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~DockManager() override; + + /** + * This function returns the global configuration flags + */ + static ConfigFlags configFlags(); + + /** + * Sets the global configuration flags for the whole docking system. + * Call this function before you create your first dock widget. + */ + static void setConfigFlags(const ConfigFlags flags); + + /** + * Set a certain config flag + */ + static void setConfigFlag(eConfigFlag flag, bool on = true); + + /** + * Returns true if the given config flag is set + */ + static bool testConfigFlag(eConfigFlag flag); + + /** + * Returns the global icon provider. + * The icon provider enables the use of custom icons in case using + * styleheets for icons is not an option. + */ + static IconProvider &iconProvider(); + + /** + * The distance the user needs to move the mouse with the left button + * hold down before a dock widget start floating + */ + static int startDragDistance(); + + /** + * Set the QtCreator settings. + */ + void setSettings(QSettings *settings); + + /** + * Adds dockwidget into the given area. + * If DockAreaWidget is not null, then the area parameter indicates the area + * into the DockAreaWidget. If DockAreaWidget is null, the Dockwidget will + * be dropped into the container. If you would like to add a dock widget + * tabified, then you need to add it to an existing dock area object + * into the CenterDockWidgetArea. The following code shows this: + * \code + * DockManager->addDockWidget(ads::CenterDockWidgetArea, NewDockWidget, + * ExisitingDockArea); + * \endcode + * \return Returns the dock area widget that contains the new DockWidget + */ + DockAreaWidget *addDockWidget(DockWidgetArea area, + DockWidget *dockWidget, + DockAreaWidget *dockAreaWidget = nullptr); + + /** + * This function will add the given Dockwidget to the given dock area as + * a new tab. + * If no dock area widget exists for the given area identifier, a new + * dock area widget is created. + */ + DockAreaWidget *addDockWidgetTab(DockWidgetArea area, DockWidget *dockWidget); + + /** + * This function will add the given Dockwidget to the given DockAreaWidget + * as a new tab. + */ + DockAreaWidget *addDockWidgetTabToArea(DockWidget *dockWidget, DockAreaWidget *dockAreaWidget); + + /** + * Adds the given DockWidget floating and returns the created + * CFloatingDockContainer instance. + */ + FloatingDockContainer *addDockWidgetFloating(DockWidget *dockWidget); + + /** + * Searches for a registered doc widget with the given ObjectName + * \return Return the found dock widget or nullptr if a dock widget with the + * given name is not registered + */ + DockWidget *findDockWidget(const QString &objectName) const; + + /** + * Remove the given Dock from the dock manager + */ + void removeDockWidget(DockWidget *dockWidget); + + /** + * This function returns a readable reference to the internal dock + * widgets map so that it is possible to iterate over all dock widgets + */ + QMap dockWidgetsMap() const; + + /** + * Returns the list of all active and visible dock containers + * Dock containers are the main dock manager and all floating widgets + */ + const QList dockContainers() const; + + /** + * Returns the list of all floating widgets + */ + const QList floatingWidgets() const; + + /** + * This function always return 0 because the main window is always behind + * any floating widget + */ + virtual unsigned int zOrderIndex() const override; + + /** + * Saves the current state of the dockmanger and all its dock widgets + * into the returned QByteArray. + * The XmlMode enables / disables the auto formatting for the XmlStreamWriter. + * If auto formatting is enabled, the output is intended and line wrapped. + * The XmlMode XmlAutoFormattingDisabled is better if you would like to have + * a more compact XML output - i.e. for storage in ini files. + */ + QByteArray saveState(int version = Version1) const; + + /** + * Restores the state of this dockmanagers dockwidgets. + * The version number is compared with that stored in state. If they do + * not match, the dockmanager's state is left unchanged, and this function + * returns false; otherwise, the state is restored, and this function + * returns true. + */ + bool restoreState(const QByteArray &state, int version = Version1); + + /** + * This function returns true between the restoringState() and + * stateRestored() signals. + */ + bool isRestoringState() const; + +signals: + /** + * This signal is emitted if the list of perspectives changed + */ + void workspaceListChanged(); + + /** + * This signal is emitted if perspectives have been removed + */ + void workspacesRemoved(); + + /** + * This signal is emitted, if the restore function is called, just before + * the dock manager starts restoring the state. + * If this function is called, nothing has changed yet + */ + void restoringState(); + + /** + * This signal is emitted if the state changed in restoreState. + * The signal is emitted if the restoreState() function is called or + * if the openWorkspace() function is called + */ + void stateRestored(); + + /** + * This signal is emitted, if the dock manager starts opening a + * perspective. + * Opening a perspective may take more than a second if there are + * many complex widgets. The application may use this signal + * to show some progress indicator or to change the mouse cursor + * into a busy cursor. + */ + void openingWorkspace(const QString &workspaceName); + + /** + * This signal is emitted if the dock manager finished opening a + * perspective + */ + void workspaceOpened(const QString &workspaceName); + + /** + * This signal is emitted, if a new floating widget has been created. + * An application can use this signal to e.g. subscribe to events of + * the newly created window. + */ + void floatingWidgetCreated(FloatingDockContainer *floatingWidget); + + /** + * This signal is emitted, if a new DockArea has been created. + * An application can use this signal to set custom icons or custom + * tooltips for the DockArea buttons. + */ + void dockAreaCreated(DockAreaWidget *dockArea); + + /** + * This signal is emitted just before the given dock widget is removed + * from the + */ + void dockWidgetAboutToBeRemoved(DockWidget *dockWidget); + + /** + * This signal is emitted if a dock widget has been removed with the remove + * removeDockWidget() function. + * If this signal is emitted, the dock widget has been removed from the + * docking system but it is not deleted yet. + */ + void dockWidgetRemoved(DockWidget *dockWidget); + +public: + void showWorkspaceMananger(); + + // higher level workspace management + QString activeWorkspace() const; + QString lastWorkspace() const; + bool autoRestorLastWorkspace() const; + QStringList workspaces(); + QDateTime workspaceDateTime(const QString &workspace) const; + Utils::FilePath workspaceNameToFileName(const QString &workspaceName) const; + + bool createWorkspace(const QString &workspace); + + bool openWorkspace(const QString &workspace); + + bool confirmWorkspaceDelete(const QStringList &workspaces); + bool deleteWorkspace(const QString &workspace); + void deleteWorkspaces(const QStringList &workspaces); + + bool cloneWorkspace(const QString &original, const QString &clone); + bool renameWorkspace(const QString &original, const QString &newName); + + bool save(); + + bool isFactoryDefaultWorkspace(const QString &workspace) const; + bool isDefaultWorkspace(const QString &workspace) const; + +signals: + void aboutToUnloadWorkspace(QString workspaceName); + void aboutToLoadWorkspace(QString workspaceName); + void workspaceLoaded(QString workspaceName); + void aboutToSaveWorkspace(); + +private: + bool write(const QByteArray &data, QString *errorString) const; +#ifdef QT_GUI_LIB + bool write(const QByteArray &data, QWidget *parent) const; +#endif +}; // class DockManager + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockoverlay.cpp b/src/libs/advanceddockingsystem/dockoverlay.cpp new file mode 100644 index 00000000000..a42962a5f56 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockoverlay.cpp @@ -0,0 +1,773 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dockoverlay.h" + +#include "dockareawidget.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace ADS { + + /** + * Private data class of DockOverlay + */ + struct DockOverlayPrivate + { + DockOverlay *q; + DockWidgetAreas m_allowedAreas = InvalidDockWidgetArea; + DockOverlayCross *m_cross; + QPointer m_targetWidget; + DockWidgetArea m_lastLocation = InvalidDockWidgetArea; + bool m_dropPreviewEnabled = true; + DockOverlay::eMode m_mode = DockOverlay::ModeDockAreaOverlay; + QRect m_dropAreaRect; + + /** + * Private data constructor + */ + DockOverlayPrivate(DockOverlay *parent) + : q(parent) + {} + }; + + /** + * Private data of DockOverlayCross class + */ + struct DockOverlayCrossPrivate + { + DockOverlayCross *q; + DockOverlay::eMode m_mode = DockOverlay::ModeDockAreaOverlay; + DockOverlay *m_dockOverlay; + QHash m_dropIndicatorWidgets; + QGridLayout *m_gridLayout; + QColor m_iconColors[5]; + bool m_updateRequired = false; + double m_lastDevicePixelRatio = 0.1; + + /** + * Private data constructor + */ + DockOverlayCrossPrivate(DockOverlayCross *parent) + : q(parent) + {} + + /** + * @param area + * @return + */ + QPoint areaGridPosition(const DockWidgetArea area); + + /** + * Palette based default icon colors + */ + QColor defaultIconColor(DockOverlayCross::eIconColor colorIndex) + { + QPalette palette = q->palette(); + switch (colorIndex) { + case DockOverlayCross::FrameColor: + return palette.color(QPalette::Active, QPalette::Highlight); + case DockOverlayCross::WindowBackgroundColor: + return palette.color(QPalette::Active, QPalette::Base); + case DockOverlayCross::OverlayColor: { + QColor color = palette.color(QPalette::Active, QPalette::Highlight); + color.setAlpha(64); + return color; + } + case DockOverlayCross::ArrowColor: + return palette.color(QPalette::Active, QPalette::Base); + case DockOverlayCross::ShadowColor: + return QColor(0, 0, 0, 64); + } + + return QColor(); + } + + /** + * Stylehseet based icon colors + */ + QColor iconColor(DockOverlayCross::eIconColor colorIndex) + { + QColor color = m_iconColors[colorIndex]; + if (!color.isValid()) { + color = defaultIconColor(colorIndex); + m_iconColors[colorIndex] = color; + } + return color; + } + + /** + * Helper function that returns the drop indicator width depending on the + * operating system + */ + qreal dropIndicatiorWidth(QLabel *label) const + { +#ifdef Q_OS_LINUX + Q_UNUSED(label) + return 40; +#else + return static_cast(label->fontMetrics().height()) * 3.f; +#endif + } + + QWidget *createDropIndicatorWidget(DockWidgetArea dockWidgetArea, DockOverlay::eMode mode) + { + QLabel *label = new QLabel(); + label->setObjectName("DockWidgetAreaLabel"); + + const qreal metric = dropIndicatiorWidth(label); + const QSizeF size(metric, metric); + + label->setPixmap(createHighDpiDropIndicatorPixmap(size, dockWidgetArea, mode)); + label->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + label->setAttribute(Qt::WA_TranslucentBackground); + label->setProperty("dockWidgetArea", dockWidgetArea); + return label; + } + + void updateDropIndicatorIcon(QWidget *dropIndicatorWidget) + { + QLabel *label = qobject_cast(dropIndicatorWidget); + const qreal metric = dropIndicatiorWidth(label); + const QSizeF size(metric, metric); + + int area = label->property("dockWidgetArea").toInt(); + label->setPixmap(createHighDpiDropIndicatorPixmap(size, + static_cast(area), + m_mode)); // TODO + } + + QPixmap createHighDpiDropIndicatorPixmap(const QSizeF &size, + DockWidgetArea dockWidgetArea, + DockOverlay::eMode mode) + { + QColor borderColor = iconColor(DockOverlayCross::FrameColor); + QColor backgroundColor = iconColor(DockOverlayCross::WindowBackgroundColor); + double devicePixelRatio = q->window()->devicePixelRatioF(); + QSizeF pixmapSize = size * devicePixelRatio; + QPixmap pixmap(pixmapSize.toSize()); + pixmap.fill(QColor(0, 0, 0, 0)); + + QPainter painter(&pixmap); + QPen pen = painter.pen(); + QRectF shadowRect(pixmap.rect()); + QRectF baseRect; + baseRect.setSize(shadowRect.size() * 0.7); + baseRect.moveCenter(shadowRect.center()); + + // Fill + QColor shadowColor = iconColor(DockOverlayCross::ShadowColor); + if (shadowColor.alpha() == 255) { + shadowColor.setAlpha(64); + } + painter.fillRect(shadowRect, shadowColor); + + // Drop area rect. + painter.save(); + QRectF areaRect; + QLineF areaLine; + QRectF nonAreaRect; + switch (dockWidgetArea) { + case TopDockWidgetArea: + areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * 0.5); + nonAreaRect = QRectF(baseRect.x(), + shadowRect.height() * 0.5, + baseRect.width(), + baseRect.height() * 0.5); + areaLine = QLineF(areaRect.bottomLeft(), areaRect.bottomRight()); + break; + case RightDockWidgetArea: + areaRect = QRectF(shadowRect.width() * 0.5, + baseRect.y(), + baseRect.width() * 0.5, + baseRect.height()); + nonAreaRect = QRectF(baseRect.x(), + baseRect.y(), + baseRect.width() * 0.5, + baseRect.height()); + areaLine = QLineF(areaRect.topLeft(), areaRect.bottomLeft()); + break; + case BottomDockWidgetArea: + areaRect = QRectF(baseRect.x(), + shadowRect.height() * 0.5, + baseRect.width(), + baseRect.height() * 0.5); + nonAreaRect = QRectF(baseRect.x(), + baseRect.y(), + baseRect.width(), + baseRect.height() * 0.5); + areaLine = QLineF(areaRect.topLeft(), areaRect.topRight()); + break; + case LeftDockWidgetArea: + areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * 0.5, baseRect.height()); + nonAreaRect = QRectF(shadowRect.width() * 0.5, + baseRect.y(), + baseRect.width() * 0.5, + baseRect.height()); + areaLine = QLineF(areaRect.topRight(), areaRect.bottomRight()); + break; + default: + break; + } + + QSizeF baseSize = baseRect.size(); + if (DockOverlay::ModeContainerOverlay == mode && dockWidgetArea != CenterDockWidgetArea) { + baseRect = areaRect; + } + + painter.fillRect(baseRect, backgroundColor); + if (areaRect.isValid()) { + pen = painter.pen(); + pen.setColor(borderColor); + QColor color = iconColor(DockOverlayCross::OverlayColor); + if (color.alpha() == 255) { + color.setAlpha(64); + } + painter.setBrush(color); + painter.setPen(Qt::NoPen); + painter.drawRect(areaRect); + + pen = painter.pen(); + pen.setWidth(1); + pen.setColor(borderColor); + pen.setStyle(Qt::DashLine); + painter.setPen(pen); + painter.drawLine(areaLine); + } + painter.restore(); + + painter.save(); + // Draw outer border + pen = painter.pen(); + pen.setColor(borderColor); + pen.setWidth(1); + painter.setBrush(Qt::NoBrush); + painter.setPen(pen); + painter.drawRect(baseRect); + + // draw window title bar + painter.setBrush(borderColor); + QRectF frameRect(baseRect.topLeft(), QSizeF(baseRect.width(), baseSize.height() / 10)); + painter.drawRect(frameRect); + painter.restore(); + + // Draw arrow for outer container drop indicators + if (DockOverlay::ModeContainerOverlay == mode && dockWidgetArea != CenterDockWidgetArea) { + QRectF arrowRect; + arrowRect.setSize(baseSize); + arrowRect.setWidth(arrowRect.width() / 4.6); + arrowRect.setHeight(arrowRect.height() / 2); + arrowRect.moveCenter(QPointF(0, 0)); + QPolygonF arrow; + arrow << arrowRect.topLeft() << QPointF(arrowRect.right(), arrowRect.center().y()) + << arrowRect.bottomLeft(); + painter.setPen(Qt::NoPen); + painter.setBrush(iconColor(DockOverlayCross::ArrowColor)); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.translate(nonAreaRect.center().x(), nonAreaRect.center().y()); + + switch (dockWidgetArea) { + case TopDockWidgetArea: + painter.rotate(-90); + break; + case RightDockWidgetArea: + break; + case BottomDockWidgetArea: + painter.rotate(90); + break; + case LeftDockWidgetArea: + painter.rotate(180); + break; + default: + break; + } + + painter.drawPolygon(arrow); + } + + pixmap.setDevicePixelRatio(devicePixelRatio); + return pixmap; + } + }; + + DockOverlay::DockOverlay(QWidget *parent, eMode mode) + : QFrame(parent) + , d(new DockOverlayPrivate(this)) + { + d->m_mode = mode; + d->m_cross = new DockOverlayCross(this); + + if (Utils::HostOsInfo::isLinuxHost()) + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint + | Qt::X11BypassWindowManagerHint); + else + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + + setWindowOpacity(1); + setWindowTitle("DockOverlay"); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); + + d->m_cross->setVisible(false); + setVisible(false); + } + + DockOverlay::~DockOverlay() + { + delete d; + } + + void DockOverlay::setAllowedAreas(DockWidgetAreas areas) + { + if (areas == d->m_allowedAreas) + return; + d->m_allowedAreas = areas; + d->m_cross->reset(); + } + + DockWidgetAreas DockOverlay::allowedAreas() const + { + return d->m_allowedAreas; + } + + DockWidgetArea DockOverlay::dropAreaUnderCursor() const + { + DockWidgetArea result = d->m_cross->cursorLocation(); + if (result != InvalidDockWidgetArea) { + return result; + } + + DockAreaWidget *dockArea = qobject_cast(d->m_targetWidget.data()); + if (!dockArea) { + return result; + } + + if (dockArea->allowedAreas().testFlag(CenterDockWidgetArea) + && dockArea->titleBarGeometry().contains(dockArea->mapFromGlobal(QCursor::pos()))) { + return CenterDockWidgetArea; + } + + return result; + } + + DockWidgetArea DockOverlay::showOverlay(QWidget *target) + { + if (d->m_targetWidget == target) { + // Hint: We could update geometry of overlay here. + DockWidgetArea dockWidgetArea = dropAreaUnderCursor(); + if (dockWidgetArea != d->m_lastLocation) { + repaint(); + d->m_lastLocation = dockWidgetArea; + } + return dockWidgetArea; + } + + d->m_targetWidget = target; + d->m_lastLocation = InvalidDockWidgetArea; + + // Move it over the target. + resize(target->size()); + QPoint topLeft = target->mapToGlobal(target->rect().topLeft()); + move(topLeft); + show(); + d->m_cross->updatePosition(); + d->m_cross->updateOverlayIcons(); + return dropAreaUnderCursor(); + } + + void DockOverlay::hideOverlay() + { + hide(); + d->m_targetWidget.clear(); + d->m_lastLocation = InvalidDockWidgetArea; + d->m_dropAreaRect = QRect(); + } + + void DockOverlay::enableDropPreview(bool enable) + { + d->m_dropPreviewEnabled = enable; + update(); + } + + bool DockOverlay::dropPreviewEnabled() const + { + return d->m_dropPreviewEnabled; + } + + void DockOverlay::paintEvent(QPaintEvent *event) + { + Q_UNUSED(event) + // Draw rect based on location + if (!d->m_dropPreviewEnabled) { + d->m_dropAreaRect = QRect(); + return; + } + + QRect rectangle = rect(); + const DockWidgetArea dockWidgetArea = dropAreaUnderCursor(); + double factor = (DockOverlay::ModeContainerOverlay == d->m_mode) ? 3 : 2; + + switch (dockWidgetArea) { + case TopDockWidgetArea: + rectangle.setHeight(static_cast(rectangle.height() / factor)); + break; + case RightDockWidgetArea: + rectangle.setX(static_cast(rectangle.width() * (1 - 1 / factor))); + break; + case BottomDockWidgetArea: + rectangle.setY(static_cast(rectangle.height() * (1 - 1 / factor))); + break; + case LeftDockWidgetArea: + rectangle.setWidth(static_cast(rectangle.width() / factor)); + break; + case CenterDockWidgetArea: + rectangle = rect(); + break; + default: + return; + } + QPainter painter(this); + QColor color = palette().color(QPalette::Active, QPalette::Highlight); + QPen pen = painter.pen(); + pen.setColor(color.darker(120)); + pen.setStyle(Qt::SolidLine); + pen.setWidth(1); + pen.setCosmetic(true); + painter.setPen(pen); + color = color.lighter(130); + color.setAlpha(64); + painter.setBrush(color); + painter.drawRect(rectangle.adjusted(0, 0, -1, -1)); + d->m_dropAreaRect = rectangle; + } + + QRect DockOverlay::dropOverlayRect() const + { + return d->m_dropAreaRect; + } + + void DockOverlay::showEvent(QShowEvent *event) + { + d->m_cross->show(); + QFrame::showEvent(event); + } + + void DockOverlay::hideEvent(QHideEvent *event) + { + d->m_cross->hide(); + QFrame::hideEvent(event); + } + + bool DockOverlay::event(QEvent *event) + { + bool result = Super::event(event); + if (event->type() == QEvent::Polish) { + d->m_cross->setupOverlayCross(d->m_mode); + } + return result; + } + + static int areaAlignment(const DockWidgetArea area) + { + switch (area) { + case TopDockWidgetArea: + return Qt::AlignHCenter | Qt::AlignBottom; + case RightDockWidgetArea: + return Qt::AlignLeft | Qt::AlignVCenter; + case BottomDockWidgetArea: + return Qt::AlignHCenter | Qt::AlignTop; + case LeftDockWidgetArea: + return Qt::AlignRight | Qt::AlignVCenter; + case CenterDockWidgetArea: + return Qt::AlignCenter; + default: + return Qt::AlignCenter; + } + } + + // DockOverlayCrossPrivate + QPoint DockOverlayCrossPrivate::areaGridPosition(const DockWidgetArea area) + { + if (DockOverlay::ModeDockAreaOverlay == m_mode) { + switch (area) { + case TopDockWidgetArea: + return QPoint(1, 2); + case RightDockWidgetArea: + return QPoint(2, 3); + case BottomDockWidgetArea: + return QPoint(3, 2); + case LeftDockWidgetArea: + return QPoint(2, 1); + case CenterDockWidgetArea: + return QPoint(2, 2); + default: + return QPoint(); + } + } else { + switch (area) { + case TopDockWidgetArea: + return QPoint(0, 2); + case RightDockWidgetArea: + return QPoint(2, 4); + case BottomDockWidgetArea: + return QPoint(4, 2); + case LeftDockWidgetArea: + return QPoint(2, 0); + case CenterDockWidgetArea: + return QPoint(2, 2); + default: + return QPoint(); + } + } + } + + DockOverlayCross::DockOverlayCross(DockOverlay *overlay) + : QWidget(overlay->parentWidget()) + , d(new DockOverlayCrossPrivate(this)) + { + d->m_dockOverlay = overlay; + + if (Utils::HostOsInfo::isLinuxHost()) + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint + | Qt::X11BypassWindowManagerHint); + else + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + + setWindowTitle("DockOverlayCross"); + setAttribute(Qt::WA_TranslucentBackground); + + d->m_gridLayout = new QGridLayout(); + d->m_gridLayout->setSpacing(0); + setLayout(d->m_gridLayout); + } + + DockOverlayCross::~DockOverlayCross() + { + delete d; + } + + void DockOverlayCross::setupOverlayCross(DockOverlay::eMode mode) + { + d->m_mode = mode; + + QHash areaWidgets; + areaWidgets.insert(TopDockWidgetArea, d->createDropIndicatorWidget(TopDockWidgetArea, mode)); + areaWidgets.insert(RightDockWidgetArea, d->createDropIndicatorWidget(RightDockWidgetArea, mode)); + areaWidgets.insert(BottomDockWidgetArea, + d->createDropIndicatorWidget(BottomDockWidgetArea, mode)); + areaWidgets.insert(LeftDockWidgetArea, d->createDropIndicatorWidget(LeftDockWidgetArea, mode)); + areaWidgets.insert(CenterDockWidgetArea, + d->createDropIndicatorWidget(CenterDockWidgetArea, mode)); + d->m_lastDevicePixelRatio = devicePixelRatioF(); + setAreaWidgets(areaWidgets); + d->m_updateRequired = false; + } + + void DockOverlayCross::updateOverlayIcons() + { + if (windowHandle()->devicePixelRatio() == d->m_lastDevicePixelRatio) { // TODO + return; + } + + for (auto Widget : d->m_dropIndicatorWidgets) { + d->updateDropIndicatorIcon(Widget); + } + d->m_lastDevicePixelRatio = devicePixelRatioF(); + } + + void DockOverlayCross::setIconColor(eIconColor colorIndex, const QColor &color) + { + d->m_iconColors[colorIndex] = color; + d->m_updateRequired = true; + } + + QColor DockOverlayCross::iconColor(eIconColor colorIndex) const + { + return d->m_iconColors[colorIndex]; + } + + void DockOverlayCross::setAreaWidgets(const QHash &widgets) + { + // Delete old widgets. + const auto values = d->m_dropIndicatorWidgets.values(); + for (auto widget : values) { + d->m_gridLayout->removeWidget(widget); + delete widget; + } + d->m_dropIndicatorWidgets.clear(); + + // Insert new widgets into grid. + d->m_dropIndicatorWidgets = widgets; + + const QHash areas = d->m_dropIndicatorWidgets; + QHash::const_iterator constIt; + for (constIt = areas.begin(); constIt != areas.end(); ++constIt) { + const DockWidgetArea area = constIt.key(); + QWidget *widget = constIt.value(); + QPoint position = d->areaGridPosition(area); + d->m_gridLayout->addWidget(widget, + position.x(), + position.y(), + static_cast(areaAlignment(area))); + } + + if (DockOverlay::ModeDockAreaOverlay == d->m_mode) { + d->m_gridLayout->setContentsMargins(0, 0, 0, 0); + d->m_gridLayout->setRowStretch(0, 1); + d->m_gridLayout->setRowStretch(1, 0); + d->m_gridLayout->setRowStretch(2, 0); + d->m_gridLayout->setRowStretch(3, 0); + d->m_gridLayout->setRowStretch(4, 1); + + d->m_gridLayout->setColumnStretch(0, 1); + d->m_gridLayout->setColumnStretch(1, 0); + d->m_gridLayout->setColumnStretch(2, 0); + d->m_gridLayout->setColumnStretch(3, 0); + d->m_gridLayout->setColumnStretch(4, 1); + } else { + d->m_gridLayout->setContentsMargins(4, 4, 4, 4); + d->m_gridLayout->setRowStretch(0, 0); + d->m_gridLayout->setRowStretch(1, 1); + d->m_gridLayout->setRowStretch(2, 1); + d->m_gridLayout->setRowStretch(3, 1); + d->m_gridLayout->setRowStretch(4, 0); + + d->m_gridLayout->setColumnStretch(0, 0); + d->m_gridLayout->setColumnStretch(1, 1); + d->m_gridLayout->setColumnStretch(2, 1); + d->m_gridLayout->setColumnStretch(3, 1); + d->m_gridLayout->setColumnStretch(4, 0); + } + reset(); + } + + DockWidgetArea DockOverlayCross::cursorLocation() const + { + const QPoint position = mapFromGlobal(QCursor::pos()); + + const QHash areas = d->m_dropIndicatorWidgets; + QHash::const_iterator constIt; + for (constIt = areas.begin(); constIt != areas.end(); ++constIt) + { + if (d->m_dockOverlay->allowedAreas().testFlag(constIt.key()) && constIt.value() + && constIt.value()->isVisible() && constIt.value()->geometry().contains(position)) { + return constIt.key(); + } + } + + return InvalidDockWidgetArea; + } + + void DockOverlayCross::showEvent(QShowEvent *) + { + if (d->m_updateRequired) { + setupOverlayCross(d->m_mode); + } + this->updatePosition(); + } + + void DockOverlayCross::updatePosition() + { + resize(d->m_dockOverlay->size()); + QPoint topLeft = d->m_dockOverlay->pos(); + QPoint offest((this->width() - d->m_dockOverlay->width()) / 2, + (this->height() - d->m_dockOverlay->height()) / 2); + QPoint crossTopLeft = topLeft - offest; + move(crossTopLeft); + } + + void DockOverlayCross::reset() + { + const QList allAreas{TopDockWidgetArea, + RightDockWidgetArea, + BottomDockWidgetArea, + LeftDockWidgetArea, + CenterDockWidgetArea}; + const DockWidgetAreas allowedAreas = d->m_dockOverlay->allowedAreas(); + + // Update visibility of area widgets based on allowedAreas. + for (auto area : allAreas) { + QPoint position = d->areaGridPosition(area); + QLayoutItem *item = d->m_gridLayout->itemAtPosition(position.x(), position.y()); + QWidget *widget = nullptr; + if (item && (widget = item->widget()) != nullptr) { + widget->setVisible(allowedAreas.testFlag(area)); + } + } + } + + void DockOverlayCross::setIconColors(const QString &colors) + { + static const QMap + colorCompenentStringMap{{"Frame", DockOverlayCross::FrameColor}, + {"Background", DockOverlayCross::WindowBackgroundColor}, + {"Overlay", DockOverlayCross::OverlayColor}, + {"Arrow", DockOverlayCross::ArrowColor}, + {"Shadow", DockOverlayCross::ShadowColor}}; + + auto colorList = colors.split(' ', QString::SkipEmptyParts); + for (const auto &colorListEntry : colorList) { + auto componentColor = colorListEntry.split('=', QString::SkipEmptyParts); + int component = colorCompenentStringMap.value(componentColor[0], -1); + if (component < 0) { + continue; + } + d->m_iconColors[component] = QColor(componentColor[1]); + } + + d->m_updateRequired = true; + } + + QString DockOverlayCross::iconColors() const + { + return QString(); + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockoverlay.h b/src/libs/advanceddockingsystem/dockoverlay.h new file mode 100644 index 00000000000..46a1e62aa66 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockoverlay.h @@ -0,0 +1,263 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" + +#include +#include +#include +#include + +class QGridLayout; + +namespace ADS { + +struct DockOverlayPrivate; +class DockOverlayCross; + +/** + * DockOverlay paints a translucent rectangle over another widget. The geometry + * of the rectangle is based on the mouse location. + */ +class ADS_EXPORT DockOverlay : public QFrame +{ + Q_OBJECT +private: + DockOverlayPrivate *d; //< private data class + friend struct DockOverlayPrivate; + friend class DockOverlayCross; + +public: + using Super = QFrame; + + enum eMode { ModeDockAreaOverlay, ModeContainerOverlay }; + + /** + * Creates a dock overlay + */ + DockOverlay(QWidget *parent, eMode Mode = ModeDockAreaOverlay); + + /** + * Virtual destructor + */ + virtual ~DockOverlay() override; + + /** + * Configures the areas that are allowed for docking + */ + void setAllowedAreas(DockWidgetAreas areas); + + /** + * Returns flags with all allowed drop areas + */ + DockWidgetAreas allowedAreas() const; + + /** + * Returns the drop area under the current cursor location + */ + DockWidgetArea dropAreaUnderCursor() const; + + /** + * Show the drop overly for the given target widget + */ + DockWidgetArea showOverlay(QWidget *target); + + /** + * Hides the overlay + */ + void hideOverlay(); + + /** + * Enables / disables the semi transparent overlay rectangle that represents + * the future area of the dropped widget + */ + void enableDropPreview(bool enable); + + /** + * Returns true if drop preview is enabled + */ + bool dropPreviewEnabled() const; + + /** + * The drop overlay rectangle for the target area + */ + QRect dropOverlayRect() const; + + /** + * Handle polish events + */ + virtual bool event(QEvent *event) override; + +protected: + virtual void paintEvent(QPaintEvent *event) override; + virtual void showEvent(QShowEvent *event) override; + virtual void hideEvent(QHideEvent *event) override; +}; + +struct DockOverlayCrossPrivate; +/** + * DockOverlayCross shows a cross with 5 different drop area possibilities. + * I could have handled everything inside DockOverlay, but because of some + * styling issues it's better to have a separate class for the cross. + * You can style the cross icon using the property system. + * \code + * ADS--DockOverlayCross + { + qproperty-iconFrameColor: palette(highlight); + qproperty-iconBackgroundColor: palette(base); + qproperty-iconOverlayColor: palette(highlight); + qproperty-iconArrowColor: rgb(227, 227, 227); + qproperty-iconShadowColor: rgb(0, 0, 0); + } + * \endcode + * Or you can use the iconColors property to pass in AARRGGBB values as + * hex string like shown in the example below. + * \code + * ADS--DockOverlayCross + * { + * qproperty-iconColors: "Frame=#ff3d3d3d Background=#ff929292 Overlay=#1f3d3d3d Arrow=#ffb4b4b4 Shadow=#40474747"; + * } + * \endcode + */ +class DockOverlayCross : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString iconColors READ iconColors WRITE setIconColors) + Q_PROPERTY(QColor iconFrameColor READ iconColor WRITE setIconFrameColor) + Q_PROPERTY(QColor iconBackgroundColor READ iconColor WRITE setIconBackgroundColor) + Q_PROPERTY(QColor iconOverlayColor READ iconColor WRITE setIconOverlayColor) + Q_PROPERTY(QColor iconArrowColor READ iconColor WRITE setIconArrowColor) + Q_PROPERTY(QColor iconShadowColor READ iconColor WRITE setIconShadowColor) + +public: + enum eIconColor { + FrameColor, ///< the color of the frame of the small window icon + WindowBackgroundColor, ///< the background color of the small window in the icon + OverlayColor, ///< the color that shows the overlay (the dock side) in the icon + ArrowColor, ///< the arrow that points into the direction + ShadowColor ///< the color of the shadow rectangle that is painted below the icons + }; + +private: + DockOverlayCrossPrivate *d; + friend struct DockOverlayCrossPrivate; + friend class DockOverlay; + +protected: + /** + * This function returns an empty string and is only here to silence + * moc + */ + QString iconColors() const; + + /** + * This is a dummy function for the property system + */ + QColor iconColor() const { return QColor(); } + void setIconFrameColor(const QColor &color) { setIconColor(FrameColor, color); } + void setIconBackgroundColor(const QColor &color) { setIconColor(WindowBackgroundColor, color); } + void setIconOverlayColor(const QColor &color) { setIconColor(OverlayColor, color); } + void setIconArrowColor(const QColor &color) { setIconColor(ArrowColor, color); } + void setIconShadowColor(const QColor &color) { setIconColor(ShadowColor, color); } + +public: + /** + * Creates an overlay cross for the given overlay + */ + DockOverlayCross(DockOverlay *overlay); + + /** + * Virtual destructor + */ + virtual ~DockOverlayCross() override; + + /** + * Sets a certain icon color + */ + void setIconColor(eIconColor colorIndex, const QColor &color); + + /** + * Returns the icon color given by ColorIndex + */ + QColor iconColor(eIconColor colorIndex) const; + + /** + * Returns the dock widget area depending on the current cursor location. + * The function checks, if the mouse cursor is inside of any drop indicator + * widget and returns the corresponding DockWidgetArea. + */ + DockWidgetArea cursorLocation() const; + + /** + * Sets up the overlay cross for the given overlay mode + */ + void setupOverlayCross(DockOverlay::eMode mode); + + /** + * Recreates the overlay icons. + */ + void updateOverlayIcons(); + + /** + * Resets and updates the + */ + void reset(); + + /** + * Updates the current position + */ + void updatePosition(); + + /** + * A string with all icon colors to set. + * You can use this property to style the overly icon via CSS stylesheet + * file. The colors are set via a color identifier and a hex AARRGGBB value like + * in the example below. + * \code + * ADS--DockOverlayCross + * { + * qproperty-iconColors: "Frame=#ff3d3d3d Background=#ff929292 Overlay=#1f3d3d3d Arrow=#ffb4b4b4 Shadow=#40474747"; + * } + */ + void setIconColors(const QString &colors); + +protected: + virtual void showEvent(QShowEvent *event) override; + void setAreaWidgets(const QHash &widgets); +}; // DockOverlayCross + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/docksplitter.cpp b/src/libs/advanceddockingsystem/docksplitter.cpp new file mode 100644 index 00000000000..826ec519330 --- /dev/null +++ b/src/libs/advanceddockingsystem/docksplitter.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "docksplitter.h" + +#include "dockareawidget.h" + +#include +#include + +static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg) + +namespace ADS +{ + /** + * Private dock splitter data + */ + struct DockSplitterPrivate + { + DockSplitter *q; + int m_visibleContentCount = 0; + + DockSplitterPrivate(DockSplitter *parent) + : q(parent) + {} + }; + + DockSplitter::DockSplitter(QWidget *parent) + : QSplitter(parent) + , d(new DockSplitterPrivate(this)) + { + //setProperty("ads-splitter", true); // TODO + setProperty("minisplitter", true); + setChildrenCollapsible(false); + } + + DockSplitter::DockSplitter(Qt::Orientation orientation, QWidget *parent) + : QSplitter(orientation, parent) + , d(new DockSplitterPrivate(this)) + {} + + DockSplitter::~DockSplitter() + { + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; + } + + bool DockSplitter::hasVisibleContent() const + { + // TODO Cache or precalculate this to speed up + for (int i = 0; i < count(); ++i) { + if (!widget(i)->isHidden()) { + return true; + } + } + + return false; + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/docksplitter.h b/src/libs/advanceddockingsystem/docksplitter.h new file mode 100644 index 00000000000..45351a34e22 --- /dev/null +++ b/src/libs/advanceddockingsystem/docksplitter.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" + +#include + +namespace ADS { + +struct DockSplitterPrivate; + +/** + * Splitter used internally instead of QSplitter with some additional + * fuctionality. + */ +class ADS_EXPORT DockSplitter : public QSplitter +{ + Q_OBJECT +private: + DockSplitterPrivate *d; + friend struct DockSplitterPrivate; + +public: + DockSplitter(QWidget *parent = nullptr); + DockSplitter(Qt::Orientation orientation, QWidget *parent = nullptr); + + /** + * Prints debug info + */ + virtual ~DockSplitter() override; + + /** + * Returns true, if any of the internal widgets is visible + */ + bool hasVisibleContent() const; +}; // class DockSplitter + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidget.cpp b/src/libs/advanceddockingsystem/dockwidget.cpp new file mode 100644 index 00000000000..63ee33a6958 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockwidget.cpp @@ -0,0 +1,625 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dockwidget.h" + +#include "ads_globals.h" +#include "dockareawidget.h" +#include "dockcomponentsfactory.h" +#include "dockcontainerwidget.h" +#include "dockmanager.h" +#include "docksplitter.h" +#include "dockwidgettab.h" +#include "floatingdockcontainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg) + +namespace ADS +{ + /** + * Private data class of DockWidget class (pimpl) + */ + struct DockWidgetPrivate + { + DockWidget *q = nullptr; + QBoxLayout *m_layout = nullptr; + QWidget *m_widget = nullptr; + DockWidgetTab *m_tabWidget = nullptr; + DockWidget::DockWidgetFeatures m_features = DockWidget::DefaultDockWidgetFeatures; + DockManager *m_dockManager = nullptr; + DockAreaWidget *m_dockArea = nullptr; + QAction *m_toggleViewAction = nullptr; + bool m_closed = false; + QScrollArea *m_scrollArea = nullptr; + QToolBar *m_toolBar = nullptr; + Qt::ToolButtonStyle m_toolBarStyleDocked = Qt::ToolButtonIconOnly; + Qt::ToolButtonStyle m_toolBarStyleFloating = Qt::ToolButtonTextUnderIcon; + QSize m_toolBarIconSizeDocked = QSize(16, 16); + QSize m_toolBarIconSizeFloating = QSize(24, 24); + bool m_isFloatingTopLevel = false; + QList m_titleBarActions; + + /** + * Private data constructor + */ + DockWidgetPrivate(DockWidget *parent); + + /** + * Show dock widget + */ + void showDockWidget(); + + /** + * Hide dock widget. + */ + void hideDockWidget(); + + /** + * Hides a dock area if all dock widgets in the area are closed. + * This function updates the current selected tab and hides the parent + * dock area if it is empty + */ + void updateParentDockArea(); + + /** + * Setup the top tool bar + */ + void setupToolBar(); + + /** + * Setup the main scroll area + */ + void setupScrollArea(); + }; + // struct DockWidgetPrivate + + DockWidgetPrivate::DockWidgetPrivate(DockWidget *parent) + : q(parent) + {} + + void DockWidgetPrivate::showDockWidget() + { + if (!m_dockArea) { + FloatingDockContainer *floatingWidget = new FloatingDockContainer(q); + floatingWidget->resize(q->size()); + floatingWidget->show(); + } else { + m_dockArea->setCurrentDockWidget(q); + m_dockArea->toggleView(true); + m_tabWidget->show(); + QSplitter *splitter = internal::findParent(m_dockArea); + while (splitter && !splitter->isVisible()) { + splitter->show(); + splitter = internal::findParent(splitter); + } + + DockContainerWidget *container = m_dockArea->dockContainer(); + if (container->isFloating()) { + FloatingDockContainer *floatingWidget + = internal::findParent(container); + floatingWidget->show(); + } + } + } + + void DockWidgetPrivate::hideDockWidget() + { + m_tabWidget->hide(); + updateParentDockArea(); + } + + void DockWidgetPrivate::updateParentDockArea() + { + if (!m_dockArea) { + return; + } + + auto nextDockWidget = m_dockArea->nextOpenDockWidget(q); + if (nextDockWidget) { + m_dockArea->setCurrentDockWidget(nextDockWidget); + } else { + m_dockArea->hideAreaWithNoVisibleContent(); + } + } + + void DockWidgetPrivate::setupToolBar() + { + m_toolBar = new QToolBar(q); + m_toolBar->setObjectName("dockWidgetToolBar"); + m_layout->insertWidget(0, m_toolBar); + m_toolBar->setIconSize(QSize(16, 16)); + m_toolBar->toggleViewAction()->setEnabled(false); + m_toolBar->toggleViewAction()->setVisible(false); + QObject::connect(q, &DockWidget::topLevelChanged, q, &DockWidget::setToolbarFloatingStyle); + } + + void DockWidgetPrivate::setupScrollArea() + { + m_scrollArea = new QScrollArea(q); + m_scrollArea->setObjectName("dockWidgetScrollArea"); + m_scrollArea->setWidgetResizable(true); + m_layout->addWidget(m_scrollArea); + } + + DockWidget::DockWidget(const QString &uniqueId, QWidget *parent) + : QFrame(parent) + , d(new DockWidgetPrivate(this)) + { + d->m_layout = new QBoxLayout(QBoxLayout::TopToBottom); + d->m_layout->setContentsMargins(0, 0, 0, 0); + d->m_layout->setSpacing(0); + setLayout(d->m_layout); + setWindowTitle(uniqueId); // temporarily use unique id as title + setObjectName(uniqueId); + + d->m_tabWidget = componentsFactory()->createDockWidgetTab(this); + d->m_toggleViewAction = new QAction(uniqueId, this); + d->m_toggleViewAction->setCheckable(true); + connect(d->m_toggleViewAction, &QAction::triggered, this, &DockWidget::toggleView); + setToolbarFloatingStyle(false); + } + + DockWidget::~DockWidget() + { + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; + } + + void DockWidget::setToggleViewActionChecked(bool checked) + { + QAction *action = d->m_toggleViewAction; + //action->blockSignals(true); + action->setChecked(checked); + //action->blockSignals(false); + } + + void DockWidget::setWidget(QWidget *widget, eInsertMode insertMode) + { + QScrollArea *scrollAreaWidget = qobject_cast(widget); + if (scrollAreaWidget || ForceNoScrollArea == insertMode) { + d->m_layout->addWidget(widget); + if (scrollAreaWidget && scrollAreaWidget->viewport()) { + scrollAreaWidget->viewport()->setProperty("dockWidgetContent", true); + } + } else { + d->setupScrollArea(); + d->m_scrollArea->setWidget(widget); + } + + d->m_widget = widget; + d->m_widget->setProperty("dockWidgetContent", true); + } + + QWidget *DockWidget::takeWidget() + { + // TODO Shouldn't m_widget being set to nullptr?! + d->m_scrollArea->takeWidget(); + d->m_layout->removeWidget(d->m_widget); + d->m_widget->setParent(nullptr); + return d->m_widget; + } + + QWidget *DockWidget::widget() const { return d->m_widget; } + + DockWidgetTab *DockWidget::tabWidget() const { return d->m_tabWidget; } + + void DockWidget::setFeatures(DockWidgetFeatures features) + { + if (d->m_features == features) { + return; + } + d->m_features = features; + emit featuresChanged(d->m_features); + d->m_tabWidget->onDockWidgetFeaturesChanged(); + } + + void DockWidget::setFeature(DockWidgetFeature flag, bool on) + { + auto currentFeatures = features(); + internal::setFlag(currentFeatures, flag, on); + setFeatures(currentFeatures); + } + + DockWidget::DockWidgetFeatures DockWidget::features() const { return d->m_features; } + + DockManager *DockWidget::dockManager() const { return d->m_dockManager; } + + void DockWidget::setDockManager(DockManager *dockManager) { d->m_dockManager = dockManager; } + + DockContainerWidget *DockWidget::dockContainer() const + { + if (d->m_dockArea) { + return d->m_dockArea->dockContainer(); + } else { + return nullptr; + } + } + + DockAreaWidget *DockWidget::dockAreaWidget() const { return d->m_dockArea; } + + bool DockWidget::isFloating() const + { + if (!isInFloatingContainer()) { + return false; + } + + return dockContainer()->topLevelDockWidget() == this; + } + + bool DockWidget::isInFloatingContainer() const + { + auto container = dockContainer(); + if (!container) { + return false; + } + + if (!container->isFloating()) { + return false; + } + + return true; + } + + bool DockWidget::isClosed() const { return d->m_closed; } + + QAction *DockWidget::toggleViewAction() const { return d->m_toggleViewAction; } + + void DockWidget::setToggleViewActionMode(eToggleViewActionMode mode) + { + if (ActionModeToggle == mode) { + d->m_toggleViewAction->setCheckable(true); + d->m_toggleViewAction->setIcon(QIcon()); + } else { + d->m_toggleViewAction->setCheckable(false); + d->m_toggleViewAction->setIcon(d->m_tabWidget->icon()); + } + } + + void DockWidget::toggleView(bool open) + { + // If the toggle view action mode is ActionModeShow, then Open is always + // true if the sender is the toggle view action + QAction *action = qobject_cast(sender()); + if (action == d->m_toggleViewAction && !d->m_toggleViewAction->isCheckable()) { + open = true; + } + // If the dock widget state is different, then we really need to toggle + // the state. If we are in the right state, then we simply make this + // dock widget the current dock widget + if (d->m_closed != !open) { + toggleViewInternal(open); + } else if (open && d->m_dockArea) { + d->m_dockArea->setCurrentDockWidget(this); + } + } + + void DockWidget::toggleViewInternal(bool open) + { + DockContainerWidget *dockContainerWidget = dockContainer(); + DockWidget *topLevelDockWidgetBefore = dockContainerWidget + ? dockContainerWidget->topLevelDockWidget() + : nullptr; + + if (open) { + d->showDockWidget(); + } else { + d->hideDockWidget(); + } + d->m_closed = !open; + //d->m_toggleViewAction->blockSignals(true); + d->m_toggleViewAction->setChecked(open); + //d->m_toggleViewAction->blockSignals(false); + if (d->m_dockArea) { + d->m_dockArea->toggleDockWidgetView(this, open); + } + + if (open && topLevelDockWidgetBefore) { + DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetBefore, false); + } + + // Here we need to call the dockContainer() function again, because if + // this dock widget was unassigned before the call to showDockWidget() then + // it has a dock container now + dockContainerWidget = dockContainer(); + DockWidget *topLevelDockWidgetAfter = dockContainerWidget + ? dockContainerWidget->topLevelDockWidget() + : nullptr; + DockWidget::emitTopLevelEventForWidget(topLevelDockWidgetAfter, true); + FloatingDockContainer *floatingContainer = dockContainerWidget->floatingWidget(); + if (floatingContainer) { + floatingContainer->updateWindowTitle(); + } + + if (!open) { + emit closed(); + } + emit viewToggled(open); + } + + void DockWidget::setDockArea(DockAreaWidget *dockArea) + { + d->m_dockArea = dockArea; + d->m_toggleViewAction->setChecked(dockArea != nullptr && !this->isClosed()); + } + + void DockWidget::saveState(QXmlStreamWriter &stream) const + { + stream.writeStartElement("widget"); + stream.writeAttribute("name", objectName()); + stream.writeAttribute("closed", QVariant::fromValue(d->m_closed).toString()); + stream.writeEndElement(); + } + + void DockWidget::flagAsUnassigned() + { + d->m_closed = true; + setParent(d->m_dockManager); + setVisible(false); + setDockArea(nullptr); + tabWidget()->setParent(this); + } + + bool DockWidget::event(QEvent *event) + { + switch (event->type()) { + case QEvent::Hide: + emit visibilityChanged(false); + break; + + case QEvent::Show: + emit visibilityChanged(geometry().right() >= 0 && geometry().bottom() >= 0); + break; + + case QEvent::WindowTitleChange : + { + const auto title = windowTitle(); + if (d->m_tabWidget) { + d->m_tabWidget->setText(title); + } + if (d->m_toggleViewAction) { + d->m_toggleViewAction->setText(title); + } + if (d->m_dockArea) { + d->m_dockArea->markTitleBarMenuOutdated(); // update tabs menu + } + emit titleChanged(title); + } + break; + + default: + break; + } + + return Super::event(event); + } + +#ifndef QT_NO_TOOLTIP + void DockWidget::setTabToolTip(const QString &text) + { + if (d->m_tabWidget) { + d->m_tabWidget->setToolTip(text); + } + if (d->m_toggleViewAction) { + d->m_toggleViewAction->setToolTip(text); + } + if (d->m_dockArea) { + d->m_dockArea->markTitleBarMenuOutdated(); //update tabs menu + } + } +#endif + + void DockWidget::setIcon(const QIcon &icon) + { + d->m_tabWidget->setIcon(icon); + if (!d->m_toggleViewAction->isCheckable()) { + d->m_toggleViewAction->setIcon(icon); + } + } + + QIcon DockWidget::icon() const { return d->m_tabWidget->icon(); } + + QToolBar *DockWidget::toolBar() const { return d->m_toolBar; } + + QToolBar *DockWidget::createDefaultToolBar() + { + if (!d->m_toolBar) { + d->setupToolBar(); + } + + return d->m_toolBar; + } + + void DockWidget::setToolBar(QToolBar *toolBar) + { + if (d->m_toolBar) { + delete d->m_toolBar; + } + + d->m_toolBar = toolBar; + d->m_layout->insertWidget(0, d->m_toolBar); + connect(this, &DockWidget::topLevelChanged, this, &DockWidget::setToolbarFloatingStyle); + setToolbarFloatingStyle(isFloating()); + } + + void DockWidget::setToolBarStyle(Qt::ToolButtonStyle style, eState state) + { + if (StateFloating == state) { + d->m_toolBarStyleFloating = style; + } else { + d->m_toolBarStyleDocked = style; + } + + setToolbarFloatingStyle(isFloating()); + } + + Qt::ToolButtonStyle DockWidget::toolBarStyle(eState state) const + { + if (StateFloating == state) { + return d->m_toolBarStyleFloating; + } else { + return d->m_toolBarStyleDocked; + } + } + + void DockWidget::setToolBarIconSize(const QSize &iconSize, eState state) + { + if (StateFloating == state) { + d->m_toolBarIconSizeFloating = iconSize; + } else { + d->m_toolBarIconSizeDocked = iconSize; + } + + setToolbarFloatingStyle(isFloating()); + } + + QSize DockWidget::toolBarIconSize(eState state) const + { + if (StateFloating == state) { + return d->m_toolBarIconSizeFloating; + } else { + return d->m_toolBarIconSizeDocked; + } + } + + void DockWidget::setToolbarFloatingStyle(bool floating) + { + if (!d->m_toolBar) { + return; + } + + auto iconSize = floating ? d->m_toolBarIconSizeFloating : d->m_toolBarIconSizeDocked; + if (iconSize != d->m_toolBar->iconSize()) { + d->m_toolBar->setIconSize(iconSize); + } + + auto buttonStyle = floating ? d->m_toolBarStyleFloating : d->m_toolBarStyleDocked; + if (buttonStyle != d->m_toolBar->toolButtonStyle()) { + d->m_toolBar->setToolButtonStyle(buttonStyle); + } + } + + void DockWidget::emitTopLevelEventForWidget(DockWidget *topLevelDockWidget, bool floating) + { + if (topLevelDockWidget) { + topLevelDockWidget->dockAreaWidget()->updateTitleBarVisibility(); + topLevelDockWidget->emitTopLevelChanged(floating); + } + } + + void DockWidget::emitTopLevelChanged(bool floating) + { + if (floating != d->m_isFloatingTopLevel) { + d->m_isFloatingTopLevel = floating; + emit topLevelChanged(d->m_isFloatingTopLevel); + } + } + + void DockWidget::setClosedState(bool closed) { d->m_closed = closed; } + + QSize DockWidget::minimumSizeHint() const { return QSize(60, 40); } + + void DockWidget::setFloating() + { + if (isClosed()) { + return; + } + d->m_tabWidget->detachDockWidget(); + } + + void DockWidget::deleteDockWidget() + { + dockManager()->removeDockWidget(this); + deleteLater(); + d->m_closed = true; + } + + void DockWidget::closeDockWidget() + { + closeDockWidgetInternal(true); + } + + bool DockWidget::closeDockWidgetInternal(bool forceClose) + { + if (!forceClose) { + emit closeRequested(); + } + + if (!forceClose && features().testFlag(DockWidget::CustomCloseHandling)) { + return false; + } + + if (features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { + // If the dock widget is floating, then we check if we also need to + // delete the floating widget + if (isFloating()) { + FloatingDockContainer* floatingWidget = internal::findParent< + FloatingDockContainer *>(this); + if (floatingWidget->dockWidgets().count() == 1) { + floatingWidget->deleteLater(); + } else { + floatingWidget->hide(); + } + } + deleteDockWidget(); + } else { + toggleView(false); + } + + return true; + } + + void DockWidget::setTitleBarActions(QList actions) + { + d->m_titleBarActions = actions; + } + + QList DockWidget::titleBarActions() const + { + return d->m_titleBarActions; + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidget.h b/src/libs/advanceddockingsystem/dockwidget.h new file mode 100644 index 00000000000..8d98809cd64 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockwidget.h @@ -0,0 +1,491 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" + +#include + +class QToolBar; +class QXmlStreamWriter; + +namespace ADS { + +struct DockWidgetPrivate; +class DockWidgetTab; +class DockManager; +class DockContainerWidget; +class DockAreaWidget; +class DockContainerWidgetPrivate; +class FloatingDockContainer; + +/** + * The QDockWidget class provides a widget that can be docked inside a + * DockManager or floated as a top-level window on the desktop. + */ +class ADS_EXPORT DockWidget : public QFrame +{ + Q_OBJECT +private: + DockWidgetPrivate *d; ///< private data (pimpl) + friend struct DockWidgetPrivate; + + /** + * Adjusts the toolbar icon sizes according to the floating state + */ + void setToolbarFloatingStyle(bool topLevel); + +protected: + friend class DockContainerWidget; + friend class DockAreaWidget; + friend class FloatingDockContainer; + friend class DockManager; + friend struct DockManagerPrivate; + friend class DockContainerWidgetPrivate; + friend class DockAreaTabBar; + friend class DockWidgetTab; + friend struct DockWidgetTabPrivate; + friend struct DockAreaTitleBarPrivate; + + /** + * Assigns the dock manager that manages this dock widget + */ + void setDockManager(DockManager *dockManager); + + /** + * If this dock widget is inserted into a dock area, the dock area will + * be registered on this widget via this function. If a dock widget is + * removed from a dock area, this function will be called with nullptr + * value. + */ + void setDockArea(DockAreaWidget *dockArea); + + /** + * This function changes the toggle view action without emitting any + * signal + */ + void setToggleViewActionChecked(bool checked); + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter &stream) const; + + /** + * This is a helper function for the dock manager to flag this widget + * as unassigned. + * When calling the restore function, it may happen, that the saved state + * contains less dock widgets then currently available. All widgets whose + * data is not contained in the saved state, are flagged as unassigned + * after the restore process. If the user shows an unassigned dock widget, + * a floating widget will be created to take up the dock widget. + */ + void flagAsUnassigned(); + + /** + * Call this function to emit a topLevelChanged() signal and to update + * the dock area tool bar visibility + */ + static void emitTopLevelEventForWidget(DockWidget *topLevelDockWidget, bool floating); + + /** + * Use this function to emit a top level changed event. + * Do never use emit topLevelChanged(). Always use this function because + * it only emits a signal if the floating state has really changed + */ + void emitTopLevelChanged(bool floating); + + /** + * Internal function for modifying the closed state when restoring + * a saved docking state + */ + void setClosedState(bool closed); + + /** + * Internal toggle view function that does not check if the widget + * already is in the given state + */ + void toggleViewInternal(bool open); + + /** + * Internal close dock widget implementation. + * The function returns true if the dock widget has been closed or hidden + */ + bool closeDockWidgetInternal(bool forceClose = false); + +public: + using Super = QFrame; + + enum DockWidgetFeature { + DockWidgetClosable = 0x01, + DockWidgetMovable = 0x02,///< this feature is not properly implemented yet and is ignored + DockWidgetFloatable = 0x04, + DockWidgetDeleteOnClose = 0x08, ///< deletes the dock widget when it is closed + CustomCloseHandling = 0x10, + DefaultDockWidgetFeatures = DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable, + AllDockWidgetFeatures = DefaultDockWidgetFeatures | DockWidgetDeleteOnClose | CustomCloseHandling, + NoDockWidgetFeatures = 0x00 + }; + Q_DECLARE_FLAGS(DockWidgetFeatures, DockWidgetFeature) + + enum eState { StateHidden, StateDocked, StateFloating }; + + /** + * Sets the widget for the dock widget to widget. + * The InsertMode defines how the widget is inserted into the dock widget. + * The content of a dock widget should be resizable do a very small size to + * prevent the dock widget from blocking the resizing. To ensure, that a + * dock widget can be resized very well, it is better to insert the content+ + * widget into a scroll area or to provide a widget that is already a scroll + * area or that contains a scroll area. + * If the InsertMode is AutoScrollArea, the DockWidget tries to automatically + * detect how to insert the given widget. If the widget is derived from + * QScrollArea (i.e. an QAbstractItemView), then the widget is inserted + * directly. If the given widget is not a scroll area, the widget will be + * inserted into a scroll area. + * To force insertion into a scroll area, you can also provide the InsertMode + * ForceScrollArea. To prevent insertion into a scroll area, you can + * provide the InsertMode ForceNoScrollArea + */ + enum eInsertMode { AutoScrollArea, ForceScrollArea, ForceNoScrollArea }; + + /** + * This mode configures the behavior of the toggle view action. + * If the mode if ActionModeToggle, then the toggle view action is + * a checkable action to show / hide the dock widget. If the mode + * is ActionModeShow, then the action is not checkable an it will + * always show the dock widget if clicked. If the mode is ActionModeShow, + * the user can only close the DockWidget with the close button. + */ + enum eToggleViewActionMode { + ActionModeToggle, //!< ActionModeToggle + ActionModeShow //!< ActionModeShow + }; + + /** + * This constructor creates a dock widget with the given title. + * The title is the text that is shown in the window title when the dock + * widget is floating and it is the title that is shown in the titlebar + * or the tab of this dock widget if it is tabified. + * The object name of the dock widget is also set to the title. The + * object name is required by the dock manager to properly save and restore + * the state of the dock widget. That means, the title needs to be unique. + * If your title is not unique or if you would like to change the title + * during runtime, you need to set a unique object name explicitly + * by calling setObjectName() after construction. + * Use the layoutFlags to configure the layout of the dock widget. + */ + DockWidget(const QString &uniqueId, QWidget *parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~DockWidget() override; + + /** + * We return a fixed minimum size hint for all dock widgets + */ + virtual QSize minimumSizeHint() const override; + + /** + * Sets the widget for the dock widget to widget. + * The InsertMode defines how the widget is inserted into the dock widget. + * The content of a dock widget should be resizable do a very small size to + * prevent the dock widget from blocking the resizing. To ensure, that a + * dock widget can be resized very well, it is better to insert the content+ + * widget into a scroll area or to provide a widget that is already a scroll + * area or that contains a scroll area. + * If the InsertMode is AutoScrollArea, the DockWidget tries to automatically + * detect how to insert the given widget. If the widget is derived from + * QScrollArea (i.e. an QAbstractItemView), then the widget is inserted + * directly. If the given widget is not a scroll area, the widget will be + * inserted into a scroll area. + * To force insertion into a scroll area, you can also provide the InsertMode + * ForceScrollArea. To prevent insertion into a scroll area, you can + * provide the InsertMode ForceNoScrollArea + */ + void setWidget(QWidget *widget, eInsertMode insertMode = AutoScrollArea); + + /** + * Remove the widget from the dock and give ownership back to the caller + */ + QWidget *takeWidget(); + + /** + * Returns the widget for the dock widget. This function returns zero if + * the widget has not been set. + */ + QWidget *widget() const; + + /** + * Returns the tab widget of this dock widget that is shown in the dock + * area title bar + */ + DockWidgetTab *tabWidget() const; + + /** + * Sets, whether the dock widget is movable, closable, and floatable. + */ + void setFeatures(DockWidgetFeatures features); + + /** + * Sets the feature flag for this dock widget if on is true; otherwise + * clears the flag. + */ + void setFeature(DockWidgetFeature flag, bool on); + + /** + * This property holds whether the dock widget is movable, closable, and + * floatable. + * By default, this property is set to a combination of DockWidgetClosable, + * DockWidgetMovable and DockWidgetFloatable. + */ + DockWidgetFeatures features() const; + + /** + * Returns the dock manager that manages the dock widget or 0 if the widget + * has not been assigned to any dock manager yet + */ + DockManager *dockManager() const; + + /** + * Returns the dock container widget this dock area widget belongs to or 0 + * if this dock widget has not been docked yet + */ + DockContainerWidget *dockContainer() const; + + /** + * Returns the dock area widget this dock widget belongs to or 0 + * if this dock widget has not been docked yet + */ + DockAreaWidget *dockAreaWidget() const; + + /** + * This property holds whether the dock widget is floating. + * A dock widget is only floating, if it is the one and only widget inside + * of a floating container. If there are more than one dock widget in a + * floating container, the all dock widgets are docked and not floating. + */ + bool isFloating() const; + + /** + * This function returns true, if this dock widget is in a floating. + * The function returns true, if the dock widget is floating and it also + * returns true if it is docked inside of a floating container. + */ + bool isInFloatingContainer() const; + + /** + * Returns true, if this dock widget is closed. + */ + bool isClosed() const; + + /** + * Returns a checkable action that can be used to show or close this dock widget. + * The action's text is set to the dock widget's window title. + */ + QAction *toggleViewAction() const; + + /** + * Configures the behavior of the toggle view action. + * \see eToggleViewActionMode for a detailed description + */ + void setToggleViewActionMode(eToggleViewActionMode mode); + + /** + * Sets the dock widget icon that is shown in tabs and in toggle view + * actions + */ + void setIcon(const QIcon &icon); + + /** + * Returns the icon that has been assigned to the dock widget + */ + QIcon icon() const; + + /** + * If the WithToolBar layout flag is enabled, then this function returns + * the dock widget toolbar. If the flag is disabled, the function returns + * a nullptr. + * This function returns the dock widget top tool bar. + * If no toolbar is assigned, this function returns nullptr. To get a valid + * toolbar you either need to create a default empty toolbar via + * createDefaultToolBar() function or you need to assign you custom + * toolbar via setToolBar(). + */ + QToolBar *toolBar() const; + + /** + * If you would like to use the default top tool bar, then call this + * function to create the default tool bar. + * After this function the toolBar() function will return a valid toolBar() + * object. + */ + QToolBar *createDefaultToolBar(); + + /** + * Assign a new tool bar that is shown above the content widget. + * The dock widget will become the owner of the tool bar and deletes it + * on destruction + */ + void setToolBar(QToolBar *toolBar); + + /** + * This function sets the tool button style for the given dock widget state. + * It is possible to switch the tool button style depending on the state. + * If a dock widget is floating, then here are more space and it is + * possible to select a style that requires more space like + * Qt::ToolButtonTextUnderIcon. For the docked state Qt::ToolButtonIconOnly + * might be better. + */ + void setToolBarStyle(Qt::ToolButtonStyle style, eState state); + + /** + * Returns the tool button style for the given docking state. + * \see setToolBarStyle() + */ + Qt::ToolButtonStyle toolBarStyle(eState state) const; + + /** + * This function sets the tool button icon size for the given state. + * If a dock widget is floating, there is more space an increasing the + * icon size is possible. For docked widgets, small icon sizes, eg. 16 x 16 + * might be better. + */ + void setToolBarIconSize(const QSize &iconSize, eState state); + + /** + * Returns the icon size for a given docking state. + * \see setToolBarIconSize() + */ + QSize toolBarIconSize(eState state) const; + + /** + * Set the actions that will be shown in the dock area title bar + * if this dock widget is the active tab. + * You should not add to many actions to the title bar, because this + * will remove the available space for the tabs. If you have a number + * of actions, just add an action with a menu to show a popup menu + * button in the title bar. + */ + void setTitleBarActions(QList actions); + + /** + * Returns a list of actions that will be inserted into the dock area title + * bar if this dock widget becomes the current widget + */ + virtual QList titleBarActions() const; + +#ifndef QT_NO_TOOLTIP + /** + * This is function sets text tooltip for title bar widget + * and tooltip for toggle view action + */ + void setTabToolTip(const QString &text); +#endif + +public: // reimplements QFrame + /** + * Emits titleChanged signal if title change event occurs + */ + virtual bool event(QEvent *event) override; + + /** + * This property controls whether the dock widget is open or closed. + * The toogleViewAction triggers this slot + */ + void toggleView(bool open = true); + + /** + * This function will make a docked widget floating + */ + void setFloating(); + + /** + * This function will delete the dock widget and its content from the + * docking system + */ + void deleteDockWidget(); + + /** + * Closes the dock widget + */ + void closeDockWidget(); + +signals: + /** + * This signal is emitted if the dock widget is opened or closed + */ + void viewToggled(bool open); + + /** + * This signal is emitted if the dock widget is closed + */ + void closed(); + + /** + * This signal is emitted if the window title of this dock widget + * changed + */ + void titleChanged(const QString &title); + + /** + * This signal is emitted when the floating property changes. + * The topLevel parameter is true if the dock widget is now floating; + * otherwise it is false. + */ + void topLevelChanged(bool topLevel); + + /** + * This signal is emitted, if close is requested + */ + void closeRequested(); + + /** + * This signal is emitted when the dock widget becomes visible (or invisible). + * This happens when the widget is hidden or shown, as well as when it is + * docked in a tabbed dock area and its tab becomes selected or unselected. + */ + void visibilityChanged(bool visible); + + /** + * This signal is emitted when the features property changes. + * The features parameter gives the new value of the property. + */ + void featuresChanged(DockWidgetFeatures features); +}; // class DockWidget + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidgettab.cpp b/src/libs/advanceddockingsystem/dockwidgettab.cpp new file mode 100644 index 00000000000..636010ce437 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockwidgettab.cpp @@ -0,0 +1,525 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dockwidgettab.h" + +#include "ads_globals.h" +#include "dockareawidget.h" +#include "dockmanager.h" +#include "dockoverlay.h" +#include "dockwidget.h" +#include "elidinglabel.h" +#include "floatingdockcontainer.h" +#include "floatingdragpreview.h" +#include "iconprovider.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg) + +namespace ADS +{ + using TabLabelType = ElidingLabel; + + /** + * Private data class of DockWidgetTab class (pimpl) + */ + struct DockWidgetTabPrivate + { + DockWidgetTab *q; + DockWidget *m_dockWidget; + QLabel *m_iconLabel = nullptr; + TabLabelType *m_titleLabel; + QPoint m_globalDragStartMousePosition; + QPoint m_dragStartMousePosition; + bool m_isActiveTab = false; + DockAreaWidget *m_dockArea = nullptr; + eDragState m_dragState = DraggingInactive; + AbstractFloatingWidget *m_floatingWidget = nullptr; + QIcon m_icon; + QAbstractButton *m_closeButton = nullptr; + QSpacerItem *m_iconTextSpacer; + QPoint m_tabDragStartPosition; + + /** + * Private data constructor + */ + DockWidgetTabPrivate(DockWidgetTab *parent); + + /** + * Creates the complete layout including all controls + */ + void createLayout(); + + /** + * Moves the tab depending on the position in the given mouse event + */ + void moveTab(QMouseEvent *event); + + /** + * Test function for current drag state + */ + bool isDraggingState(eDragState dragState) const { return this->m_dragState == dragState; } + + /** + * Starts floating of the dock widget that belongs to this title bar + * Returns true, if floating has been started and false if floating + * is not possible for any reason + */ + bool startFloating(eDragState draggingState = DraggingFloatingWidget); + + /** + * Returns true if the given config flag is set + */ + bool testConfigFlag(DockManager::eConfigFlag flag) const + { + return DockManager::configFlags().testFlag(flag); + } + + /** + * Creates the close button as QPushButton or as QToolButton + */ + QAbstractButton *createCloseButton() const + { + if (testConfigFlag(DockManager::TabCloseButtonIsToolButton)) { + auto button = new QToolButton(); + button->setAutoRaise(true); + return button; + } else { + return new QPushButton(); + } + } + + template + AbstractFloatingWidget *createFloatingWidget(T *widget, bool opaqueUndocking) + { + if (opaqueUndocking) { + return new FloatingDockContainer(widget); + } else { + auto w = new FloatingDragPreview(widget); + QObject::connect(w, &FloatingDragPreview::draggingCanceled, q, [=]() { + m_dragState = DraggingInactive; + }); + return w; + } + } + + /** + * Saves the drag start position in global and local coordinates + */ + void saveDragStartMousePosition(const QPoint &globalPos) + { + m_globalDragStartMousePosition = globalPos; + m_dragStartMousePosition = q->mapFromGlobal(globalPos); + } + }; + // struct DockWidgetTabPrivate + + DockWidgetTabPrivate::DockWidgetTabPrivate(DockWidgetTab *parent) + : q(parent) + {} + + void DockWidgetTabPrivate::createLayout() + { + m_titleLabel = new TabLabelType(); + m_titleLabel->setElideMode(Qt::ElideRight); + m_titleLabel->setText(m_dockWidget->windowTitle()); + m_titleLabel->setObjectName("dockWidgetTabLabel"); + m_titleLabel->setAlignment(Qt::AlignCenter); + QObject::connect(m_titleLabel, &ElidingLabel::elidedChanged, q, &DockWidgetTab::elidedChanged); + + m_closeButton = createCloseButton(); + m_closeButton->setObjectName("tabCloseButton"); + internal::setButtonIcon(m_closeButton, QStyle::SP_TitleBarCloseButton, TabCloseIcon); + m_closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + q->onDockWidgetFeaturesChanged(); + internal::setToolTip(m_closeButton, QObject::tr("Close Tab")); + QObject::connect(m_closeButton, + &QAbstractButton::clicked, + q, + &DockWidgetTab::closeRequested); + + QFontMetrics fontMetrics(m_titleLabel->font()); + int spacing = qRound(fontMetrics.height() / 4.0); + + // Fill the layout + QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::LeftToRight); + boxLayout->setContentsMargins(2 * spacing, 0, 0, 0); + boxLayout->setSpacing(0); + q->setLayout(boxLayout); + boxLayout->addWidget(m_titleLabel, 1); + boxLayout->addSpacing(spacing); + boxLayout->addWidget(m_closeButton); + boxLayout->addSpacing(qRound(spacing * 4.0 / 3.0)); + boxLayout->setAlignment(Qt::AlignCenter); + + m_titleLabel->setVisible(true); + } + + void DockWidgetTabPrivate::moveTab(QMouseEvent *event) + { + event->accept(); + QPoint distance = event->globalPos() - m_globalDragStartMousePosition; + distance.setY(0); + auto targetPos = distance + m_tabDragStartPosition; + targetPos.rx() = qMax(targetPos.x(), 0); + targetPos.rx() = qMin(q->parentWidget()->rect().right() - q->width() + 1, targetPos.rx()); + q->move(targetPos); + q->raise(); + } + + bool DockWidgetTabPrivate::startFloating(eDragState draggingState) + { + auto dockContainer = m_dockWidget->dockContainer(); + qCInfo(adsLog) << "isFloating " << dockContainer->isFloating(); + qCInfo(adsLog) << "areaCount " << dockContainer->dockAreaCount(); + qCInfo(adsLog) << "widgetCount " << m_dockWidget->dockAreaWidget()->dockWidgetsCount(); + // if this is the last dock widget inside of this floating widget, + // then it does not make any sense, to make it floating because + // it is already floating + if (dockContainer->isFloating() && (dockContainer->visibleDockAreaCount() == 1) + && (m_dockWidget->dockAreaWidget()->dockWidgetsCount() == 1)) { + return false; + } + + qCInfo(adsLog) << "startFloating"; + m_dragState = draggingState; + QSize size = m_dockArea->size(); + AbstractFloatingWidget *floatingWidget = nullptr; + bool opaqueUndocking = DockManager::configFlags().testFlag(DockManager::OpaqueUndocking) + || (DraggingFloatingWidget != draggingState); + + // If section widget has multiple tabs, we take only one tab + // If it has only one single tab, we can move the complete + // dock area into floating widget + if (m_dockArea->dockWidgetsCount() > 1) { + floatingWidget = createFloatingWidget(m_dockWidget, opaqueUndocking); + } else { + floatingWidget = createFloatingWidget(m_dockArea, opaqueUndocking); + } + + if (DraggingFloatingWidget == draggingState) { + floatingWidget->startFloating(m_dragStartMousePosition, size, DraggingFloatingWidget, q); + auto Overlay = m_dockWidget->dockManager()->containerOverlay(); + Overlay->setAllowedAreas(OuterDockAreas); + this->m_floatingWidget = floatingWidget; + } else { + floatingWidget->startFloating(m_dragStartMousePosition, size, DraggingInactive, nullptr); + } + + return true; + } + + DockWidgetTab::DockWidgetTab(DockWidget *dockWidget, QWidget *parent) + : QFrame(parent) + , d(new DockWidgetTabPrivate(this)) + { + setAttribute(Qt::WA_NoMousePropagation, true); + d->m_dockWidget = dockWidget; + d->createLayout(); + } + + DockWidgetTab::~DockWidgetTab() + { + qCInfo(adsLog) << Q_FUNC_INFO; + delete d; + } + + void DockWidgetTab::mousePressEvent(QMouseEvent *event) + { + if (event->button() == Qt::LeftButton) { + event->accept(); + d->saveDragStartMousePosition(event->globalPos()); + d->m_dragState = DraggingMousePressed; + emit clicked(); + return; + } + Super::mousePressEvent(event); + } + + void DockWidgetTab::mouseReleaseEvent(QMouseEvent *event) + { + if (event->button() == Qt::LeftButton) { + auto currentDragState = d->m_dragState; + d->m_globalDragStartMousePosition = QPoint(); + d->m_dragStartMousePosition = QPoint(); + d->m_dragState = DraggingInactive; + + switch (currentDragState) { + case DraggingTab: + // End of tab moving, emit signal + if (d->m_dockArea) { + emit moved(event->globalPos()); + } + break; + + case DraggingFloatingWidget: + d->m_floatingWidget->finishDragging(); + break; + + default:; // do nothing + } + } + + Super::mouseReleaseEvent(event); + } + + void DockWidgetTab::mouseMoveEvent(QMouseEvent *event) + { + if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { + d->m_dragState = DraggingInactive; + Super::mouseMoveEvent(event); + return; + } + + // move floating window + if (d->isDraggingState(DraggingFloatingWidget)) { + d->m_floatingWidget->moveFloating(); + Super::mouseMoveEvent(event); + return; + } + + // move tab + if (d->isDraggingState(DraggingTab)) { + // Moving the tab is always allowed because it does not mean moving the + // dock widget around + d->moveTab(event); + } + + auto mappedPos = mapToParent(event->pos()); + bool mouseOutsideBar = (mappedPos.x() < 0) || (mappedPos.x() > parentWidget()->rect().right()); + // Maybe a fixed drag distance is better here ? + int dragDistanceY = qAbs(d->m_globalDragStartMousePosition.y() - event->globalPos().y()); + if (dragDistanceY >= DockManager::startDragDistance() || mouseOutsideBar) { + // If this is the last dock area in a dock container with only + // one single dock widget it does not make sense to move it to a new + // floating widget and leave this one empty + if (d->m_dockArea->dockContainer()->isFloating() + && d->m_dockArea->openDockWidgetsCount() == 1 + && d->m_dockArea->dockContainer()->visibleDockAreaCount() == 1) { + return; + } + + // Floating is only allowed for widgets that are floatable + // If we do non opaque undocking, then can create the drag preview + // if the widget is movable. + auto features = d->m_dockWidget->features(); + if (features.testFlag(DockWidget::DockWidgetFloatable) + || (features.testFlag(DockWidget::DockWidgetMovable) + && !DockManager::testConfigFlag(DockManager::OpaqueUndocking))) { + // If we undock, we need to restore the initial position of this + // tab because it looks strange if it remains on its dragged position + if (d->isDraggingState(DraggingTab) + && !DockManager::configFlags().testFlag(DockManager::OpaqueUndocking)) { + parentWidget()->layout()->update(); + } + d->startFloating(); + } + return; + } else if (d->m_dockArea->openDockWidgetsCount() > 1 + && (event->globalPos() - d->m_globalDragStartMousePosition).manhattanLength() + >= QApplication::startDragDistance()) // Wait a few pixels before start moving + { + // If we start dragging the tab, we save its initial position to + // restore it later + if (DraggingTab != d->m_dragState) { + d->m_tabDragStartPosition = this->pos(); + } + d->m_dragState = DraggingTab; + return; + } + + Super::mouseMoveEvent(event); + } + + void DockWidgetTab::contextMenuEvent(QContextMenuEvent *event) + { + event->accept(); + if (d->isDraggingState(DraggingFloatingWidget)) { + return; + } + + d->saveDragStartMousePosition(event->globalPos()); + QMenu menu(this); + + const bool isFloatable = d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable); + const bool isNotOnlyTabInContainer = !d->m_dockArea->dockContainer()->hasTopLevelDockWidget(); + const bool isDetachable = isFloatable && isNotOnlyTabInContainer; + + auto action = menu.addAction(tr("Detach"), this, &DockWidgetTab::detachDockWidget); + action->setEnabled(isDetachable); + menu.addSeparator(); + action = menu.addAction(tr("Close"), this, &DockWidgetTab::closeRequested); + action->setEnabled(isClosable()); + menu.addAction(tr("Close Others"), this, &DockWidgetTab::closeOtherTabsRequested); + menu.exec(event->globalPos()); + } + + bool DockWidgetTab::isActiveTab() const { return d->m_isActiveTab; } + + void DockWidgetTab::setActiveTab(bool active) + { + bool dockWidgetClosable = d->m_dockWidget->features().testFlag( + DockWidget::DockWidgetClosable); + bool activeTabHasCloseButton = d->testConfigFlag(DockManager::ActiveTabHasCloseButton); + bool allTabsHaveCloseButton = d->testConfigFlag(DockManager::AllTabsHaveCloseButton); + bool tabHasCloseButton = (activeTabHasCloseButton && active) | allTabsHaveCloseButton; + d->m_closeButton->setVisible(dockWidgetClosable && tabHasCloseButton); + if (d->m_isActiveTab == active) { + return; + } + + d->m_isActiveTab = active; + + style()->unpolish(this); + style()->polish(this); + d->m_titleLabel->style()->unpolish(d->m_titleLabel); + d->m_titleLabel->style()->polish(d->m_titleLabel); + update(); + updateGeometry(); + + emit activeTabChanged(); + } + + DockWidget *DockWidgetTab::dockWidget() const { return d->m_dockWidget; } + + void DockWidgetTab::setDockAreaWidget(DockAreaWidget *dockArea) { d->m_dockArea = dockArea; } + + DockAreaWidget *DockWidgetTab::dockAreaWidget() const { return d->m_dockArea; } + + void DockWidgetTab::setIcon(const QIcon &icon) + { + QBoxLayout *boxLayout = qobject_cast(layout()); + if (!d->m_iconLabel && icon.isNull()) { + return; + } + + if (!d->m_iconLabel) { + d->m_iconLabel = new QLabel(); + d->m_iconLabel->setAlignment(Qt::AlignVCenter); + d->m_iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + internal::setToolTip(d->m_iconLabel, d->m_titleLabel->toolTip()); + boxLayout->insertWidget(0, d->m_iconLabel, Qt::AlignVCenter); + boxLayout->insertSpacing(1, qRound(1.5 * boxLayout->contentsMargins().left() / 2.0)); + } else if (icon.isNull()) { + // Remove icon label and spacer item + boxLayout->removeWidget(d->m_iconLabel); + boxLayout->removeItem(boxLayout->itemAt(0)); + delete d->m_iconLabel; + d->m_iconLabel = nullptr; + } + + d->m_icon = icon; + if (d->m_iconLabel) { + d->m_iconLabel->setPixmap( + icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this))); + d->m_iconLabel->setVisible(true); + } + } + + const QIcon &DockWidgetTab::icon() const { return d->m_icon; } + + QString DockWidgetTab::text() const { return d->m_titleLabel->text(); } + + void DockWidgetTab::mouseDoubleClickEvent(QMouseEvent *event) + { + // If this is the last dock area in a dock container it does not make + // sense to move it to a new floating widget and leave this one empty + if ((!d->m_dockArea->dockContainer()->isFloating() || d->m_dockArea->dockWidgetsCount() > 1) + && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) { + d->saveDragStartMousePosition(event->globalPos()); + d->startFloating(DraggingInactive); + } + + Super::mouseDoubleClickEvent(event); + } + + void DockWidgetTab::setVisible(bool visible) + { + // Just here for debugging to insert debug output + Super::setVisible(visible); + } + + void DockWidgetTab::setText(const QString &title) { d->m_titleLabel->setText(title); } + bool DockWidgetTab::isTitleElided() const { return d->m_titleLabel->isElided(); } + + bool DockWidgetTab::isClosable() const + { + return d->m_dockWidget + && d->m_dockWidget->features().testFlag(DockWidget::DockWidgetClosable); + } + + void DockWidgetTab::detachDockWidget() + { + if (!d->m_dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) { + return; + } + d->saveDragStartMousePosition(QCursor::pos()); + d->startFloating(DraggingInactive); + } + + bool DockWidgetTab::event(QEvent *event) + { +#ifndef QT_NO_TOOLTIP + if (event->type() == QEvent::ToolTipChange) { + const auto text = toolTip(); + d->m_titleLabel->setToolTip(text); + } +#endif + return Super::event(event); + } + + void DockWidgetTab::onDockWidgetFeaturesChanged() + { + auto features = d->m_dockWidget->features(); + auto sizePolicy = d->m_closeButton->sizePolicy(); + sizePolicy.setRetainSizeWhenHidden( + features.testFlag(DockWidget::DockWidgetClosable) + && d->testConfigFlag(DockManager::RetainTabSizeWhenCloseButtonHidden)); + d->m_closeButton->setSizePolicy(sizePolicy); + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/dockwidgettab.h b/src/libs/advanceddockingsystem/dockwidgettab.h new file mode 100644 index 00000000000..95e973fc4f1 --- /dev/null +++ b/src/libs/advanceddockingsystem/dockwidgettab.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" + +#include + +namespace ADS { + +class DockWidget; +class DockAreaWidget; +struct DockWidgetTabPrivate; + +/** + * A dock widget tab that shows a title and an icon. + * The dock widget tab is shown in the dock area title bar to switch between + * tabbed dock widgets + */ +class ADS_EXPORT DockWidgetTab : public QFrame +{ + Q_OBJECT + Q_PROPERTY(bool activeTab READ isActiveTab WRITE setActiveTab NOTIFY activeTabChanged) + +private: + DockWidgetTabPrivate *d; ///< private data (pimpl) + friend struct DockWidgetTabPrivate; + friend class DockWidget; + void onDockWidgetFeaturesChanged(); + void detachDockWidget(); + +protected: + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + virtual void contextMenuEvent(QContextMenuEvent *event) override; + + /** + * Double clicking the tab widget makes the assigned dock widget floating + */ + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + +public: + using Super = QFrame; + /** + * Default Constructor + * param[in] DockWidget The dock widget this title bar belongs to + * param[in] parent The parent widget of this title bar + */ + DockWidgetTab(DockWidget *DockWidget, QWidget *parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~DockWidgetTab() override; + + /** + * Returns true, if this is the active tab + */ + bool isActiveTab() const; + + /** + * Set this true to make this tab the active tab + */ + void setActiveTab(bool active); + + /** + * Returns the dock widget this title widget belongs to + */ + DockWidget *dockWidget() const; + + /** + * Sets the dock area widget the dockWidget returned by dockWidget() + * function belongs to. + */ + void setDockAreaWidget(DockAreaWidget *dockArea); + + /** + * Returns the dock area widget this title bar belongs to. + * \return This function returns 0 if the dock widget that owns this title + * bar widget has not been added to any dock area yet. + */ + DockAreaWidget *dockAreaWidget() const; + + /** + * Sets the icon to show in title bar + */ + void setIcon(const QIcon &icon); + + /** + * Returns the icon + */ + const QIcon &icon() const; + + /** + * Returns the tab text + */ + QString text() const; + + /** + * Sets the tab text + */ + void setText(const QString &title); + + /** + * Returns true if text is elided on the tab's title + */ + bool isTitleElided() const; + + /** + * This function returns true if the assigned dock widget is closable + */ + bool isClosable() const; + + /** + * Track event ToolTipChange and set child ToolTip + */ + virtual bool event(QEvent *event) override; + + virtual void setVisible(bool visible) override; + +signals: + void activeTabChanged(); + void clicked(); + void closeRequested(); + void closeOtherTabsRequested(); + void moved(const QPoint &globalPosition); + void elidedChanged(bool elided); +}; // class DockWidgetTab + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/elidinglabel.cpp b/src/libs/advanceddockingsystem/elidinglabel.cpp new file mode 100644 index 00000000000..dfd812bae2a --- /dev/null +++ b/src/libs/advanceddockingsystem/elidinglabel.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "elidinglabel.h" + +#include + +namespace ADS { + /** + * Private data of public ClickableLabel + */ + struct ElidingLabelPrivate + { + ElidingLabel *q; + Qt::TextElideMode m_elideMode = Qt::ElideNone; + QString m_text; + bool m_isElided = false; + + ElidingLabelPrivate(ElidingLabel *parent) + : q(parent) + {} + + void elideText(int width); + + /** + * Convenience function to check if the + */ + bool isModeElideNone() const { return Qt::ElideNone == m_elideMode; } + }; + + void ElidingLabelPrivate::elideText(int width) + { + if (isModeElideNone()) + return; + + QFontMetrics fm = q->fontMetrics(); + QString str = fm.elidedText(m_text, m_elideMode, width - q->margin() * 2 - q->indent()); + if (str == QChar(0x2026)) + str = m_text.at(0); + + bool wasElided = m_isElided; + m_isElided = str != m_text; + if (m_isElided != wasElided) + emit q->elidedChanged(m_isElided); + + q->QLabel::setText(str); + } + + ElidingLabel::ElidingLabel(QWidget *parent, Qt::WindowFlags flags) + : QLabel(parent, flags) + , d(new ElidingLabelPrivate(this)) + {} + + ElidingLabel::ElidingLabel(const QString &text, QWidget *parent, Qt::WindowFlags flags) + : QLabel(text, parent, flags) + , d(new ElidingLabelPrivate(this)) + { + d->m_text = text; + internal::setToolTip(this, text); + } + + ElidingLabel::~ElidingLabel() + { + delete d; + } + + Qt::TextElideMode ElidingLabel::elideMode() const + { + return d->m_elideMode; + } + + void ElidingLabel::setElideMode(Qt::TextElideMode mode) + { + d->m_elideMode = mode; + d->elideText(size().width()); + } + + bool ElidingLabel::isElided() const + { + return d->m_isElided; + } + + void ElidingLabel::mouseReleaseEvent(QMouseEvent *event) + { + Super::mouseReleaseEvent(event); + if (event->button() != Qt::LeftButton) { + return; + } + + emit clicked(); + } + + void ElidingLabel::mouseDoubleClickEvent(QMouseEvent *event) + { + Q_UNUSED(event) + emit doubleClicked(); + Super::mouseDoubleClickEvent(event); + } + + void ElidingLabel::resizeEvent(QResizeEvent *event) + { + if (!d->isModeElideNone()) { + d->elideText(event->size().width()); + } + Super::resizeEvent(event); + } + + QSize ElidingLabel::minimumSizeHint() const + { + if (pixmap() != nullptr || d->isModeElideNone()) { + return QLabel::minimumSizeHint(); + } + const QFontMetrics &fm = fontMetrics(); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + QSize size(fm.horizontalAdvance(d->m_text.left(2) + "…"), fm.height()); +#else + QSize size(fm.width(d->m_text.left(2) + "…"), fm.height()); +#endif + return size; + } + + QSize ElidingLabel::sizeHint() const + { + if (pixmap() != nullptr || d->isModeElideNone()) { + return QLabel::sizeHint(); + } + const QFontMetrics &fm = fontMetrics(); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + QSize size(fm.horizontalAdvance(d->m_text), QLabel::sizeHint().height()); +#else + QSize size(fm.width(d->m_text), QLabel::sizeHint().height()); +#endif + return size; + } + + void ElidingLabel::setText(const QString &text) + { + if (d->isModeElideNone()) { + Super::setText(text); + } else { + d->m_text = text; + internal::setToolTip(this, text); + d->elideText(this->size().width()); + } + } + + QString ElidingLabel::text() const + { + return d->m_text; + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/elidinglabel.h b/src/libs/advanceddockingsystem/elidinglabel.h new file mode 100644 index 00000000000..5bfeb28b06b --- /dev/null +++ b/src/libs/advanceddockingsystem/elidinglabel.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" + +#include + +namespace ADS { + +struct ElidingLabelPrivate; + +/** + * A QLabel that supports eliding text. + * Because the functions setText() and text() are no virtual functions setting + * and reading the text via a pointer to the base class QLabel does not work + * properly + */ +class ADS_EXPORT ElidingLabel : public QLabel +{ + Q_OBJECT +private: + ElidingLabelPrivate *d; + friend struct ElidingLabelPrivate; + +protected: + virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; + virtual void mouseDoubleClickEvent(QMouseEvent *ev) override; + +public: + using Super = QLabel; + + ElidingLabel(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::Widget); + ElidingLabel(const QString &text, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::Widget); + virtual ~ElidingLabel() override; + + /** + * Returns the text elide mode. + * The default mode is ElideNone + */ + Qt::TextElideMode elideMode() const; + + /** + * Sets the text elide mode + */ + void setElideMode(Qt::TextElideMode mode); + + /** + * This function indicates whether the text on this label is currently elided + */ + bool isElided() const; + +public: // reimplements QLabel + virtual QSize minimumSizeHint() const override; + virtual QSize sizeHint() const override; + void setText(const QString &text); + QString text() const; + +signals: + /** + * This signal is emitted if the user clicks on the label (i.e. pressed + * down then released while the mouse cursor is inside the label) + */ + void clicked(); + + /** + * This signal is emitted if the user does a double click on the label + */ + void doubleClicked(); + + /** + * This signal is emitted when isElided() state of this label is changed + */ + void elidedChanged(bool elided); +}; //class ElidingLabel + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp new file mode 100644 index 00000000000..a9c1c33510f --- /dev/null +++ b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp @@ -0,0 +1,563 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "floatingdockcontainer.h" + +#include "dockareawidget.h" +#include "dockcontainerwidget.h" +#include "dockmanager.h" +#include "dockoverlay.h" +#include "dockwidget.h" +#include "linux/floatingwidgettitlebar.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg) + +namespace ADS +{ + AbstractFloatingWidget::~AbstractFloatingWidget() = default; + + static unsigned int zOrderCounter = 0; + /** + * Private data class of FloatingDockContainer class (pimpl) + */ + struct FloatingDockContainerPrivate + { + FloatingDockContainer *q; + DockContainerWidget *m_dockContainer; + unsigned int m_zOrderIndex = ++zOrderCounter; + QPointer m_dockManager; + eDragState m_draggingState = DraggingInactive; + QPoint m_dragStartMousePosition; + DockContainerWidget *m_dropContainer = nullptr; + DockAreaWidget *m_singleDockArea = nullptr; + QWidget *m_mouseEventHandler = nullptr; + FloatingWidgetTitleBar *m_titleBar = nullptr; + + /** + * Private data constructor + */ + FloatingDockContainerPrivate(FloatingDockContainer *parent); + + void titleMouseReleaseEvent(); + void updateDropOverlays(const QPoint &globalPosition); + + /** + * Returns true if the given config flag is set + */ + static bool testConfigFlag(DockManager::eConfigFlag flag) + { + return DockManager::configFlags().testFlag(flag); + } + + /** + * Tests is a certain state is active + */ + bool isState(eDragState stateId) const { return stateId == m_draggingState; } + + void setState(eDragState stateId) { m_draggingState = stateId; } + + void setWindowTitle(const QString &text) + { + if (Utils::HostOsInfo::isLinuxHost()) + m_titleBar->setTitle(text); + else + q->setWindowTitle(text); + } + + void reflectCurrentWidget(DockWidget *currentWidget) + { + // reflect CurrentWidget's title if configured to do so, otherwise display application name as window title + if (testConfigFlag(DockManager::FloatingContainerHasWidgetTitle)) { + setWindowTitle(currentWidget->windowTitle()); + } else { + setWindowTitle(qApp->applicationDisplayName()); + } + + // reflect CurrentWidget's icon if configured to do so, otherwise display application icon as window icon + QIcon CurrentWidgetIcon = currentWidget->icon(); + if (testConfigFlag(DockManager::FloatingContainerHasWidgetIcon) + && !CurrentWidgetIcon.isNull()) + { + q->setWindowIcon(currentWidget->icon()); + } else { + q->setWindowIcon(QApplication::windowIcon()); + } + } + }; + // struct FloatingDockContainerPrivate + + FloatingDockContainerPrivate::FloatingDockContainerPrivate(FloatingDockContainer *parent) + : q(parent) + {} + + void FloatingDockContainerPrivate::titleMouseReleaseEvent() + { + setState(DraggingInactive); + if (!m_dropContainer) { + return; + } + + if (m_dockManager->dockAreaOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea + || m_dockManager->containerOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea) { + // Resize the floating widget to the size of the highlighted drop area rectangle + DockOverlay *overlay = m_dockManager->containerOverlay(); + if (!overlay->dropOverlayRect().isValid()) { + overlay = m_dockManager->dockAreaOverlay(); + } + + QRect rect = overlay->dropOverlayRect(); + int frameWidth = (q->frameSize().width() - q->rect().width()) / 2; + int titleBarHeight = q->frameSize().height() - q->rect().height() - frameWidth; + if (rect.isValid()) { + QPoint topLeft = overlay->mapToGlobal(rect.topLeft()); + topLeft.ry() += titleBarHeight; + q->setGeometry(QRect(topLeft, QSize(rect.width(), rect.height() - titleBarHeight))); + QApplication::processEvents(); + } + m_dropContainer->dropFloatingWidget(q, QCursor::pos()); + } + + m_dockManager->containerOverlay()->hideOverlay(); + m_dockManager->dockAreaOverlay()->hideOverlay(); + } + + void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &globalPosition) + { + if (!q->isVisible() || !m_dockManager) { + return; + } + + auto containers = m_dockManager->dockContainers(); + DockContainerWidget *topContainer = nullptr; + for (auto containerWidget : containers) { + if (!containerWidget->isVisible()) { + continue; + } + + if (m_dockContainer == containerWidget) { + continue; + } + + QPoint mappedPos = containerWidget->mapFromGlobal(globalPosition); + if (containerWidget->rect().contains(mappedPos)) { + if (!topContainer || containerWidget->isInFrontOf(topContainer)) { + topContainer = containerWidget; + } + } + } + + m_dropContainer = topContainer; + auto containerOverlay = m_dockManager->containerOverlay(); + auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); + + if (!topContainer) { + containerOverlay->hideOverlay(); + dockAreaOverlay->hideOverlay(); + return; + } + + int visibleDockAreas = topContainer->visibleDockAreaCount(); + containerOverlay->setAllowedAreas(visibleDockAreas > 1 ? OuterDockAreas : AllDockAreas); + DockWidgetArea containerArea = containerOverlay->showOverlay(topContainer); + containerOverlay->enableDropPreview(containerArea != InvalidDockWidgetArea); + auto dockArea = topContainer->dockAreaAt(globalPosition); + if (dockArea && dockArea->isVisible() && visibleDockAreas > 0) { + dockAreaOverlay->enableDropPreview(true); + dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea + : dockArea->allowedAreas()); + DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); + + // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in + // the title bar. If the ContainerArea is valid then we ignore the dock area of the + // dockAreaOverlay() and disable the drop preview + if ((area == CenterDockWidgetArea) && (containerArea != InvalidDockWidgetArea)) { + dockAreaOverlay->enableDropPreview(false); + containerOverlay->enableDropPreview(true); + } else { + containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); + } + } else { + dockAreaOverlay->hideOverlay(); + } + } + + FloatingDockContainer::FloatingDockContainer(DockManager *dockManager) + : FloatingWidgetBaseType(dockManager) + , d(new FloatingDockContainerPrivate(this)) + { + d->m_dockManager = dockManager; + d->m_dockContainer = new DockContainerWidget(dockManager, this); + connect(d->m_dockContainer, + &DockContainerWidget::dockAreasAdded, + this, + &FloatingDockContainer::onDockAreasAddedOrRemoved); + connect(d->m_dockContainer, + &DockContainerWidget::dockAreasRemoved, + this, + &FloatingDockContainer::onDockAreasAddedOrRemoved); + +#ifdef Q_OS_LINUX + d->m_titleBar = new FloatingWidgetTitleBar(this); + setWindowFlags(windowFlags() | Qt::Tool); + QDockWidget::setWidget(d->m_dockContainer); + QDockWidget::setFloating(true); + QDockWidget::setFeatures(QDockWidget::AllDockWidgetFeatures); + setTitleBarWidget(d->m_titleBar); + connect(d->m_titleBar, + &FloatingWidgetTitleBar::closeRequested, + this, + &FloatingDockContainer::close); +#else + setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); + QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::TopToBottom); + boxLayout->setContentsMargins(0, 0, 0, 0); + boxLayout->setSpacing(0); + setLayout(boxLayout); + boxLayout->addWidget(d->m_dockContainer); +#endif + dockManager->registerFloatingWidget(this); + } + + FloatingDockContainer::FloatingDockContainer(DockAreaWidget *dockArea) + : FloatingDockContainer(dockArea->dockManager()) + { + d->m_dockContainer->addDockArea(dockArea); + if (Utils::HostOsInfo::isLinuxHost()) + d->m_titleBar->enableCloseButton(isClosable()); + + auto dw = topLevelDockWidget(); + if (dw) { + dw->emitTopLevelChanged(true); + } + } + + FloatingDockContainer::FloatingDockContainer(DockWidget *dockWidget) + : FloatingDockContainer(dockWidget->dockManager()) + { + d->m_dockContainer->addDockWidget(CenterDockWidgetArea, dockWidget); + if (Utils::HostOsInfo::isLinuxHost()) + d->m_titleBar->enableCloseButton(isClosable()); + + auto dw = topLevelDockWidget(); + if (dw) { + dw->emitTopLevelChanged(true); + } + } + + FloatingDockContainer::~FloatingDockContainer() + { + qCInfo(adsLog) << Q_FUNC_INFO; + if (d->m_dockManager) { + d->m_dockManager->removeFloatingWidget(this); + } + delete d; + } + + DockContainerWidget *FloatingDockContainer::dockContainer() const { return d->m_dockContainer; } + + void FloatingDockContainer::changeEvent(QEvent *event) + { + QWidget::changeEvent(event); + if ((event->type() == QEvent::ActivationChange) && isActiveWindow()) { + qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::ActivationChange"; + d->m_zOrderIndex = ++zOrderCounter; + return; + } + } + + void FloatingDockContainer::moveEvent(QMoveEvent *event) + { + QWidget::moveEvent(event); + switch (d->m_draggingState) { + case DraggingMousePressed: + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + break; + + case DraggingFloatingWidget: + d->updateDropOverlays(QCursor::pos()); + if (Utils::HostOsInfo::isMacHost()) { + // In macOS when hiding the DockAreaOverlay the application would set + // the main window as the active window for some reason. This fixes + // that by resetting the active window to the floating widget after + // updating the overlays. + QApplication::setActiveWindow(this); + } + break; + default: + break; + } + } + + void FloatingDockContainer::closeEvent(QCloseEvent *event) + { + qCInfo(adsLog) << Q_FUNC_INFO; + d->setState(DraggingInactive); + event->ignore(); + + if (isClosable()) { + auto dw = topLevelDockWidget(); + if (dw && dw->features().testFlag(DockWidget::DockWidgetDeleteOnClose)) { + if (!dw->closeDockWidgetInternal()) { + return; + } + } + + this->hide(); + } + } + + void FloatingDockContainer::hideEvent(QHideEvent *event) + { + Super::hideEvent(event); + if (event->spontaneous()) { + return; + } + + // Prevent toogleView() events during restore state + if (d->m_dockManager->isRestoringState()) { + return; + } + + for (auto dockArea : d->m_dockContainer->openedDockAreas()) { + for (auto dockWidget : dockArea->openedDockWidgets()) { + dockWidget->toggleView(false); + } + } + } + + void FloatingDockContainer::showEvent(QShowEvent *event) { Super::showEvent(event); } + + bool FloatingDockContainer::event(QEvent *event) + { + switch (d->m_draggingState) { + case DraggingInactive: { + // Normally we would check here, if the left mouse button is pressed. + // But from QT version 5.12.2 on the mouse events from + // QEvent::NonClientAreaMouseButtonPress return the wrong mouse button + // The event always returns Qt::RightButton even if the left button is clicked. + // It is really great to work around the whole NonClientMouseArea bugs +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 2)) + if (event->type() + == QEvent:: + NonClientAreaMouseButtonPress /*&& QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)*/) { + qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonPress" + << event->type(); + d->setState(DraggingMousePressed); + } +#else + if (e->type() == QEvent::NonClientAreaMouseButtonPress + && QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)) { + qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonPress" + << e->type(); + d->setState(DraggingMousePressed); + } +#endif + } break; + + case DraggingMousePressed: + switch (event->type()) { + case QEvent::NonClientAreaMouseButtonDblClick: + qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonDblClick"; + d->setState(DraggingInactive); + break; + + case QEvent::Resize: + // If the first event after the mouse press is a resize event, then + // the user resizes the window instead of dragging it around. + // But there is one exception. If the window is maximized, + // then dragging the window via title bar will cause the widget to + // leave the maximized state. This in turn will trigger a resize event. + // To know, if the resize event was triggered by user via moving a + // corner of the window frame or if it was caused by a windows state + // change, we check, if we are not in maximized state. + if (!isMaximized()) { + d->setState(DraggingInactive); + } + break; + + default: + break; + } + break; + + case DraggingFloatingWidget: + if (event->type() == QEvent::NonClientAreaMouseButtonRelease) { + qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonRelease"; + d->titleMouseReleaseEvent(); + } + break; + + default: + break; + } + +#if (ADS_DEBUG_LEVEL > 0) + qDebug() << "FloatingDockContainer::event " << event->type(); +#endif + return QWidget::event(event); + } + + void FloatingDockContainer::startFloating(const QPoint &dragStartMousePos, + const QSize &size, + eDragState dragState, + QWidget *mouseEventHandler) + { + resize(size); + d->setState(dragState); + d->m_dragStartMousePosition = dragStartMousePos; + + if (Utils::HostOsInfo::isLinuxHost()) { + if (DraggingFloatingWidget == dragState) { + setAttribute(Qt::WA_X11NetWmWindowTypeDock, true); + d->m_mouseEventHandler = mouseEventHandler; + if (d->m_mouseEventHandler) { + d->m_mouseEventHandler->grabMouse(); + } + } + } + moveFloating(); + show(); + } + + void FloatingDockContainer::moveFloating() + { + int borderSize = (frameSize().width() - size().width()) / 2; + const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition + - QPoint(borderSize, 0); + move(moveToPos); + } + + bool FloatingDockContainer::isClosable() const + { + return d->m_dockContainer->features().testFlag(DockWidget::DockWidgetClosable); + } + + void FloatingDockContainer::onDockAreasAddedOrRemoved() + { + qCInfo(adsLog) << Q_FUNC_INFO; + auto topLevelDockArea = d->m_dockContainer->topLevelDockArea(); + if (topLevelDockArea) { + d->m_singleDockArea = topLevelDockArea; + DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget(); + d->reflectCurrentWidget(currentWidget); + connect(d->m_singleDockArea, + &DockAreaWidget::currentChanged, + this, + &FloatingDockContainer::onDockAreaCurrentChanged); + } else { + if (d->m_singleDockArea) { + disconnect(d->m_singleDockArea, + &DockAreaWidget::currentChanged, + this, + &FloatingDockContainer::onDockAreaCurrentChanged); + d->m_singleDockArea = nullptr; + } + d->setWindowTitle(qApp->applicationDisplayName()); + setWindowIcon(QApplication::windowIcon()); + } + } + + void FloatingDockContainer::updateWindowTitle() + { + auto topLevelDockArea = d->m_dockContainer->topLevelDockArea(); + if (topLevelDockArea) { + DockWidget *currentWidget = topLevelDockArea->currentDockWidget(); + d->reflectCurrentWidget(currentWidget); + } else { + d->setWindowTitle(qApp->applicationDisplayName()); + setWindowIcon(QApplication::windowIcon()); + } + } + + void FloatingDockContainer::onDockAreaCurrentChanged(int index) + { + Q_UNUSED(index) + DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget(); + d->reflectCurrentWidget(currentWidget); + } + + bool FloatingDockContainer::restoreState(DockingStateReader &stream, bool testing) + { + if (!d->m_dockContainer->restoreState(stream, testing)) { + return false; + } + + onDockAreasAddedOrRemoved(); + return true; + } + + bool FloatingDockContainer::hasTopLevelDockWidget() const + { + return d->m_dockContainer->hasTopLevelDockWidget(); + } + + DockWidget *FloatingDockContainer::topLevelDockWidget() const + { + return d->m_dockContainer->topLevelDockWidget(); + } + + QList FloatingDockContainer::dockWidgets() const + { + return d->m_dockContainer->dockWidgets(); + } + + void FloatingDockContainer::finishDragging() + { + qCInfo(adsLog) << Q_FUNC_INFO; + + if (Utils::HostOsInfo::isLinuxHost()) { + setAttribute(Qt::WA_X11NetWmWindowTypeDock, false); + setWindowOpacity(1); + activateWindow(); + if (d->m_mouseEventHandler) { + d->m_mouseEventHandler->releaseMouse(); + d->m_mouseEventHandler = nullptr; + } + } + d->titleMouseReleaseEvent(); + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.h b/src/libs/advanceddockingsystem/floatingdockcontainer.h new file mode 100644 index 00000000000..83be9c2662f --- /dev/null +++ b/src/libs/advanceddockingsystem/floatingdockcontainer.h @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" + +#include +#include + +#ifdef Q_OS_LINUX +using FloatingWidgetBaseType = QDockWidget; +#else +using FloatingWidgetBaseType = QWidget; +#endif + +namespace ADS { + +struct FloatingDockContainerPrivate; +class DockManager; +struct DockManagerPrivate; +class DockAreaWidget; +class DockContainerWidget; +class DockWidget; +class DockManager; +class DockAreaTabBar; +class DockWidgetTab; +struct DockWidgetTabPrivate; +class DockAreaTitleBar; +struct DockAreaTitleBarPrivate; +class FloatingWidgetTitleBar; +class DockingStateReader; + +/** + * Pure virtual interface for floating widgets. + * This interface is used for opaque and non-opaque undocking. If opaque + * undocking is used, the a real FloatingDockContainer widget will be created + */ +class AbstractFloatingWidget +{ +public: + virtual ~AbstractFloatingWidget() = 0; + /** + * Starts floating. + * This function should get called typically from a mouse press event + * handler + */ + virtual void startFloating(const QPoint &dragStartMousePos, + const QSize &size, + eDragState dragState, + QWidget *mouseEventHandler) + = 0; + + /** + * Moves the widget to a new position relative to the position given when + * startFloating() was called. + * This function should be called from a mouse mouve event handler to + * move the floating widget on mouse move events. + */ + virtual void moveFloating() = 0; + + /** + * Tells the widget that to finish dragging if the mouse is released. + * This function should be called from a mouse release event handler + * to finish the dragging + */ + virtual void finishDragging() = 0; +}; + +/** + * This implements a floating widget that is a dock container that accepts + * docking of dock widgets like the main window and that can be docked into + * another dock container. + * Every floating window of the docking system is a FloatingDockContainer. + */ +class ADS_EXPORT FloatingDockContainer : public FloatingWidgetBaseType, + public AbstractFloatingWidget +{ + Q_OBJECT +private: + FloatingDockContainerPrivate *d; ///< private data (pimpl) + friend struct FloatingDockContainerPrivate; + friend class DockManager; + friend struct DockManagerPrivate; + friend class DockAreaTabBar; + friend struct DockWidgetTabPrivate; + friend class DockWidgetTab; + friend class DockAreaTitleBar; + friend struct DockAreaTitleBarPrivate; + friend class DockWidget; + friend class DockAreaWidget; + friend class FloatingWidgetTitleBar; + + void onDockAreasAddedOrRemoved(); + void onDockAreaCurrentChanged(int Index); + +protected: + /** + * Starts floating at the given global position. + * Use moveToGlobalPos() to move the widget to a new position + * depending on the start position given in Pos parameter + */ + virtual void startFloating(const QPoint &dragStartMousePos, + const QSize &size, + eDragState dragState, + QWidget *mouseEventHandler) override; + + /** + * Call this function to start dragging the floating widget + */ + void startDragging(const QPoint &dragStartMousePos, + const QSize &size, + QWidget *mouseEventHandler) + { + startFloating(dragStartMousePos, size, DraggingFloatingWidget, mouseEventHandler); + } + + /** + * Call this function if you explicitly want to signal that dragging has + * finished + */ + virtual void finishDragging() override; + + /** + * Call this function if you just want to initialize the position + * and size of the floating widget + */ + void initFloatingGeometry(const QPoint &dragStartMousePos, const QSize &size) + { + startFloating(dragStartMousePos, size, DraggingInactive, nullptr); + } + + /** + * Moves the widget to a new position relative to the position given when + * startFloating() was called + */ + void moveFloating() override; + + /** + * Restores the state from given stream. + * If Testing is true, the function only parses the data from the given + * stream but does not restore anything. You can use this check for + * faulty files before you start restoring the state + */ + bool restoreState(DockingStateReader &stream, bool testing); + + /** + * Call this function to update the window title + */ + void updateWindowTitle(); + +protected: // reimplements QWidget + virtual void changeEvent(QEvent *event) override; + virtual void moveEvent(QMoveEvent *event) override; + virtual bool event(QEvent *event) override; + virtual void closeEvent(QCloseEvent *event) override; + virtual void hideEvent(QHideEvent *event) override; + virtual void showEvent(QShowEvent *event) override; + +public: + using Super = QWidget; + + /** + * Create empty floating widget - required for restore state + */ + FloatingDockContainer(DockManager *dockManager); + + /** + * Create floating widget with the given dock area + */ + FloatingDockContainer(DockAreaWidget *dockArea); + + /** + * Create floating widget with the given dock widget + */ + FloatingDockContainer(DockWidget *dockWidget); + + /** + * Virtual Destructor + */ + virtual ~FloatingDockContainer() override; + + /** + * Access function for the internal dock container + */ + DockContainerWidget *dockContainer() const; + + /** + * This function returns true, if it can be closed. + * It can be closed, if all dock widgets in all dock areas can be closed + */ + bool isClosable() const; + + /** + * This function returns true, if this floating widget has only one single + * visible dock widget in a single visible dock area. + * The single dock widget is a real top level floating widget because no + * other widgets are docked. + */ + bool hasTopLevelDockWidget() const; + + /** + * This function returns the first dock widget in the first dock area. + * If the function hasSingleDockWidget() returns true, then this function + * returns this single dock widget. + */ + DockWidget *topLevelDockWidget() const; + + /** + * This function returns a list of all dock widget in this floating widget. + * This is a simple convenience function that simply calls the dockWidgets() + * function of the internal container widget. + */ + QList dockWidgets() const; +}; // class FloatingDockContainer + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/floatingdragpreview.cpp b/src/libs/advanceddockingsystem/floatingdragpreview.cpp new file mode 100644 index 00000000000..d816eddc31f --- /dev/null +++ b/src/libs/advanceddockingsystem/floatingdragpreview.cpp @@ -0,0 +1,355 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "floatingdragpreview.h" + +#include "dockareawidget.h" +#include "dockcontainerwidget.h" +#include "dockmanager.h" +#include "dockoverlay.h" +#include "dockwidget.h" + +#include +#include +#include +#include +#include + +#include + +static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtDebugMsg) + +namespace ADS +{ + /** + * Private data class (pimpl) + */ + struct FloatingDragPreviewPrivate + { + FloatingDragPreview *q; + QWidget *m_content; + DockAreaWidget *m_contentSourceArea = nullptr; + DockContainerWidget *m_contenSourceContainer = nullptr; + QPoint m_dragStartMousePosition; + DockManager *m_dockManager; + DockContainerWidget *m_dropContainer = nullptr; + qreal m_windowOpacity; + bool m_hidden = false; + QPixmap m_contentPreviewPixmap; + + /** + * Private data constructor + */ + FloatingDragPreviewPrivate(FloatingDragPreview *parent); + void updateDropOverlays(const QPoint &globalPosition); + + void setHidden(bool value) + { + m_hidden = value; + q->update(); + } + + /** + * Cancel dragging and emit the draggingCanceled event + */ + void cancelDragging() + { + emit q->draggingCanceled(); + m_dockManager->containerOverlay()->hideOverlay(); + m_dockManager->dockAreaOverlay()->hideOverlay(); + q->close(); + } + }; + // struct FloatingDragPreviewPrivate + + void FloatingDragPreviewPrivate::updateDropOverlays(const QPoint &globalPosition) + { + if (!q->isVisible() || !m_dockManager) { + return; + } + + auto containers = m_dockManager->dockContainers(); + DockContainerWidget *topContainer = nullptr; + for (auto containerWidget : containers) { + if (!containerWidget->isVisible()) { + continue; + } + + QPoint mappedPosition = containerWidget->mapFromGlobal(globalPosition); + if (containerWidget->rect().contains(mappedPosition)) { + if (!topContainer || containerWidget->isInFrontOf(topContainer)) { + topContainer = containerWidget; + } + } + } + + m_dropContainer = topContainer; + auto containerOverlay = m_dockManager->containerOverlay(); + auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); + auto dockDropArea = dockAreaOverlay->dropAreaUnderCursor(); + auto containerDropArea = containerOverlay->dropAreaUnderCursor(); + + if (!topContainer) { + containerOverlay->hideOverlay(); + dockAreaOverlay->hideOverlay(); + if (DockManager::configFlags().testFlag(DockManager::DragPreviewIsDynamic)) { + setHidden(false); + } + return; + } + + int visibleDockAreas = topContainer->visibleDockAreaCount(); + containerOverlay->setAllowedAreas(visibleDockAreas > 1 ? OuterDockAreas : AllDockAreas); + DockWidgetArea containerArea = containerOverlay->showOverlay(topContainer); + containerOverlay->enableDropPreview(containerArea != InvalidDockWidgetArea); + auto dockArea = topContainer->dockAreaAt(globalPosition); + if (dockArea && dockArea->isVisible() && visibleDockAreas > 0 + && dockArea != m_contentSourceArea) { + dockAreaOverlay->enableDropPreview(true); + dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea + : dockArea->allowedAreas()); + DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); + + // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in the + // title bar. If the ContainerArea is valid then we ignore the dock area of the + // dockAreaOverlay() and disable the drop preview + if ((area == CenterDockWidgetArea) && (containerArea != InvalidDockWidgetArea)) { + dockAreaOverlay->enableDropPreview(false); + containerOverlay->enableDropPreview(true); + } else { + containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); + } + } else { + dockAreaOverlay->hideOverlay(); + if (dockArea == m_contentSourceArea && InvalidDockWidgetArea == containerDropArea) { + m_dropContainer = nullptr; + } + } + + if (DockManager::configFlags().testFlag(DockManager::DragPreviewIsDynamic)) { + setHidden(dockDropArea != InvalidDockWidgetArea + || containerDropArea != InvalidDockWidgetArea); + } + } + + FloatingDragPreviewPrivate::FloatingDragPreviewPrivate(FloatingDragPreview *parent) + : q(parent) + {} + + FloatingDragPreview::FloatingDragPreview(QWidget *content, QWidget *parent) + : QWidget(parent) + , d(new FloatingDragPreviewPrivate(this)) + { + d->m_content = content; + setAttribute(Qt::WA_DeleteOnClose); + if (DockManager::configFlags().testFlag(DockManager::DragPreviewHasWindowFrame)) { + setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); + } else { + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); + } + + if (Utils::HostOsInfo::isLinuxHost()) { + auto flags = windowFlags(); + flags |= Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint; + setWindowFlags(flags); + } + + setWindowOpacity(0.6); + + // Create a static image of the widget that should get undocked + // This is like some kind preview image like it is uses in drag and drop operations + if (DockManager::configFlags().testFlag(DockManager::DragPreviewShowsContentPixmap)) { + d->m_contentPreviewPixmap = QPixmap(content->size()); + content->render(&d->m_contentPreviewPixmap); + } + connect(qApp, + &QApplication::applicationStateChanged, + this, + &FloatingDragPreview::onApplicationStateChanged); // TODO + } + + FloatingDragPreview::FloatingDragPreview(DockWidget *content) + : FloatingDragPreview(static_cast(content), + content->dockManager()) // TODO static_cast? + { + d->m_dockManager = content->dockManager(); + if (content->dockAreaWidget()->openDockWidgetsCount() == 1) { + d->m_contentSourceArea = content->dockAreaWidget(); + d->m_contenSourceContainer = content->dockContainer(); + } + setWindowTitle(content->windowTitle()); + // We need to install an event filter for the given content + // widget to receive the escape key press + content->dockAreaWidget()->installEventFilter(this); + } + + FloatingDragPreview::FloatingDragPreview(DockAreaWidget *content) + : FloatingDragPreview(static_cast(content), + content->dockManager()) // TODO static_cast? + { + d->m_dockManager = content->dockManager(); + d->m_contentSourceArea = content; + d->m_contenSourceContainer = content->dockContainer(); + setWindowTitle(content->currentDockWidget()->windowTitle()); + + // We need to install an event filter for the given Content + // widget to receive the escape key press + content->installEventFilter(this); + } + + FloatingDragPreview::~FloatingDragPreview() { delete d; } + + void FloatingDragPreview::moveFloating() + { + int borderSize = (frameSize().width() - size().width()) / 2; + const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition + - QPoint(borderSize, 0); + move(moveToPos); + } + + void FloatingDragPreview::startFloating(const QPoint &dragStartMousePos, + const QSize &size, + eDragState dragState, + QWidget *mouseEventHandler) + { + Q_UNUSED(mouseEventHandler) + Q_UNUSED(dragState) + resize(size); + d->m_dragStartMousePosition = dragStartMousePos; + moveFloating(); + show(); + } + + void FloatingDragPreview::moveEvent(QMoveEvent *event) + { + QWidget::moveEvent(event); + d->updateDropOverlays(QCursor::pos()); + } + + void FloatingDragPreview::finishDragging() + { + qCInfo(adsLog) << Q_FUNC_INFO; + auto dockDropArea = d->m_dockManager->dockAreaOverlay()->dropAreaUnderCursor(); + auto containerDropArea = d->m_dockManager->containerOverlay()->dropAreaUnderCursor(); + bool dropPossible = (dockDropArea != InvalidDockWidgetArea) + || (containerDropArea != InvalidDockWidgetArea); + if (d->m_dropContainer && dropPossible) { + d->m_dropContainer->dropWidget(d->m_content, QCursor::pos()); + } else { + DockWidget *dockWidget = qobject_cast(d->m_content); + FloatingDockContainer *floatingWidget = nullptr; + + if (dockWidget && dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) { + floatingWidget = new FloatingDockContainer(dockWidget); + } else { + DockAreaWidget *dockArea = qobject_cast(d->m_content); + if (dockArea->features().testFlag(DockWidget::DockWidgetFloatable)) { + floatingWidget = new FloatingDockContainer(dockArea); + } + } + + if (floatingWidget) { + floatingWidget->setGeometry(this->geometry()); + floatingWidget->show(); + if (!DockManager::configFlags().testFlag(DockManager::DragPreviewHasWindowFrame)) { + QApplication::processEvents(); + int frameHeight = floatingWidget->frameGeometry().height() - floatingWidget->geometry().height(); + QRect fixedGeometry = this->geometry(); + fixedGeometry.adjust(0, frameHeight, 0, 0); + floatingWidget->setGeometry(fixedGeometry); + } + } + } + + this->close(); + d->m_dockManager->containerOverlay()->hideOverlay(); + d->m_dockManager->dockAreaOverlay()->hideOverlay(); + } + + void FloatingDragPreview::paintEvent(QPaintEvent *event) + { + Q_UNUSED(event) + if (d->m_hidden) { + return; + } + + QPainter painter(this); + if (DockManager::configFlags().testFlag(DockManager::DragPreviewShowsContentPixmap)) { + painter.drawPixmap(QPoint(0, 0), d->m_contentPreviewPixmap); + } + + // If we do not have a window frame then we paint a QRubberBand like frameless window + if (!DockManager::configFlags().testFlag(DockManager::DragPreviewHasWindowFrame)) { + QColor color = palette().color(QPalette::Active, QPalette::Highlight); + QPen pen = painter.pen(); + pen.setColor(color.darker(120)); + pen.setStyle(Qt::SolidLine); + pen.setWidth(1); + pen.setCosmetic(true); + painter.setPen(pen); + color = color.lighter(130); + color.setAlpha(64); + painter.setBrush(color); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); + } + } + + void FloatingDragPreview::onApplicationStateChanged(Qt::ApplicationState state) + { + if (state != Qt::ApplicationActive) { + disconnect(qApp, + &QApplication::applicationStateChanged, + this, + &FloatingDragPreview::onApplicationStateChanged); + d->cancelDragging(); + } + } + + bool FloatingDragPreview::eventFilter(QObject *watched, QEvent *event) + { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + watched->removeEventFilter(this); + d->cancelDragging(); + } + } + + return false; + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/floatingdragpreview.h b/src/libs/advanceddockingsystem/floatingdragpreview.h new file mode 100644 index 00000000000..2c0defba3b0 --- /dev/null +++ b/src/libs/advanceddockingsystem/floatingdragpreview.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "floatingdockcontainer.h" + +#include + +namespace ADS { + +class DockWidget; +class DockAreaWidget; +struct FloatingDragPreviewPrivate; + +/** + * A floating overlay is a temporary floating widget that is just used to + * indicate the floating widget movement. + * This widget is used as a placeholder for drag operations for non-opaque + * docking + */ +class FloatingDragPreview : public QWidget, public AbstractFloatingWidget +{ + Q_OBJECT +private: + FloatingDragPreviewPrivate *d; + friend struct FloatingDragPreviewPrivate; + + /** + * Cancel non opaque undocking if application becomes inactive + */ + void onApplicationStateChanged(Qt::ApplicationState state); + +protected: + /** + * Updates the drop overlays + */ + virtual void moveEvent(QMoveEvent *event) override; + + /** + * Cares about painting the + */ + virtual void paintEvent(QPaintEvent *event) override; + + /** + * The content is a DockArea or a DockWidget + */ + FloatingDragPreview(QWidget *content, QWidget *parent); + +public: + using Super = QWidget; + + /** + * Creates an instance for undocking the DockWidget in Content parameter + */ + FloatingDragPreview(DockWidget *content); + + /** + * Creates an instance for undocking the DockArea given in Content + * parameters + */ + FloatingDragPreview(DockAreaWidget *content); + + /** + * Delete private data + */ + ~FloatingDragPreview() override; + + /** + * We filter the events of the assigned content widget to receive + * escape key presses for canceling the drag operation + */ + virtual bool eventFilter(QObject *watched, QEvent *event) override; + +public: // implements AbstractFloatingWidget + virtual void startFloating(const QPoint &dragStartMousePos, + const QSize &size, + eDragState dragState, + QWidget *mouseEventHandler) override; + + /** + * Moves the widget to a new position relative to the position given when + * startFloating() was called + */ + virtual void moveFloating() override; + + /** + * Finishes dragging. + * Hides the dock overlays and executes the real undocking and docking + * of the assigned Content widget + */ + virtual void finishDragging() override; + +signals: + /** + * This signal is emitted, if dragging has been canceled by escape key + * or by active application switching via task manager + */ + void draggingCanceled(); +}; // class FloatingDragPreview + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/iconprovider.cpp b/src/libs/advanceddockingsystem/iconprovider.cpp new file mode 100644 index 00000000000..6a6f46752bb --- /dev/null +++ b/src/libs/advanceddockingsystem/iconprovider.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "iconprovider.h" + +#include + +namespace ADS { + /** + * Private data class (pimpl) + */ + struct IconProviderPrivate + { + IconProvider *q; + QVector m_userIcons{IconCount, QIcon()}; + + /** + * Private data constructor + */ + IconProviderPrivate(IconProvider *parent); + }; + // struct LedArrayPanelPrivate + + IconProviderPrivate::IconProviderPrivate(IconProvider *parent) + : q(parent) + {} + + IconProvider::IconProvider() + : d(new IconProviderPrivate(this)) + {} + + IconProvider::~IconProvider() + { + delete d; + } + + QIcon IconProvider::customIcon(eIcon iconId) const + { + Q_ASSERT(iconId < d->m_userIcons.size()); + return d->m_userIcons[iconId]; + } + + void IconProvider::registerCustomIcon(eIcon iconId, const QIcon &icon) + { + Q_ASSERT(iconId < d->m_userIcons.size()); + d->m_userIcons[iconId] = icon; + } + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/iconprovider.h b/src/libs/advanceddockingsystem/iconprovider.h new file mode 100644 index 00000000000..25c59b220b4 --- /dev/null +++ b/src/libs/advanceddockingsystem/iconprovider.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ads_globals.h" + +#include + +namespace ADS { + +struct IconProviderPrivate; + +/** + * This object provides all icons that are required by the advanced docking + * system. + * The IconProvider enables the user to register custom icons in case using + * stylesheets is not an option. + */ +class ADS_EXPORT IconProvider +{ +private: + IconProviderPrivate *d; ///< private data (pimpl) + friend struct IconProviderPrivate; + +public: + /** + * Default Constructor + */ + IconProvider(); + + /** + * Virtual Destructor + */ + virtual ~IconProvider(); + + /** + * The function returns a custom icon if one is registered and a null Icon + * if no custom icon is registered + */ + QIcon customIcon(eIcon iconId) const; + + /** + * Registers a custom icon for the given IconId + */ + void registerCustomIcon(eIcon iconId, const QIcon &icon); +}; // class IconProvider + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/images/close-button-disabled.svg b/src/libs/advanceddockingsystem/images/close-button-disabled.svg new file mode 100644 index 00000000000..9c60c74ad82 --- /dev/null +++ b/src/libs/advanceddockingsystem/images/close-button-disabled.svg @@ -0,0 +1,122 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libs/advanceddockingsystem/images/close-button.svg b/src/libs/advanceddockingsystem/images/close-button.svg new file mode 100644 index 00000000000..679bffcf60a --- /dev/null +++ b/src/libs/advanceddockingsystem/images/close-button.svg @@ -0,0 +1,119 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp new file mode 100644 index 00000000000..52eb6e784b5 --- /dev/null +++ b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "floatingwidgettitlebar.h" + +#include "ads_globals.h" +#include "elidinglabel.h" +#include "floatingdockcontainer.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace ADS { + +using TabLabelType = ElidingLabel; +using tCloseButton = QPushButton; + +/** + * @brief Private data class of public interface CFloatingWidgetTitleBar + */ +struct FloatingWidgetTitleBarPrivate +{ + FloatingWidgetTitleBar *q; ///< public interface class + QLabel *m_iconLabel = nullptr; + TabLabelType *m_titleLabel; + tCloseButton *m_closeButton = nullptr; + FloatingDockContainer *m_floatingWidget = nullptr; + eDragState m_dragState = DraggingInactive; + + FloatingWidgetTitleBarPrivate(FloatingWidgetTitleBar *parent) + : q(parent) + {} + + /** + * Creates the complete layout including all controls + */ + void createLayout(); +}; + +void FloatingWidgetTitleBarPrivate::createLayout() +{ + m_titleLabel = new TabLabelType(); + m_titleLabel->setElideMode(Qt::ElideRight); + m_titleLabel->setText("DockWidget->windowTitle()"); + m_titleLabel->setObjectName("floatingTitleLabel"); + m_titleLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + + m_closeButton = new tCloseButton(); + m_closeButton->setObjectName("floatingTitleCloseButton"); + m_closeButton->setFlat(true); + + // The standard icons do does not look good on high DPI screens + QIcon closeIcon; + QPixmap normalPixmap = q->style()->standardPixmap(QStyle::SP_TitleBarCloseButton, + nullptr, + m_closeButton); + closeIcon.addPixmap(normalPixmap, QIcon::Normal); + closeIcon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), QIcon::Disabled); + m_closeButton->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarCloseButton)); + m_closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_closeButton->setVisible(true); + m_closeButton->setFocusPolicy(Qt::NoFocus); + q->connect(m_closeButton, &QPushButton::clicked, q, &FloatingWidgetTitleBar::closeRequested); + + QFontMetrics fontMetrics(m_titleLabel->font()); + int spacing = qRound(fontMetrics.height() / 4.0); + + // Fill the layout + QBoxLayout *layout = new QBoxLayout(QBoxLayout::LeftToRight); + layout->setContentsMargins(6, 0, 0, 0); + layout->setSpacing(0); + q->setLayout(layout); + layout->addWidget(m_titleLabel, 1); + layout->addSpacing(spacing); + layout->addWidget(m_closeButton); + layout->setAlignment(Qt::AlignCenter); + + m_titleLabel->setVisible(true); +} + +FloatingWidgetTitleBar::FloatingWidgetTitleBar(FloatingDockContainer *parent) + : QWidget(parent) + , d(new FloatingWidgetTitleBarPrivate(this)) +{ + d->m_floatingWidget = parent; + d->createLayout(); +} + +FloatingWidgetTitleBar::~FloatingWidgetTitleBar() +{ + delete d; +} + +void FloatingWidgetTitleBar::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + d->m_dragState = DraggingFloatingWidget; + d->m_floatingWidget->startDragging(event->pos(), d->m_floatingWidget->size(), this); + return; + } + Super::mousePressEvent(event); +} + +void FloatingWidgetTitleBar::mouseReleaseEvent(QMouseEvent *event) +{ + d->m_dragState = DraggingInactive; + if (d->m_floatingWidget) { + d->m_floatingWidget->finishDragging(); + } + Super::mouseReleaseEvent(event); +} + +void FloatingWidgetTitleBar::mouseMoveEvent(QMouseEvent *event) +{ + if (!(event->buttons() & Qt::LeftButton) || DraggingInactive == d->m_dragState) { + d->m_dragState = DraggingInactive; + Super::mouseMoveEvent(event); + return; + } + + // move floating window + if (DraggingFloatingWidget == d->m_dragState) { + d->m_floatingWidget->moveFloating(); + Super::mouseMoveEvent(event); + return; + } + Super::mouseMoveEvent(event); +} + +void FloatingWidgetTitleBar::enableCloseButton(bool enable) +{ + d->m_closeButton->setEnabled(enable); +} + +void FloatingWidgetTitleBar::setTitle(const QString &text) +{ + d->m_titleLabel->setText(text); +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h new file mode 100644 index 00000000000..f35e096a68f --- /dev/null +++ b/src/libs/advanceddockingsystem/linux/floatingwidgettitlebar.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Uwe Kindler +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace ADS { + +class FloatingDockContainer; +struct FloatingWidgetTitleBarPrivate; + +/** + * Titlebar for floating widgets to capture non client are mouse events. + * Linux does not support NonClieantArea mouse events like + * QEvent::NonClientAreaMouseButtonPress. Because these events are required + * for the docking system to work properly, we use our own titlebar here to + * capture the required mouse events. + */ +class FloatingWidgetTitleBar : public QWidget +{ + Q_OBJECT +private: + FloatingWidgetTitleBarPrivate *d; ///< private data (pimpl) + +protected: + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + +public: + using Super = QWidget; + explicit FloatingWidgetTitleBar(FloatingDockContainer *parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~FloatingWidgetTitleBar() override; + + /** + * Enables / disables the window close button. + */ + void enableCloseButton(bool enable); + + /** + * Sets the window title, that means, the text of the internal tile label. + */ + void setTitle(const QString &text); + +signals: + /** + * This signal is emitted, if the close button is clicked. + */ + void closeRequested(); +}; + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/linux/linux.pri b/src/libs/advanceddockingsystem/linux/linux.pri new file mode 100644 index 00000000000..bc22027a088 --- /dev/null +++ b/src/libs/advanceddockingsystem/linux/linux.pri @@ -0,0 +1,4 @@ +VPATH += $$PWD +SOURCES += $$PWD/floatingwidgettitlebar.cpp + +HEADERS += $$PWD/floatingwidgettitlebar.h diff --git a/src/libs/advanceddockingsystem/resources.qrc b/src/libs/advanceddockingsystem/resources.qrc new file mode 100644 index 00000000000..facf3347516 --- /dev/null +++ b/src/libs/advanceddockingsystem/resources.qrc @@ -0,0 +1,6 @@ + + + images/close-button.svg + images/close-button-disabled.svg + + diff --git a/src/libs/advanceddockingsystem/workspacedialog.cpp b/src/libs/advanceddockingsystem/workspacedialog.cpp new file mode 100644 index 00000000000..ec4d6a23e7e --- /dev/null +++ b/src/libs/advanceddockingsystem/workspacedialog.cpp @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "workspacedialog.h" + +#include "dockmanager.h" + +#include + +#include +#include + +namespace ADS { + +class WorkspaceValidator : public QValidator +{ +public: + WorkspaceValidator(QObject *parent, const QStringList &workspaces); + void fixup(QString &input) const override; + QValidator::State validate(QString &input, int &pos) const override; + +private: + QStringList m_workspaces; +}; + +WorkspaceValidator::WorkspaceValidator(QObject *parent, const QStringList &workspaces) + : QValidator(parent) + , m_workspaces(workspaces) +{} + +QValidator::State WorkspaceValidator::validate(QString &input, int &pos) const +{ + Q_UNUSED(pos) + + if (input.contains(QLatin1Char('/')) || input.contains(QLatin1Char(':')) + || input.contains(QLatin1Char('\\')) || input.contains(QLatin1Char('?')) + || input.contains(QLatin1Char('*')) || input.contains(QLatin1Char('_'))) + return QValidator::Invalid; + + if (m_workspaces.contains(input)) + return QValidator::Intermediate; + else + return QValidator::Acceptable; +} + +void WorkspaceValidator::fixup(QString &input) const +{ + int i = 2; + QString copy; + do { + copy = input + QLatin1String(" (") + QString::number(i) + QLatin1Char(')'); + ++i; + } while (m_workspaces.contains(copy)); + input = copy; +} + +WorkspaceNameInputDialog::WorkspaceNameInputDialog(DockManager *manager, QWidget *parent) + : QDialog(parent) + , m_manager(manager) +{ + auto hlayout = new QVBoxLayout(this); + auto label = new QLabel(tr("Enter the name of the workspace:"), this); + hlayout->addWidget(label); + m_newWorkspaceLineEdit = new QLineEdit(this); + m_newWorkspaceLineEdit->setValidator(new WorkspaceValidator(this, m_manager->workspaces())); + hlayout->addWidget(m_newWorkspaceLineEdit); + auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, + Qt::Horizontal, + this); + m_okButton = buttons->button(QDialogButtonBox::Ok); + m_switchToButton = new QPushButton; + buttons->addButton(m_switchToButton, QDialogButtonBox::AcceptRole); + connect(m_switchToButton, &QPushButton::clicked, [this]() { m_usedSwitchTo = true; }); + connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + hlayout->addWidget(buttons); + setLayout(hlayout); +} + +void WorkspaceNameInputDialog::setActionText(const QString &actionText, + const QString &openActionText) +{ + m_okButton->setText(actionText); + m_switchToButton->setText(openActionText); +} + +void WorkspaceNameInputDialog::setValue(const QString &value) +{ + m_newWorkspaceLineEdit->setText(value); +} + +QString WorkspaceNameInputDialog::value() const +{ + return m_newWorkspaceLineEdit->text(); +} + +bool WorkspaceNameInputDialog::isSwitchToRequested() const +{ + return m_usedSwitchTo; +} + +WorkspaceDialog::WorkspaceDialog(DockManager *manager, QWidget *parent) + : QDialog(parent) + , m_manager(manager) +{ + m_ui.setupUi(this); + m_ui.workspaceView->setActivationMode(Utils::DoubleClickActivation); + + connect(m_ui.btCreateNew, + &QAbstractButton::clicked, + m_ui.workspaceView, + &WorkspaceView::createNewWorkspace); + connect(m_ui.btClone, + &QAbstractButton::clicked, + m_ui.workspaceView, + &WorkspaceView::cloneCurrentWorkspace); + connect(m_ui.btDelete, + &QAbstractButton::clicked, + m_ui.workspaceView, + &WorkspaceView::deleteSelectedWorkspaces); + connect(m_ui.btSwitch, + &QAbstractButton::clicked, + m_ui.workspaceView, + &WorkspaceView::switchToCurrentWorkspace); + connect(m_ui.btRename, + &QAbstractButton::clicked, + m_ui.workspaceView, + &WorkspaceView::renameCurrentWorkspace); + connect(m_ui.workspaceView, + &WorkspaceView::activated, + m_ui.workspaceView, + &WorkspaceView::switchToCurrentWorkspace); + + connect(m_ui.workspaceView, &WorkspaceView::selected, this, &WorkspaceDialog::updateActions); + connect(m_ui.workspaceView, &WorkspaceView::workspaceSwitched, this, &QDialog::reject); + + m_ui.whatsAWorkspaceLabel->setOpenExternalLinks(true); +} + +void WorkspaceDialog::setAutoLoadWorkspace(bool check) +{ + m_ui.autoLoadCheckBox->setChecked(check); +} + +bool WorkspaceDialog::autoLoadWorkspace() const +{ + return m_ui.autoLoadCheckBox->checkState() == Qt::Checked; +} + +DockManager *WorkspaceDialog::dockManager() const +{ + return m_manager; +} + +void WorkspaceDialog::updateActions(const QStringList &workspaces) +{ + if (workspaces.isEmpty()) { + m_ui.btDelete->setEnabled(false); + m_ui.btRename->setEnabled(false); + m_ui.btClone->setEnabled(false); + m_ui.btSwitch->setEnabled(false); + return; + } + const bool defaultIsSelected = workspaces.contains("default"); // TODO use const var + const bool activeIsSelected = Utils::anyOf(workspaces, [this](const QString &workspace) { + return workspace == m_manager->activeWorkspace(); + }); + m_ui.btDelete->setEnabled(!defaultIsSelected && !activeIsSelected); + m_ui.btRename->setEnabled(workspaces.size() == 1 && !defaultIsSelected); + m_ui.btClone->setEnabled(workspaces.size() == 1); + m_ui.btSwitch->setEnabled(workspaces.size() == 1); +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/workspacedialog.h b/src/libs/advanceddockingsystem/workspacedialog.h new file mode 100644 index 00000000000..fb3d6240030 --- /dev/null +++ b/src/libs/advanceddockingsystem/workspacedialog.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ui_workspacedialog.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QLineEdit; +class QPushButton; +QT_END_NAMESPACE + +namespace ADS { + +class DockManager; + +class WorkspaceDialog : public QDialog +{ + Q_OBJECT + +public: + explicit WorkspaceDialog(DockManager *manager, QWidget *parent = nullptr); + + void setAutoLoadWorkspace(bool); + bool autoLoadWorkspace() const; + + DockManager *dockManager() const; + +private: + void updateActions(const QStringList &workspaces); + + Ui::WorkspaceDialog m_ui; + + DockManager *m_manager = nullptr; +}; + +class WorkspaceNameInputDialog : public QDialog +{ + Q_OBJECT + +public: + explicit WorkspaceNameInputDialog(DockManager *manager, QWidget *parent); + + void setActionText(const QString &actionText, const QString &openActionText); + void setValue(const QString &value); + QString value() const; + bool isSwitchToRequested() const; + +private: + QLineEdit *m_newWorkspaceLineEdit = nullptr; + QPushButton *m_switchToButton = nullptr; + QPushButton *m_okButton = nullptr; + bool m_usedSwitchTo = false; + + DockManager *m_manager; +}; + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/workspacedialog.ui b/src/libs/advanceddockingsystem/workspacedialog.ui new file mode 100644 index 00000000000..db38f9fa925 --- /dev/null +++ b/src/libs/advanceddockingsystem/workspacedialog.ui @@ -0,0 +1,172 @@ + + + ADS::WorkspaceDialog + + + + 0 + 0 + 373 + 282 + + + + Workspace Manager + + + + + + + 1 + 1 + + + + + + + + 0 + + + 0 + + + + + &New + + + + + + + &Rename + + + + + + + C&lone + + + + + + + &Delete + + + + + + + &Switch To + + + true + + + + + + + Qt::Vertical + + + + 85 + 48 + + + + + + + + + + Restore last workspace on startup + + + + + + + Qt::Horizontal + + + + + + + <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-workspaces.html">What is a Workspace?</a> + + + + + + + + 0 + 0 + + + + Qt::Vertical + + + QDialogButtonBox::Close + + + true + + + + + + + + WorkspaceView + QTreeView +
workspaceview.h
+
+
+ + + + buttonBox + rejected() + ADS::WorkspaceDialog + reject() + + + 191 + 244 + + + 114 + 237 + + + + + buttonBox + accepted() + ADS::WorkspaceDialog + accept() + + + 246 + 237 + + + 78 + 216 + + + + +
diff --git a/src/libs/advanceddockingsystem/workspacemodel.cpp b/src/libs/advanceddockingsystem/workspacemodel.cpp new file mode 100644 index 00000000000..4a0b20b7466 --- /dev/null +++ b/src/libs/advanceddockingsystem/workspacemodel.cpp @@ -0,0 +1,269 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "workspacemodel.h" + +#include "dockmanager.h" +#include "workspacedialog.h" + +#include +#include +#include + +#include +#include + +namespace ADS { + +WorkspaceModel::WorkspaceModel(DockManager *manager, QObject *parent) + : QAbstractTableModel(parent) + , m_manager(manager) +{ + m_sortedWorkspaces = m_manager->workspaces(); + connect(m_manager, &DockManager::workspaceLoaded, this, &WorkspaceModel::resetWorkspaces); +} + +int WorkspaceModel::indexOfWorkspace(const QString &workspace) +{ + return m_sortedWorkspaces.indexOf(workspace); +} + +QString WorkspaceModel::workspaceAt(int row) const +{ + return m_sortedWorkspaces.value(row, QString()); +} + +QVariant WorkspaceModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant result; + if (orientation == Qt::Horizontal) { + switch (role) { + case Qt::DisplayRole: + switch (section) { + case 0: + result = tr("Workspace"); + break; + case 1: + result = tr("Last Modified"); + break; + } // switch (section) + break; + } // switch (role) + } + return result; +} + +int WorkspaceModel::columnCount(const QModelIndex &) const +{ + static int sectionCount = 0; + if (sectionCount == 0) { + // headers sections defining possible columns + while (!headerData(sectionCount, Qt::Horizontal, Qt::DisplayRole).isNull()) + sectionCount++; + } + + return sectionCount; +} + +int WorkspaceModel::rowCount(const QModelIndex &) const +{ + return m_sortedWorkspaces.count(); +} + +QStringList pathsToBaseNames(const QStringList &paths) +{ + return Utils::transform(paths, + [](const QString &path) { return QFileInfo(path).completeBaseName(); }); +} + +QStringList pathsWithTildeHomePath(const QStringList &paths) +{ + return Utils::transform(paths, [](const QString &path) { + return Utils::withTildeHomePath(QDir::toNativeSeparators(path)); + }); +} + +QVariant WorkspaceModel::data(const QModelIndex &index, int role) const +{ + QVariant result; + if (index.isValid()) { + QString workspaceName = m_sortedWorkspaces.at(index.row()); + + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case 0: + result = workspaceName; + break; + case 1: + result = m_manager->workspaceDateTime(workspaceName); + break; + } // switch (section) + break; + case Qt::FontRole: { + QFont font; + if (m_manager->isDefaultWorkspace(workspaceName)) + font.setItalic(true); + else + font.setItalic(false); + if (m_manager->activeWorkspace() == workspaceName + && !m_manager->isFactoryDefaultWorkspace(workspaceName)) + font.setBold(true); + else + font.setBold(false); + result = font; + } break; + case DefaultWorkspaceRole: + result = m_manager->isDefaultWorkspace(workspaceName); + break; + case LastWorkspaceRole: + result = m_manager->lastWorkspace() == workspaceName; + break; + case ActiveWorkspaceRole: + result = m_manager->activeWorkspace() == workspaceName; + break; + } // switch (role) + } + + return result; +} + +QHash WorkspaceModel::roleNames() const +{ + static QHash extraRoles{{Qt::DisplayRole, "workspaceName"}, + {DefaultWorkspaceRole, "defaultWorkspace"}, + {LastWorkspaceRole, "activeWorkspace"}, + {ActiveWorkspaceRole, "lastWorkspace"}}; + return QAbstractTableModel::roleNames().unite(extraRoles); +} + +void WorkspaceModel::sort(int column, Qt::SortOrder order) +{ + beginResetModel(); + const auto cmp = [this, column, order](const QString &s1, const QString &s2) { + bool isLess; + if (column == 0) + isLess = s1 < s2; + else + isLess = m_manager->workspaceDateTime(s1) < m_manager->workspaceDateTime(s2); + if (order == Qt::DescendingOrder) + isLess = !isLess; + return isLess; + }; + Utils::sort(m_sortedWorkspaces, cmp); + endResetModel(); +} + +bool WorkspaceModel::isDefaultVirgin() const +{ + return false; //m_manager->isFactoryDefaultWorkspace(); // TODO +} + +void WorkspaceModel::resetWorkspaces() +{ + beginResetModel(); + m_sortedWorkspaces = m_manager->workspaces(); + endResetModel(); +} + +void WorkspaceModel::newWorkspace(QWidget *parent) +{ + WorkspaceNameInputDialog workspaceInputDialog(m_manager, parent); + workspaceInputDialog.setWindowTitle(tr("New Workspace Name")); + workspaceInputDialog.setActionText(tr("&Create"), tr("Create and &Open")); + + runWorkspaceNameInputDialog(&workspaceInputDialog, [this](const QString &newName) { + m_manager->createWorkspace(newName); + }); +} + +void WorkspaceModel::cloneWorkspace(QWidget *parent, const QString &workspace) +{ + WorkspaceNameInputDialog workspaceInputDialog(m_manager, parent); + workspaceInputDialog.setWindowTitle(tr("New Workspace Name")); + workspaceInputDialog.setActionText(tr("&Clone"), tr("Clone and &Open")); + workspaceInputDialog.setValue(workspace + " (2)"); + + runWorkspaceNameInputDialog(&workspaceInputDialog, [this, workspace](const QString &newName) { + m_manager->cloneWorkspace(workspace, newName); + }); +} + +void WorkspaceModel::deleteWorkspaces(const QStringList &workspaces) +{ + if (!m_manager->confirmWorkspaceDelete(workspaces)) + return; + beginResetModel(); + m_manager->deleteWorkspaces(workspaces); + endResetModel(); +} + +void WorkspaceModel::renameWorkspace(QWidget *parent, const QString &workspace) +{ + WorkspaceNameInputDialog workspaceInputDialog(m_manager, parent); + workspaceInputDialog.setWindowTitle(tr("Rename Workspace")); + workspaceInputDialog.setActionText(tr("&Rename"), tr("Rename and &Open")); + workspaceInputDialog.setValue(workspace); + + runWorkspaceNameInputDialog(&workspaceInputDialog, [this, workspace](const QString &newName) { + m_manager->renameWorkspace(workspace, newName); + }); +} + +void WorkspaceModel::switchToWorkspace(const QString &workspace) +{ + m_manager->openWorkspace(workspace); + emit workspaceSwitched(); +} + +void WorkspaceModel::runWorkspaceNameInputDialog(WorkspaceNameInputDialog *workspaceInputDialog, + std::function createWorkspace) +{ + if (workspaceInputDialog->exec() == QDialog::Accepted) { + QString newWorkspace = workspaceInputDialog->value(); + if (newWorkspace.isEmpty() || m_manager->workspaces().contains(newWorkspace)) + return; + beginResetModel(); + createWorkspace(newWorkspace); + m_sortedWorkspaces = m_manager->workspaces(); + endResetModel(); + + if (workspaceInputDialog->isSwitchToRequested()) + switchToWorkspace(newWorkspace); + emit workspaceCreated(newWorkspace); + } +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/workspacemodel.h b/src/libs/advanceddockingsystem/workspacemodel.h new file mode 100644 index 00000000000..ac4abae37fe --- /dev/null +++ b/src/libs/advanceddockingsystem/workspacemodel.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include + +namespace ADS { + +class DockManager; +class WorkspaceNameInputDialog; + +class WorkspaceModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum { DefaultWorkspaceRole = Qt::UserRole + 1, LastWorkspaceRole, ActiveWorkspaceRole }; + + explicit WorkspaceModel(DockManager *manager, QObject *parent = nullptr); + + int indexOfWorkspace(const QString &workspace); + QString workspaceAt(int row) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; + + Q_SCRIPTABLE bool isDefaultVirgin() const; + +signals: + void workspaceSwitched(); + void workspaceCreated(const QString &workspaceName); + +public: + void resetWorkspaces(); + void newWorkspace(QWidget *parent); + void cloneWorkspace(QWidget *parent, const QString &workspace); + void deleteWorkspaces(const QStringList &workspaces); + void renameWorkspace(QWidget *parent, const QString &workspace); + void switchToWorkspace(const QString &workspace); + +private: + void runWorkspaceNameInputDialog(WorkspaceNameInputDialog *workspaceInputDialog, + std::function createWorkspace); + + QStringList m_sortedWorkspaces; + DockManager *m_manager; +}; + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/workspaceview.cpp b/src/libs/advanceddockingsystem/workspaceview.cpp new file mode 100644 index 00000000000..3a5182011ba --- /dev/null +++ b/src/libs/advanceddockingsystem/workspaceview.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "workspaceview.h" + +#include "dockmanager.h" +#include "workspacedialog.h" + +#include + +#include +#include +#include +#include + +namespace ADS { + +// custom item delegate class +class RemoveItemFocusDelegate : public QStyledItemDelegate +{ +public: + RemoveItemFocusDelegate(QObject *parent = nullptr) + : QStyledItemDelegate(parent) + {} + +protected: + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + +void RemoveItemFocusDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + opt.state &= ~QStyle::State_HasFocus; + QStyledItemDelegate::paint(painter, opt, index); +} + +WorkspaceDialog *WorkspaceView::castToWorkspaceDialog(QWidget *widget) +{ + auto dialog = qobject_cast(widget); + Q_ASSERT(dialog); + return dialog; +} + +WorkspaceView::WorkspaceView(QWidget *parent) + : Utils::TreeView(parent) + , m_manager(WorkspaceView::castToWorkspaceDialog(parent)->dockManager()) + , m_workspaceModel(m_manager) +{ + setItemDelegate(new RemoveItemFocusDelegate(this)); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::ExtendedSelection); + setWordWrap(false); + setRootIsDecorated(false); + setSortingEnabled(true); + + setModel(&m_workspaceModel); + sortByColumn(0, Qt::AscendingOrder); + + // Ensure that the full workspace name is visible. + header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + + QItemSelection firstRow(m_workspaceModel.index(0, 0), + m_workspaceModel.index(0, m_workspaceModel.columnCount() - 1)); + selectionModel()->select(firstRow, QItemSelectionModel::QItemSelectionModel::SelectCurrent); + + connect(this, &Utils::TreeView::activated, [this](const QModelIndex &index) { + emit activated(m_workspaceModel.workspaceAt(index.row())); + }); + connect(selectionModel(), &QItemSelectionModel::selectionChanged, [this] { + emit selected(selectedWorkspaces()); + }); + + connect(&m_workspaceModel, + &WorkspaceModel::workspaceSwitched, + this, + &WorkspaceView::workspaceSwitched); + connect(&m_workspaceModel, + &WorkspaceModel::modelReset, + this, + &WorkspaceView::selectActiveWorkspace); + connect(&m_workspaceModel, + &WorkspaceModel::workspaceCreated, + this, + &WorkspaceView::selectWorkspace); +} + +void WorkspaceView::createNewWorkspace() +{ + m_workspaceModel.newWorkspace(this); +} + +void WorkspaceView::deleteSelectedWorkspaces() +{ + deleteWorkspaces(selectedWorkspaces()); +} + +void WorkspaceView::deleteWorkspaces(const QStringList &workspaces) +{ + m_workspaceModel.deleteWorkspaces(workspaces); +} + +void WorkspaceView::cloneCurrentWorkspace() +{ + m_workspaceModel.cloneWorkspace(this, currentWorkspace()); +} + +void WorkspaceView::renameCurrentWorkspace() +{ + m_workspaceModel.renameWorkspace(this, currentWorkspace()); +} + +void WorkspaceView::switchToCurrentWorkspace() +{ + m_workspaceModel.switchToWorkspace(currentWorkspace()); +} + +QString WorkspaceView::currentWorkspace() +{ + return m_workspaceModel.workspaceAt(selectionModel()->currentIndex().row()); +} + +WorkspaceModel *WorkspaceView::workspaceModel() +{ + return &m_workspaceModel; +} + +void WorkspaceView::selectActiveWorkspace() +{ + selectWorkspace(m_manager->activeWorkspace()); +} + +void WorkspaceView::selectWorkspace(const QString &workspaceName) +{ + int row = m_workspaceModel.indexOfWorkspace(workspaceName); + selectionModel()->setCurrentIndex(model()->index(row, 0), + QItemSelectionModel::ClearAndSelect + | QItemSelectionModel::Rows); +} + +void WorkspaceView::showEvent(QShowEvent *event) +{ + Utils::TreeView::showEvent(event); + selectActiveWorkspace(); + setFocus(); +} + +void WorkspaceView::keyPressEvent(QKeyEvent *event) +{ + if (event->key() != Qt::Key_Delete) { + TreeView::keyPressEvent(event); + return; + } + const QStringList workspaces = selectedWorkspaces(); + if (!workspaces.contains("default") + && !Utils::anyOf(workspaces, [this](const QString &workspace) { + return workspace == m_manager->activeWorkspace(); + })) { + deleteWorkspaces(workspaces); + } +} + +QStringList WorkspaceView::selectedWorkspaces() const +{ + return Utils::transform(selectionModel()->selectedRows(), [this](const QModelIndex &index) { + return m_workspaceModel.workspaceAt(index.row()); + }); +} + +} // namespace ADS diff --git a/src/libs/advanceddockingsystem/workspaceview.h b/src/libs/advanceddockingsystem/workspaceview.h new file mode 100644 index 00000000000..7edde4d62eb --- /dev/null +++ b/src/libs/advanceddockingsystem/workspaceview.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or (at your option) any later version. +** The licenses are as published by the Free Software Foundation +** and appearing in the file LICENSE.LGPLv21 included in the packaging +** of this file. Please review the following information to ensure +** the GNU Lesser General Public License version 2.1 requirements +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "workspacemodel.h" + +#include + +#include + +namespace ADS { + +class DockManager; +class WorkspaceDialog; + +class WorkspaceView : public Utils::TreeView +{ + Q_OBJECT + +public: + explicit WorkspaceView(QWidget *parent = nullptr); + + void createNewWorkspace(); + void deleteSelectedWorkspaces(); + void cloneCurrentWorkspace(); + void renameCurrentWorkspace(); + void switchToCurrentWorkspace(); + + QString currentWorkspace(); + WorkspaceModel *workspaceModel(); + void selectActiveWorkspace(); + void selectWorkspace(const QString &workspaceName); + +signals: + void activated(const QString &workspace); + void selected(const QStringList &workspaces); + void workspaceSwitched(); + +private: + void showEvent(QShowEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + + void deleteWorkspaces(const QStringList &workspaces); + QStringList selectedWorkspaces() const; + + static WorkspaceDialog *castToWorkspaceDialog(QWidget *widget); + + DockManager *m_manager; + WorkspaceModel m_workspaceModel; +}; + +} // namespace ADS diff --git a/src/libs/libs.pro b/src/libs/libs.pro index 550a6b0b97f..40dce690eba 100644 --- a/src/libs/libs.pro +++ b/src/libs/libs.pro @@ -3,6 +3,7 @@ include(../../qtcreator.pri) TEMPLATE = subdirs SUBDIRS += \ + advanceddockingsystem \ aggregation \ extensionsystem \ utils \ diff --git a/src/libs/libs.qbs b/src/libs/libs.qbs index 84fc626501b..0c3c8733706 100644 --- a/src/libs/libs.qbs +++ b/src/libs/libs.qbs @@ -3,6 +3,7 @@ import qbs Project { name: "Libs" references: [ + "advanceddockingsystem/advanceddockingsystem.qbs", "aggregation/aggregation.qbs", "clangsupport/clangsupport.qbs", "cplusplus/cplusplus.qbs",