diff --git a/.reuse/dep5 b/.reuse/dep5 index e6a01f544..53533dfab 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -68,21 +68,11 @@ Files: src/plugin/* Copyright: UnionTech Software Technology Co., Ltd. License: GPL-3.0-or-later -# commom -Files: src/common/* -Copyright: UnionTech Software Technology Co., Ltd. -License: GPL-3.0-or-later - # tool Files: src/tool/* Copyright: UnionTech Software Technology Co., Ltd. License: GPL-3.0-or-later -# service -Files: src/*.service -Copyright: UnionTech Software Technology Co., Ltd. -License: GPL-3.0-or-later - # FlameGraph Files: 3rdparty/scripts/* Copyright: 2013 Brendan Gregg diff --git a/3rdparty/unioncode-qscintilla214/scintilla/src/Editor.cpp b/3rdparty/unioncode-qscintilla214/scintilla/src/Editor.cpp index 8e6567a7d..b8ad28601 100644 --- a/3rdparty/unioncode-qscintilla214/scintilla/src/Editor.cpp +++ b/3rdparty/unioncode-qscintilla214/scintilla/src/Editor.cpp @@ -5,6 +5,7 @@ // Copyright 1998-2011 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. +#include #include #include #include @@ -2403,7 +2404,7 @@ void Editor::NotifyPainted() { NotifyParent(scn); } -void Editor::NotifyIndicatorClick(bool click, Sci::Position position, int modifiers) { +bool Editor::NotifyIndicatorClick(bool click, Sci::Position position, int modifiers) { const int mask = pdoc->decorations->AllOnFor(position); if ((click && mask) || pdoc->decorations->ClickNotified()) { SCNotification scn = {}; @@ -2413,6 +2414,9 @@ void Editor::NotifyIndicatorClick(bool click, Sci::Position position, int modifi scn.position = position; NotifyParent(scn); } + + std::bitset<32> flags(static_cast(mask)); + return flags[INDIC_COMPOSITIONTHICK]; } bool Editor::NotifyMarginClick(Point pt, int modifiers) { @@ -4491,9 +4495,10 @@ void Editor::ButtonDownWithModifiers(Point pt, unsigned int curTime, int modifie sel.SetMoveExtends(false); if (NotifyMarginClick(pt, modifiers)) - return; + return; - NotifyIndicatorClick(true, newPos.Position(), modifiers); + if (NotifyIndicatorClick(true, newPos.Position(), modifiers)) + return; const bool inSelMargin = PointInSelMargin(pt); // In margin ctrl+(double)click should always select everything diff --git a/3rdparty/unioncode-qscintilla214/scintilla/src/Editor.h b/3rdparty/unioncode-qscintilla214/scintilla/src/Editor.h index 55b6f56aa..618b29f92 100644 --- a/3rdparty/unioncode-qscintilla214/scintilla/src/Editor.h +++ b/3rdparty/unioncode-qscintilla214/scintilla/src/Editor.h @@ -430,7 +430,7 @@ class Editor : public EditModel, public DocWatcher { void NotifyHotSpotReleaseClick(Sci::Position position, int modifiers); bool NotifyUpdateUI(); void NotifyPainted(); - void NotifyIndicatorClick(bool click, Sci::Position position, int modifiers); + bool NotifyIndicatorClick(bool click, Sci::Position position, int modifiers); bool NotifyMarginClick(Point pt, int modifiers); bool NotifyMarginRightClick(Point pt, int modifiers); void NotifyNeedShown(Sci::Position pos, Sci::Position len); diff --git a/3rdparty/unioncode-qscintilla214/src/qsciscintilla.cpp b/3rdparty/unioncode-qscintilla214/src/qsciscintilla.cpp index eea69888b..c69242ff0 100644 --- a/3rdparty/unioncode-qscintilla214/src/qsciscintilla.cpp +++ b/3rdparty/unioncode-qscintilla214/src/qsciscintilla.cpp @@ -54,6 +54,8 @@ const char userSeparator = '\x04'; // The default fold margin width. static const int defaultFoldMarginWidth = 14; +static const int MinFontZoomSize = 5; + // The default set of characters that make up a word. static const char *defaultWordChars = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -919,6 +921,11 @@ int QsciScintilla::blockIndent(int line) } else if (line == l) ind += ind_width; + else { + const auto &lineText = text(l).trimmed(); + if (lineText.size() > 1 && lineText.endsWith(":")) + ind += ind_width; + } return ind; } @@ -1725,10 +1732,15 @@ void QsciScintilla::zoomOut(int range) zoomTo(SendScintilla(SCI_GETZOOM) - range); } - // Zoom out a single point. void QsciScintilla::zoomOut() { + int value = SendScintilla(SCI_GETZOOM); + auto font = lex ? lex->defaultFont() : this->font(); + auto fontSize = font.pointSize() * (100 + 10 * value) / 100; + if (fontSize < MinFontZoomSize) + return; + SendScintilla(SCI_ZOOMOUT); } diff --git a/CMakeLists.txt b/CMakeLists.txt index 28114afb2..e3d37dc40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,13 +15,11 @@ if (NOT VERSION) endif() message(STATUS "build version ${VERSION}") +option(ENABLE_SANITIZE_CHECK "Enable Sanitize Check " OFF) set(CMAKE_VERBOSE_MAKEFILE ON) -if ((NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "sw_64") - AND (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "loongarch64") - AND (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "mips64") - AND (CMAKE_BUILD_TYPE STREQUAL "Debug")) +if (ENABLE_SANITIZE_CHECK AND (CMAKE_BUILD_TYPE STREQUAL "Debug")) message("debug type open sanitize check") set(CMAKE_CXX_FLAGS "-fsanitize=undefined,address,leak -fno-omit-frame-pointer") set(CMAKE_C_FLAGS "-fsanitize=undefined,address,leak -fno-omit-frame-pointer") @@ -31,15 +29,6 @@ if ((NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "sw_64") # set(CMAKE_L_FLAGS "-fsanitize=thread") endif() -# Setup install paths -# /usr/bin/dde-file-manager -# execute bin file -# /usr/lib/x86_64-linux-gnu/dde-file-manager -# dir plugins && dir tools abv thumbnail -# /usr/include/dde-file-manager -# public *.h && private dir *.h -# /usr/share/dde-file-manager -# subdir"database mimetypeassociations mimetypes templates translations" set(LIBRARY_INSTALL_PREFIX "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/${PROJECT_NAME}") #set(ARCHIVE_INSTALL_RPEFIX "") undefined the static lib archive @@ -66,7 +55,6 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/build/) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/build/) link_directories(${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) -# if no debug, can't out in code define key '__FUNCTION__' and so on add_definitions(-DQT_MESSAGELOGCONTEXT) # Setup the environment @@ -105,17 +93,12 @@ foreach(QtModule ${QtFindModules}) message("QtModule found ${QtModule} OK!") endforeach() -# Use PkgConfig modules -# You can setting mini version for "glib-2.0>=2.10 gtk+-2.0" in the list set(PkgConfigFindModules) include(FindPkgConfig) foreach(PkgConfigModule ${PkgConfigFindModules}) pkg_check_modules(PkgUseModuleVal REQUIRED ${PkgConfigModule}) - #can use target_link_libraries(xxx ${PkgUseModule}) list(APPEND PkgUseModules ${PkgUseModuleVal_LDFLAGS}) include_directories(${PkgUseModuleVal_INCLUDE_DIRS}) - # link_libraries(${PkgUseModuleVal_LIBRARIES}) - # link_directories(${PkgUseModuleVal_LINK_LIBRARIES}) endforeach(PkgConfigModule) include_directories(${PROJECT_SOURCE_DIR}/src) diff --git a/LICENSE b/LICENSE index 94a9ed024..513d1c01f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,304 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . +GNU LESSER GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. + +"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". + +The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. +You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. +If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: + + a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. +The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license document. + +4. Combined Works. +You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: + + a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license document. + + c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. + + e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) + +5. Combined Libraries. +You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. +The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the Program. + +To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. +A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . diff --git a/LICENSES/LGPL-3.0-or-later.txt b/LICENSES/LGPL-3.0-or-later.txt new file mode 100644 index 000000000..513d1c01f --- /dev/null +++ b/LICENSES/LGPL-3.0-or-later.txt @@ -0,0 +1,304 @@ +GNU LESSER GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. + +"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". + +The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. +You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. +If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: + + a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. +The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license document. + +4. Combined Works. +You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: + + a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license document. + + c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. + + e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) + +5. Combined Libraries. +You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. +The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the Program. + +To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. +A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . diff --git a/README.en.md b/README.en.md index f47a5c8d8..2117f8beb 100644 --- a/README.en.md +++ b/README.en.md @@ -45,6 +45,8 @@ _The **dev** branch is current development branch, build dependencies may change ## Installation + *If you install the IDE on Ubuntu by source code, please refer to the [DTK dependent installation](./docs/dtk-install-guide.en.md). After the installation of the dtk environment and the basic qt environment, you can install the IDE through the following source installation method.* + 1. Make sure you have installed all dependencies. ``` shell @@ -111,7 +113,7 @@ We encourage you to report issues and contribute changes ## License -deepin-unioncode is licensed under [GPL-3.0-or-later](LICENSE) +deepin-unioncode is licensed under [LGPL-3.0-or-later](LICENSE) ## 3rdparty support diff --git a/README.md b/README.md index 5cc510a8c..99c8ef334 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ _当前的开发分支为**dev**,编译依赖可能会在没有更新本说明 ## 安装 +​ *如若在Ubuntu上使用源码安装方式安装IDE,请先参考[DTK依赖安装](./docs/dtk-install-guide.md),在DTK环境以及基本qt环境安装完成后,可通过下面源码安装的方式安装本IDE。* + 1. 确保已经安装所有依赖库。 @@ -112,7 +114,7 @@ $ sudo cmake --build build --target install ## 开源许可证 -deepin-unioncode 在 [GPL-3.0-or-later](LICENSE.txt)下发布。 +deepin-unioncode 在 [LGPL-3.0-or-later](LICENSE)下发布。 ## 三方库支持 diff --git a/assets/configures/editorstyle_cpp.support b/assets/configures/editorstyle_cpp.support index 72f5b4d53..024c1107f 100644 --- a/assets/configures/editorstyle_cpp.support +++ b/assets/configures/editorstyle_cpp.support @@ -19,7 +19,7 @@ "Foreground": "#d6bb9a" }, "Property": { - "Foreground": "#ff8080" + "Foreground": "#d6cf9a" }, "Parameter": { "Foreground": "#d6bb9a" @@ -75,7 +75,7 @@ "Foreground": "#092e64" }, "Property": { - "Foreground": "#800080" + "Foreground": "#800000" }, "Parameter": { "Foreground": "#092e64" @@ -111,4 +111,4 @@ "Foreground": "#008000" } } -} \ No newline at end of file +} diff --git a/assets/configures/language.support b/assets/configures/language.support index 9b4482476..807c43147 100644 --- a/assets/configures/language.support +++ b/assets/configures/language.support @@ -44,5 +44,8 @@ "tokenWords" : { "1" : "await break case catch class const continue debugger default delete do else enum export extends false finally for function if implements import in instanceof interface let new null package private protected public return super switch static this throw try true typeof var void while with yield" } + }, + "shell" : { + "mimeType" : ["application/x-shellscript"] } } diff --git a/assets/configures/unioncode.desktop b/assets/configures/unioncode.desktop index 7af2cb0f6..4d3c470ad 100644 --- a/assets/configures/unioncode.desktop +++ b/assets/configures/unioncode.desktop @@ -1,15 +1,14 @@ [Desktop Entry] -Actions=new-empty-window; Categories=Utility;TextEditor;Development;IDE; Comment=Code Editing. Redefined. Exec=/usr/bin/deepin-unioncode -GenericName=Text Editor +GenericName=Deepin Union Code Icon=/usr/share/deepin-unioncode/configures/icons/ide.svg Keywords=deepin-unioncode; MimeType=text/plain;application/x-code-workspace; Name=Deepin Union Code Path=/usr/bin StartupNotify=false -StartupWMClass=Code +StartupWMClass=UnionCode Type=Application X-Deepin-Vendor=user-custom diff --git a/assets/templates/projects/dfm-extension/CMakeLists.txt b/assets/templates/projects/dfm-extension/CMakeLists.txt index 41f90503b..825904b95 100644 --- a/assets/templates/projects/dfm-extension/CMakeLists.txt +++ b/assets/templates/projects/dfm-extension/CMakeLists.txt @@ -5,7 +5,11 @@ project(%{ProjectName}) set(CMAKE_CXX_STANDARD 17) # build -find_package(dfm-extension REQUIRED) +find_package(dfm-extension) +if (NOT dfm-extension_FOUND) + message(FATAL_ERROR "Could not find the libdfm-extension-dev, please download it first and then try again") +endif() + file(GLOB_RECURSE SRCS CONFIGURE_DEPENDS "./*.h" "./*.cpp" diff --git a/assets/templates/projects/dtkapplication/CMakeLists.txt b/assets/templates/projects/dtkapplication/CMakeLists.txt new file mode 100644 index 000000000..bba709ea1 --- /dev/null +++ b/assets/templates/projects/dtkapplication/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.7) + +project(%{ProjectName}) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + +# Find the library +find_package(PkgConfig REQUIRED) +find_package(Qt5Widgets REQUIRED) +find_package(DtkWidget REQUIRED) + +add_executable(${PROJECT_NAME} "main.cpp" resources.qrc) + +target_include_directories(%{ProjectName} PUBLIC ${DtkWidget_INCLUDE_DIRS} ${OBJECT_BINARY_DIR}) +target_link_libraries(%{ProjectName} + ${DtkWidget_LIBRARIES} + ${Qt5Widgets_LIBRARIES} +) diff --git a/assets/templates/projects/dtkapplication/images/logo.svg b/assets/templates/projects/dtkapplication/images/logo.svg new file mode 100644 index 000000000..e4b65efb1 --- /dev/null +++ b/assets/templates/projects/dtkapplication/images/logo.svg @@ -0,0 +1,16 @@ + + + + application-x-executable + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/assets/templates/projects/dtkapplication/main.cpp b/assets/templates/projects/dtkapplication/main.cpp new file mode 100644 index 000000000..9afd9b349 --- /dev/null +++ b/assets/templates/projects/dtkapplication/main.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +DWIDGET_USE_NAMESPACE + +int main(int argc, char *argv[]) +{ + QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + DApplication a(argc, argv); + a.setOrganizationName("deepin"); + a.setApplicationName("dtk-application"); + a.setApplicationVersion("1.0"); + a.setProductIcon(QIcon(":/images/logo.svg")); + a.setProductName("Dtk Application"); + a.setApplicationDescription("This is a dtk template application."); + + a.loadTranslator(); + a.setApplicationDisplayName(QCoreApplication::translate("Main", "DTK Application")); + + // 保存程序的窗口主题设置 + DApplicationSettings as; + Q_UNUSED(as) + + DMainWindow w; + w.titlebar()->setIcon(QIcon(":/images/logo.svg")); + w.titlebar()->setTitle("Hello dtk"); + // 设置标题,宽度不够会隐藏标题文字 + w.setMinimumSize(QSize(600, 200)); + + QWidget *cw = new QWidget(&w); + QVBoxLayout *layout = new QVBoxLayout(cw); + QDate today = QDate::currentDate(); + DProgressBar *yearProgressBar = new DProgressBar(); + yearProgressBar->setMaximum(today.daysInYear()); + // 绑定字体大小 + DFontSizeManager::instance()->bind(yearProgressBar, DFontSizeManager::T1); + + yearProgressBar->setAlignment(Qt::AlignCenter); + QObject::connect(yearProgressBar, &DProgressBar::valueChanged, yearProgressBar, [yearProgressBar](int value){ + yearProgressBar->setFormat(QString("您的 %1 使用进度: %2%").arg(QDate::currentDate().year()) + .arg(value * 100 / yearProgressBar->maximum())); + }); + + layout->addWidget(yearProgressBar); + w.setCentralWidget(cw); + w.show(); + + auto animation = new QPropertyAnimation(yearProgressBar, "value"); + animation->setDuration(5000); + animation->setStartValue(0); + animation->setEndValue(today.dayOfYear()); + animation->start(); + + Dtk::Widget::moveToCenter(&w); + + return a.exec(); +} diff --git a/assets/templates/projects/dtkapplication/resources.qrc b/assets/templates/projects/dtkapplication/resources.qrc new file mode 100644 index 000000000..16bf36d39 --- /dev/null +++ b/assets/templates/projects/dtkapplication/resources.qrc @@ -0,0 +1,5 @@ + + + images/logo.svg + + diff --git a/assets/templates/projects/dtkapplication/wizard.json b/assets/templates/projects/dtkapplication/wizard.json new file mode 100644 index 000000000..3dcc6650d --- /dev/null +++ b/assets/templates/projects/dtkapplication/wizard.json @@ -0,0 +1,95 @@ +{ + "version": 1, + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qt4ProjectManager.Qt4Project" ], + "id": "Deepin DTK Application", + "type": "project", + "wizardType": "QTCtype", + "category": "F.Application", + "trDescription": "Create a simple DTK template.", + "trDisplayName": "Dtk Widgets Application", + "trDisplayCategory": "Application", + "icon": "../../global/guiapplication.png", + "enabled": "%{JS: [ %{Plugins} ].indexOf('CppEditor') >= 0 && ([ %{Plugins} ].indexOf('QmakeProjectManager') >= 0 || [ %{Plugins} ].indexOf('QbsProjectManager') >= 0 || [ %{Plugins} ].indexOf('CMakeProjectManager') >= 0)}", + + "options": + [ + { "key": "ProjectFile", "value": "%{JS: '%{BuildSystem}' === 'qmake' ? '%{ProFile}' : '%{CMakeFile}'}"}, + { "key": "ProFile", "value": "%{JS: Util.fileName('%{ProjectDirectory}/%{ProjectName}', 'pro')}" }, + { "key": "QbsFile", "value": "%{JS: Util.fileName('%{ProjectDirectory}/%{ProjectName}', 'qbs')}" }, + { "key": "CMakeFile", "value": "%{ProjectDirectory}/CMakeLists.txt" }, + { "key": "CppFileName", "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src')}" } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project" + }, + { + "trDisplayName": "Define Build System", + "trShortTitle": "Build System", + "typeId": "Fields", + "enabled": "%{JS: ! %{IsSubproject}}", + "data": + [ + { + "name": "BuildSystem", + "trDisplayName": "Build system:", + "type": "ComboBox", + "data": + { + "index": 0, + "items": + [ + { + "trKey": "CMake", + "value": "cmake", + "condition": "%{JS: [ %{Plugins} ].indexOf('CMakeProjectManager') >= 0}" + } + ] + } + } + ] + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{JS: ! %{IsSubproject}}", + "data": { "projectFilePath": "%{ProjectFile}" } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "CMakeLists.txt", + "openAsProject": true, + "replaceKeys": ["%{ProjectName}"], + "condition": "%{JS: '%{BuildSystem}' === 'cmake'}" + }, + { + "source": "resources.qrc" + }, + { + "source": "images/logo.svg" + }, + { + "source": "main.cpp", + "target": "%{CppFileName}", + "openInEditor": true + } + ] + } + ] +} diff --git a/assets/templates/projects/qtconsoleapp/CMakeLists.txt b/assets/templates/projects/qtconsoleapp/CMakeLists.txt new file mode 100644 index 000000000..a7967bc84 --- /dev/null +++ b/assets/templates/projects/qtconsoleapp/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.0.0) + +project(%{ProjectName} VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +find_package(Qt5Core) + +add_executable(${PROJECT_NAME} main.cpp) + +target_link_libraries(${PROJECT_NAME} Qt5::Core) diff --git a/assets/templates/projects/qtconsoleapp/main.cpp b/assets/templates/projects/qtconsoleapp/main.cpp new file mode 100644 index 000000000..63096ebcf --- /dev/null +++ b/assets/templates/projects/qtconsoleapp/main.cpp @@ -0,0 +1,11 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + qInfo() << "hello world"; + + return a.exec(); +} diff --git a/assets/templates/projects/qtconsoleapp/wizard.json b/assets/templates/projects/qtconsoleapp/wizard.json new file mode 100644 index 000000000..5b5b1880f --- /dev/null +++ b/assets/templates/projects/qtconsoleapp/wizard.json @@ -0,0 +1,92 @@ +{ + "version": 1, + "type":"project", + "wizardType":"QTCtype", + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], + "id": "E.QtCore", + "category": "F.Application", + "trDescription": "Creates a project containing a single main.cpp file with a stub implementation.\n\nPreselects a desktop Qt for building the application if available.", + "trDisplayName": "Qt Console Application", + "trDisplayCategory": "Application", + "icon": "../../global/consoleapplication.png", + "featuresRequired": [ "QtSupport.Wizards.FeatureQt" ], + "enabled": "%{JS: [ %{Plugins} ].indexOf('QmakeProjectManager') >= 0 || [ %{Plugins} ].indexOf('QbsProjectManager') >= 0 || [ %{Plugins} ].indexOf('CMakeProjectManager') >= 0}", + + "options": + [ + { "key": "ProjectFile", "value": "%{JS: '%{BuildSystem}' === 'qmake' ? '%{ProFile}' : ('%{BuildSystem}' === 'cmake' ? '%{CMakeFile}' : '%{QbsFile}')}" }, + { "key": "ProFile", "value": "%{JS: Util.fileName('%{ProjectDirectory}/%{ProjectName}', 'pro')}" }, + { "key": "QbsFile", "value": "%{JS: Util.fileName('%{ProjectDirectory}/%{ProjectName}', 'qbs')}" }, + { "key": "CMakeFile", "value": "%{ProjectDirectory}/CMakeLists.txt" }, + { "key": "CppFileName", "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src')}" } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": { "trDescription": "This wizard creates a simple Qt-based console application." } + }, + { + "trDisplayName": "Define Build System", + "trShortTitle": "Build System", + "typeId": "Fields", + "enabled": "%{JS: ! %{IsSubproject}}", + "data": + [ + { + "name": "BuildSystem", + "trDisplayName": "Build system:", + "type": "ComboBox", + "data": + { + "index": 0, + "items": + [ + { + "trKey": "CMake", + "value": "cmake", + "condition": "%{JS: [ %{Plugins} ].indexOf('CMakeProjectManager') >= 0}" + } + ] + } + } + ] + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{JS: ! %{IsSubproject}}", + "data": { "projectFilePath": "%{ProjectFile}" } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "CMakeLists.txt", + "openAsProject": true, + "replaceKeys": ["%{ProjectName}"], + "condition": "%{JS: '%{BuildSystem}' === 'cmake'}" + }, + + { + "source": "main.cpp", + "target": "%{CppFileName}", + "openInEditor": true + } + ] + } + ] +} diff --git a/assets/templates/templates.json b/assets/templates/templates.json index 4d2c0dff2..fd6ef5dbc 100644 --- a/assets/templates/templates.json +++ b/assets/templates/templates.json @@ -55,9 +55,22 @@ "name": "dfm-extension", "path": "projects/dfm-extension", "leafNode":true + }, + { + "name": "dtk-application", + "path": "projects/dtkapplication", + "leafNode":true } ] - } + }, + { + "type": "Qt", + "templates":[{ + "name": "QtConsoleApplication", + "path": "projects/qtconsoleapp", + "leafNode": true + }] + } ], "Files": [{ "type": "C++", diff --git a/assets/translations/en_US.ts b/assets/translations/en_US.ts index 34d2f53f3..69c2d6dbf 100644 --- a/assets/translations/en_US.ts +++ b/assets/translations/en_US.ts @@ -1,6 +1,14 @@ + + APTInstaller + + + Install packages by apt + + + ActionLocator @@ -9,6 +17,175 @@ + + AdvancedSearchWidget + + + Collapse All + + + + + Expand All + + + + + Clear Search Results + + + + + Refresh + + + + + Stop Search + + + + + Search + + + + + Match Case + + + + + Match Whole Word + + + + + Use Regular Expression + + + + + Replace + + + + + Replace All + + + + + All Projects + + + + + Current Project + + + + + Current File + + + + + Toggle Search Details + + + + + Scope: + + + + + + e.g.*.ts,src/**/include + + + + + BinaryToolsConfigView + + + Files To Include: + + + + + Files To Exclude: + + + + + All projects path is empty, please import! + + + + + Current projects path is empty, please import! + + + + + No files are currently open, please open! + + + + + Advance Search + + + + + Cancel + button + + + + + Continue + button + + + + + No results found. + + + + + Replace 1 occurence across 1 file? + + + + + Replace 1 occurence across 1 file with %1? + + + + + Replace %1 occurences across 1 file? + + + + + Replace %1 occurences across 1 file with %2? + + + + + Replace %1 occurences across %2 files? + + + + + Replace %1 occurences across %2 files with %3? + + + AdvancedSettingsDialog @@ -16,6 +193,9 @@ Advanced Settings + + + BinaryToolsConfigViewPrivate Missing Hint: @@ -70,33 +250,46 @@ AppOutputPane - + default - + Stop Running Program + + + BinaryToolsModel - + Close OutputPane - + + Clear Output + + + + + Filter + + + + Process is running, kill process? - + kill button - + Cancel button @@ -105,17 +298,17 @@ Application - + <br/>Built on %1 %2 in %3<br/> - + deepin-unioncode - + Deepin Union Code is a lightweight integrated development environment, featured with multilingual and cross platform compatibility. @@ -165,38 +358,86 @@ + + AttachInfoDialog + + + debugger: + + + + + filter: + + + + + Process Id + + + + + Process Path + + + + + Update + + + + + Cancel + + + + + Attaching + + + BehaviorWidget - + Spaces Only - + Tabs Only - + Tabs And Indentation - + Enable automatic indentation - + Tab policy: - + Tab size: + + + Editor Tip + + + + + Tip Active Time(ms): + + BinaryToolsConfigView @@ -274,26 +515,23 @@ - - Tool icon: + + Select Executabel Path - - Working directory: + + Select Working Directory - - - BinaryToolsConfigViewPrivate - - Select Executabel Path + + Tool icon: - - Select Working Directory + + Working directory: @@ -330,68 +568,81 @@ - - The tool (%1) execution program does not exist. Install and run it again + + Start execute tool "%1". + - - Ok + + The tool is running. Please stop it before running. + - - Start execute "%1": "%2" "%3" in workspace "%4". - + + The tool (%1) execution program does not exist. Install and run it again - - Configure... + + Ok - - &Console + + Configure... - + The tool "%1" exited normally. - + The tool "%1" exited with code %2. - + The tool "%1" crashed. - + Execute tool "%1" finished. - + Cancel - + Install - - + + The tool has set the working directory, but the working directory parsing is empty. Please check and try again. + + + + + + The tool has set the channel data, but the channel data parsing is empty. Please check and try again. + + + + + + &Application Output @@ -563,37 +814,37 @@ BreakpointView - + Enable selected breakpoints - + Disable selected breakpoints - + Remove selected breakpoints - + Remove all breakpoints - + Enable all breakpoints - + Disable all breakpoints - + Edit Condition @@ -601,7 +852,7 @@ BuildManager - + Compile Output @@ -631,42 +882,47 @@ - + + Clear Output + + + + &Build - + Execute command failed! - + Start execute command: "%1" "%2" in workspace "%3". - + The process "%1" exited normally. - + The process "%1" exited with code %2. - + The process "%1" crashed. - + Execute command finished. @@ -675,12 +931,7 @@ BuildPropertyPage - - Browse - - - - + Output direcotry: @@ -696,7 +947,7 @@ BuilderReceiver - + Error parsing project symbol workspace: %0 language: %1 @@ -708,12 +959,12 @@ storage: %2 CMakeBuilderGenerator - + The build command of %1 project is null! please install it in console with "sudo apt install cmake", and then restart the tool. - + The path of "%1" is not exist! please check and reopen the project. @@ -721,12 +972,12 @@ storage: %2 CMakeDebug - + The gdb is required, please install it in console with "sudo apt install gdb", and then restart the tool, reselect the CMake Debugger in Options Dialog... - + Request cxx dap port failed, please retry. @@ -734,7 +985,7 @@ storage: %2 CmakeAsynParse - + build @@ -742,55 +993,71 @@ storage: %2 CmakeProjectGenerator - + Run CMake + + + DebuggerPlugin - + Clear CMake - + + Files in project %1 have changed, needs to run cmake to update + + + + Properties - + File are not automatically added to the CmakeList.txt file to the Cmake project. Copy the path to the source files to the clipboard? + + + DetailPropertyWidget - + Copy to Clipboard? - + Ok - + + Project Properties + + + + Build - + Run - CodeCompletionExtendWidgetPrivate + CodeCompletionExtendWidget - + From: - + Shortcut: @@ -811,77 +1078,77 @@ storage: %2 CodeEditor - + Save All Documents - + Close All Documents - + Print - + backward - + forward - + Close Current Editor - + Switch Header/Source - + Follow Symbol Under Cursor - + Toggle Breakpoint - + Find Usages - + Rename Symbol Under Cursor - + Current document - + Current document content - + Backward - + Forward @@ -931,10 +1198,15 @@ storage: %2 CodeGeex - + Please login to use CodeGeeX. + + + Enable Qt Debug Level + + CodeLens @@ -1170,25 +1442,35 @@ storage: %2 - Console + ConsoleManager + + + Terminal + + - - &Console + + New Terminal ConsoleWidget - + copy - + paste + + + Enter current project root path + + ContextModule @@ -1201,22 +1483,27 @@ storage: %2 Controller - + Open Document - + Hide ContextWidget - + + Show docks in this view + + + + Expand All - + Fold All @@ -1270,163 +1557,182 @@ storage: %2 DAPDebugger - - + + The debugee has Terminated. - + The debugee has Exited. - + Input Condition Expression - + Condition - + When the breakpoint is reached, it will be hit only when the expression is true - - + + + Cancel - - + + can`t attaching to debugee when debuging other application + + + + + The gdb is required, please install it in console with "sudo apt install gdb", and then restart the tool, reselect the CMake Debugger in Options Dialog... + + + + + Request cxx dap port failed, please retry. + + + + + Ok - + <Unknown> name - + <Unknown> meaning - + <p>The inferior stopped because it received a signal from the operating system.<p><table><tr><td>Signal name : </td><td>%1</td></tr><tr><td>Signal meaning : </td><td>%2</td></tr></table> - + Signal Received - + New Evaluator Expression - + Enter an expression to evaluate - + Threads: - + Stack List - + Add New Expression Evaluator - + Remove This Evaluator + + + NotificationItemWidget - + Name - + Value - + Type - + Breakpoint List - + Please build first. Build : Ctrl + B - + Is preparing dependence, please waiting for a moment - + Is getting the dap port, please waiting for a moment - + Requesting debug port... - + The dap port is not ready, please retry. - + Debugging starts - + Start debugging coredump file: - + The coredump target file is error: - + The coredump file is error: @@ -1467,11 +1773,6 @@ The dap port is not ready, please retry. Cancel - - - Replace - - DToolBar @@ -1489,13 +1790,13 @@ The dap port is not ready, please retry. DTreeView - + The delete operation will be removed fromthe disk and will not be recoverable after this operation. Delete anyway? - + Delete Warning @@ -1503,28 +1804,51 @@ Delete anyway? DebuggerPlugin - + debug - + &Application Output + + DependenceManager + + + Cancel + + + + + Install + + + + + Request to install some dependency packages. Do you want to install them? + + + DetailPropertyWidget - + Python interpreter: - + Executable File: + + + Run in terminal: + + DetailWidget @@ -1563,6 +1887,9 @@ Delete anyway? The filename can't be empty! + + + QAction The project can't be empty! @@ -1606,32 +1933,42 @@ Delete anyway? DetailsView - + Description - + Dependency - + reLaunch when changed! - + + Settings + + + + + Relaunch required! + + + + This platform not support doc display! - + unload - + load @@ -1639,7 +1976,7 @@ Delete anyway? DisplayDocView - + Documents @@ -1647,7 +1984,7 @@ Delete anyway? DisplayProjectView - + Projects @@ -1665,6 +2002,14 @@ Delete anyway? + + EditorLabel + + + Line %1 Column %2 + + + EditorSettingsWidgetGenerator @@ -1684,17 +2029,17 @@ Delete anyway? EnvironmentView - + append - + reduce - + reset @@ -1702,115 +2047,129 @@ Delete anyway? EnvironmentWidget - + Enable All Environment - - Enable Qt Debug Level + + append + + + + + reduce + + + + + reset FileTreeView - + Error, Can't move to trash: - + Error, Can't delete: - + New Document Name - - + + New Document - - - + + Ok - + Open - - + + New Folder - + Delete operation not be recoverable, delete anyway? - + Delete Warining - + Cancel - + + OK + + + + New File Name - + New File - + New Folder Name - + Error: Can't create new document or folder, please check whether the name already exists! - + Error: Can't create new document or folder, parent not's dir - + Move To Trash - + Remove - + Rename - + Recover From Trash @@ -1818,15 +2177,13 @@ Delete anyway? FindPlugin - + Advanced Find - - - - Advanced &Search + + ADVANCED SEARCH @@ -1880,85 +2237,142 @@ Delete anyway? - FindToolWindow + FontColorWidget - - All Projects + + Font - - Current Project + + Family: - - Current File + + Size: - - thread + + Zoom: + + + GenerateDialog - - - e.g.*.ts,src/**/include + + New LingLong Project - - Ok + + Please input application`s name - - All projects path is empty, please import! + + 0.0.0.1 + + + + + Please input description + + + + + Kind: + + + + + Local +need to manually copy the source code to this path + + + + + Remote + + + + + ID: + + + + + Name + + + + + Project Path: + + + + + Version: + + + + + Description: + + + + + Depends : - - - Current project path is empty, please import! + + Execute Command: - - Scope is not selected, please select! + + Source Type: - - Search for text is empty, please input! + + Cancel - - Searching, please wait... + + Confirm + + + + + Choose path - FontColorWidget + QMenu - - Font + + %1 is already existed - - Family: + + This item can not be empty - - Size: + + Project Path is empty or not exist - - Zoom: + + Version must be in the format of *.*.*.* @@ -2020,32 +2434,52 @@ Delete anyway? GitMenuManager - - + + Log of "%1" - - + + Diff of "%1" - + Blame of "%1" - + Current File - + Current Project + + + Git Log + + + + + Git Blame + + + + + Git Diff + + + + + &Git + + GitTabWidget @@ -2126,10 +2560,15 @@ Delete anyway? GradleProjectGenerator - + Properties + + + Project Properties + + GradleWidget @@ -2250,22 +2689,22 @@ Delete anyway? JSDebugger - + Stac&kFrame - + Break&points - + ScriptWidget - + ErrorLogWidget @@ -2312,6 +2751,14 @@ Delete anyway? + + JavaUtil + + + Unable to find the javap application. Please install openjdk-11-jdk or another version first. + + + KitsManagerWidget @@ -2365,48 +2812,69 @@ Delete anyway? - LSPStyle + LLBuilderGenerator + + + Can`t find linglong-builder tool + + + + + The path of "%1" is not exist! please check and reopen the project. + + + + + LLGenerator + + + debug of Linglong project is not supported + + + + + LanguageClientHandlerPrivate - + Refactor - + Rename Symbol Under Cursor - + Switch Header/Source - + Follow Symbol Under Cursor - + Find Usages - - Range Formatting + + Format Selection LocatorManager - + Enter command - - + + Enter command %1 @@ -2423,44 +2891,87 @@ repos path: %0 MacroExpander - - %1: Full path including file name. + + %1: Full path including file name. + + + + + %1: Full path excluding file name. + + + + + %1: File name without path. + + + + + %1: File base name without path and suffix. + + + + + MainDialog + + + + Tip + + + + + Can not find kit. + + + + + MainFrame + + + New + + + + + Installed + + + + + Repository - - %1: Full path excluding file name. + + Running - - %1: File name without path. + + Search - - %1: File base name without path and suffix. + + Run - - - MainDialog - - - Tip + + Install - - Can not find kit. + + UnInstall MainWindow - + Hide Dock Widget @@ -2516,6 +3027,11 @@ repos path: %0 Properties + + + Project Properties + + MavenWidget @@ -2628,26 +3144,34 @@ repos path: %0 OutputPane - + Additional output omitted - + Copy - + Clear - + Select All + + PIPInstaller + + + Install packages by pip + + + Performance @@ -2674,10 +3198,18 @@ repos path: %0 + + PluginManagerModule + + + Extensions + + + PluginStoreWidget - + Search Extension @@ -2716,10 +3248,25 @@ repos path: %0 ProjectCore - + Open activted project`s property dialog + + + Auto Focus + + + + + Opened Files + + + + + Focus File + + ProjectPane @@ -2734,7 +3281,7 @@ repos path: %0 - + Choose path @@ -2742,69 +3289,93 @@ repos path: %0 ProjectTree - - Show Contain Folder + + + + New Document - - - - New Document + + + New Directory + + + + + Show Containing Folder - - + + Rename - + Open In Terminal - + Delete Document - - + + New Document Name - - - - - + + + + + + + Ok - + + New Dirctory Name + + + + + New Dirctory + + + + Delete operation not be recoverable, delete anyway? - + Delete: - - - + + A directory with name %1 already exists. please reanme it + + + + + + A file with name %1 already exists. Would you like to overwrite it? - - - + + + + Cancel @@ -2812,17 +3383,17 @@ repos path: %0 PropertiesDialog - + Project Properties - + Cancel - + Apply @@ -2830,22 +3401,22 @@ repos path: %0 PythonDebug - + There is no opened python file, please open. - + The python3 is needed, please select it in options dialog or install it. - + The debugpy is needed, please use command "pip3 install debugpy" install and retry. - + Request python dap port failed, please retry. @@ -2866,22 +3437,22 @@ repos path: %0 QAction - + Checkout repository - + Open repository - + New Folder - + New Document @@ -2896,17 +3467,17 @@ repos path: %0 - + Project Active - + Project Close - + Project Info @@ -2926,157 +3497,162 @@ repos path: %0 - + Open Project - + Open Document - + Open File - + Open Recent Documents - + Open Recent Folders - + New File or Project - + Quit - + Build - + Rebuild - - Cancel + + Start Debugging - - Start Debugging + + Attaching to Running Program - + Running - + Interrupt - + Continue - + Abort Debugging - + Restart Debugging - + Step Over - + Step In - + Step Out - + Remote Debug - + Search - + Options - + Plugins - + Binary Tools - + Report Bug - + Help Documents - + About Plugins - + Valgrind Memcheck - + Clean Cache - + + Cancel Build + + + + Valgrind Helgrind - + User Action Analyse @@ -3105,7 +3681,7 @@ Delete anyway? - + Open path failed, current repos not svn subdir @@ -3152,27 +3728,7 @@ not exists support files: %0 - - Scope: - - - - - Search for: - - - - - File pattern: - - - - - Exclusion pattern: - - - - + Build configuration: @@ -3240,35 +3796,30 @@ not exists support files: %0 QMenu - + &File - + &Build - + &Debug - + &Tools - + &Help - - - &Edit - - QMessageBox @@ -3282,16 +3833,6 @@ not exists support files: %0 attach processId no exites! - - - Save Changes - - - - - The file has unsaved changes, will save? - - QObject @@ -3424,28 +3965,28 @@ not exists support files: %0 - + Build Steps - + Clean Steps - - Runtime Env + + Build Environment - + CMake config - + Variable @@ -3453,7 +3994,7 @@ not exists support files: %0 - + Value @@ -3509,12 +4050,17 @@ not exists support files: %0 - + + Advanced Search + + + + New File - + New Project @@ -3534,7 +4080,7 @@ not exists support files: %0 - + AI @@ -3597,36 +4143,26 @@ not exists support files: %0 Revert All - - - Search - - - - - Search && Replace - - QTabWidget - + Projects - - Symbol + + &Terminal - - &Console + + Symbol - + Search &Results @@ -3681,38 +4217,38 @@ not exists support files: %0 RecentDisplay - + Open Document - + Open File - + Open Project - + New File or Project - + Recent Open - + Documents - + clear all @@ -3723,14 +4259,14 @@ not exists support files: %0 - + Cancel button - + Delete button @@ -3741,12 +4277,12 @@ not exists support files: %0 - + Confirm to clear the record of the opened project? - + No Project @@ -4121,32 +4657,32 @@ not exists support files: %0 RunConfigPane - + Here is the executable path - + Executable path: - + Command line arguments: - - Browse + + Run in terminal: - + Working directory: - + Working directory @@ -4154,7 +4690,7 @@ not exists support files: %0 RunPropertyPage - + Run configuration: @@ -4162,42 +4698,42 @@ not exists support files: %0 Runner - + &Application Output - + Error: execute command error! The reason is unknown. - + Start execute command: "%1" "%2" in workspace "%3". - + The process "%1" exited normally. - + The process "%1" exited with code %2. - + The process "%1" crashed. - + Execute command finished. @@ -4210,6 +4746,9 @@ not exists support files: %0 Default + + + QPushButton Comment @@ -4668,55 +5207,59 @@ not exists support files: %0 - SearchResultWindow + SearchResultItemDelegate - - Replace + + Replace All - - Replacing, please wait... + + Replace - - Repalce text is empty, will continue? + + Dismiss + + + SearchResultWidget - - No + + Replace All - - Yes + + + Dismiss - - Replacement successful! + + Copy Path - - %1 matches found. + + Replace - - Search completed, %1 matches found. + + <font color='blue'>1</font> result in <font color='blue'>1</font> file - - No match found! + + <font color='blue'>%1</font> results in <font color='blue'>1</font> file - - Replace failed! + + <font color='blue'>%1</font> results in <font color='blue'>%2</font> files @@ -4916,58 +5459,94 @@ not exists support files: %0 SvnClientWidget - + Checkout repository - + Open repository - + select local reops + + SymbolLocator + + + Symbols in Current Document + + + TabBarPrivate + + + Save Changes + + + + + The file %1 has unsaved changes, will save? + + + + + Save + button + + + Do Not Save + button + + + + + Cancel + button + + + + Copy File Path - + Copy File Name - + Close This File - + Close All Files - + Close All Files Except This - - Open File Location + + Show Containing Folder TabWidgetPrivate - + File Operation @@ -4980,70 +5559,94 @@ not exists support files: %0 + + TextEditor + + + Save File + + + + + The file "%1" has no write permission. Please add write permission and try again + + + + + Ok + button + + + TextEditorPrivate - + Refactor - + Undo - + Redo - + Cut - + Copy - + Paste - + Delete - + Select All - + Remove Breakpoint - + Disable Breakpoint - + Enable Breakpoint - + Add Condition + Add a breakpoint on line %1 + + + + jump to %1 line @@ -5069,12 +5672,12 @@ not exists support files: %0 - + Translate - + Output Code @@ -5130,17 +5733,17 @@ not exists support files: %0 VariableChooser - + Variables - + Insert Variable - + Current Value: %1 @@ -5148,12 +5751,12 @@ not exists support files: %0 WorkspaceWidgetPrivate - + Add/Delete Comment - + Add/Remove Comment @@ -5163,70 +5766,81 @@ not exists support files: %0 - + + + Show opened files + + + + + &Show open files + + + + The file <i>%1</i> has been changed on disk.Do you want to reload it? - + File Has Been Changed - + Yes button - + Yes To All button - + No button - + No To All button - - + + Close button - + The file <i>%1</i> has been removed from disk. Do you want to save it under a different name, or close the editor? - + File Has Been Removed - + Save button - + Save As button - + Close All button @@ -5263,7 +5877,15 @@ not exists support files: %0 loadingWidget - loading··· + loading... + + + + + pathChooser + + + Choose path diff --git a/assets/translations/zh_CN.ts b/assets/translations/zh_CN.ts index a20803da3..7c0ca055f 100644 --- a/assets/translations/zh_CN.ts +++ b/assets/translations/zh_CN.ts @@ -1,6 +1,14 @@ + + APTInstaller + + + Install packages by apt + 通过APT安装包 + + ActionLocator @@ -9,6 +17,175 @@ 显示可用的操作 + + AdvancedSearchWidget + + + Collapse All + 折叠所有 + + + + Expand All + 展开所有 + + + + Clear Search Results + 清空搜索结果 + + + + Refresh + 刷新 + + + + Stop Search + 停止搜索 + + + + Search + 搜索 + + + + Match Case + 区分大小写 + + + + Match Whole Word + 匹配整段字符 + + + + Use Regular Expression + 使用正则表达式 + + + + Replace + 替换 + + + + Replace All + 替换全部 + + + + All Projects + 所有工程 + + + + Current Project + 当前工程 + + + + Current File + 当前文件 + + + + Toggle Search Details + 切换搜索详情 + + + + Scope: + 范围: + + + + + e.g.*.ts,src/**/include + 例如.*.ts,src/**/include + + + + Files To Include: + 包含文件: + + + + Files To Exclude: + 不包含文件: + + + + All projects path is empty, please import! + 所有项目路径均为空,请导入! + + + + Current projects path is empty, please import! + 当前工程路径为空,请先导入! + + + + BinaryToolsConfigView + + + No files are currently open, please open! + 目前没有打开任何文件,请打开! + + + + Advance Search + 高级搜索 + + + + Cancel + button + 取消 + + + + Continue + button + 继续 + + + + No results found. + 没有匹配的结果. + + + + Replace 1 occurence across 1 file? + 替换1个文件中的1个结果? + + + + Replace 1 occurence across 1 file with %1? + 使用 %1 替换1个文件中的1个结果? + + + + Replace %1 occurences across 1 file? + 替换1个文件中的%1个结果? + + + + Replace %1 occurences across 1 file with %2? + 使用 %2 替换1个文件中的%1个结果? + + + + Replace %1 occurences across %2 files? + 替换%2个文件中的%1个结果? + + + + Replace %1 occurences across %2 files with %3? + 使用 %3 替换%2个文件中的%1个结果? + + AdvancedSettingsDialog @@ -36,6 +213,9 @@ Trigger Event: 触发事件: + + + BinaryToolsConfigViewPrivate None @@ -70,33 +250,43 @@ AppOutputPane - + default - default + 默认 - + Stop Running Program 终止运行程序 - + Close OutputPane 关闭输出面板 - + + Clear Output + 清空输出 + + + + Filter + 过滤器 + + + Process is running, kill process? 程序正在运行,要终止程序吗? - + kill button 终止 - + Cancel button 取消 @@ -105,17 +295,17 @@ Application - + <br/>Built on %1 %2 in %3<br/> 创建于%1 %2, %3平台 - + deepin-unioncode - + Deepin Union Code is a lightweight integrated development environment, featured with multilingual and cross platform compatibility. Deepin Union Code是一款具有多语言、跨平台兼容特性的轻量级集成开发环境. @@ -165,38 +355,86 @@ 在这里提问,按Enter键发送... + + AttachInfoDialog + + + debugger: + 调试器: + + + + filter: + 过滤器: + + + + Process Id + 进程ID + + + + Process Path + 进程路径 + + + + Update + 更新 + + + + Cancel + 取消 + + + + Attaching + 关联 + + BehaviorWidget - + Spaces Only 仅空格 - + Tabs Only 仅制表符 - + Tabs And Indentation 制表符和缩进 - + Enable automatic indentation 自动缩进 - + Tab policy: 缩进策略: - + Tab size: 制表符长度: + + + Editor Tip + 编辑器提示 + + + + Tip Active Time(ms): + 提示响应时间(毫秒): + BinaryToolsConfigView @@ -273,19 +511,6 @@ Advanced Settings 高级设置 - - - Tool icon: - 工具图标: - - - - Working directory: - 工作目录: - - - - BinaryToolsConfigViewPrivate Select Executabel Path @@ -296,6 +521,16 @@ Select Working Directory 选择工作目录 + + + Tool icon: + 工具图标: + + + + Working directory: + 工作目录: + BinaryToolsDialog @@ -330,73 +565,89 @@ 默认分组 - + + Start execute tool "%1". + + 开始执行工具 “%1”。 + + + + + The tool is running. Please stop it before running. + + 这个工具正在运行。请将其停止后再运行。 + + + + The tool (%1) execution program does not exist. Install and run it again 工具 (%1) 执行程序不存在,请安装并重试 - + Ok 确定 - - Start execute "%1": "%2" "%3" in workspace "%4". - - 开始在工作区 "%4" 执行 "%1": "%2" "%3" 。 - - - - + Configure... 配置... - - &Console - 控制台(&C) - - - + The tool "%1" exited normally. 工具 "%1" 正常退出。 - + The tool "%1" exited with code %2. 工具 "%1" 以code %2 结束。 - + The tool "%1" crashed. 工具 "%1" 崩溃。 - + Execute tool "%1" finished. 工具 "%1" 执行完成。 - + Cancel 取消 - + Install 安装 - - + + The tool has set the working directory, but the working directory parsing is empty. Please check and try again. + + 这个工具设置了工作目录,但是工具目录解析为空。请检测后重试。 + + + + + The tool has set the channel data, but the channel data parsing is empty. Please check and try again. + + 这个工具设置了管道数据,但是管道数据解析为空。请检测后重试。 + + + + + &Application Output 应用程序输出(&A) @@ -451,6 +702,9 @@ Breakpoint by Function 按函数设置的断点 + + + CommentConfigWidget Breakpoint by Address @@ -568,37 +822,37 @@ BreakpointView - + Enable selected breakpoints 启用选中断点 - + Disable selected breakpoints 禁用选中断点 - + Remove selected breakpoints 删除选中断点 - + Remove all breakpoints 删除所有断点 - + Enable all breakpoints 启用所有断点 - + Disable all breakpoints 禁用所有断点 - + Edit Condition 编辑条件 @@ -606,7 +860,7 @@ BuildManager - + Compile Output 编译输出 @@ -636,47 +890,52 @@ 警告 - + + Clear Output + 清空输出 + + + &Build 编译(&B) - + Execute command failed! 执行命令失败! - + Start execute command: "%1" "%2" in workspace "%3". 开始在工作区%3中执行%1 %2命令。 - + The process "%1" exited normally. 进程%1正常退出。 - + The process "%1" exited with code %2. 进程%1退出,代码为%2。 - + The process "%1" crashed. 进程 %1崩溃。 - + Execute command finished. 命令执行完成。 @@ -686,12 +945,7 @@ BuildPropertyPage - - Browse - 浏览 - - - + Output direcotry: 输出目录: @@ -707,7 +961,7 @@ BuilderReceiver - + Error parsing project symbol workspace: %0 language: %1 @@ -722,12 +976,12 @@ storage: %2 CMakeBuilderGenerator - + The build command of %1 project is null! please install it in console with "sudo apt install cmake", and then restart the tool. %1项目的生成命令为空!请使用“sudo apt install cmake”在控制台中安装它,然后重新启动该工具。 - + The path of "%1" is not exist! please check and reopen the project. "%1" 不存在!请检查并重新打开工程 @@ -735,12 +989,12 @@ storage: %2 CMakeDebug - + The gdb is required, please install it in console with "sudo apt install gdb", and then restart the tool, reselect the CMake Debugger in Options Dialog... gdb尚未安装,请使用“sudo apt install gdb”将其安装在控制台中,然后重新启动工具,在选项对话框中重新选择CMake调试器... - + Request cxx dap port failed, please retry. 请求cxx dap端口失败,请重新尝试。 @@ -748,7 +1002,7 @@ storage: %2 CmakeAsynParse - + build 编译 @@ -756,55 +1010,68 @@ storage: %2 CmakeProjectGenerator - + Run CMake 执行CMake + + + DebuggerPlugin - + Clear CMake 清除CMake - + + Files in project %1 have changed, needs to run cmake to update + 项目%1中的文件已经更改,需要运行 cmake 来更新 + + + Properties 工程属性 - + File are not automatically added to the CmakeList.txt file to the Cmake project. Copy the path to the source files to the clipboard? 文件不会自动添加到 Cmake 项目的 CmakeList.txt 文件中。将源文件的路径复制到剪贴板? - + Copy to Clipboard? 复制到剪贴板? - + Ok 确定 - + + Project Properties + 工程属性 + + + Build 编译 - + Run 运行 - CodeCompletionExtendWidgetPrivate + CodeCompletionExtendWidget - + From: - 从: + 来自于: - + Shortcut: 快捷键: @@ -825,77 +1092,77 @@ storage: %2 CodeEditor - + Save All Documents 保存所有文件 - + Close All Documents 关闭所有文件 - + Print 打印 - + backward 后退 - + forward 前进 - + Close Current Editor 关闭当前编辑器 - + Switch Header/Source 切换头文件/源文件 - + Follow Symbol Under Cursor 跟随光标下符号 - + Toggle Breakpoint 切换断点 - + Find Usages 查找引用 - + Rename Symbol Under Cursor 重命名 - + Current document 当前文档 - + Current document content 当前文档内容 - + Backward 后退 - + Forward 前进 @@ -945,10 +1212,15 @@ storage: %2 CodeGeex - + Please login to use CodeGeeX. 请登录后使用CodeGeex. + + + Enable Qt Debug Level + 启用Qt Debug调试等级 + CodeLens @@ -1066,6 +1338,151 @@ storage: %2 The group name cannot be empty 分组名不能为空 + + + Searching, please wait... + 搜索中,请等待.... + + + + FontColorWidget + + + Font + 字体 + + + + Family: + 字体族: + + + + Size: + 大小: + + + + Zoom: + 缩放: + + + + GitClientPrivate + + + Failed to retrieve data. + 检索数据失败。 + + + + GitDiffEditor + + + Skipped %n lines... + + 跳过 %n 行... + + + + + Binary files differ + 二进制文件差异 + + + + Skipped unknown number of lines... + 跳过未知行数... + + + + [%1] %2 + [%1] %2 + + + + GitDiffWidget + + + + + + No difference. + 文件无修改。 + + + + + Waiting for data... + 等待数据... + + + + Retrieving data failed. + 检索数据失败。 + + + + GitMenuManager + + + + Log of "%1" + Log of "%1" + + + + + Diff of "%1" + Diff of "%1" + + + + Blame of "%1" + Blame of "%1" + + + + Current File + 当前文件 + + + + Current Project + 当前工程 + + + + GitTabWidget + + + + Git Log "%1" + + + + + + Git Blame "%1" + + + + + + Git Diff "%1" + + + + + Git Show "%1" + + + + + + Working... + 处理中... + CommentConfigWidget @@ -1190,25 +1607,35 @@ storage: %2 - Console + ConsoleManager + + + Terminal + 终端 + - - &Console - 控制台(&C) + + New Terminal + 新终端 ConsoleWidget - + copy 复制 - + paste 粘贴 + + + Enter current project root path + 进入到当前项目的根目录 + ContextModule @@ -1221,22 +1648,27 @@ storage: %2 Controller - + Open Document 打开文件 - + Hide ContextWidget 隐藏内容区 - + + Show docks in this view + 当前视图中的窗口 + + + Expand All 展开所有 - + Fold All 折叠所有 @@ -1290,8 +1722,8 @@ storage: %2 DAPDebugger - - + + The debugee has Terminated. @@ -1300,135 +1732,154 @@ The debugee has Terminated. - + The debugee has Exited. 调试已退出。 - + Input Condition Expression 输入条件表达式 - + Condition 条件 - + When the breakpoint is reached, it will be hit only when the expression is true 当断点到达,仅在表达式为真时触发 - - + + + Cancel 取消 - - + + can`t attaching to debugee when debuging other application + 调试其他应用程序时不能附加到进程 + + + + The gdb is required, please install it in console with "sudo apt install gdb", and then restart the tool, reselect the CMake Debugger in Options Dialog... + gdb尚未安装,请在控制台中使用“sudo apt install gdb”将其安装,然后重新启动工具,在选项对话框中重新选择CMake调试器... + + + + Request cxx dap port failed, please retry. + 请求cxx dap端口失败,请重新尝试。 + + + + Ok 确认 - + <Unknown> name - + <Unknown> meaning - + <p>The inferior stopped because it received a signal from the operating system.<p><table><tr><td>Signal name : </td><td>%1</td></tr><tr><td>Signal meaning : </td><td>%2</td></tr></table> <p>下位机停止,因为它收到了来自操作系统的信号。<p><table><tr><td>信号名: </td><td>%1</td></tr><tr><td>信号含义: </td><td>%2</td></tr></table> - + Signal Received 信号已接收 - + New Evaluator Expression 新评估表达式 - + Enter an expression to evaluate 输入表达式以评估 - + Threads: 线程: - + Stack List 堆栈列表 - + Add New Expression Evaluator 添加新评估表达式 - + Remove This Evaluator 删除评估 - + Name 名称 + + + NotificationItemWidget - + Value - + Type 类型 - + Breakpoint List 断点列表 - + Please build first. Build : Ctrl + B 请先编译工程。 编译:Ctrl + B - + Is preparing dependence, please waiting for a moment 正在加载依赖项,请等待 - + Is getting the dap port, please waiting for a moment 正在获取dap端口,请等待 - + Requesting debug port... 请求调试端口... - + The dap port is not ready, please retry. @@ -1437,22 +1888,22 @@ dap端口未就绪,请重试。 - + Debugging starts 调试开始 - + Start debugging coredump file: 开始调试coredump文件: - + The coredump target file is error: coredump目标文件错误: - + The coredump file is error: coredump文件错误: @@ -1493,11 +1944,6 @@ dap端口未就绪,请重试。 Cancel 取消 - - - Replace - 替换 - DToolBar @@ -1515,14 +1961,14 @@ dap端口未就绪,请重试。 DTreeView - + The delete operation will be removed fromthe disk and will not be recoverable after this operation. Delete anyway? 删除操作将从磁盘中删除,此操作后将无法恢复。 是否仍要删除? - + Delete Warning 忽略警告 @@ -1530,28 +1976,51 @@ Delete anyway? DebuggerPlugin - + debug 调试 - + &Application Output 应用程序输出(&A) + + DependenceManager + + + Cancel + 取消 + + + + Install + 安装 + + + + Request to install some dependency packages. Do you want to install them? + 请求安装一些依赖包。您想要安装它们吗? + + DetailPropertyWidget - + Python interpreter: Python解释器: - + Executable File: 执行文件: + + + Run in terminal: + 在终端中运行: + DetailWidget @@ -1590,6 +2059,9 @@ Delete anyway? The filename can't be empty! 文件名不能为空! + + + QAction The project can't be empty! @@ -1633,32 +2105,42 @@ Delete anyway? DetailsView - + Description 描述 - + Dependency 依赖 - + reLaunch when changed! 修改后请重启! - + + Settings + 设置 + + + + Relaunch required! + 需要重启! + + + This platform not support doc display! 该平台不支持doc显示! - + unload 卸载 - + load 加载 @@ -1666,7 +2148,7 @@ Delete anyway? DisplayDocView - + Documents 文件 @@ -1674,7 +2156,7 @@ Delete anyway? DisplayProjectView - + Projects 工程 @@ -1692,6 +2174,14 @@ Delete anyway? 正在下载%1... + + EditorLabel + + + Line %1 Column %2 + 行 %1 列 %2 + + EditorSettingsWidgetGenerator @@ -1711,17 +2201,17 @@ Delete anyway? EnvironmentView - + append 增加 - + reduce 减少 - + reset 重置 @@ -1729,115 +2219,129 @@ Delete anyway? EnvironmentWidget - + Enable All Environment 启用所有环境 - - Enable Qt Debug Level - 启用Qt Debug调试等级 + + append + 增加 + + + + reduce + 减少 + + + + reset + 重置 FileTreeView - + Error, Can't move to trash: 错误,无法移到废纸篓: - + Error, Can't delete: 错误,无法删除: - + New Document Name 新文件名 - - + + New Document 新建文件 - - - + + Ok - + Open 打开 - - + + New Folder 新建文件夹 - + Delete operation not be recoverable, delete anyway? 删除操作不可逆转,是否删除? - + Delete Warining 删除警告 - + Cancel 取消 - + + OK + 确定 + + + New File Name 新文件名 - + New File 创建新文件 - + New Folder Name 新文件夹 - + Error: Can't create new document or folder, please check whether the name already exists! 无法创建文件或文件夹,请检查文件名是否已经存在! - + Error: Can't create new document or folder, parent not's dir 非目录下无法在创建文件或文件夹 - + Move To Trash 移到回收站 - + Remove 删除 - + Rename 重命名 - + Recover From Trash 恢复 @@ -1845,16 +2349,14 @@ Delete anyway? FindPlugin - + Advanced Find 高级查找 - - - - Advanced &Search - 高级查找( &S) + + ADVANCED SEARCH + 高级搜索 @@ -1907,86 +2409,141 @@ Delete anyway? - FindToolWindow + FontColorWidget - - All Projects - 所有工程 + + Font + 字体 - - Current Project - 当前工程 + + Family: + 字体族: - - Current File - 当前文件 + + Size: + 大小: - - thread - 线索 + + Zoom: + 缩放: + + + GenerateDialog - - - e.g.*.ts,src/**/include - 例如.*.ts,src/**/include + + New LingLong Project + 新建玲珑项目 - - Ok - 确定 + + Please input application`s name + 请输入应用名称 - - All projects path is empty, please import! - 所有项目路径均为空,请导入! + + 0.0.0.1 + - - - Current project path is empty, please import! - 当前工程路径为空,请导入! + + Please input description + 请输入描述 - - Scope is not selected, please select! - 未选择范围,请选择! + + Kind: + 类型: - - Search for text is empty, please input! - 搜索文本为空,请输入! + + Local +need to manually copy the source code to this path + 本地 +需要手动将源码拷贝到项目路径 - - Searching, please wait... - 搜索中,请等待.... + + Remote + 远程 - - - FontColorWidget - - Font - 字体 + + ID: + - - Family: - 字体族: + + Name + 名称 - - Size: - 大小: + + Project Path: + 项目路径: - - Zoom: - 缩放: + + Version: + 版本: + + + + Description: + 描述: + + + + Depends : + 依赖: + + + + Execute Command: + 运行命令: + + + + Source Type: + 源码类型: + + + + Cancel + 取消 + + + + Confirm + 确定 + + + + Choose path + 选择路径 + + + + %1 is already existed + %1 已经存在 + + + + This item can not be empty + 该项不能为空 + + + + Project Path is empty or not exist + 项目路径为空或不存在 + + + + Version must be in the format of *.*.*.* + 版本号必须为*.*.*.*的格式 @@ -2021,6 +2578,16 @@ Delete anyway? [%1] %2 [%1] %2 + + + Save Changes + 保存更改 + + + + The file has unsaved changes, will save? + 该文件有未保存的更改,是否保存? + GitDiffWidget @@ -2047,32 +2614,52 @@ Delete anyway? GitMenuManager - - + + Log of "%1" Log of "%1" - - + + Diff of "%1" Diff of "%1" - + Blame of "%1" Blame of "%1" - + Current File 当前文件 - + Current Project 当前工程 + + + Git Log + + + + + Git Blame + + + + + Git Diff + + + + + &Git + + GitTabWidget @@ -2080,24 +2667,24 @@ Delete anyway? Git Log "%1" - + Git Blame "%1" - + Git Diff "%1" - + Git Show "%1" - + @@ -2153,10 +2740,15 @@ Delete anyway? GradleProjectGenerator - + Properties 工程属性 + + + Project Properties + 工程属性 + GradleWidget @@ -2202,7 +2794,7 @@ Delete anyway? Blame %1 (%2) - + @@ -2277,22 +2869,22 @@ Delete anyway? JSDebugger - + Stac&kFrame 堆栈列表(&K) - + Break&points 断点列表(&P) - + ScriptWidget 脚本窗口 - + ErrorLogWidget 错误日志窗口 @@ -2339,6 +2931,14 @@ Delete anyway? JDK + + JavaUtil + + + Unable to find the javap application. Please install openjdk-11-jdk or another version first. + 无法找到javap应用,请先安装openjdk-11-jdk或更高版本. + + KitsManagerWidget @@ -2392,48 +2992,69 @@ Delete anyway? - LSPStyle + LLBuilderGenerator + + + Can`t find linglong-builder tool + 没有找到玲珑构建工具 + + + + The path of "%1" is not exist! please check and reopen the project. + "%1"路径不存在,请检查路径并重新打开工程. + + + + LLGenerator - + + debug of Linglong project is not supported + 玲珑项目暂不支持调试 + + + + LanguageClientHandlerPrivate + + Refactor 重构 - + Rename Symbol Under Cursor 重命名 - + Switch Header/Source 切换头文件/源文件 - + Follow Symbol Under Cursor 跟随光标下符号 - + Find Usages 查找引用 - - Range Formatting - 范围格式 + + Format Selection + 格式化选中部分 LocatorManager - + Enter command 键入命令 - - + + Enter command %1 键入命令 %1 @@ -2474,21 +3095,64 @@ repos path: %0 MainDialog - - + + Tip 提示 - + Can not find kit. 找不到配套工具。 + + MainFrame + + + New + 新建 + + + + Installed + 安装 + + + + Repository + 仓库 + + + + Running + 运行中 + + + + Search + 搜索 + + + + Run + 运行 + + + + Install + 安装 + + + + UnInstall + 卸载 + + MainWindow - + Hide Dock Widget 隐藏驻留区 @@ -2544,6 +3208,11 @@ repos path: %0 Properties 工程属性 + + + Project Properties + 工程属性 + MavenWidget @@ -2582,7 +3251,7 @@ repos path: %0 The build command of %1 project is null! please install it in console with "sudo apt install ninja-build", and then restart the tool. - %1项目的生成命令为空!请使用“sudo apt install ninja build”在控制台中安装它,然后重新启动该工具。 + %1项目的生成命令为空!请使用“sudo apt install ninja-build”在控制台中安装它,然后重新启动该工具。 @@ -2656,26 +3325,34 @@ repos path: %0 OutputPane - + Additional output omitted 省略附加输出 - + Copy 复制 - + Clear - 清扫 + 清除 - + Select All 选择全部 + + PIPInstaller + + + Install packages by pip + 通过PIP安装包 + + Performance @@ -2702,10 +3379,18 @@ repos path: %0 版权 + + PluginManagerModule + + + Extensions + 扩展 + + PluginStoreWidget - + Search Extension 查找扩展 @@ -2740,13 +3425,28 @@ repos path: %0 language: 语言: - - - ProjectCore + + + ProjectCore + + + Open activted project`s property dialog + 打开已激活的工程属性对话框 + + + + Auto Focus + 自动聚焦 + + + + Opened Files + 打开文件 + - - Open activted project`s property dialog - 打开已激活的工程属性对话框 + + Focus File + 聚焦文件 @@ -2762,7 +3462,7 @@ repos path: %0 路径: - + Choose path 选择路径 @@ -2770,69 +3470,93 @@ repos path: %0 ProjectTree - - Show Contain Folder - 显示包含目录 - - - - - + + + New Document 新建文件 - - + + + New Directory + 新建文件夹 + + + + Show Containing Folder + 显示所在文件夹 + + + + Rename 重命名 - + Open In Terminal - 在终端打开 + 在终端中打开 - + Delete Document 删除文件 - - + + New Document Name 新文件名 - - - - - + + + + + + + Ok 确定 - + + New Dirctory Name + 新文件夹名 + + + + New Dirctory + 新建文件夹 + + + Delete operation not be recoverable, delete anyway? 删除操作不可逆转,是否删除? - + Delete: 删除: - - - + + A directory with name %1 already exists. please reanme it + 名称为% 1的目录已经存在。请将其重命名 + + + + + A file with name %1 already exists. Would you like to overwrite it? 名称为"%1" 的文件已经存在,您想覆盖它吗? - - - + + + + Cancel 取消 @@ -2840,17 +3564,17 @@ repos path: %0 PropertiesDialog - + Project Properties 工程属性 - + Cancel 取消 - + Apply 应用 @@ -2858,22 +3582,22 @@ repos path: %0 PythonDebug - + There is no opened python file, please open. 没有打开的python文件,请打开。 - + The python3 is needed, please select it in options dialog or install it. 需要python3,请在选项对话框中选择它或安装它。 - + The debugpy is needed, please use command "pip3 install debugpy" install and retry. 需要debugpy,请使用命令pip3 install debugpy安装并重试。 - + Request python dap port failed, please retry. 请求python dap端口失败,请重试。 @@ -2894,32 +3618,37 @@ repos path: %0 QAction - + Checkout repository 检出仓库 - + Open repository 打开仓库 - + New Folder 新建文件夹 - + New Document 新建文件 - + Clean Cache 清除缓存 - + + Cancel Build + 取消编译 + + + Options 选项 @@ -2934,17 +3663,17 @@ repos path: %0 程序 - + Project Active 激活工程 - + Project Close 关闭工程 - + Project Info 工程信息 @@ -2964,147 +3693,147 @@ repos path: %0 转到定义 - + Open Project 打开工程 - + Open Document 打开文件 - + Open File 打开文件 - + Open Recent Documents 打开最近的文件 - + Open Recent Folders 打开最近的文件夹 - + New File or Project 新建文件或工程 - + Quit 退出 - + Build 编译 - + Rebuild 重新编译 - - Cancel - 取消 - - - + Start Debugging 开始调试 - + + Attaching to Running Program + 关联到正在运行的程序 + + + Running 运行 - + Interrupt 中断 - + Continue 继续 - + Abort Debugging 停止调试 - + Restart Debugging 重启调试器 - + Step Over 单步跳过 - + Step In 单步进入 - + Step Out 单步跳出 - + Remote Debug 远程调试 - + Search 搜索 - + Plugins 插件 - + Binary Tools 二进制工具 - + Report Bug 报告Bug - + Help Documents 帮助文档 - + About Plugins 关于插件 - + Valgrind Memcheck Valgrind内存检测 - + Valgrind Helgrind Valgrind死锁检测 - + User Action Analyse 用户行为分析 @@ -3134,7 +3863,7 @@ Delete anyway? 从远程仓库检出 - + Open path failed, current repos not svn subdir 打开路径失败,当前远程仓库不是svn子目录 @@ -3182,27 +3911,7 @@ not exists support files: %0 密码: - - Scope: - 范围: - - - - Search for: - 搜索: - - - - File pattern: - 包含的文件: - - - - Exclusion pattern: - 排除的文件: - - - + Build configuration: 编译配置: @@ -3270,35 +3979,30 @@ not exists support files: %0 QMenu - + &File 文件(&F) - + &Build 编译(&B) - + &Debug 调试(&D) - + &Tools 工具(&T) - + &Help 帮助(&H) - - - &Edit - 编辑(&E) - QMessageBox @@ -3312,16 +4016,6 @@ not exists support files: %0 attach processId no exites! 附加进程ID不存在! - - - Save Changes - 保存更改 - - - - The file has unsaved changes, will save? - 该文件有未保存的更改,是否保存? - QObject @@ -3454,28 +4148,28 @@ not exists support files: %0 - + Build Steps 编译步骤 - + Clean Steps 清扫步骤 - - Runtime Env - 运行时环境 + + Build Environment + 编译环境 - + CMake config CMake 配置 - + Variable 变量名 @@ -3483,7 +4177,7 @@ not exists support files: %0 - + Value @@ -3539,12 +4233,17 @@ not exists support files: %0 - + + Advanced Search + 高级搜索 + + + New File 新建文件 - + New Project 新建工程 @@ -3564,7 +4263,7 @@ not exists support files: %0 目标 - + AI @@ -3627,36 +4326,26 @@ not exists support files: %0 Revert All 全部还原 - - - Search - 搜索 - - - - Search && Replace - 搜索并替换 - QTabWidget - + Projects 工程 - - Symbol - 符号 + + &Terminal + 终端(&T) - - &Console - 控制台(&C) + + Symbol + 符号 - + Search &Results 查找结果(&R) @@ -3711,38 +4400,38 @@ not exists support files: %0 RecentDisplay - + Open Document 打开文件 - + Open File 打开文件 - + Open Project 打开工程 - + New File or Project 新建文件或工程 - + Recent Open 最近打开 - + Documents 文件 - + clear all 清除 @@ -3753,14 +4442,14 @@ not exists support files: %0 - + Cancel button 取消 - + Delete button 删除 @@ -3771,12 +4460,12 @@ not exists support files: %0 工程 - + Confirm to clear the record of the opened project? 确定清空`已打开工程`记录? - + No Project 无记录 @@ -3801,7 +4490,7 @@ not exists support files: %0 IP: - + @@ -4151,32 +4840,32 @@ not exists support files: %0 RunConfigPane - + Here is the executable path 以下是可执行路径 - + Executable path: 可执行文件路径: - + Command line arguments: 命令行参数: - - Browse - 浏览 + + Run in terminal: + 在终端中运行: - + Working directory: 工作目录: - + Working directory 工作目录 @@ -4184,7 +4873,7 @@ not exists support files: %0 RunPropertyPage - + Run configuration: 运行时配置: @@ -4192,47 +4881,47 @@ not exists support files: %0 Runner - + &Application Output 应用程序输出(&A) - + Error: execute command error! The reason is unknown. 错误:执行命令错误!原因未知。 - + Start execute command: "%1" "%2" in workspace "%3". 开始在工作区%3中执行%1 %2命令。 - + The process "%1" exited normally. 进程%1正常退出。 - + The process "%1" exited with code %2. 进程%1退出,代码为%2。 - + The process "%1" crashed. 进程 %1崩溃。 - + Execute command finished. 命令执行完成。 @@ -4704,56 +5393,96 @@ not exists support files: %0 - SearchResultWindow + SearchResultItemDelegate + + + Replace All + 替换全部 + - + Replace 替换 - - Replacing, please wait... - 替换中,请等待... + + Dismiss + 放弃 + + + SearchResultWidget - - Repalce text is empty, will continue? - 替换文本为空,是否继续? + + Replace All + 替换全部 - - No - 取消 + + + Dismiss + 放弃 - - Yes - 确定 + + Copy Path + 复制路径 + + + + Replace + 替换 + + + + <font color='blue'>1</font> result in <font color='blue'>1</font> file + <font color='blue'>1</font> 个结果在 <font color='blue'>1</font> 个文件 + + + + <font color='blue'>%1</font> results in <font color='blue'>1</font> file + <font color='blue'>%1</font> 个结果在 <font color='blue'>1</font> 个文件 + + + + <font color='blue'>%1</font> results in <font color='blue'>%2</font> files + <font color='blue'>%1</font> 个结果在 <font color='blue'>%2</font> 个文件中 + + + + ShortCut + + + Shortcut Invalid + 快捷键不合法 - - Replacement successful! - 替换成功! + + Shortcut Invalid, Please enter again! + 快捷键不合法,请重新输入! - - %1 matches found. - 匹配到%1 个。 + + Shortcut Repeated + 快捷键重复 - - Search completed, %1 matches found. - 搜索完成,匹配到%1 个。 + + Shortcut Repeated, Please enter again! + 快捷键重复,请重新输入! - - No match found! - 没有匹配的结果! + + OK + 确定 + + + ShortCutEdit - - Replace failed! - 替换失败! + + Clear + 清除 @@ -4952,58 +5681,94 @@ not exists support files: %0 SvnClientWidget - + Checkout repository 检出仓库 - + Open repository 打开仓库 - + select local reops 选择本地reops + + SymbolLocator + + + Symbols in Current Document + 当前文档的符号 + + TabBarPrivate + + + Save Changes + 保存更改 + + + + The file %1 has unsaved changes, will save? + 文件 %1 有未保存的更改,是否保存? + + + + Save + button + 保存 + + Do Not Save + button + 不保存 + + + + Cancel + button + 取消 + + + Copy File Path 拷贝文件路径 - + Copy File Name 拷贝文件名 - + Close This File 关闭 - + Close All Files 关闭所有文件 - + Close All Files Except This 关闭除该文件的所有文件 - - Open File Location - 打开文件位置 + + Show Containing Folder + 显示所在文件夹 TabWidgetPrivate - + File Operation 文件操作 @@ -5016,70 +5781,94 @@ not exists support files: %0 找不到文件:%1 + + TextEditor + + + Save File + 保存文件 + + + + The file "%1" has no write permission. Please add write permission and try again + 文件“%1”没有写权限。请添加写权限并重试 + + + + Ok + button + 确定 + + TextEditorPrivate - + Refactor 重构 - + Undo 撤销 - + Redo 恢复 - + Cut 剪切 - + Copy 复制 - + Paste 粘贴 - + Delete 删除 - + Select All 选择全部 - + Remove Breakpoint 移除断点 - + Disable Breakpoint 禁用断点 - + Enable Breakpoint 启用断点 - + Add Condition 添加条件 + Add a breakpoint on line %1 + 在第 %1 行添加断点 + + + jump to %1 line 跳转到 %1 行 @@ -5105,12 +5894,12 @@ not exists support files: %0 请输入要翻译的代码 - + Translate 翻译 - + Output Code 输出代码 @@ -5166,17 +5955,17 @@ not exists support files: %0 VariableChooser - + Variables 变量 - + Insert Variable 插入变量 - + Current Value: %1 当前值:%1 @@ -5184,12 +5973,12 @@ not exists support files: %0 WorkspaceWidgetPrivate - + Add/Delete Comment 添加/删除注释 - + Add/Remove Comment 添加/删除注释 @@ -5199,70 +5988,81 @@ not exists support files: %0 &添加/删除注释 - + + + Show opened files + 显示打开的文件 + + + + &Show open files + &显示打开的文件 + + + The file <i>%1</i> has been changed on disk.Do you want to reload it? %1 被修改,是否重新加载? - + File Has Been Changed 文件发生修改 - + Yes button 确定 - + Yes To All button 应用所有 - + No button 取消 - + No To All button 取消所有 - - + + Close button 关闭 - + The file <i>%1</i> has been removed from disk. Do you want to save it under a different name, or close the editor? %1 被移除,是否需要另存为,或者关闭当前编辑页? - + File Has Been Removed 文件被移除 - + Save button 保存 - + Save As button 另存为 - + Close All button 关闭所有 @@ -5303,8 +6103,16 @@ not exists support files: %0 loadingWidget - loading··· - 加载中··· + loading... + 加载中... + + + + pathChooser + + + Choose path + 选择路径 diff --git a/debian/changelog b/debian/changelog index 1af035f26..c891e416f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,19 @@ +deepin-unioncode (1.3.7) unstable; urgency=medium + + * Optimize searcher + * Add symbol tree + * Add linglong + * bug fix + + -- luzhen Mon, 22 Jul 2024 16:32:09 +0800 + +deepin-unioncode (1.3.4) unstable; urgency=medium + + * Bug fix + * Optimize LSP + + -- luzhen Wed, 26 Jun 2024 15:31:26 +0800 + deepin-unioncode (1.3.1) unstable; urgency=medium * Change toolbar layout diff --git a/debian/control b/debian/control index 5ce7540da..3c058a4bd 100644 --- a/debian/control +++ b/debian/control @@ -37,7 +37,8 @@ Build-Depends: libdtkwidget-dev, libdtkcore-dev, libdtkcore5-bin, - libkf5syntaxhighlighting-dev + libkf5syntaxhighlighting-dev, + libyaml-cpp-dev Standards-version: 3.9.8 Homepage: http://www.deepin.org @@ -45,14 +46,14 @@ Package: deepin-unioncode Architecture: any Depends: ${shlibs:Depends}, - ${misc:Depends} + ${misc:Depends}, + python3-pip Recommends: cmake, make, gdb, g++, gcc, git, - npm, clangd-13|clangd, python3-pip Description: IDE 'Deepin Union Code' diff --git a/docs/dtk-install-guide.en.md b/docs/dtk-install-guide.en.md new file mode 100644 index 000000000..14b364b34 --- /dev/null +++ b/docs/dtk-install-guide.en.md @@ -0,0 +1,61 @@ +## DTK Dependency Installation + +For deepin-unioncode, the required dependency libraries include dtkcore, dtkwidget, dtkgui, and qt5integration, and this section describes the installation of these dependencies. + +### 1. Source Code Pull + +First you need to download the source code of the dtk dependency library in the [deepin github community](https://github.com/linuxdeepin): + +[dtkcore](https://github.com/linuxdeepin/dtkcore),[dtkwidget](https://github.com/linuxdeepin/dtkwidget),[dtkgui](https://github.com/linuxdeepin/dtkgui),[qt5integration](https://github.com/linuxdeepin/qt5integration) + +The dtkcore might rely on [dtkcommon](https://github.com/linuxdeepin/dtkcommon),qt5integration will depend on the [qt5platform-plugins](https://github.com/linuxdeepin/qt5platform-plugins) + +### 2. Environmental Installation + +First you need to install the qt environment and execute the following command + +```shell +sudo apt update +sudo apt install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev qt5-private-dev +``` + +### 3. DTK Installation + +After the source code is pulled down, ensure that the required dependencies are properly installed and execute the following command in the directory location where the source code for each dependency library is located + +```shell +sudo apt build-dep . +``` + +There are two ways to install dtk dependencies, one is the source code installation, and the second is the build-your-own-package installation. The source installation method is described in the README document of the respective dependent libraries, and will not be repeated here. This section describes how to install using a software package: + +In the source root of the respective dependent library, use the following command to package + +```shell +dpkg-buildpackage -us -uc -b -j16 +``` + +"-j16" Specifies the parameter for multi-core execution, which can be increased or decreased according to the number of processor cores and performance. + +The following errors may occur when installing dependent libraries: + +![image-20240605152527906](./dtk-install/error1.png) + +Cause: The dependent version failed to be properly packaged + +The solution: + +```shell +vim debian/rules +# Find "override_dh_shlibdeps:" in the opened file +# Add "dh_shlibdeps -- dpkg-shlibdps-params =--ignore-missing-info" +``` + +![image-20240605153447864](./dtk-install/solution1.png) + +After all dependencies are successfully packaged, you can use the installation package to install the dependencies. After the installation, run commands to check whether the dependencies are successfully installed and whether their versions meet requirements. + +```shell +apt policy libdtkcore5 +# Other dependencies are viewed in the same way +``` diff --git a/docs/dtk-install-guide.md b/docs/dtk-install-guide.md new file mode 100644 index 000000000..fec1861da --- /dev/null +++ b/docs/dtk-install-guide.md @@ -0,0 +1,61 @@ +## DTK依赖安装 + +对于Deepin-IDE,所需要的依赖库包括dtkcore,dtkwidget,dtkgui以及qt5integration,本部分介绍这几个依赖的安装。 + +### 1. 源码拉取 + +首先需要在深度的github社区中下载dtk依赖库的源码: + +[dtkcore](https://github.com/linuxdeepin/dtkcore),[dtkwidget](https://github.com/linuxdeepin/dtkwidget),[dtkgui](https://github.com/linuxdeepin/dtkgui),[qt5integration](https://github.com/linuxdeepin/qt5integration) + +其中,dtkcore可能会依赖[dtkcommon](https://github.com/linuxdeepin/dtkcommon),qt5integration会依赖[qt5platform-plugins](https://github.com/linuxdeepin/qt5platform-plugins) + +### 2. 环境安装 + +首先需要安装qt环境,按照以下命令执行 + +```shell +sudo apt update +sudo apt install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev qt5-private-dev +``` + +### 3. 安装 + +源码拉下来之后,确保所需依赖都已正确安装,在每个依赖库的源码所在的目录位置执行以下命令 + +```shell +sudo apt build-dep . +``` + +接下来有两种方式来安装dtk依赖,一种是源码安装方式,第二种是自己构建软件包安装方式。源码安装方式在对应的各自依赖库的README文档中有介绍,这里不再赘述。这里介绍使用软件包安装方式: + +在各自依赖库的源码根目录下,使用以下命令打包 + +```shell +dpkg-buildpackage -us -uc -b -j16 +``` + +-j16为多核执行,可根据处理器核数和性能酌情增减。 + +安装依赖库时可能会出现以下错误: + +![image-20240605152527906](./dtk-install/error1.png) + +原因:依赖的版本问题导致无法正常打包 + +解决方法: + +```shell +vim debian/rules +# 在打开的文件中找到override_dh_shlibdeps: +# 添加dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info +``` + +![image-20240605153447864](./dtk-install/solution1.png) + +在所有依赖都成功打包后,可直接使用安装包进行依赖的安装,安装完成后使用命令查看依赖是否安装成功,以及依赖的版本是否满足要求。 + +```shell +apt policy libdtkcore5 +# 其他依赖同理 +``` diff --git a/docs/dtk-install/error1.png b/docs/dtk-install/error1.png new file mode 100644 index 000000000..a0fd086bc Binary files /dev/null and b/docs/dtk-install/error1.png differ diff --git a/docs/dtk-install/solution1.png b/docs/dtk-install/solution1.png new file mode 100644 index 000000000..f27e3416a Binary files /dev/null and b/docs/dtk-install/solution1.png differ diff --git a/docs/extended-guide.md b/docs/extended-guide.md index d5045dbcd..566a474b5 100644 --- a/docs/extended-guide.md +++ b/docs/extended-guide.md @@ -328,3 +328,56 @@ services 是 IDE 提供的服务接口,该接口是通过动态绑定的形式 启动 deepin-unioncode,点击“文件”>"新建文件或工程">"弹出模板创建页面": ![](./rc-guide/new-file-or-project.png) + +## 四、二进制工具 + +### 1、简介 + +该模块为开发者提供了二进制工具接入的配置界面,实现二进制工具的配置、增添、删除、重命名等。二进制工具的配置界面如下图所示: + + + +### 2、配置工具 + +以配置一个打印当前文件路径的工具为例。 + +1. 添加工具:点击工具列表下的+进行工具的添加; + +2. 编辑工具:对工具的描述、可执行文件、参数、工作目录等参数进行配置; + + - 描述:打印当前文件路径; + + - 可执行文件:echo; + + - 参数:可以通过点击编辑框内的插入变量按钮插入宏命令(双击CurrentDocument:FilePath); + + + + - 输出/错误输出:用于设置工具输出方式,这里选择【在应用程序输出中展示】; + + - 添加到工具栏:将工具添加到工具栏,便于运行; + + - 工具图标:选择一个用于展示工具的图标; + +3. 高级设置:高级设置包含缺失提示、安装命令、管道数据、触发事件。 + + - 缺失提示:一段当工具可执行文件不存在时的提示信息; + - 安装命令:用于安装该工具可执行文件的命令; + - 管道数据:设置该工具的管道数据; + - 触发事件:触发该工具执行的事件,例如:文件保存时、工程激活时等。 + +4. 运行该工具:通过点击顶部工具栏图标或者应用菜单项运行,查看打印结果。 + + + +### 3、运行工具 + +二进制工具提供了两种运行工具的方式: + +1. 通过应用菜单进行运行; + + + +2. 通过点击工具栏图标进行运行(需要再配置工具时勾选添加到工具栏)。 + + diff --git a/docs/rc-guide/binary-tool.png b/docs/rc-guide/binary-tool.png new file mode 100644 index 000000000..2b3f56f23 Binary files /dev/null and b/docs/rc-guide/binary-tool.png differ diff --git a/docs/rc-guide/edit.png b/docs/rc-guide/edit.png new file mode 100644 index 000000000..3b2fa42a7 Binary files /dev/null and b/docs/rc-guide/edit.png differ diff --git a/docs/rc-guide/macro-var.png b/docs/rc-guide/macro-var.png new file mode 100644 index 000000000..e954583db Binary files /dev/null and b/docs/rc-guide/macro-var.png differ diff --git a/docs/rc-guide/run-tool-1.png b/docs/rc-guide/run-tool-1.png new file mode 100644 index 000000000..6dd2b9c3e Binary files /dev/null and b/docs/rc-guide/run-tool-1.png differ diff --git a/docs/rc-guide/run-tool-2.png b/docs/rc-guide/run-tool-2.png new file mode 100644 index 000000000..ef24c58d2 Binary files /dev/null and b/docs/rc-guide/run-tool-2.png differ diff --git a/docs/rc-guide/tool-result.png b/docs/rc-guide/tool-result.png new file mode 100644 index 000000000..6fb1ea4ca Binary files /dev/null and b/docs/rc-guide/tool-result.png differ diff --git a/src/app/main.cpp b/src/app/main.cpp index fb8a861bd..a4cfaf161 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -11,6 +11,7 @@ #include #include +#include DWIDGET_USE_NAMESPACE @@ -99,6 +100,13 @@ void voidMessageOutput(QtMsgType type, const QMessageLogContext &context, const int main(int argc, char *argv[]) { + // some platform opengl drive with wrong,so use OpenGLES instead. + if (QSysInfo::currentCpuArchitecture().contains("arm")) { + QSurfaceFormat format; + format.setRenderableType(QSurfaceFormat::OpenGLES); + format.setDefaultFormat(format); + } + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); DApplication a(argc, argv); diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 8524581f7..8304fc06c 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -1,41 +1,19 @@ project(base) -set(CXX_H - abstractmenu.h - abstractaction.h - abstractwidget.h - abstractdebugger.h - abstractlexerproxy.h - abstractoutputparser.h - abstractlocator.h - basefilelocator.h - baseitemdelegate.h - abstracteditwidget.h - ) - -set(CXX_CPP - abstractmenu.cpp - abstractaction.cpp - abstractwidget.cpp - abstractdebugger.cpp - abstractlexerproxy.cpp - abstractoutputparser.cpp - abstractlocator.cpp - basefilelocator.cpp - baseitemdelegate.cpp - abstracteditwidget.cpp - ) +FILE(GLOB_RECURSE PROJECT_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" +) add_library( ${PROJECT_NAME} - SHARED - ${CXX_H} - ${CXX_CPP} - ) + STATIC + ${PROJECT_SOURCES} +) target_link_libraries( ${PROJECT_NAME} ${DtkWidget_LIBRARIES} - ) +) install(TARGETS ${PROJECT_NAME} DESTINATION ${LIBRARY_INSTALL_PREFIX}) diff --git a/src/base/abstractaction.cpp b/src/base/abstractaction.cpp index d770440ba..f41121cde 100644 --- a/src/base/abstractaction.cpp +++ b/src/base/abstractaction.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "abstractaction.h" diff --git a/src/base/abstractaction.h b/src/base/abstractaction.h index b9d731c93..d13b11567 100644 --- a/src/base/abstractaction.h +++ b/src/base/abstractaction.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ABSTRACTNAVACTION_H #define ABSTRACTNAVACTION_H diff --git a/src/base/abstractdebugger.cpp b/src/base/abstractdebugger.cpp index a47a85615..0fef070e4 100644 --- a/src/base/abstractdebugger.cpp +++ b/src/base/abstractdebugger.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "abstractdebugger.h" diff --git a/src/base/abstractdebugger.h b/src/base/abstractdebugger.h index a5e785a01..e65a9281d 100644 --- a/src/base/abstractdebugger.h +++ b/src/base/abstractdebugger.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ABSTRACTDEBUGGER_H #define ABSTRACTDEBUGGER_H @@ -47,6 +47,7 @@ class AbstractDebugger : public QObject virtual void startDebug() = 0; virtual void startDebugRemote(const RemoteInfo &info) = 0; + virtual void attachDebug(const QString &processId) = 0; virtual void detachDebug() = 0; virtual void interruptDebug() = 0; diff --git a/src/base/abstracteditwidget.cpp b/src/base/abstracteditwidget.cpp index 27d406208..15eb43f80 100644 --- a/src/base/abstracteditwidget.cpp +++ b/src/base/abstracteditwidget.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "abstracteditwidget.h" diff --git a/src/base/abstracteditwidget.h b/src/base/abstracteditwidget.h index 365798fb3..b6d1c6511 100644 --- a/src/base/abstracteditwidget.h +++ b/src/base/abstracteditwidget.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ABSTRACTEDITWIDGET_H #define ABSTRACTEDITWIDGET_H diff --git a/src/base/abstractinstaller.cpp b/src/base/abstractinstaller.cpp new file mode 100644 index 000000000..89ec93ce6 --- /dev/null +++ b/src/base/abstractinstaller.cpp @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "abstractinstaller.h" + +AbstractInstaller::AbstractInstaller(QObject *parent) + : QObject(parent) +{ +} diff --git a/src/base/abstractinstaller.h b/src/base/abstractinstaller.h new file mode 100644 index 000000000..c7909d0a4 --- /dev/null +++ b/src/base/abstractinstaller.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef ABSTRACTINSTALLER_H +#define ABSTRACTINSTALLER_H + +#include + +struct InstallInfo +{ + QString installer; + QString plugin; + QStringList packageList; +}; + +class AbstractInstaller : public QObject +{ + Q_OBJECT +public: + explicit AbstractInstaller(QObject *parent = nullptr); + + virtual QString description() = 0; + virtual bool checkInstalled(const QString &package) = 0; + virtual void install(const InstallInfo &info) = 0; +}; + +#endif // ABSTRACTINSTALLER_H diff --git a/src/base/abstractlexerproxy.cpp b/src/base/abstractlexerproxy.cpp index 07c36c067..e9473562e 100644 --- a/src/base/abstractlexerproxy.cpp +++ b/src/base/abstractlexerproxy.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "abstractlexerproxy.h" diff --git a/src/base/abstractlexerproxy.h b/src/base/abstractlexerproxy.h index 4725484ce..77acfafe3 100644 --- a/src/base/abstractlexerproxy.h +++ b/src/base/abstractlexerproxy.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ABSTRACTLEXERPROXY_H #define ABSTRACTLEXERPROXY_H diff --git a/src/base/abstractlocator.cpp b/src/base/abstractlocator.cpp index 6985314c6..79fde478f 100644 --- a/src/base/abstractlocator.cpp +++ b/src/base/abstractlocator.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "abstractlocator.h" #include "common/util/fuzzymatcher.h" diff --git a/src/base/abstractlocator.h b/src/base/abstractlocator.h index 2d13a7480..3280cb26a 100644 --- a/src/base/abstractlocator.h +++ b/src/base/abstractlocator.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ABSTRACTLOCATOR_H #define ABSTRACTLOCATOR_H diff --git a/src/base/abstractmenu.cpp b/src/base/abstractmenu.cpp index d1bc8491f..dc6bcbfc2 100644 --- a/src/base/abstractmenu.cpp +++ b/src/base/abstractmenu.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "abstractmenu.h" diff --git a/src/base/abstractmenu.h b/src/base/abstractmenu.h index 602678bd4..7bf9ceb29 100644 --- a/src/base/abstractmenu.h +++ b/src/base/abstractmenu.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ABSTRACTMENU_H #define ABSTRACTMENU_H diff --git a/src/base/abstractoutputparser.cpp b/src/base/abstractoutputparser.cpp index 306f62ec1..c10e3cb73 100644 --- a/src/base/abstractoutputparser.cpp +++ b/src/base/abstractoutputparser.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "abstractoutputparser.h" diff --git a/src/base/abstractoutputparser.h b/src/base/abstractoutputparser.h index 894912cdb..a945b5905 100644 --- a/src/base/abstractoutputparser.h +++ b/src/base/abstractoutputparser.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ABSTRACTOUTPUTPARSER_H #define ABSTRACTOUTPUTPARSER_H diff --git a/src/base/abstractwidget.cpp b/src/base/abstractwidget.cpp index 07a83a705..a41656b9a 100644 --- a/src/base/abstractwidget.cpp +++ b/src/base/abstractwidget.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "abstractwidget.h" diff --git a/src/base/abstractwidget.h b/src/base/abstractwidget.h index badf383af..4c511dbc8 100644 --- a/src/base/abstractwidget.h +++ b/src/base/abstractwidget.h @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ABSTRACTWIDGET_H #define ABSTRACTWIDGET_H +#include + class AbstractWidgetPrivate; class AbstractWidget { @@ -13,6 +15,10 @@ class AbstractWidget AbstractWidget(void *qWidget); virtual ~AbstractWidget(); void* qWidget(); + void setDisplayIcon(QIcon icon) { displayIcon = icon; } + QIcon getDisplayIcon() { return displayIcon; } +private: + QIcon displayIcon; }; #endif // ABSTRACTWIDGET_H diff --git a/src/base/basefilelocator.cpp b/src/base/basefilelocator.cpp index 5a7b52271..b473d4146 100644 --- a/src/base/basefilelocator.cpp +++ b/src/base/basefilelocator.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "basefilelocator.h" #include "services/editor/editorservice.h" diff --git a/src/base/basefilelocator.h b/src/base/basefilelocator.h index ec38332e4..efbbd867c 100644 --- a/src/base/basefilelocator.h +++ b/src/base/basefilelocator.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef BASEFILELOCATOR_H #define BASEFILELOCATOR_H diff --git a/src/base/baseitemdelegate.cpp b/src/base/baseitemdelegate.cpp index 5b0a83ad2..0d7217697 100644 --- a/src/base/baseitemdelegate.cpp +++ b/src/base/baseitemdelegate.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "baseitemdelegate.h" diff --git a/src/base/baseitemdelegate.h b/src/base/baseitemdelegate.h index ea71cba9a..a22b3c524 100644 --- a/src/base/baseitemdelegate.h +++ b/src/base/baseitemdelegate.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef BASEITEMDELEGATE_H #define BASEITEMDELEGATE_H diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index bc557f1bd..5ac36f28d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -9,6 +9,10 @@ find_package(Qt5Network) set(CMAKE_CXX_STANDARD 17) +add_definitions( + -DCOMMON_LIBRARY + ) + FILE(GLOB COMMON_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.h" "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" @@ -27,6 +31,11 @@ add_library( ${COMMON_FILES} resource/common.qrc ) + +target_include_directories(${PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) target_link_libraries( ${PROJECT_NAME} diff --git a/src/common/actionmanager/actionmanager.cpp b/src/common/actionmanager/actionmanager.cpp index 117ea171b..dd7257733 100644 --- a/src/common/actionmanager/actionmanager.cpp +++ b/src/common/actionmanager/actionmanager.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "actionmanager.h" #include "util/shortcututil.h" diff --git a/src/common/actionmanager/actionmanager.h b/src/common/actionmanager/actionmanager.h index 367b9b6e9..6205ba0db 100644 --- a/src/common/actionmanager/actionmanager.h +++ b/src/common/actionmanager/actionmanager.h @@ -1,17 +1,18 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ACTIONMANAGER_H #define ACTIONMANAGER_H +#include "common/common_global.h" #include "command.h" #include #include class ActionManagerPrivate; -class ActionManager : public QObject +class COMMON_EXPORT ActionManager : public QObject { Q_OBJECT public: diff --git a/src/common/actionmanager/command.cpp b/src/common/actionmanager/command.cpp index 1a2ea8b2e..5f76b48f3 100644 --- a/src/common/actionmanager/command.cpp +++ b/src/common/actionmanager/command.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "command.h" diff --git a/src/common/actionmanager/command.h b/src/common/actionmanager/command.h index 3675a3f57..a62f794ab 100644 --- a/src/common/actionmanager/command.h +++ b/src/common/actionmanager/command.h @@ -1,17 +1,19 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef COMMAND_H #define COMMAND_H +#include "common/common_global.h" + #include #include class QAction; class QKeySequence; class ActionPrivate; -class Command : public QObject +class COMMON_EXPORT Command : public QObject { Q_OBJECT public: diff --git a/src/common/common.h b/src/common/common.h index 17a1d47d6..e1b749df1 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef COMMON_H #define COMMON_H diff --git a/src/common/common_global.h b/src/common/common_global.h new file mode 100644 index 000000000..b908b2e90 --- /dev/null +++ b/src/common/common_global.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef COMMON_GLOBAL_H +#define COMMON_GLOBAL_H + + +#if defined(COMMON_LIBRARY) +# define COMMON_EXPORT Q_DECL_EXPORT +#else +# define COMMON_EXPORT Q_DECL_IMPORT +#endif + + +#endif // COMMON_GLOBAL_H diff --git a/src/common/dialog/commondialog.cpp b/src/common/dialog/commondialog.cpp index d9e5dd049..7473e9cf9 100644 --- a/src/common/dialog/commondialog.cpp +++ b/src/common/dialog/commondialog.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "commondialog.h" diff --git a/src/common/dialog/commondialog.h b/src/common/dialog/commondialog.h index b8bf78b0a..7aa444d6c 100644 --- a/src/common/dialog/commondialog.h +++ b/src/common/dialog/commondialog.h @@ -1,11 +1,12 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef COMMONDIALOG_H #define COMMONDIALOG_H #include "common/widget/singlechoicebox.h" +#include "common/common_global.h" #include @@ -18,7 +19,7 @@ DWIDGET_USE_NAMESPACE -class CommonDialog final +class COMMON_EXPORT CommonDialog final { Q_DISABLE_COPY(CommonDialog) CommonDialog() = delete; diff --git a/src/common/dialog/pip3dialog.cpp b/src/common/dialog/pip3dialog.cpp index 090cf8b06..5e2c4eb54 100644 --- a/src/common/dialog/pip3dialog.cpp +++ b/src/common/dialog/pip3dialog.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "pip3dialog.h" diff --git a/src/common/dialog/pip3dialog.h b/src/common/dialog/pip3dialog.h index 13c645eb9..2f30d8844 100644 --- a/src/common/dialog/pip3dialog.h +++ b/src/common/dialog/pip3dialog.h @@ -1,13 +1,14 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef PIP3DIALOG_H #define PIP3DIALOG_H #include "processdialog.h" +#include "common/common_global.h" -class Pip3Dialog : public ProcessDialog +class COMMON_EXPORT Pip3Dialog : public ProcessDialog { Q_OBJECT public: diff --git a/src/common/dialog/processdialog.cpp b/src/common/dialog/processdialog.cpp index 4ed80f95e..f154dfc73 100644 --- a/src/common/dialog/processdialog.cpp +++ b/src/common/dialog/processdialog.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "processdialog.h" diff --git a/src/common/dialog/processdialog.h b/src/common/dialog/processdialog.h index 36d888e6e..b06ade456 100644 --- a/src/common/dialog/processdialog.h +++ b/src/common/dialog/processdialog.h @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef PROCESSDIALOG_H #define PROCESSDIALOG_H +#include "common/common_global.h" + #include #include #include @@ -14,7 +16,7 @@ DWIDGET_USE_NAMESPACE -class ProcessDialog : public DAbstractDialog +class COMMON_EXPORT ProcessDialog : public DAbstractDialog { Q_OBJECT public: diff --git a/src/common/dialog/propertiesdialog.cpp b/src/common/dialog/propertiesdialog.cpp index 514db4cff..3c6dba2a3 100644 --- a/src/common/dialog/propertiesdialog.cpp +++ b/src/common/dialog/propertiesdialog.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "propertiesdialog.h" #include "common/widget/pagewidget.h" @@ -30,6 +30,7 @@ PropertiesDialog::PropertiesDialog(QWidget *parent) bool PropertiesDialog::insertPropertyPanel(const QString &itemName, PageWidget *panel) { widgts.insert(itemName, panel); + panel->readConfig(); leftBarModel->setStringList(leftBarModel->stringList() << itemName); int index = stackWidget->count(); stackWidget->insertWidget(index, panel); @@ -99,12 +100,14 @@ void PropertiesDialog::setupUi(DAbstractDialog *Dialog) DFrame *contentFrame = new DFrame(); contentFrame->setLineWidth(0); - auto contentlayout = new QHBoxLayout(contentFrame); - contentlayout->setContentsMargins(0, 0, 0, 0); + auto contentLayout = new QHBoxLayout(contentFrame); + contentLayout->setContentsMargins(0, 0, 0, 0); + contentLayout->setSpacing(0); - DTitlebar *titleBar = new DTitlebar(Dialog); + titleBar = new DTitlebar(Dialog); titleBar->setMinimumHeight(43); titleBar->setMenuVisible(false); + titleBar->setTitle(tr("Project Properties")); titleBar->setIcon(QIcon::fromTheme("ide")); vLayout->addWidget(titleBar); @@ -141,7 +144,8 @@ void PropertiesDialog::setupUi(DAbstractDialog *Dialog) box_layout->setSpacing(9); DVerticalLine *vLine = new DVerticalLine(this); vLine->setLineWidth(1); - box_layout->setContentsMargins(0, 10, 0, 20); + vLine->setFixedHeight(28); + box_layout->setContentsMargins(0, 0, 0, 20); auto cancelBtn = new DPushButton(tr("Cancel"), this); connect(cancelBtn, &DPushButton::clicked, [this] { @@ -170,17 +174,21 @@ void PropertiesDialog::setupUi(DAbstractDialog *Dialog) bgGroup->setItemMargins(QMargins(0, 0, 0, 0)); bgGroup->setBackgroundRole(QPalette::Window); bgGroup->setUseWidgetBackground(false); - - QWidget *wrapperWidget = new QWidget(bgGroup); - QHBoxLayout *hLay = new QHBoxLayout(wrapperWidget); - bgGpLayout->addWidget(wrapperWidget); + bgGpLayout->setMargin(0); + + DFrame *wrapperFrame = new DFrame(bgGroup); + wrapperFrame->setLineWidth(0); + wrapperFrame->setContentsMargins(10, 10, 10, 10); + QHBoxLayout *hLay = new QHBoxLayout(wrapperFrame); + hLay->setContentsMargins(0, 0, 0, 0); + bgGpLayout->addWidget(wrapperFrame); hLay->addLayout(rightLayout); DStyle::setFrameRadius(bgGroup, 0); DStyle::setFrameRadius(contentFrame, 0); - contentlayout->addLayout(leftLayout); - contentlayout->addWidget(bgGroup); + contentLayout->addLayout(leftLayout); + contentLayout->addWidget(bgGroup); Dialog->setFixedSize(QSize(828, 800)); } @@ -198,6 +206,11 @@ void PropertiesDialog::saveAllConfig() accept(); } +void PropertiesDialog::setCurrentTitle(const QString &title) +{ + titleBar->setTitle(title); +} + void PropertiesDialog::saveSingleConfig() { int index = stackWidget->currentIndex(); diff --git a/src/common/dialog/propertiesdialog.h b/src/common/dialog/propertiesdialog.h index c9bc2a3c9..522a5c869 100644 --- a/src/common/dialog/propertiesdialog.h +++ b/src/common/dialog/propertiesdialog.h @@ -1,15 +1,18 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef PROPERTIESDIALOG_H #define PROPERTIESDIALOG_H +#include "common/common_global.h" + #include #include #include #include #include +#include #include #include @@ -27,7 +30,7 @@ class QStringListModel; class QStackedWidget; class PageWidget; -class PropertiesDialog : public DAbstractDialog +class COMMON_EXPORT PropertiesDialog : public DAbstractDialog { Q_OBJECT public: @@ -35,6 +38,7 @@ class PropertiesDialog : public DAbstractDialog bool insertPropertyPanel(const QString &itemName, PageWidget *panel); void showPropertyPanel(const QString &itemName, const QString &tabName); + void setCurrentTitle(const QString &title); signals: public slots: @@ -57,6 +61,7 @@ public slots: DStackedWidget *stackWidget = nullptr; QStringList leftBarValues; + DTitlebar *titleBar = nullptr; }; #endif // PROPERTIESDIALOG_H diff --git a/src/common/dialog/wgetdialog.cpp b/src/common/dialog/wgetdialog.cpp index 67fa14b54..7ea1172eb 100644 --- a/src/common/dialog/wgetdialog.cpp +++ b/src/common/dialog/wgetdialog.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "wgetdialog.h" diff --git a/src/common/dialog/wgetdialog.h b/src/common/dialog/wgetdialog.h index 4af670cd5..6de4fa51d 100644 --- a/src/common/dialog/wgetdialog.h +++ b/src/common/dialog/wgetdialog.h @@ -1,13 +1,14 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef WGETDIALOG_H #define WGETDIALOG_H +#include "common/common_global.h" #include "processdialog.h" -class WGetDialog : public ProcessDialog +class COMMON_EXPORT WGetDialog : public ProcessDialog { Q_OBJECT public: diff --git a/src/common/find/abstractdocumentfind.h b/src/common/find/abstractdocumentfind.h index 5bcafbd01..212873a9e 100644 --- a/src/common/find/abstractdocumentfind.h +++ b/src/common/find/abstractdocumentfind.h @@ -1,13 +1,15 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ABSTRACTDOCUMENTFIND_H #define ABSTRACTDOCUMENTFIND_H +#include "common/common_global.h" + #include -class AbstractDocumentFind : public QObject +class COMMON_EXPORT AbstractDocumentFind : public QObject { Q_OBJECT public: @@ -18,11 +20,11 @@ class AbstractDocumentFind : public QObject virtual void findNext(const QString &txt) = 0; virtual void findPrevious(const QString &txt) = 0; - virtual void replace(const QString &before, const QString &after) {}; - virtual void replaceFind(const QString &before, const QString &after) {}; - virtual void replaceAll(const QString &before, const QString &after) {}; - virtual void findStringChanged() {}; - virtual bool supportsReplace() const { return true; }; + virtual void replace(const QString &before, const QString &after) {} + virtual void replaceFind(const QString &before, const QString &after) {} + virtual void replaceAll(const QString &before, const QString &after) {} + virtual void findStringChanged() {} + virtual bool supportsReplace() const { return true; } }; #endif // ABSTRACTDOCUMENTFIND_H diff --git a/src/common/find/outputdocumentfind.cpp b/src/common/find/outputdocumentfind.cpp index c63c9e014..a8377f5aa 100644 --- a/src/common/find/outputdocumentfind.cpp +++ b/src/common/find/outputdocumentfind.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "outputdocumentfind.h" #include "widget/outputpane.h" @@ -79,6 +79,11 @@ OutputDocumentFind::OutputDocumentFind(OutputPane *parent) d->textEdit = parent->edit(); } +OutputDocumentFind::~OutputDocumentFind() +{ + delete d; +} + QString OutputDocumentFind::findString() const { QTextCursor cursor = d->textCursor(); diff --git a/src/common/find/outputdocumentfind.h b/src/common/find/outputdocumentfind.h index 91e05954e..9b63fbfac 100644 --- a/src/common/find/outputdocumentfind.h +++ b/src/common/find/outputdocumentfind.h @@ -1,19 +1,21 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef OUTPUTDOCUMENTFIND_H #define OUTPUTDOCUMENTFIND_H +#include "common/common_global.h" #include "abstractdocumentfind.h" class OutputPane; class OutputDocumentFindPrivate; -class OutputDocumentFind : public AbstractDocumentFind +class COMMON_EXPORT OutputDocumentFind : public AbstractDocumentFind { Q_OBJECT public: explicit OutputDocumentFind(OutputPane *parent); + ~OutputDocumentFind(); virtual QString findString() const override; virtual void findNext(const QString &txt) override; diff --git a/src/common/inotify/inotify.cpp b/src/common/inotify/inotify.cpp index f763b0663..77f587876 100644 --- a/src/common/inotify/inotify.cpp +++ b/src/common/inotify/inotify.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "inotify.h" #include "private/inotify_p.h" diff --git a/src/common/inotify/inotify.h b/src/common/inotify/inotify.h index ad1527cb5..6786f2ddb 100644 --- a/src/common/inotify/inotify.h +++ b/src/common/inotify/inotify.h @@ -1,14 +1,16 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef INOTIFY_H #define INOTIFY_H +#include "common/common_global.h" + #include class InotifyPrivate; -class Inotify : public QObject +class COMMON_EXPORT Inotify : public QObject { Q_OBJECT Q_DISABLE_COPY(Inotify) diff --git a/src/common/inotify/private/inotify_hook.h b/src/common/inotify/private/inotify_hook.h index 3dd114035..728652234 100644 --- a/src/common/inotify/private/inotify_hook.h +++ b/src/common/inotify/private/inotify_hook.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef INOTIFY_HOOK_H #define INOTIFY_HOOK_H diff --git a/src/common/inotify/private/inotify_impl.h b/src/common/inotify/private/inotify_impl.h index 70988557f..903b381bc 100644 --- a/src/common/inotify/private/inotify_impl.h +++ b/src/common/inotify/private/inotify_impl.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef INOTIFY_IMPL_H #define INOTIFY_IMPL_H diff --git a/src/common/inotify/private/inotify_linux.h b/src/common/inotify/private/inotify_linux.h index 99f9e5100..b02a03a48 100644 --- a/src/common/inotify/private/inotify_linux.h +++ b/src/common/inotify/private/inotify_linux.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef INOTIFY_LINUX_H #define INOTIFY_LINUX_H diff --git a/src/common/inotify/private/inotify_p.h b/src/common/inotify/private/inotify_p.h index 1a2dff225..407e2d7a7 100644 --- a/src/common/inotify/private/inotify_p.h +++ b/src/common/inotify/private/inotify_p.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef INOTIFY_P_H #define INOTIFY_P_H diff --git a/src/common/lsp/client/client.cpp b/src/common/lsp/client/client.cpp index 4df128f4b..0212553ef 100644 --- a/src/common/lsp/client/client.cpp +++ b/src/common/lsp/client/client.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "client.h" #include "private/client_p.h" @@ -42,7 +42,7 @@ Client::Client() qRegisterMetaType("lsp::Locations"); qRegisterMetaType("lsp::CompletionProvider"); qRegisterMetaType("lsp::SignatureHelps"); - qRegisterMetaType("lsp::Highlights"); + qRegisterMetaType("lsp::DocumentHighlight"); qRegisterMetaType>("QList"); qRegisterMetaType("lsp::DefinitionProvider"); qRegisterMetaType("lsp::DiagnosticsParams"); @@ -55,6 +55,8 @@ Client::Client() qRegisterMetaType("newlsp::Range"); qRegisterMetaType("newlsp::PublishDiagnosticsParams"); qRegisterMetaType("newlsp::DocumentColorParams"); + qRegisterMetaType("newlsp::DocumentSymbol"); + qRegisterMetaType("newlsp::SymbolInformation"); } Client::~Client() @@ -241,7 +243,8 @@ void Client::rangeFormatting(const QString &filePath, const DocumentRangeFormatt d->callMethod(lsp::V_TEXTDOCUMENT_RANGEFORMATTING, QJsonDocument::fromJson( QByteArray::fromStdString(toJsonValueStr(params))) - .object(), filePath); + .object(), + filePath); } void Client::onTypeFormatting(const DocumentOnTypeFormattingParams ¶ms) @@ -363,9 +366,9 @@ void Client::definitionRequest(const QString &filePath, const lsp::Position &pos d->callMethod(lsp::V_TEXTDOCUMENT_DEFINITION, lsp::definition(filePath, pos), filePath); } -void Client::completionRequest(const QString &filePath, const lsp::Position &pos) +void Client::completionRequest(const QString &filePath, const lsp::Position &pos, const lsp::CompletionContext &context) { - d->callMethod(lsp::V_TEXTDOCUMENT_COMPLETION, lsp::completion(filePath, pos), filePath); + d->callMethod(lsp::V_TEXTDOCUMENT_COMPLETION, lsp::completion(filePath, pos, context), filePath); } void Client::signatureHelpRequest(const QString &filePath, const lsp::Position &pos) @@ -506,7 +509,26 @@ bool ClientPrivate::symbolResult(const QJsonObject &jsonObj) auto calledID = jsonObj.value(K_ID).toInt(); if (requestSave.keys().contains(calledID) && requestSave.value(calledID).method == lsp::V_TEXTDOCUMENT_DOCUMENTSYMBOL) { + auto filePath = requestSave.value(calledID).file; requestSave.remove(calledID); + + QList docSymbols; + QList symbolInfos; + auto value = jsonObj.value(K_RESULT); + if (value.isArray()) { + auto array = value.toArray(); + if (!array.isEmpty()) { + auto arrayObj = array.first().toObject(); + if (arrayObj.contains("range")) { + docSymbols = parseDocumentSymbol(array); + } else { + symbolInfos = parseDocumentSymbolInfo(array); + } + } + } + + emit q->symbolResult(docSymbols, filePath); + emit q->symbolResult(symbolInfos, filePath); return true; } return false; @@ -853,6 +875,7 @@ bool ClientPrivate::completionResult(const QJsonObject &jsonObj) (lsp::InsertTextFormat)itemObj.value("insertTextFormat").toInt(), (lsp::CompletionItem::Kind)(itemObj.value("kind").toInt()), itemObj.value("label").toString(), + itemObj.value("detail").toString(), itemObj.value("score").toDouble(), itemObj.value("sortText").toString(), textEdit @@ -970,7 +993,15 @@ bool ClientPrivate::docHighlightResult(const QJsonObject &jsonObj) auto calledID = jsonObj.value(K_ID).toInt(); if (requestSave.keys().contains(calledID) && requestSave.value(calledID).method == lsp::V_TEXTDOCUMENT_DOCUMENTHIGHLIGHT) { + auto filePath = requestSave.value(calledID).file; requestSave.remove(calledID); + + auto result = jsonObj.value(K_RESULT); + QList docHighlightList; + if (result.isArray()) + docHighlightList = parseDocumentHighlight(result.toArray()); + + emit q->documentHighlightResult(docHighlightList, filePath); return true; } return false; @@ -1024,8 +1055,13 @@ bool ClientPrivate::exitResult(const QJsonObject &jsonObj) if (requestSave.keys().contains(calledID) && requestSave.value(calledID).method == lsp::V_EXIT) { requestSave.remove(calledID); + + auto ret = jsonObj.value(K_RESULT).toString(); + QUrl url(ret); + emit q->switchHeaderSourceResult(url.toLocalFile()); return true; } + return false; } @@ -1158,6 +1194,104 @@ bool ClientPrivate::diagnosticsCalled(const QJsonObject &jsonObj) return true; } +QList ClientPrivate::parseDocumentSymbol(const QJsonArray &array) +{ + QList docSymbols; + for (const auto &value : array) { + if (!value.isObject()) + continue; + + auto obj = value.toObject(); + DocumentSymbol symbol; + symbol.name = obj.value("name").toString(); + symbol.kind = obj.value("kind").toInt(); + symbol.range = parseRange(obj.value("range").toObject()); + symbol.selectionRange = parseRange(obj.value("selectionRange").toObject()); + symbol.children = parseDocumentSymbol(obj.value("children").toArray()); + + auto val = obj.value("detail"); + symbol.detail = val.isUndefined() ? std::nullopt : std::make_optional(val.toString()); + val = obj.value("deprecated"); + symbol.deprecated = val.isUndefined() ? std::nullopt : std::make_optional(val.toBool()); + + docSymbols.append(symbol); + } + + return docSymbols; +} + +QList ClientPrivate::parseDocumentSymbolInfo(const QJsonArray &array) +{ + QList symbolInfos; + for (const auto &value : array) { + if (!value.isObject()) + continue; + + auto obj = value.toObject(); + SymbolInformation info; + info.name = obj.value("name").toString(); + info.kind = obj.value("kind").toInt(); + info.location = parseLocation(obj.value("location").toObject()); + + auto val = obj.value("deprecated"); + info.deprecated = val.isUndefined() ? std::nullopt : std::make_optional(val.toBool()); + val = obj.value("containerName"); + info.containerName = val.isUndefined() ? std::nullopt : std::make_optional(val.toString()); + + symbolInfos.append(info); + } + + return symbolInfos; +} + +QList ClientPrivate::parseDocumentHighlight(const QJsonArray &array) +{ + QList highlightList; + for (const auto &value : array) { + if (!value.isObject()) + continue; + + DocumentHighlight dh; + auto obj = value.toObject(); + auto val = obj.value("kind"); + dh.kind = value.isUndefined() ? std::nullopt : std::make_optional(val.toInt()); + dh.range = parseRange(obj.value("range").toObject()); + + highlightList.append(dh); + } + + return highlightList; +} + +Range ClientPrivate::parseRange(const QJsonObject &obj) +{ + Range range; + auto iter = obj.begin(); + for (; iter != obj.end(); ++iter) { + const auto &valObj = iter.value().toObject(); + int line = valObj.value("line").toInt(); + int character = valObj.value("character").toInt(); + if (iter.key() == "start") { + range.start.line = line; + range.start.character = character; + } else if (iter.key() == "end") { + range.end.line = line; + range.end.character = character; + } + } + + return range; +} + +Location ClientPrivate::parseLocation(const QJsonObject &obj) +{ + Location location; + location.range = parseRange(obj.value("range").toObject()); + location.uri = obj.value("uri").toString().toStdString(); + + return location; +} + bool ClientPrivate::serverCalled(const QJsonObject &jsonObj) { if (diagnosticsCalled(jsonObj)) @@ -1273,4 +1407,5 @@ QStringList ClientPrivate::cvtStringList(const QJsonArray &array) } return ret; } + } // namespace newlsp diff --git a/src/common/lsp/client/client.h b/src/common/lsp/client/client.h index 72425cbc5..1ef3a635f 100644 --- a/src/common/lsp/client/client.h +++ b/src/common/lsp/client/client.h @@ -1,10 +1,11 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef CLIENT_H #define CLIENT_H +#include "common/common_global.h" #include "common/lsp/protocol/protocol.h" #include "common/lsp/protocol/newprotocol.h" @@ -13,7 +14,7 @@ namespace newlsp { class ClientPrivate; -class Client : public QProcess +class COMMON_EXPORT Client : public QProcess { Q_OBJECT ClientPrivate *const d; @@ -152,7 +153,7 @@ public slots: void symbolRequest(const QString &filePath); // yes void renameRequest(const QString &filePath, const lsp::Position &pos, const QString &newName); //yes void definitionRequest(const QString &filePath, const lsp::Position &pos); // yes - void completionRequest(const QString &filePath, const lsp::Position &pos); // yes + void completionRequest(const QString &filePath, const lsp::Position &pos, const lsp::CompletionContext &context); // yes void signatureHelpRequest(const QString &filePath, const lsp::Position &pos); // yes void referencesRequest(const QString &filePath, const lsp::Position &pos); void docHighlightRequest(const QString &filePath, const lsp::Position &pos); @@ -169,7 +170,6 @@ public slots: void requestResult(const lsp::Locations &locations); void requestResult(const lsp::CompletionProvider &completionProvider); void requestResult(const lsp::SignatureHelps &signatureHelps); - void requestResult(const lsp::Highlights &highlights); void requestResult(const QList &tokensResult, const QString &filePath); void requestResult(const lsp::References &refs); void switchHeaderSourceResult(const QString &filePath); @@ -179,6 +179,9 @@ public slots: void definitionRes(const std::vector &locations, const QString &filePath); void definitionRes(const std::vector &locations, const QString &filePath); void rangeFormattingRes(const std::vector &edits, const QString &filePath); + void symbolResult(const QList &docSymbols, const QString &filePath); + void symbolResult(const QList &symbolInfos, const QString &filePath); + void documentHighlightResult(const QList &docHighlightLsit, const QString &filePath); /* server request */ void publishDiagnostics(const newlsp::PublishDiagnosticsParams &diagnostics); // textDocument/publishDiagnostics diff --git a/src/common/lsp/client/private/client_p.h b/src/common/lsp/client/private/client_p.h index 891a9915f..b216ae9a7 100644 --- a/src/common/lsp/client/private/client_p.h +++ b/src/common/lsp/client/private/client_p.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef CLIENT_P_H #define CLIENT_P_H @@ -62,6 +62,12 @@ class ClientPrivate : public newlsp::StdoutJsonRpcParser bool serverCalled(const QJsonObject &jsonObj); // not found result key from json && found key method bool diagnosticsCalled(const QJsonObject &jsonObj); + QList parseDocumentSymbol(const QJsonArray &array); + QList parseDocumentSymbolInfo(const QJsonArray &array); + QList parseDocumentHighlight(const QJsonArray &array); + Range parseRange(const QJsonObject &obj); + Location parseLocation(const QJsonObject &obj); + public Q_SLOTS: // readed parse void doReadStdoutLine(); diff --git a/src/common/lsp/client/stdoutjsonrpcparser.cpp b/src/common/lsp/client/stdoutjsonrpcparser.cpp index 89e3b5be1..0cfc411e1 100644 --- a/src/common/lsp/client/stdoutjsonrpcparser.cpp +++ b/src/common/lsp/client/stdoutjsonrpcparser.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "stdoutjsonrpcparser.h" #include "common/lsp/protocol/new/jsonrpcparser.h" diff --git a/src/common/lsp/client/stdoutjsonrpcparser.h b/src/common/lsp/client/stdoutjsonrpcparser.h index ca9767e7b..ed43e2866 100644 --- a/src/common/lsp/client/stdoutjsonrpcparser.h +++ b/src/common/lsp/client/stdoutjsonrpcparser.h @@ -1,16 +1,18 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef STDOUTJSONRPCPARSER_H #define STDOUTJSONRPCPARSER_H +#include "common_global.h" + #include namespace newlsp { class JsonRpcParser; -class StdoutJsonRpcParser : public QObject +class COMMON_EXPORT StdoutJsonRpcParser : public QObject { Q_OBJECT JsonRpcParser *const d; diff --git a/src/common/lsp/lsp.h b/src/common/lsp/lsp.h index fbb6cb581..e75fdb263 100644 --- a/src/common/lsp/lsp.h +++ b/src/common/lsp/lsp.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef LSP_H #define LSP_H diff --git a/src/common/lsp/protocol/new/basicjsonstructures.cpp b/src/common/lsp/protocol/new/basicjsonstructures.cpp index 9191e9c66..655a8ab6d 100644 --- a/src/common/lsp/protocol/new/basicjsonstructures.cpp +++ b/src/common/lsp/protocol/new/basicjsonstructures.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "basicjsonstructures.h" diff --git a/src/common/lsp/protocol/new/basicjsonstructures.h b/src/common/lsp/protocol/new/basicjsonstructures.h index 859d25b71..86e745d77 100644 --- a/src/common/lsp/protocol/new/basicjsonstructures.h +++ b/src/common/lsp/protocol/new/basicjsonstructures.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef BASICJSONSTRUCTURES_H #define BASICJSONSTRUCTURES_H @@ -24,7 +24,7 @@ extern const QString K_PARAMS; extern const QString H_CONTENT_LENGTH; extern const QString H_CONTENT_TYPE; extern const QString H_CHARSET; -extern const QString RK_CONTENT_LENGTH; // RegExp Key +extern const QString RK_CONTENT_LENGTH; // RegExp Key extern const QString RK_CONTENT_TYPE; extern const QString RK_CHARSET; @@ -49,8 +49,7 @@ std::string toJsonValueStr(const std::string &value); std::string toJsonValueStr(const std::vector &vecInt); std::string toJsonValueStr(const std::vector &vecString); -namespace json -{ +namespace json { std::string addScope(const std::string &src); std::string delScope(const std::string &obj); std::string mergeObjs(const std::vector &objs); @@ -65,17 +64,17 @@ struct KV typedef T type_second; KV() = delete; KV(const std::string &key, const T &value) - : key(key), value(value){} + : key(key), value(value) {} }; -template +template std::string addValue(const std::string &src, const KV &kv) { std::string temp; if (kv.key.empty()) return temp; - temp = formatKey(kv.key) + ":" + toJsonValueStr(kv.value); + temp = formatKey(kv.key) + ":" + toJsonValueStr(kv.value); if (!src.empty()) return src + "," + temp; @@ -83,17 +82,17 @@ std::string addValue(const std::string &src, const KV &kv) return temp; } -template +template std::string addValue(const std::string &src, const KV> &kv) { std::string ret = src; if (kv.value) { - ret = addValue(src, json::KV{kv.key, kv.value.value()}); + ret = addValue(src, json::KV { kv.key, kv.value.value() }); } return ret; } -template +template std::string addValue(const std::string &src, const KV> &kv) { std::string temp; @@ -118,12 +117,12 @@ std::string addValue(const std::string &src, const KV> &kv) return temp; } -template +template std::string addValue(const std::string &src, const KV>> &kv) { std::string ret; if (kv.value) { - ret = addValue(src, json::KV{kv.key, kv.value.value()}); + ret = addValue(src, json::KV { kv.key, kv.value.value() }); } return ret; } @@ -312,7 +311,6 @@ enum_def(SemanticTokenTypes, std::string) enum_exp Decorator = "decorator"; }; - enum_def(SemanticTokenModifiers, std::string) { enum_exp Declaration = "declaration"; @@ -341,13 +339,15 @@ enum_def(DiagnosticSeverity, int) enum_exp Hint = 4; }; -enum_def(CompletionTriggerKind, int) { +enum_def(CompletionTriggerKind, int) +{ enum_exp Invoked = 1; enum_exp TriggerCharacter = 2; enum_exp TriggerForIncompleteCompletions = 3; }; -enum_def(InsertTextFormat, int) { +enum_def(InsertTextFormat, int) +{ enum_exp PlainText = 1; enum_exp Snippet = 2; }; @@ -358,13 +358,15 @@ enum_def(InlayHintKind, int) enum_exp Parameter = 2; }; -enum_def(MonikerKind, std::string){ +enum_def(MonikerKind, std::string) +{ enum_exp Import = "import"; - enum_exp Export = "export"; // source export, conflict with native grammar + enum_exp Export = "export"; // source export, conflict with native grammar enum_exp Local = "local"; }; -enum_def(UniquenessLevel, std::string) { +enum_def(UniquenessLevel, std::string) +{ enum_exp document = "document"; enum_exp project = "project"; enum_exp group = "group"; @@ -399,7 +401,8 @@ enum_def(NotebookCellKind, int) enum_exp Code = 2; }; -enum_def(DocumentHighlightKind, int){ +enum_def(DocumentHighlightKind, int) +{ enum_exp Text = 1; enum_exp Read = 2; enum_exp Write = 3; @@ -411,15 +414,17 @@ enum_def(CodeActionTriggerKind, int) enum_exp Automatic = 2; }; -} // BasicEnum +} // BasicEnum typedef std::string DocumentUri; -typedef std::string URI ; +typedef std::string URI; struct ProgressToken : std::any { - ProgressToken(int val) : std::any(val){} - ProgressToken(const std::string &val) : std::any(val){} + ProgressToken(int val) + : std::any(val) {} + ProgressToken(const std::string &val) + : std::any(val) {} }; std::string toJsonValueStr(const ProgressToken &val); @@ -429,7 +434,7 @@ struct Position int character; Position() = default; Position(int line, int character) - : line(line), character(character){} + : line(line), character(character) {} }; std::string toJsonValueStr(const Position &val); @@ -439,7 +444,19 @@ struct Range Position end; Range() = default; Range(const Position &start, const Position &end) - : start(start), end(end){} + : start(start), end(end) {} + inline bool contains(const Position &pos) const + { + if (pos.line > start.line && pos.line < end.line) { + return true; + } else if (pos.line == start.line && pos.character >= start.character) { + return !(pos.line == end.line && pos.character > end.character); + } else if (pos.line == end.line && pos.character <= end.character) { + return true; + } + + return false; + } }; std::string toJsonValueStr(const Range &val); @@ -485,7 +502,7 @@ struct DocumentFilter }; std::string toJsonValueStr(const DocumentFilter &val); -struct DocumentSelector: std::vector +struct DocumentSelector : std::vector { }; std::string toJsonValueStr(const DocumentSelector &val); @@ -515,10 +532,13 @@ std::string toJsonValueStr(const AnnotatedTextEdit &val); struct TextDocumentEdit { - struct Edits : std::vector, std::vector { + struct Edits : std::vector, std::vector + { Edits() = default; - Edits(const std::vector &val) : std::vector(val){} - Edits(const std::vector &val) : std::vector(val){} + Edits(const std::vector &val) + : std::vector(val) {} + Edits(const std::vector &val) + : std::vector(val) {} }; OptionalVersionedTextDocumentIdentifier textDocument; Edits edits; @@ -558,13 +578,13 @@ struct Diagnostic { Range range; std::optional severity; - std::optional code; // int or string + std::optional code; // int or string std::optional codeDescription; std::optional source; std::optional message; std::optional> tags; std::optional> relatedInformation; - std::optional data; // unknown; + std::optional data; // unknown; }; std::string toJsonValueStr(const Diagnostic &val); @@ -591,7 +611,7 @@ std::string toJsonValueStr(const CreateFileOptions &val); struct CreateFile { - const std::string kind{"create"}; + const std::string kind { "create" }; DocumentUri uri; std::optional options; std::optional annotationId; @@ -607,7 +627,7 @@ std::string toJsonValueStr(const RenameFileOptions &val); struct RenameFile { - const std::string kind{"rename"}; + const std::string kind { "rename" }; DocumentUri oldUri; DocumentUri newUri; std::optional options; @@ -624,24 +644,34 @@ std::string toJsonValueStr(const DeleteFileOptions &val); struct DeleteFile { - const std::string kind{"delete"}; + const std::string kind { "delete" }; DocumentUri uri; std::optional options; std::optional annotationId; }; -std::string toJsonValueStr(const DeleteFile &val);; +std::string toJsonValueStr(const DeleteFile &val); +; struct WorkspaceEdit { // { [uri: DocumentUri]: TextEdit[]; }; - struct Changes : std::map> {}; - struct ChangeAnnotations : std::map {}; - struct DocumentChanges : std::any { + struct Changes : std::map> + { + }; + struct ChangeAnnotations : std::map + { + }; + struct DocumentChanges : std::any + { DocumentChanges() = default; - DocumentChanges(const std::vector &val) : std::any(val){} - DocumentChanges(const std::vector &val) : std::any(val){} - DocumentChanges(const std::vector &val) : std::any(val){} - DocumentChanges(const std::vector &val) : std::any(val){} + DocumentChanges(const std::vector &val) + : std::any(val) {} + DocumentChanges(const std::vector &val) + : std::any(val) {} + DocumentChanges(const std::vector &val) + : std::any(val) {} + DocumentChanges(const std::vector &val) + : std::any(val) {} }; std::optional changes; // ( TextDocumentEdit[] | (TextDocumentEdit | CreateFile | RenameFile | DeleteFile)[]); @@ -655,7 +685,7 @@ std::string toJsonValueStr(const WorkspaceEdit &val); struct WorkDoneProgressBegin { - const std::string kind {"begin"}; + const std::string kind { "begin" }; std::string title; std::optional boolean; std::optional message; @@ -664,7 +694,7 @@ struct WorkDoneProgressBegin struct WorkDoneProgressReport { - const std::string kind {"report"}; + const std::string kind { "report" }; std::optional cancellable; std::optional message; std::optional percentage; @@ -672,7 +702,7 @@ struct WorkDoneProgressReport struct WorkDoneProgressEnd { - const std::string kind {"end"}; + const std::string kind { "end" }; std::optional message; }; @@ -694,6 +724,6 @@ struct PartialResultParams }; std::string toJsonValueStr(const PartialResultParams ¶ms); -} // lsp +} // lsp -#endif // BASICJSONSTRUCTURES_H +#endif // BASICJSONSTRUCTURES_H diff --git a/src/common/lsp/protocol/new/documentsynchronization.cpp b/src/common/lsp/protocol/new/documentsynchronization.cpp index c91d67db4..9ebd99b3a 100644 --- a/src/common/lsp/protocol/new/documentsynchronization.cpp +++ b/src/common/lsp/protocol/new/documentsynchronization.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "documentsynchronization.h" diff --git a/src/common/lsp/protocol/new/documentsynchronization.h b/src/common/lsp/protocol/new/documentsynchronization.h index ada2c630e..c66ef3b86 100644 --- a/src/common/lsp/protocol/new/documentsynchronization.h +++ b/src/common/lsp/protocol/new/documentsynchronization.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef DOCUMENTSYNCHRONIZATION_H #define DOCUMENTSYNCHRONIZATION_H diff --git a/src/common/lsp/protocol/new/extendedproject.cpp b/src/common/lsp/protocol/new/extendedproject.cpp index deff1dced..f487ce101 100644 --- a/src/common/lsp/protocol/new/extendedproject.cpp +++ b/src/common/lsp/protocol/new/extendedproject.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "extendedproject.h" diff --git a/src/common/lsp/protocol/new/extendedproject.h b/src/common/lsp/protocol/new/extendedproject.h index 9120db178..ff3902a54 100644 --- a/src/common/lsp/protocol/new/extendedproject.h +++ b/src/common/lsp/protocol/new/extendedproject.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef EXTENDEDPROJECT_H #define EXTENDEDPROJECT_H diff --git a/src/common/lsp/protocol/new/jsonrpcparser.cpp b/src/common/lsp/protocol/new/jsonrpcparser.cpp index b6e2a5638..7ed1e93ae 100644 --- a/src/common/lsp/protocol/new/jsonrpcparser.cpp +++ b/src/common/lsp/protocol/new/jsonrpcparser.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "jsonrpcparser.h" #include "basicjsonstructures.h" diff --git a/src/common/lsp/protocol/new/jsonrpcparser.h b/src/common/lsp/protocol/new/jsonrpcparser.h index 335a5b1a3..0f3d751fb 100644 --- a/src/common/lsp/protocol/new/jsonrpcparser.h +++ b/src/common/lsp/protocol/new/jsonrpcparser.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef JSONRPCPARSER_H #define JSONRPCPARSER_H diff --git a/src/common/lsp/protocol/new/languagefeatures.cpp b/src/common/lsp/protocol/new/languagefeatures.cpp index 54fa909d8..74be466ed 100644 --- a/src/common/lsp/protocol/new/languagefeatures.cpp +++ b/src/common/lsp/protocol/new/languagefeatures.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "languagefeatures.h" diff --git a/src/common/lsp/protocol/new/languagefeatures.h b/src/common/lsp/protocol/new/languagefeatures.h index 67cbc67fc..70bc44835 100644 --- a/src/common/lsp/protocol/new/languagefeatures.h +++ b/src/common/lsp/protocol/new/languagefeatures.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef LANGUAGEFEATURES_H #define LANGUAGEFEATURES_H @@ -407,22 +407,22 @@ struct DocumentSymbolParams : WorkDoneProgressParams, std::string toJsonValueStr(const DocumentSymbolParams &val); struct DocumentSymbol { - std::string name; - std::optional detail; + QString name; + std::optional detail; newlsp::Enum::SymbolKind::type_value kind; std::optional> tags; std::optional deprecated; Range range; Range selectionRange; - std::optional> children; + std::optional> children; }; struct SymbolInformation { - std::string name; + QString name; newlsp::Enum::SymbolKind::type_value kind; std::optional> tags; std::optional deprecated; Location location; - std::optional containerName; + std::optional containerName; }; /** Semantic Tokens diff --git a/src/common/lsp/protocol/new/lifecyclemessage.cpp b/src/common/lsp/protocol/new/lifecyclemessage.cpp index 3964e7afd..abd0ee923 100644 --- a/src/common/lsp/protocol/new/lifecyclemessage.cpp +++ b/src/common/lsp/protocol/new/lifecyclemessage.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "lifecyclemessage.h" diff --git a/src/common/lsp/protocol/new/lifecyclemessage.h b/src/common/lsp/protocol/new/lifecyclemessage.h index 8d5d040e1..8d82d56b2 100644 --- a/src/common/lsp/protocol/new/lifecyclemessage.h +++ b/src/common/lsp/protocol/new/lifecyclemessage.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef LIFECYCLEMESSAHE_H #define LIFECYCLEMESSAHE_H diff --git a/src/common/lsp/protocol/newprotocol.cpp b/src/common/lsp/protocol/newprotocol.cpp index e24f122ad..181ef7956 100644 --- a/src/common/lsp/protocol/newprotocol.cpp +++ b/src/common/lsp/protocol/newprotocol.cpp @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "newprotocol.h" diff --git a/src/common/lsp/protocol/newprotocol.h b/src/common/lsp/protocol/newprotocol.h index 80b26b970..e36dd998b 100644 --- a/src/common/lsp/protocol/newprotocol.h +++ b/src/common/lsp/protocol/newprotocol.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef NEWPROTOCOL_H #define NEWPROTOCOL_H diff --git a/src/common/lsp/protocol/protocol.cpp b/src/common/lsp/protocol/protocol.cpp index 828a51fad..f873825a3 100644 --- a/src/common/lsp/protocol/protocol.cpp +++ b/src/common/lsp/protocol/protocol.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "protocol.h" #include "newprotocol.h" @@ -85,6 +85,8 @@ const QString K_CHARACTER { "character" }; const QString K_LINE { "line" }; const QString K_CONTEXT { "context" }; const QString K_INCLUDEDECLARATION { "includeDeclaration" }; +const QString K_TRIGGERKIND { "triggerKind" }; +const QString K_TRIGGERCHARACTER { "triggerCharacter" }; const QString K_ERROR { "error" }; const QString K_CODE { "code" }; @@ -237,7 +239,8 @@ QJsonObject initialize(const QString &workspaceFolder, const QString &language, { "colorProvider", QJsonObject { { "dynamicRegistration", true } } }, { "declaration", QJsonObject { { "dynamicRegistration", true }, { "linkSupport", true } } }, { "semanticHighlightingCapabilities", QJsonObject { { "semanticHighlighting", true } } }, - { "semanticTokens", capabilitiesSemanticTokens } } }, + { "semanticTokens", capabilitiesSemanticTokens }, + { "completion", QJsonObject { { "editsNearCursor", true } } } } }, { "workspace", workspace() }, { "foldingRangeProvider", @@ -900,7 +903,7 @@ QJsonObject rename(const QString &filePath, const Position &pos, const QString & return params; } -QJsonObject completion(const QString &filePath, const Position &pos) +QJsonObject completion(const QString &filePath, const Position &pos, const CompletionContext &context) { QJsonObject textDocument { { K_URI, QUrl::fromLocalFile(filePath).toString() } @@ -911,9 +914,15 @@ QJsonObject completion(const QString &filePath, const Position &pos) { K_LINE, pos.line } }; + QJsonObject completionContext { + { K_TRIGGERCHARACTER, context.triggerCharacter.value_or(QString()) }, + { K_TRIGGERKIND, context.kind } + }; + QJsonObject params { { K_TEXTDOCUMENT, textDocument }, - { K_POSITION, position } + { K_POSITION, position }, + { K_CONTEXT, completionContext } }; return params; diff --git a/src/common/lsp/protocol/protocol.h b/src/common/lsp/protocol/protocol.h index 0fecac494..7e6317c13 100644 --- a/src/common/lsp/protocol/protocol.h +++ b/src/common/lsp/protocol/protocol.h @@ -1,11 +1,12 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef LANGUAGESERVERPROTOCOL_H #define LANGUAGESERVERPROTOCOL_H #include "common/type/menuext.h" +#include "common/common_global.h" #include #include @@ -35,8 +36,8 @@ extern const QString K_INITIALIZATIONOPTIONS; extern const QString K_PROCESSID; extern const QString K_ROOTPATH; extern const QString K_ROOTURI; -extern const QString K_URI; // value QString from file url -extern const QString K_VERSION; // value int +extern const QString K_URI; // value QString from file url +extern const QString K_VERSION; // value int extern const QString K_LANGUAGEID; extern const QString K_TEXT; extern const QString K_CONTAINERNAME; @@ -49,16 +50,16 @@ extern const QString K_NewText; extern const QString H_CONTENT_LENGTH; extern const QString V_2_0; -extern const QString V_INITIALIZE; //has request result -extern const QString V_SHUTDOWN; //has request result -extern const QString V_EXIT; //has request result -extern const QString V_TEXTDOCUMENT_DIDOPEN; //no request result -extern const QString V_TEXTDOCUMENT_PUBLISHDIAGNOSTICS; //server call -extern const QString V_TEXTDOCUMENT_DIDCHANGE; //no request result, json error -extern const QString V_TEXTDOCUMENT_DOCUMENTSYMBOL; // has request result -extern const QString V_TEXTDOCUMENT_HOVER; // has request result +extern const QString V_INITIALIZE; //has request result +extern const QString V_SHUTDOWN; //has request result +extern const QString V_EXIT; //has request result +extern const QString V_TEXTDOCUMENT_DIDOPEN; //no request result +extern const QString V_TEXTDOCUMENT_PUBLISHDIAGNOSTICS; //server call +extern const QString V_TEXTDOCUMENT_DIDCHANGE; //no request result, json error +extern const QString V_TEXTDOCUMENT_DOCUMENTSYMBOL; // has request result +extern const QString V_TEXTDOCUMENT_HOVER; // has request result extern const QString V_TEXTDOCUMENT_RENAME; -extern const QString V_TEXTDOCUMENT_DEFINITION ; +extern const QString V_TEXTDOCUMENT_DEFINITION; extern const QString V_TEXTDOCUMENT_DIDCLOSE; extern const QString V_TEXTDOCUMENT_COMPLETION; extern const QString V_TEXTDOCUMENT_SIGNATUREHELP; @@ -68,9 +69,9 @@ extern const QString V_TEXTDOCUMENT_SEMANTICTOKENS; extern const QString V_TEXTDOCUMENT_SEMANTICTOKENS_FULL; extern const QString V_TEXTDOCUMENT_SEMANTICTOKENS_RANGE; extern const QString V_TEXTDOCUMENT_SWITCHHEADERSOURCE; -inline static QString V_TEXTDOCUMENT_DOCUMENTCOLOR{"textDocument/documentColor"}; -inline static QString V_TEXTDOCUMENT_FORMATTING{"textDocument/formatting"}; -inline static QString V_TEXTDOCUMENT_RANGEFORMATTING{"textDocument/rangeFormatting"}; +inline static QString V_TEXTDOCUMENT_DOCUMENTCOLOR { "textDocument/documentColor" }; +inline static QString V_TEXTDOCUMENT_FORMATTING { "textDocument/formatting" }; +inline static QString V_TEXTDOCUMENT_RANGEFORMATTING { "textDocument/rangeFormatting" }; extern const QString K_WORKSPACEFOLDERS; extern const QString K_CONTENTCHANGES; @@ -117,8 +118,8 @@ enum_def(SemanticTokenType, QString) enum_def(SemanticTokenModifier, QString) { - enum_exp Declaration = "declaration"; //声明 - enum_exp Definition = "declaration"; //定义 + enum_exp Declaration = "declaration"; //声明 + enum_exp Definition = "definition"; //定义 enum_exp Readonly = "readonly"; enum_exp Static = "static"; enum_exp Deprecated = "deprecated"; @@ -131,8 +132,8 @@ enum_def(SemanticTokenModifier, QString) struct Position { - int line; - int character; + int line = -1; + int character = -1; }; struct Range @@ -148,7 +149,9 @@ struct Location Range range; QUrl fileUrl; }; -struct Locations :public QList{}; +struct Locations : public QList +{ +}; struct DiagnosticRelatedInformation { @@ -158,7 +161,7 @@ struct DiagnosticRelatedInformation struct Diagnostic { - enum Severity{ + enum Severity { Unkown = 0, Error = 1, Warning = 2, @@ -172,7 +175,9 @@ struct Diagnostic Severity severity; QString source; }; -struct Diagnostics : QVector{}; +struct Diagnostics : QVector +{ +}; struct DiagnosticsParams { @@ -190,8 +195,7 @@ struct Symbol }; typedef QList Symbols; -enum InsertTextFormat -{ +enum InsertTextFormat { PlainText = 1, Snippet = 2, }; @@ -202,14 +206,28 @@ struct TextEdit Range range; }; -struct AdditionalTextEdits:QList{}; +struct AdditionalTextEdits : QList +{ +}; struct Documentation { - QString kind; // markdown or plaintext + QString kind; // markdown or plaintext QString value; }; +enum CompletionTriggerKind { + Invoked = 1, + TriggerCharacter = 2, + TriggerForIncompleteCompletions = 3 +}; + +struct CompletionContext +{ + CompletionTriggerKind kind = CompletionTriggerKind::Invoked; + std::optional triggerCharacter; +}; + struct CompletionItem { enum Kind { @@ -246,12 +264,15 @@ struct CompletionItem InsertTextFormat insertTextFormat; CompletionItem::Kind kind; QString label; + QString detail; double score; QString sortText; TextEdit textEdit; }; -struct CompletionItems : public QList{}; +struct CompletionItems : public QList +{ +}; struct CompletionProvider { @@ -259,13 +280,16 @@ struct CompletionProvider CompletionItems items; }; -struct SignatureHelp //暂时留空 +struct SignatureHelp //暂时留空 +{ +}; +struct SignatureHelps : QList { - }; -struct SignatureHelps : QList{}; -struct DefinitionProvider : public Locations{}; +struct DefinitionProvider : public Locations +{ +}; struct Contents { @@ -273,19 +297,26 @@ struct Contents QString value; }; -struct Hover //暂时留空 +struct Hover //暂时留空 { Contents contents; Range range; }; -struct Highlight //暂时留空 +struct DocumentHighlight { + enum DocumentHighlightKind { + Text = 1, + Read = 2, + Write = 3 + }; + std::optional kind; + Range range; }; -typedef QList Highlights; +typedef QList DocumentHighlights; -struct Data //from result key "data" +struct Data //from result key "data" { Position start; int length; @@ -295,8 +326,12 @@ struct Data //from result key "data" struct SemanticTokensProvider { - struct Full{bool delta;}; - struct Legend{ + struct Full + { + bool delta; + }; + struct Legend + { QStringList tokenTypes; QStringList tokenModifiers; }; @@ -305,8 +340,7 @@ struct SemanticTokensProvider bool range; }; -enum TextDocumentSyncKind -{ +enum TextDocumentSyncKind { None = 0, Full = 1, Incremental = 2 @@ -323,7 +357,7 @@ struct TextDocumentIdentifier QUrl documentUri; }; -struct VersionedTextDocumentIdentifier: public TextDocumentIdentifier +struct VersionedTextDocumentIdentifier : public TextDocumentIdentifier { int version; }; @@ -360,22 +394,25 @@ struct RenameChange : public TextDocumentIdentifier AdditionalTextEdits edits; }; -struct References : public Locations{}; +struct References : public Locations +{ +}; -namespace new_initialize{ +namespace new_initialize { } -typedef QVariant ProgressToken; // integer | string; +typedef QVariant ProgressToken; // integer | string; struct WorkDoneProgressParams { ProgressToken token; - std::any value; // any + std::any value; // any }; -struct WorkspaceEditClientCapabilities +struct COMMON_EXPORT WorkspaceEditClientCapabilities { - struct changeAnnotationSupport{ + struct changeAnnotationSupport + { /** * Whether the client groups edits with equal labels into tree nodes, * for instance all edits labelled with "Changes in Strings" would @@ -410,7 +447,7 @@ QJsonObject didClose(const QString &filePath); QJsonObject hover(const QString &filePath, const Position &pos); QJsonObject symbol(const QString &filePath); QJsonObject rename(const QString &filePath, const Position &pos, const QString &newName); -QJsonObject completion(const QString &filePath, const Position &pos); +QJsonObject completion(const QString &filePath, const Position &pos, const CompletionContext &context); QJsonObject definition(const QString &filePath, const Position &pos); QJsonObject signatureHelp(const QString &filePath, const Position &pos); QJsonObject references(const QString &filePath, const Position &pos); @@ -424,10 +461,8 @@ QJsonObject exit(); bool isRequestResult(const QJsonObject &object); bool isRequestError(const QJsonObject &object); - -} // namespace lsp - +} // namespace lsp Q_DECLARE_METATYPE(lsp::Range) -#endif // LANGUAGESERVERPROTOCOL_H +#endif // LANGUAGESERVERPROTOCOL_H diff --git a/src/common/lsp/server/route.cpp b/src/common/lsp/server/route.cpp index 2978543e0..99efb10b8 100644 --- a/src/common/lsp/server/route.cpp +++ b/src/common/lsp/server/route.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "route.h" diff --git a/src/common/lsp/server/route.h b/src/common/lsp/server/route.h index f8f8d3948..7c508086d 100644 --- a/src/common/lsp/server/route.h +++ b/src/common/lsp/server/route.h @@ -1,17 +1,18 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ROUTE_H #define ROUTE_H #include "common/lsp/protocol/newprotocol.h" +#include "common/common_global.h" #include #include namespace newlsp { -class Route : public QObject +class COMMON_EXPORT Route : public QObject { Q_OBJECT public: diff --git a/src/common/lsp/server/serverapplication.cpp b/src/common/lsp/server/serverapplication.cpp index b40f5412f..d1360b29c 100644 --- a/src/common/lsp/server/serverapplication.cpp +++ b/src/common/lsp/server/serverapplication.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "serverapplication.h" diff --git a/src/common/lsp/server/serverapplication.h b/src/common/lsp/server/serverapplication.h index b4d0ee3c5..86a4eeb46 100644 --- a/src/common/lsp/server/serverapplication.h +++ b/src/common/lsp/server/serverapplication.h @@ -1,10 +1,11 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef SERVERAPPLICATION_H #define SERVERAPPLICATION_H +#include "common/common_global.h" #include "common/lsp/server/servercmdparse.h" #include "common/lsp/server/stdinjsonrpcparser.h" @@ -16,7 +17,7 @@ namespace newlsp { class ServerApplicationPrivate; -class ServerApplication : public QObject, ServerCmdParse +class COMMON_EXPORT ServerApplication : public QObject, ServerCmdParse { Q_OBJECT ServerApplicationPrivate *const d; diff --git a/src/common/lsp/server/servercmdparse.cpp b/src/common/lsp/server/servercmdparse.cpp index 287f0be3f..3f0c07209 100644 --- a/src/common/lsp/server/servercmdparse.cpp +++ b/src/common/lsp/server/servercmdparse.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "servercmdparse.h" diff --git a/src/common/lsp/server/servercmdparse.h b/src/common/lsp/server/servercmdparse.h index 6034d1a9d..e94cb25f1 100644 --- a/src/common/lsp/server/servercmdparse.h +++ b/src/common/lsp/server/servercmdparse.h @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef SERVERCMDPARSE_H #define SERVERCMDPARSE_H +#include "common/common_global.h" + #include #include #include @@ -18,7 +20,7 @@ extern const QString tcp; extern const QString portDefault; extern const QString parentPidDefault; class ServerCmdParsePrivate; -class ServerCmdParse : QCommandLineParser +class COMMON_EXPORT ServerCmdParse : QCommandLineParser { ServerCmdParsePrivate *const d; public: diff --git a/src/common/lsp/server/stdinjsonrpcparser.cpp b/src/common/lsp/server/stdinjsonrpcparser.cpp index 33f900eaa..0254befaf 100644 --- a/src/common/lsp/server/stdinjsonrpcparser.cpp +++ b/src/common/lsp/server/stdinjsonrpcparser.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "stdinjsonrpcparser.h" #include "common/lsp/protocol/new/jsonrpcparser.h" diff --git a/src/common/lsp/server/stdinjsonrpcparser.h b/src/common/lsp/server/stdinjsonrpcparser.h index 8e0cd3b85..a049c4ea7 100644 --- a/src/common/lsp/server/stdinjsonrpcparser.h +++ b/src/common/lsp/server/stdinjsonrpcparser.h @@ -1,17 +1,18 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef STDINJSONRPCPARSER_H #define STDINJSONRPCPARSER_H #include "stdinreadloop.h" +#include "common/common_global.h" namespace newlsp { class JsonRpcParser; class StdinJsonRpcParserPrivate; -class StdinJsonRpcParser : public StdinReadLoop +class COMMON_EXPORT StdinJsonRpcParser : public StdinReadLoop { Q_OBJECT JsonRpcParser *const d; diff --git a/src/common/lsp/server/stdinreadloop.cpp b/src/common/lsp/server/stdinreadloop.cpp index d1fc6e692..f96a2fb8e 100644 --- a/src/common/lsp/server/stdinreadloop.cpp +++ b/src/common/lsp/server/stdinreadloop.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "stdinreadloop.h" diff --git a/src/common/lsp/server/stdinreadloop.h b/src/common/lsp/server/stdinreadloop.h index 7f6541674..593a6438a 100644 --- a/src/common/lsp/server/stdinreadloop.h +++ b/src/common/lsp/server/stdinreadloop.h @@ -1,14 +1,16 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef STDINREADLOOP_H #define STDINREADLOOP_H +#include "common/common_global.h" + #include class StdinReadLoopPrivate; -class StdinReadLoop : public QThread +class COMMON_EXPORT StdinReadLoop : public QThread { Q_OBJECT StdinReadLoopPrivate *const d; diff --git a/src/common/project/projectinfo.h b/src/common/project/projectinfo.h index c37b51611..12e855f4a 100644 --- a/src/common/project/projectinfo.h +++ b/src/common/project/projectinfo.h @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef PROJECTINFO_H #define PROJECTINFO_H +#include "common/common_global.h" + #include #include #include @@ -16,7 +18,7 @@ enum ProjectInfoRole = Qt::ItemDataRole::UserRole, }; -class ProjectInfo +class COMMON_EXPORT ProjectInfo { public: ProjectInfo() : data({}) {} @@ -56,6 +58,8 @@ class ProjectInfo inline QString runWorkspaceDir() const {return data["RunWorkspaceDir"].toString();} inline void setRunEnvironment(const QStringList &envs) {data["RunEnvironment"] = envs;} inline QStringList runEnvironment() const {return data["RunEnvironment"].toStringList();} + inline void setRunInTerminal(bool isRunInTerminal) {data["RunInTerminal"] = isRunInTerminal;} + inline bool runInTerminal() const {return data["RunInTerminal"].toBool();} bool operator == (const ProjectInfo &other) const {return data == other.data;} bool isEmpty() const {return data.isEmpty();} diff --git a/src/common/resource/common.qrc b/src/common/resource/common.qrc index e3045633f..56942808d 100644 --- a/src/common/resource/common.qrc +++ b/src/common/resource/common.qrc @@ -6,7 +6,8 @@ light/icons/error_16px.svg dark/icons/warning_16px.svg dark/icons/error_16px.svg - texts/common_stop_16px.svg - texts/common_close_16px.svg + texts/common_stop_16px.svg + texts/common_close_16px.svg + texts/clear_log_16px.svg diff --git a/src/common/resource/texts/clear_log_16px.svg b/src/common/resource/texts/clear_log_16px.svg new file mode 100644 index 000000000..b685950fe --- /dev/null +++ b/src/common/resource/texts/clear_log_16px.svg @@ -0,0 +1,11 @@ + + + ICON / action /delete + + + + + + + + \ No newline at end of file diff --git a/src/common/supportfile/dapconfig.cpp b/src/common/supportfile/dapconfig.cpp index ebca1ad26..cd03fd903 100644 --- a/src/common/supportfile/dapconfig.cpp +++ b/src/common/supportfile/dapconfig.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "dapconfig.h" #include "util/custompaths.h" diff --git a/src/common/supportfile/dapconfig.h b/src/common/supportfile/dapconfig.h index f7b2847ea..8a7b40f03 100644 --- a/src/common/supportfile/dapconfig.h +++ b/src/common/supportfile/dapconfig.h @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef DAPCONFIG_H #define DAPCONFIG_H +#include "common_global.h" + #include namespace support_file { @@ -17,7 +19,7 @@ struct JavaDapPluginConfig { QString jreExecute; }; -class DapSupportConfig final +class COMMON_EXPORT DapSupportConfig final { public: static QString globalPath(); diff --git a/src/common/supportfile/editorstyle.cpp b/src/common/supportfile/editorstyle.cpp index 62810f186..d6580bcac 100644 --- a/src/common/supportfile/editorstyle.cpp +++ b/src/common/supportfile/editorstyle.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "editorstyle.h" #include "util/custompaths.h" diff --git a/src/common/supportfile/editorstyle.h b/src/common/supportfile/editorstyle.h index 8eef6877b..c53739d22 100644 --- a/src/common/supportfile/editorstyle.h +++ b/src/common/supportfile/editorstyle.h @@ -1,15 +1,17 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef EDITORSTYLE_H #define EDITORSTYLE_H +#include "common/common_global.h" + #include namespace support_file { -struct EditorStyle +struct COMMON_EXPORT EditorStyle { static QString globalPath(const QString &languageID); static QString userPath(const QString &languageID); diff --git a/src/common/supportfile/language.cpp b/src/common/supportfile/language.cpp index 4eb31ef49..fb06c0451 100644 --- a/src/common/supportfile/language.cpp +++ b/src/common/supportfile/language.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "language.h" #include "util/custompaths.h" @@ -86,6 +86,9 @@ QString Language::id(const QString &filePath) QJsonArray mimeArray = langObjChild.value(Key_2::get()->mimeType).toArray(); for (auto suffix : suffixArray) { + if (suffix.toString().isEmpty()) + continue; + if (info.fileName().endsWith(suffix.toString())) return id; @@ -94,7 +97,6 @@ QString Language::id(const QString &filePath) } for (auto base : baseArray) { - if (info.fileName() == base.toString() || info.fileName().toLower() == base.toString().toLower()) { return id; diff --git a/src/common/supportfile/language.h b/src/common/supportfile/language.h index b3ff3d10d..65fb3c383 100644 --- a/src/common/supportfile/language.h +++ b/src/common/supportfile/language.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef LANGUAGE_H #define LANGUAGE_H diff --git a/src/common/supportfile/windowstyle.cpp b/src/common/supportfile/windowstyle.cpp index 4b91aae30..f25d158a5 100644 --- a/src/common/supportfile/windowstyle.cpp +++ b/src/common/supportfile/windowstyle.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "windowstyle.h" #include "util/custompaths.h" diff --git a/src/common/supportfile/windowstyle.h b/src/common/supportfile/windowstyle.h index 3ab108669..e9979aedf 100644 --- a/src/common/supportfile/windowstyle.h +++ b/src/common/supportfile/windowstyle.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef WINDOWSTYLE_H #define WINDOWSTYLE_H diff --git a/src/common/toolchain/toolchain.cpp b/src/common/toolchain/toolchain.cpp index 81581f1c9..432c9ff7a 100644 --- a/src/common/toolchain/toolchain.cpp +++ b/src/common/toolchain/toolchain.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "toolchain.h" #include "util/processutil.h" diff --git a/src/common/toolchain/toolchain.h b/src/common/toolchain/toolchain.h index b5ddb944b..6cde019ec 100644 --- a/src/common/toolchain/toolchain.h +++ b/src/common/toolchain/toolchain.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef TOOLCHAIN_H #define TOOLCHAIN_H diff --git a/src/common/type/constants.cpp b/src/common/type/constants.cpp index e38d7ca03..0b5a8e456 100644 --- a/src/common/type/constants.cpp +++ b/src/common/type/constants.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "constants.h" #include "config.h" diff --git a/src/common/type/constants.h b/src/common/type/constants.h index 4bffa5b3f..36a34d149 100644 --- a/src/common/type/constants.h +++ b/src/common/type/constants.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef CONSTANTS_H #define CONSTANTS_H diff --git a/src/common/type/menuext.h b/src/common/type/menuext.h index cb1217c70..fba5bc7a5 100644 --- a/src/common/type/menuext.h +++ b/src/common/type/menuext.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef MENUEXT_H #define MENUEXT_H diff --git a/src/common/type/task.cpp b/src/common/type/task.cpp index e63c0d9ec..1878c0cc0 100644 --- a/src/common/type/task.cpp +++ b/src/common/type/task.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "task.h" diff --git a/src/common/type/task.h b/src/common/type/task.h index 16367a302..3b59abc9c 100644 --- a/src/common/type/task.h +++ b/src/common/type/task.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef TASK_H #define TASK_H diff --git a/src/common/unilog.h b/src/common/unilog.h index ab246d339..b0c6b6033 100644 --- a/src/common/unilog.h +++ b/src/common/unilog.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef UNILOG_H #define UNILOG_H @@ -14,8 +14,16 @@ #define uniInfo(...) qInfo(__VA_ARGS__) #define uniWarning(...) qWarning(__VA_ARGS__) #define uniCritical(...) qCritical(__VA_ARGS__) -#else -#define NO_DEBUG(LOG) QMessageLogger().noDebug(LOG) +#else + +class NoDebug +{ +public: + template + inline NoDebug &operator<<(const T &) { return *this; } +}; + +#define NO_DEBUG(LOG) NoDebug() #define uniDebug(...) NO_DEBUG(__VA_ARGS__) #define uniInfo(...) NO_DEBUG(__VA_ARGS__) #define uniWarning(...) NO_DEBUG(__VA_ARGS__) diff --git a/src/common/util/commandparser.cpp b/src/common/util/commandparser.cpp index 54324cc6d..db157e423 100644 --- a/src/common/util/commandparser.cpp +++ b/src/common/util/commandparser.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "commandparser.h" #include "util/custompaths.h" diff --git a/src/common/util/commandparser.h b/src/common/util/commandparser.h index 356db1842..19bceebd1 100644 --- a/src/common/util/commandparser.h +++ b/src/common/util/commandparser.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef COMMANDPARSER_H #define COMMANDPARSER_H diff --git a/src/common/util/config.h.in b/src/common/util/config.h.in index ad857ff78..a31c3980f 100644 --- a/src/common/util/config.h.in +++ b/src/common/util/config.h.in @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef LIBRARY_INSTALL_PREFIX #define LIBRARY_INSTALL_PREFIX "${LIBRARY_INSTALL_PREFIX}" diff --git a/src/common/util/customicons.cpp b/src/common/util/customicons.cpp index e2c45714c..59a988e2f 100644 --- a/src/common/util/customicons.cpp +++ b/src/common/util/customicons.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "customicons.h" #include diff --git a/src/common/util/customicons.h b/src/common/util/customicons.h index 684ed56dc..dffb8690d 100644 --- a/src/common/util/customicons.h +++ b/src/common/util/customicons.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef CUSTOMICON_H #define CUSTOMICON_H diff --git a/src/common/util/custompaths.cpp b/src/common/util/custompaths.cpp index 724997333..a9bf13a83 100644 --- a/src/common/util/custompaths.cpp +++ b/src/common/util/custompaths.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "custompaths.h" #include "fileoperation.h" diff --git a/src/common/util/custompaths.h b/src/common/util/custompaths.h index 744bf3f87..52e34ac93 100644 --- a/src/common/util/custompaths.h +++ b/src/common/util/custompaths.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef GLOBALCONFIGURE_H #define GLOBALCONFIGURE_H diff --git a/src/common/util/downloadutil.cpp b/src/common/util/downloadutil.cpp index 0d8fd7b88..428680cdc 100644 --- a/src/common/util/downloadutil.cpp +++ b/src/common/util/downloadutil.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "downloadutil.h" diff --git a/src/common/util/downloadutil.h b/src/common/util/downloadutil.h index 9078035cf..5017cd746 100644 --- a/src/common/util/downloadutil.h +++ b/src/common/util/downloadutil.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef DOWNLOADUTIL_H #define DOWNLOADUTIL_H diff --git a/src/common/util/environment.cpp b/src/common/util/environment.cpp index 9f836bb33..25b0a366d 100644 --- a/src/common/util/environment.cpp +++ b/src/common/util/environment.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "config.h" #include "environment.h" diff --git a/src/common/util/environment.h b/src/common/util/environment.h index 2067b487a..56255c472 100644 --- a/src/common/util/environment.h +++ b/src/common/util/environment.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ENVIRONMENT_H #define ENVIRONMENT_H diff --git a/src/common/util/eventdefinitions.cpp b/src/common/util/eventdefinitions.cpp index 335971ee5..eda8c46be 100644 --- a/src/common/util/eventdefinitions.cpp +++ b/src/common/util/eventdefinitions.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "eventdefinitions.h" diff --git a/src/common/util/eventdefinitions.h b/src/common/util/eventdefinitions.h index 9326b4b06..fd3057782 100644 --- a/src/common/util/eventdefinitions.h +++ b/src/common/util/eventdefinitions.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef EVENTDEFINITIONS_H #define EVENTDEFINITIONS_H @@ -20,10 +20,12 @@ OPI_OBJECT(project, OPI_INTERFACE(openProject, "kitName", "language", "workspace") OPI_INTERFACE(activeProject, "kitName", "language", "workspace") // out - OPI_INTERFACE(activedProject, "projectInfo") + OPI_INTERFACE(activatedProject, "projectInfo") OPI_INTERFACE(deletedProject, "projectInfo") OPI_INTERFACE(createdProject, "projectInfo") OPI_INTERFACE(projectUpdated, "projectInfo") + OPI_INTERFACE(projectNodeExpanded, "modelIndex") + OPI_INTERFACE(projectNodeCollapsed, "modelIndex") OPI_INTERFACE(fileDeleted, "filePath", "kit") OPI_INTERFACE(openProjectPropertys, "projectInfo") @@ -40,9 +42,11 @@ OPI_OBJECT(debugger, OPI_OBJECT(editor, // in OPI_INTERFACE(openFile, "workspace", "fileName") + OPI_INTERFACE(closeFile, "fileName") OPI_INTERFACE(back) OPI_INTERFACE(forward) OPI_INTERFACE(gotoLine, "fileName", "line") + OPI_INTERFACE(gotoPosition, "fileName", "line", "column") // (AnnotationType)type OPI_INTERFACE(addAnnotation, "fileName", "title", "content", "line", "type") @@ -59,11 +63,12 @@ OPI_OBJECT(editor, OPI_INTERFACE(setBreakpointEnabled, "fileName", "line", "enabled") OPI_INTERFACE(clearAllBreakpoint) // out + OPI_INTERFACE(lineChanged, "fileName", "startLine", "added") OPI_INTERFACE(fileOpened, "fileName") OPI_INTERFACE(fileClosed, "fileName") OPI_INTERFACE(fileSaved, "fileName") OPI_INTERFACE(switchedFile, "fileName") - OPI_INTERFACE(breakpointAdded, "fileName", "line") + OPI_INTERFACE(breakpointAdded, "fileName", "line", "enabled") OPI_INTERFACE(breakpointRemoved, "fileName", "line") OPI_INTERFACE(breakpointStatusChanged, "fileName", "line", "enabled") OPI_INTERFACE(textChanged) @@ -82,10 +87,14 @@ OPI_OBJECT(symbol, ) OPI_OBJECT(uiController, + //receivce OPI_INTERFACE(doSwitch, "actionText") OPI_INTERFACE(switchContext, "name") OPI_INTERFACE(switchWorkspace, "name") + + //send OPI_INTERFACE(switchToWidget, "name") + OPI_INTERFACE(modeRaised, "mode") ) OPI_OBJECT(notifyManager, diff --git a/src/common/util/fileoperation.cpp b/src/common/util/fileoperation.cpp index b90f71bd4..44a7d7860 100644 --- a/src/common/util/fileoperation.cpp +++ b/src/common/util/fileoperation.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "fileoperation.h" #include "processutil.h" diff --git a/src/common/util/fileoperation.h b/src/common/util/fileoperation.h index 77c2e97f6..2402b3664 100644 --- a/src/common/util/fileoperation.h +++ b/src/common/util/fileoperation.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef FILEOPERATION_H #define FILEOPERATION_H diff --git a/src/common/util/fileutils.cpp b/src/common/util/fileutils.cpp index 54c157bf2..0051d4045 100644 --- a/src/common/util/fileutils.cpp +++ b/src/common/util/fileutils.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "fileutils.h" #include diff --git a/src/common/util/fileutils.h b/src/common/util/fileutils.h index f480b5aeb..7ff3896d2 100644 --- a/src/common/util/fileutils.h +++ b/src/common/util/fileutils.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef FILEUTILS_H #define FILEUTILS_H diff --git a/src/common/util/fuzzymatcher.cpp b/src/common/util/fuzzymatcher.cpp index 6e742b32a..e4c5fcfc2 100644 --- a/src/common/util/fuzzymatcher.cpp +++ b/src/common/util/fuzzymatcher.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "fuzzymatcher.h" diff --git a/src/common/util/fuzzymatcher.h b/src/common/util/fuzzymatcher.h index e4d20329a..35533c841 100644 --- a/src/common/util/fuzzymatcher.h +++ b/src/common/util/fuzzymatcher.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef FUZZYMATCHER_H #define FUZZYMATCHER_H diff --git a/src/common/util/hostsysteminfo.h b/src/common/util/hostsysteminfo.h new file mode 100644 index 000000000..4631e4d9b --- /dev/null +++ b/src/common/util/hostsysteminfo.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef HOSTSYSTEMINFO_H +#define HOSTSYSTEMINFO_H + +#include "common/common_global.h" +#include "util/oshelper.h" + +#include + +namespace Utils { + +class COMMON_EXPORT HostSystemInfo +{ +public: + static constexpr OsType hostOs() + { +#if defined(Q_OS_WIN) + return OsTypeWindows; +#elif defined(Q_OS_LINUX) + return OsTypeLinux; +#elif defined(Q_OS_UNIX) + return OsTypeOtherUnix; +#else + return OsTypeOther; +#endif + } + + static constexpr bool isWindowsHost() { return hostOs() == OsTypeWindows; } + static constexpr bool isLinuxHost() { return hostOs() == OsTypeLinux; } + static constexpr bool isAnyUnixHost() + { +#ifdef Q_OS_UNIX + return true; +#else + return false; +#endif + } + + static QString withExecutableSuffix(const QString &executable) + { + return OsHelper::withExecutableSuffix(hostOs(), executable); + } + + static QChar pathListSeparator() + { + return OsHelper::pathListSeparator(hostOs()); + } +}; + +} // namespace Utils + +#endif // HOSTSYSTEMINFO_H diff --git a/src/common/util/macroexpander.cpp b/src/common/util/macroexpander.cpp index 54f086139..4c9625ef6 100644 --- a/src/common/util/macroexpander.cpp +++ b/src/common/util/macroexpander.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "macroexpander.h" diff --git a/src/common/util/macroexpander.h b/src/common/util/macroexpander.h index 6603b9a37..a90929043 100644 --- a/src/common/util/macroexpander.h +++ b/src/common/util/macroexpander.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef MACROEXPANDER_H #define MACROEXPANDER_H diff --git a/src/common/util/oshelper.h b/src/common/util/oshelper.h new file mode 100644 index 000000000..e96cd5ce6 --- /dev/null +++ b/src/common/util/oshelper.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef OSHELPER_H +#define OSHELPER_H + +#include + +#include + +#define WIN_EXE_SUFFIX ".exe" + +namespace Utils { + +// Add more as needed. +enum OsType { OsTypeWindows, OsTypeLinux, OsTypeOtherUnix, OsTypeOther }; + +namespace OsHelper { + +inline QString withExecutableSuffix(OsType osType, const QString &executable) +{ + QString finalName = executable; + if (osType == OsTypeWindows) + finalName += QLatin1String(WIN_EXE_SUFFIX); + return finalName; +} + +inline QChar pathListSeparator(OsType osType) +{ + return QLatin1Char(osType == OsTypeWindows ? ';' : ':'); +} + +inline QString pathWithNativeSeparators(OsType osType, const QString &pathName) +{ + if (osType == OsTypeWindows) { + const int pos = pathName.indexOf('/'); + if (pos >= 0) { + QString n = pathName; + std::replace(std::begin(n) + pos, std::end(n), '/', '\\'); + return n; + } + } + return pathName; +} + +} // namespace OSHELPER_H +} // namespace Utils + +#endif diff --git a/src/common/util/polkit.cpp b/src/common/util/polkit.cpp index 592188428..fd1d6a7e0 100644 --- a/src/common/util/polkit.cpp +++ b/src/common/util/polkit.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "polkit.h" #include "processutil.h" diff --git a/src/common/util/polkit.h b/src/common/util/polkit.h index df398bd14..b66975f0d 100644 --- a/src/common/util/polkit.h +++ b/src/common/util/polkit.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef POLKIT_H #define POLKIT_H diff --git a/src/common/util/processutil.cpp b/src/common/util/processutil.cpp index 53c8f1af0..c173dd7fb 100644 --- a/src/common/util/processutil.cpp +++ b/src/common/util/processutil.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "processutil.h" diff --git a/src/common/util/processutil.h b/src/common/util/processutil.h index 80a628c18..eb3461467 100644 --- a/src/common/util/processutil.h +++ b/src/common/util/processutil.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef PROCESSCALLER_H #define PROCESSCALLER_H diff --git a/src/common/util/qtcassert.h b/src/common/util/qtcassert.h index 8de995d79..6fbff0193 100644 --- a/src/common/util/qtcassert.h +++ b/src/common/util/qtcassert.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef QTCASSERT_H #define QTCASSERT_H diff --git a/src/common/util/shortcututil.cpp b/src/common/util/shortcututil.cpp index 8fd40e051..821429889 100644 --- a/src/common/util/shortcututil.cpp +++ b/src/common/util/shortcututil.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "shortcututil.h" diff --git a/src/common/util/shortcututil.h b/src/common/util/shortcututil.h index d4b566ddb..255f51634 100644 --- a/src/common/util/shortcututil.h +++ b/src/common/util/shortcututil.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef SHORTCUTUTIL_H #define SHORTCUTUTIL_H diff --git a/src/common/util/singleton.h b/src/common/util/singleton.h index 2e004eb8e..a377ca05c 100644 --- a/src/common/util/singleton.h +++ b/src/common/util/singleton.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef SINGLETON_H #define SINGLETON_H diff --git a/src/common/util/themeparser.cpp b/src/common/util/themeparser.cpp index 74498f6ba..c68dceb7c 100644 --- a/src/common/util/themeparser.cpp +++ b/src/common/util/themeparser.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "themeparser.h" diff --git a/src/common/util/themeparser.h b/src/common/util/themeparser.h index 95603be0f..e47bd93c2 100644 --- a/src/common/util/themeparser.h +++ b/src/common/util/themeparser.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef THEMEPARSER_H #define THEMEPARSER_H diff --git a/src/common/util/utils.h b/src/common/util/utils.h index 6dc5b1d5b..04b5ce179 100644 --- a/src/common/util/utils.h +++ b/src/common/util/utils.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef UTILS_H #define UTILS_H diff --git a/src/common/widget/appoutputpane.cpp b/src/common/widget/appoutputpane.cpp index 7bb5b7dea..60e7839b5 100644 --- a/src/common/widget/appoutputpane.cpp +++ b/src/common/widget/appoutputpane.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "appoutputpane.h" #include "util/utils.h" @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -35,8 +36,11 @@ class OutputWindowPrivate DWidget *tabbar { nullptr }; DFrame *hLine { nullptr }; DComboBox *tabChosser { nullptr }; + QHBoxLayout *toolLayout { nullptr }; DToolButton *closeProcessBtn { nullptr }; DToolButton *closePaneBtn { nullptr }; + DToolButton *clearLogBtn { nullptr }; + DLineEdit *filterEdit { nullptr }; QMap toolBars; QMap toolBarBindsToPane; @@ -99,6 +103,12 @@ void AppOutputPane::initUi() void AppOutputPane::initTabWidget() { + auto createVLine = [this]{ + DVerticalLine *vLine = new DVerticalLine(this); + vLine->setFixedHeight(20); + return vLine; + }; + d->tabbar = new DWidget(this); d->tabbar->setFixedHeight(38); d->tabbar->setAutoFillBackground(true); @@ -129,9 +139,29 @@ void AppOutputPane::initTabWidget() d->closePaneBtn->setToolTip(tr("Close OutputPane")); d->closePaneBtn->setEnabled(false); - tabLayout->addWidget(d->tabChosser); - tabLayout->addWidget(d->closePaneBtn); - tabLayout->addWidget(d->closeProcessBtn); + d->clearLogBtn = new DToolButton(d->tabbar); + d->clearLogBtn->setIconSize({ 16, 16 }); + d->clearLogBtn->setFixedSize({ 26, 26}); + d->clearLogBtn->setIcon(QIcon::fromTheme("clear_log")); + d->clearLogBtn->setToolTip(tr("Clear Output")); + + d->toolLayout = new QHBoxLayout; + d->toolLayout->addWidget(d->tabChosser); + d->toolLayout->addWidget(d->closePaneBtn); + d->toolLayout->addWidget(d->closeProcessBtn); + + d->filterEdit = new DLineEdit(d->tabbar); + d->filterEdit->setPlaceholderText(tr("Filter")); + d->filterEdit->setFixedSize(120, 28); + + tabLayout->addLayout(d->toolLayout); + tabLayout->addWidget(createVLine()); + tabLayout->addSpacing(2); + tabLayout->addWidget(d->filterEdit); + tabLayout->addSpacing(2); + tabLayout->addWidget(createVLine()); + tabLayout->addWidget(d->clearLogBtn); + tabLayout->addStretch(1); d->tabbar->hide(); connect(d->tabChosser, QOverload::of(&DComboBox::currentIndexChanged), this, [=](int index) { @@ -152,8 +182,11 @@ void AppOutputPane::initTabWidget() else d->toolBars[toolbarName]->setVisible(false); } + + if (pane) + pane->updateFilter(d->filterEdit->text(), false, false); }); - connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, this, [=](){ + connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, this, [=]() { auto pal = d->tabChosser->palette(); pal.setColor(QPalette::Light, pal.color(QPalette::Base)); pal.setColor(QPalette::Dark, pal.color(QPalette::Base)); @@ -166,6 +199,16 @@ void AppOutputPane::initTabWidget() d->closeProcessBtn->setEnabled(false); }); connect(d->closePaneBtn, &DToolButton::clicked, this, &AppOutputPane::slotCloseOutputPane); + connect(d->filterEdit, &DLineEdit::textChanged, this, [=](const QString &text) { + auto outputPane = qobject_cast(d->stackWidget->currentWidget()); + if (outputPane) + outputPane->updateFilter(text, false, false); + }); + connect(d->clearLogBtn, &DToolButton::clicked, this, [this] { + auto outputPane = qobject_cast(d->stackWidget->currentWidget()); + if (outputPane) + outputPane->clearContents(); + }); } void AppOutputPane::stop(const QString &id) @@ -317,7 +360,7 @@ void AppOutputPane::registerItemToToolBar(const QString& toolbarName, QAction *a auto toolBtn = utils::createIconButton(action, d->tabbar); toolBtn->setFixedSize(26, 26); - + if (addSeparator) { DVerticalLine *line = new DVerticalLine(d->tabbar); line->setFixedHeight(20); @@ -338,9 +381,8 @@ void AppOutputPane::bindToolBarToPane(const QString &toolbarName, OutputPane *pa } d->toolBarBindsToPane.insert(toolbarName, pane); - auto layout = qobject_cast(d->tabbar->layout()); auto toolbar = d->toolBars[toolbarName]; - layout->addWidget(toolbar); + d->toolLayout->addWidget(toolbar); if (d->stackWidget->currentWidget() == pane) { toolbar->setVisible(true); } diff --git a/src/common/widget/appoutputpane.h b/src/common/widget/appoutputpane.h index bb8be63b2..9bf8e695a 100644 --- a/src/common/widget/appoutputpane.h +++ b/src/common/widget/appoutputpane.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef APPOUTPUTPANE_H #define APPOUTPUTPANE_H diff --git a/src/common/widget/collapsewidget.cpp b/src/common/widget/collapsewidget.cpp index f28f97838..3f700a181 100644 --- a/src/common/widget/collapsewidget.cpp +++ b/src/common/widget/collapsewidget.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "collapsewidget.h" #include "detailsbutton.h" diff --git a/src/common/widget/collapsewidget.h b/src/common/widget/collapsewidget.h index cc7391f8f..8d8ed9583 100644 --- a/src/common/widget/collapsewidget.h +++ b/src/common/widget/collapsewidget.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef COLLAPSEWIDGET_H #define COLLAPSEWIDGET_H diff --git a/src/common/widget/configurewidget.cpp b/src/common/widget/configurewidget.cpp index 1087d6fad..45b79993c 100644 --- a/src/common/widget/configurewidget.cpp +++ b/src/common/widget/configurewidget.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "configurewidget.h" #include "collapsewidget.h" diff --git a/src/common/widget/configurewidget.h b/src/common/widget/configurewidget.h index b10484684..2999e9869 100644 --- a/src/common/widget/configurewidget.h +++ b/src/common/widget/configurewidget.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef CONFIGUREWIDGET_H #define CONFIGUREWIDGET_H diff --git a/src/common/widget/detailsbutton.cpp b/src/common/widget/detailsbutton.cpp index 5b52dbf72..43c4f648d 100644 --- a/src/common/widget/detailsbutton.cpp +++ b/src/common/widget/detailsbutton.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "detailsbutton.h" diff --git a/src/common/widget/detailsbutton.h b/src/common/widget/detailsbutton.h index eb87c3f75..9de4c0eee 100644 --- a/src/common/widget/detailsbutton.h +++ b/src/common/widget/detailsbutton.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef DETAILSBUTTON_H #define DETAILSBUTTON_H diff --git a/src/common/widget/elidedlabel.cpp b/src/common/widget/elidedlabel.cpp index 6ce9b8f22..3cc755de6 100644 --- a/src/common/widget/elidedlabel.cpp +++ b/src/common/widget/elidedlabel.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "elidedlabel.h" diff --git a/src/common/widget/elidedlabel.h b/src/common/widget/elidedlabel.h index a59d05a6e..3b8e29c91 100644 --- a/src/common/widget/elidedlabel.h +++ b/src/common/widget/elidedlabel.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef ELIDEDLABEL_H #define ELIDEDLABEL_H diff --git a/src/common/widget/generator.cpp b/src/common/widget/generator.cpp index 89574139e..1f329ac24 100644 --- a/src/common/widget/generator.cpp +++ b/src/common/widget/generator.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "generator.h" diff --git a/src/common/widget/generator.h b/src/common/widget/generator.h index 2bed70d54..36c043f8f 100644 --- a/src/common/widget/generator.h +++ b/src/common/widget/generator.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef GENERATOR_H #define GENERATOR_H diff --git a/src/common/widget/messagebox.cpp b/src/common/widget/messagebox.cpp index 5a26cc2eb..01a902605 100644 --- a/src/common/widget/messagebox.cpp +++ b/src/common/widget/messagebox.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "messagebox.h" diff --git a/src/common/widget/messagebox.h b/src/common/widget/messagebox.h index f36f305cb..9be36eb16 100644 --- a/src/common/widget/messagebox.h +++ b/src/common/widget/messagebox.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef MESSAGEBOX_H #define MESSAGEBOX_H diff --git a/src/common/widget/outputpane.cpp b/src/common/widget/outputpane.cpp index 933f96440..ffcb41454 100644 --- a/src/common/widget/outputpane.cpp +++ b/src/common/widget/outputpane.cpp @@ -1,9 +1,8 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "outputpane.h" -#include "common/common.h" #include #include @@ -12,6 +11,9 @@ #include #include +#include +#include +#include /** * @brief Output text color. @@ -21,24 +23,33 @@ const QColor kMessageOutput(0, 135, 135); constexpr int kDefaultMaxCharCount = 10000000; DWIDGET_USE_NAMESPACE +DGUI_USE_NAMESPACE class OutputPanePrivate { public: - explicit OutputPanePrivate() + struct Output { - } + QString text; + OutputPane::AppendMode mode; + OutputPane::OutputFormat format; + }; - ~OutputPanePrivate() - { - } + explicit OutputPanePrivate(){}; +public: bool enforceNewline = false; bool scrollToBottom = true; int maxCharCount = kDefaultMaxCharCount; QTextCursor cursor; DPlainTextEdit *outputEdit = nullptr; DMenu *menu = nullptr; + + QList outputList; + QTimer outputTimer; + + QString filterText; + int lastBlockNumber = -1; }; OutputPane::OutputPane(QWidget *parent) @@ -46,6 +57,7 @@ OutputPane::OutputPane(QWidget *parent) d(new OutputPanePrivate()) { initUI(); + initTimer(); } OutputPane::~OutputPane() @@ -58,21 +70,11 @@ OutputPane::~OutputPane() void OutputPane::initUI() { - if(DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::ColorType::DarkType) { - textColorNormal = QColor(255, 255, 255, 180); - }else { - textColorNormal = QColor(0, 0, 0, 180); - } - QObject::connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, [&](){ - textColorNormal = QColor(255 - textColorNormal.red(), 255 - textColorNormal.green(), 255 - textColorNormal.blue(), 180); - this->update(); - }); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); d->outputEdit = new DPlainTextEdit(this); - d->outputEdit->setReadOnly(true); d->outputEdit->setLineWidth(0); d->outputEdit->setContextMenuPolicy(Qt::ContextMenuPolicy::ActionsContextMenu); @@ -84,6 +86,13 @@ void OutputPane::initUI() mainLayout->addWidget(d->outputEdit); } +void OutputPane::initTimer() +{ + d->outputTimer.setSingleShot(true); + d->outputTimer.setInterval(100); + connect(&d->outputTimer, &QTimer::timeout, this, &OutputPane::handleNextOutput); +} + void OutputPane::clearContents() { d->outputEdit->clear(); @@ -96,6 +105,41 @@ QString OutputPane::normalizeNewlines(const QString &text) return res; } +void OutputPane::handleNextOutput() +{ + auto &output = d->outputList.first(); + QTextCharFormat textFormat; + switch (output.format) { + case OutputFormat::StdOut: + textFormat.setFontWeight(QFont::Normal); + break; + case OutputFormat::StdErr: + textFormat.setForeground(kErrorMessageTextColor); + textFormat.setFontWeight(QFont::Normal); + break; + case OutputFormat::NormalMessage: + textFormat.setForeground(kMessageOutput); + break; + case OutputFormat::ErrorMessage: + textFormat.setForeground(kErrorMessageTextColor); + textFormat.setFontWeight(QFont::Bold); + break; + default: + textFormat.setFontWeight(QFont::Normal); + } + + if (output.text.size() <= d->maxCharCount) { + appendCustomText(output.text, output.mode, textFormat); + d->outputList.removeFirst(); + } else { + appendCustomText(output.text.left(d->maxCharCount), output.mode, textFormat); + output.text.remove(0, d->maxCharCount); + } + + if (!d->outputList.isEmpty()) + d->outputTimer.start(); +} + bool OutputPane::isScrollbarAtBottom() const { return d->outputEdit->verticalScrollBar()->value() == d->outputEdit->verticalScrollBar()->maximum(); @@ -133,6 +177,7 @@ void OutputPane::appendCustomText(const QString &textIn, AppendMode mode, const qDebug() << "Maximum limit exceeded : " << d->maxCharCount; return; } + if (!d->cursor.atEnd()) d->cursor.movePosition(QTextCursor::End); @@ -141,7 +186,6 @@ void OutputPane::appendCustomText(const QString &textIn, AppendMode mode, const d->cursor.removeSelectedText(); } - d->cursor.beginEditBlock(); auto text = mode == OverWrite ? textIn.trimmed() : normalizeNewlines(doNewlineEnforcement(textIn)); d->cursor.insertText(text, format); @@ -150,36 +194,36 @@ void OutputPane::appendCustomText(const QString &textIn, AppendMode mode, const tmp.setFontWeight(QFont::Bold); d->cursor.insertText(doNewlineEnforcement(tr("Additional output omitted") + QLatin1Char('\n')), tmp); } - d->cursor.endEditBlock(); + + if (!d->filterText.isEmpty()) + filterContent(false, false); scrollToBottom(); } void OutputPane::appendText(const QString &text, OutputFormat format, AppendMode mode) { - QTextCharFormat textFormat; - switch (format) { - case OutputFormat::StdOut: - textFormat.setForeground(textColorNormal); - textFormat.setFontWeight(QFont::Normal); - break; - case OutputFormat::StdErr: - textFormat.setForeground(kErrorMessageTextColor); - textFormat.setFontWeight(QFont::Normal); - break; - case OutputFormat::NormalMessage: - textFormat.setForeground(kMessageOutput); - break; - case OutputFormat::ErrorMessage: - textFormat.setForeground(kErrorMessageTextColor); - textFormat.setFontWeight(QFont::Bold); - break; - default: - textFormat.setForeground(textColorNormal); - textFormat.setFontWeight(QFont::Normal); + if (d->outputList.isEmpty() + || d->outputList.last().mode != mode + || d->outputList.last().format != format) { + d->outputList.append({ text, mode, format }); + } else { + auto &output = d->outputList.last(); + output.text.append(text); } - appendCustomText(text, mode, textFormat); + if (!d->outputTimer.isActive()) + d->outputTimer.start(); +} + +QTextDocument *OutputPane::document() const +{ + return d->outputEdit->document(); +} + +QPlainTextEdit *OutputPane::edit() const +{ + return d->outputEdit; } QTextDocument *OutputPane::document() const @@ -245,3 +289,42 @@ QList OutputPane::actionFactory() return list; } + +void OutputPane::updateFilter(const QString &filterText, bool caseSensitive, bool isRegexp) +{ + d->outputEdit->setReadOnly(!filterText.isEmpty()); + + d->filterText = filterText; + d->lastBlockNumber = -1; + filterContent(caseSensitive, isRegexp); +} + +void OutputPane::filterContent(bool caseSensitive, bool isRegexp) +{ + auto document = d->outputEdit->document(); + auto lastBlock = document->findBlockByLineNumber(d->lastBlockNumber); + if (!lastBlock.isValid()) + lastBlock = document->begin(); + + if (isRegexp) { + QRegularExpression regExp(d->filterText); + if (!caseSensitive) + regExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + + for (; lastBlock != document->end(); lastBlock = lastBlock.next()) + lastBlock.setVisible(d->filterText.isEmpty() || regExp.match(lastBlock.text()).hasMatch()); + } else { + if (caseSensitive) { + for (; lastBlock != document->end(); lastBlock = lastBlock.next()) + lastBlock.setVisible(d->filterText.isEmpty() || lastBlock.text().contains(d->filterText)); + } else { + for (; lastBlock != document->end(); lastBlock = lastBlock.next()) + lastBlock.setVisible(d->filterText.isEmpty() || lastBlock.text().toLower().contains(d->filterText.toLower())); + } + } + + d->lastBlockNumber = d->outputEdit->document()->lastBlock().blockNumber(); + d->outputEdit->setDocument(this->document()); + + scrollToBottom(); +} diff --git a/src/common/widget/outputpane.h b/src/common/widget/outputpane.h index 5922b1451..b93142fa3 100644 --- a/src/common/widget/outputpane.h +++ b/src/common/widget/outputpane.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef OUTPUTPANE_H #define OUTPUTPANE_H @@ -41,15 +41,18 @@ class OutputPane : public QWidget static OutputPane* instance(); - void addApplicationOutPane(); + void updateFilter(const QString &filterText, bool caseSensitive = false, bool isRegexp = false); // support all param later + void filterContent(bool caseSensitive = false, bool isRegexp = false); protected: void contextMenuEvent(QContextMenuEvent * event) override; private: void initUI(); + void initTimer(); QWidget *createFindPlaceHolder(); QString normalizeNewlines(const QString &text); + void handleNextOutput(); void appendCustomText(const QString &text, AppendMode mode, const QTextCharFormat &format = QTextCharFormat()); bool isScrollbarAtBottom() const; QString doNewlineEnforcement(const QString &out); diff --git a/src/common/widget/pagewidget.cpp b/src/common/widget/pagewidget.cpp index 16660fec9..d92ce6a9b 100644 --- a/src/common/widget/pagewidget.cpp +++ b/src/common/widget/pagewidget.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "pagewidget.h" diff --git a/src/common/widget/pagewidget.h b/src/common/widget/pagewidget.h index cf86132b2..7b4fc0e72 100644 --- a/src/common/widget/pagewidget.h +++ b/src/common/widget/pagewidget.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef PAGEWIDGET_H #define PAGEWIDGET_H diff --git a/src/common/widget/singlechoicebox.cpp b/src/common/widget/singlechoicebox.cpp index 240da2d27..e0c44460d 100644 --- a/src/common/widget/singlechoicebox.cpp +++ b/src/common/widget/singlechoicebox.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "singlechoicebox.h" diff --git a/src/common/widget/singlechoicebox.h b/src/common/widget/singlechoicebox.h index 9ad4fa824..92253cfb2 100644 --- a/src/common/widget/singlechoicebox.h +++ b/src/common/widget/singlechoicebox.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef SINGLECHOICEBOX_H #define SINGLECHOICEBOX_H diff --git a/src/common/widget/statuswidget.cpp b/src/common/widget/statuswidget.cpp index 31fab12ed..b746ca032 100644 --- a/src/common/widget/statuswidget.cpp +++ b/src/common/widget/statuswidget.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "statuswidget.h" #include diff --git a/src/common/widget/statuswidget.h b/src/common/widget/statuswidget.h index f17198f28..0b12ac9db 100644 --- a/src/common/widget/statuswidget.h +++ b/src/common/widget/statuswidget.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef STATUSWIDGET_H #define STATUSWIDGET_H diff --git a/src/common/widget/variablechooser.cpp b/src/common/widget/variablechooser.cpp index c3c0efe37..d346c0aaf 100644 --- a/src/common/widget/variablechooser.cpp +++ b/src/common/widget/variablechooser.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "variablechooser.h" #include "util/qtcassert.h" diff --git a/src/common/widget/variablechooser.h b/src/common/widget/variablechooser.h index 40373e0dc..6954f3c9c 100644 --- a/src/common/widget/variablechooser.h +++ b/src/common/widget/variablechooser.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef VARIABLECHOOSER_H #define VARIABLECHOOSER_H diff --git a/src/framework/event/event.h b/src/framework/event/event.h index 8012fbdce..98e081d5e 100644 --- a/src/framework/event/event.h +++ b/src/framework/event/event.h @@ -21,7 +21,7 @@ class EventPrivate; * 事件数据源,只能当做类使用不可继承 * 禁止被继承 */ -class Event final +class DPF_EXPORT Event final { EventPrivate *d; friend Q_CORE_EXPORT QDebug operator <<(QDebug, const Event &); diff --git a/src/framework/event/eventcallproxy.h b/src/framework/event/eventcallproxy.h index 8f0e3ca19..9895ff80e 100644 --- a/src/framework/event/eventcallproxy.h +++ b/src/framework/event/eventcallproxy.h @@ -18,7 +18,7 @@ DPF_BEGIN_NAMESPACE -class EventCallProxy final +class DPF_EXPORT EventCallProxy final { template friend class AutoEventHandlerRegister; diff --git a/src/framework/lifecycle/lifecycle.h b/src/framework/lifecycle/lifecycle.h index 8d9349f7d..69010453d 100644 --- a/src/framework/lifecycle/lifecycle.h +++ b/src/framework/lifecycle/lifecycle.h @@ -15,7 +15,7 @@ DPF_BEGIN_NAMESPACE -class LifeCycle final : public QObject +class DPF_EXPORT LifeCycle final : public QObject { Q_OBJECT public: diff --git a/src/framework/lifecycle/plugin.h b/src/framework/lifecycle/plugin.h index 47b6ae95b..f0bfdd416 100644 --- a/src/framework/lifecycle/plugin.h +++ b/src/framework/lifecycle/plugin.h @@ -33,7 +33,7 @@ class PluginContext; * Q_PLUGIN_METADATA 可参阅Qt宏定义 * PLUGIN_INTERFACE */ -class Plugin : public QObject +class DPF_EXPORT Plugin : public QObject { Q_OBJECT public: diff --git a/src/framework/lifecycle/plugindepend.h b/src/framework/lifecycle/plugindepend.h index 16529ec9a..e7f7ad783 100644 --- a/src/framework/lifecycle/plugindepend.h +++ b/src/framework/lifecycle/plugindepend.h @@ -7,7 +7,7 @@ #include "framework/framework_global.h" -#include +#include DPF_BEGIN_NAMESPACE @@ -36,9 +36,30 @@ class PluginDepend final PluginDepend(); }; +class PluginInstallDepend final +{ + friend class PluginManager; + friend class PluginManagerPrivate; + friend class PluginMetaObject; + friend Q_CORE_EXPORT QDebug operator<<(QDebug, const PluginDepend &); + + QString installerName; + QStringList packageList; + +public: + PluginInstallDepend(const PluginInstallDepend &depend); + PluginInstallDepend &operator=(const PluginInstallDepend &depend); + QString installer() const { return installerName; } + QStringList packages() const { return packageList; } + +private: + PluginInstallDepend(); +}; + QT_BEGIN_NAMESPACE #ifndef QT_NO_DEBUG_STREAM Q_CORE_EXPORT QDebug operator<< (QDebug, const DPF_NAMESPACE::PluginDepend &); +Q_CORE_EXPORT QDebug operator<< (QDebug, const DPF_NAMESPACE::PluginInstallDepend &); #endif //QT_NO_DEBUG_STREAM QT_END_NAMESPACE diff --git a/src/framework/lifecycle/pluginmanager.h b/src/framework/lifecycle/pluginmanager.h index 6f0739ac9..14b71927c 100644 --- a/src/framework/lifecycle/pluginmanager.h +++ b/src/framework/lifecycle/pluginmanager.h @@ -27,7 +27,7 @@ class PluginManagerPrivate; * 其中重要的特性为:plugin IID (插件身份标识) 可参阅Qt插件规范; * 此处目前只支持Plugin接口插件的动态库形式插件加载 */ -class PluginManager: public QObject +class DPF_EXPORT PluginManager: public QObject { Q_OBJECT friend class PluginManagerPrivate; diff --git a/src/framework/lifecycle/pluginmetaobject.cpp b/src/framework/lifecycle/pluginmetaobject.cpp index a65add964..8c8ca48a6 100644 --- a/src/framework/lifecycle/pluginmetaobject.cpp +++ b/src/framework/lifecycle/pluginmetaobject.cpp @@ -99,6 +99,11 @@ QList PluginMetaObject::depends() const return d->depends; } +QList PluginMetaObject::installDepends() const +{ + return d->installDepends; +} + /** * @brief PluginMetaObject::pluginState * 获取插件当前状态 @@ -172,6 +177,7 @@ PluginMetaObject::PluginMetaObject(const PluginMetaObject &meta) d->description = meta.description(); d->urlLink = meta.urlLink(); d->depends = meta.depends(); + d->installDepends = meta.installDepends(); d->state = pluginState(); d->plugin = plugin(); d->loader = meta.d->loader; @@ -190,6 +196,7 @@ PluginMetaObject &PluginMetaObject::operator =(const PluginMetaObject &meta) d->description = meta.description(); d->urlLink = meta.urlLink(); d->depends = meta.depends(); + d->installDepends = meta.installDepends(); d->state = pluginState(); d->plugin = plugin(); d->loader = meta.d->loader; @@ -259,6 +266,22 @@ PluginDepend &PluginDepend::operator =(const PluginDepend &depend) return *this; } +PluginInstallDepend::PluginInstallDepend() +{ +} + +PluginInstallDepend::PluginInstallDepend(const PluginInstallDepend &depend) +{ + installerName = depend.installer(); + packageList = depend.packages(); +} + +PluginInstallDepend &PluginInstallDepend::operator=(const PluginInstallDepend &depend) +{ + installerName = depend.installer(); + packageList = depend.packages(); + return *this; +} QT_BEGIN_NAMESPACE /** @@ -278,6 +301,16 @@ Q_CORE_EXPORT QDebug operator<<(QDebug out, const DPF_NAMESPACE::PluginDepend &d return out; } +Q_CORE_EXPORT QDebug operator<<(QDebug out, const DPF_NAMESPACE::PluginInstallDepend &depend) +{ + DPF_USE_NAMESPACE + out << "PluginInstallDepend(" << QString("0x%0").arg(qint64(&depend),0,16) << "){"; + out << PLUGIN_INSTALLERNAME << " : " << depend.installer() << "; "; + out << PLUGIN_PACKAGES << " : " << depend.packages() << "; "; + out << "}"; + return out; +} + /** * @brief operator << * 重定向全局Debug打印PluginMetaObject对象的函数 @@ -300,6 +333,7 @@ Q_CORE_EXPORT QDebug operator<< (QDebug out, const DPF_NAMESPACE::PluginMetaObje out << PLUGIN_LICENSE << ":" << metaObj.license() << "; "; out << PLUGIN_URLLINK << ":" << metaObj.urlLink() << "; "; out << PLUGIN_DEPENDS << ":" << metaObj.depends() << ";"; + out << PLUGIN_INSTALLDEPENDS << ":" << metaObj.installDepends() << ";"; out << "}"; return out; } diff --git a/src/framework/lifecycle/pluginmetaobject.h b/src/framework/lifecycle/pluginmetaobject.h index 46f56fd0b..10cf479fc 100644 --- a/src/framework/lifecycle/pluginmetaobject.h +++ b/src/framework/lifecycle/pluginmetaobject.h @@ -20,7 +20,7 @@ DPF_BEGIN_NAMESPACE * @tparam 传入插件对象接口例如 Plugin */ template -class PluginMetaT1 : public QSharedData +class DPF_EXPORT PluginMetaT1 : public QSharedData { Q_DISABLE_COPY(PluginMetaT1) public: @@ -59,7 +59,7 @@ class PluginService; * @details 该类与SharedPointer配套使用时是线程安全的 */ class PluginMetaObjectPrivate; -class PluginMetaObject final : public PluginMetaT1 +class DPF_EXPORT PluginMetaObject final : public PluginMetaT1 { QSharedPointer d; @@ -96,6 +96,7 @@ class PluginMetaObject final : public PluginMetaT1 QString description() const; QString urlLink() const; QList depends() const; + QList installDepends() const; State pluginState() const; QSharedPointer plugin(); bool isEnabledBySettings(); diff --git a/src/framework/lifecycle/pluginsetting.h b/src/framework/lifecycle/pluginsetting.h index e31372961..f2fab081e 100644 --- a/src/framework/lifecycle/pluginsetting.h +++ b/src/framework/lifecycle/pluginsetting.h @@ -18,7 +18,7 @@ const char ENABLED[] = "enabled"; const char DISABLED_PLUGINS[] = "Plugins/Disabled"; const char ENABLED_PLUGINS[] = "Plugins/Enabled"; -class PluginSetting final : public QSettings +class DPF_EXPORT PluginSetting final : public QSettings { Q_OBJECT public: diff --git a/src/framework/lifecycle/private/pluginmanager_p.cpp b/src/framework/lifecycle/private/pluginmanager_p.cpp index 85809eaef..c347e9fa1 100644 --- a/src/framework/lifecycle/private/pluginmanager_p.cpp +++ b/src/framework/lifecycle/private/pluginmanager_p.cpp @@ -439,6 +439,24 @@ void PluginManagerPrivate::readJsonToMeta(const PluginMetaObjectPointer &metaObj ++ itera; } + QJsonArray &&installDependsArray = metaData.value(PLUGIN_INSTALLDEPENDS).toArray(); + auto iter = installDependsArray.begin(); + while (iter != installDependsArray.end()) { + QJsonObject &&dependObj = iter->toObject(); + QString &&installerName = dependObj.value(PLUGIN_INSTALLERNAME).toString(); + QJsonArray &&packageArray = dependObj.value(PLUGIN_PACKAGES).toArray(); + PluginInstallDepend depend; + depend.installerName = installerName; + + auto packageIter = packageArray.begin(); + while (packageIter != packageArray.end()) { + depend.packageList.append(packageIter->toString()); + ++packageIter; + } + metaObject->d->installDepends.append(depend); + ++iter; + } + metaObject->d->state = PluginMetaObject::Readed; dpfCheckTimeEnd(); diff --git a/src/framework/lifecycle/private/pluginmetaobject_p.h b/src/framework/lifecycle/private/pluginmetaobject_p.h index b2fc1c9f8..dad4da346 100644 --- a/src/framework/lifecycle/private/pluginmetaobject_p.h +++ b/src/framework/lifecycle/private/pluginmetaobject_p.h @@ -34,6 +34,10 @@ const char PLUGIN_LICENSE[] = "License"; const char PLUGIN_URLLINK[] = "UrlLink"; /// @nrief PLUGIN_VENDOR 插件依赖 const char PLUGIN_DEPENDS[] = "Depends"; +/// @nrief PLUGIN_INSTALLDEPENDS 插件安装依赖 +const char PLUGIN_INSTALLDEPENDS[] = "InstallDepends"; +const char PLUGIN_INSTALLERNAME[] = "InstallerName"; +const char PLUGIN_PACKAGES[] = "Packages"; class PluginMetaObject; class PluginMetaObjectPrivate @@ -56,6 +60,7 @@ class PluginMetaObjectPrivate QString error; PluginMetaObject::State state; QList depends; + QList installDepends; QSharedPointer plugin; QSharedPointer loader; QSharedPointer context; diff --git a/src/framework/listener/listener.h b/src/framework/listener/listener.h index bf28e73b7..92081b53d 100644 --- a/src/framework/listener/listener.h +++ b/src/framework/listener/listener.h @@ -12,7 +12,7 @@ DPF_BEGIN_NAMESPACE class ListenerPrivate; -class Listener final : public QObject +class DPF_EXPORT Listener final : public QObject { Q_OBJECT friend class ListenerPrivate; diff --git a/src/framework/log/codetimecheck.h b/src/framework/log/codetimecheck.h index 7eb8a02af..2961d7ad6 100644 --- a/src/framework/log/codetimecheck.h +++ b/src/framework/log/codetimecheck.h @@ -30,7 +30,7 @@ DPF_BEGIN_NAMESPACE * 代码埋点时间检查模块,可加编译参数进行屏蔽 * DPF_NO_CHECK_TIME (cmake -DDPF_NO_CHECK_TIME) */ -class CodeCheckTime final +class DPF_EXPORT CodeCheckTime final { public: explicit CodeCheckTime() = delete; diff --git a/src/framework/log/frameworklog.h b/src/framework/log/frameworklog.h index c8e31baa2..bd51a01b8 100644 --- a/src/framework/log/frameworklog.h +++ b/src/framework/log/frameworklog.h @@ -50,7 +50,7 @@ DPF_BEGIN_NAMESPACE * @brief The FrameworkLog class * 框架日志打印模块,内部封装输出重定向与日志格式化 */ -class FrameworkLog final +class DPF_EXPORT FrameworkLog final { public: explicit FrameworkLog() = delete; diff --git a/src/framework/log/logutils.h b/src/framework/log/logutils.h index 7b25b04b3..8326c0e51 100644 --- a/src/framework/log/logutils.h +++ b/src/framework/log/logutils.h @@ -12,7 +12,7 @@ DPF_BEGIN_NAMESPACE -class LogUtils final +class DPF_EXPORT LogUtils final { public: explicit LogUtils() = delete; diff --git a/src/framework/service/pluginservicecontext.h b/src/framework/service/pluginservicecontext.h index 3f6c8c8dc..463c17579 100644 --- a/src/framework/service/pluginservicecontext.h +++ b/src/framework/service/pluginservicecontext.h @@ -14,7 +14,7 @@ DPF_BEGIN_NAMESPACE -class PluginServiceContext final : public QObject, +class DPF_EXPORT PluginServiceContext final : public QObject, public QtClassFactory, public QtClassManager { @@ -41,7 +41,7 @@ class PluginServiceContext final : public QObject, // auto register all services template -class AutoServiceRegister +class DPF_EXPORT AutoServiceRegister { public: AutoServiceRegister() diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 785a0acf3..81359d82a 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -44,3 +44,4 @@ add_subdirectory(binarytools) add_subdirectory(commandproxy) add_subdirectory(codegeex) add_subdirectory(git) +add_subdirectory(linglong) diff --git a/src/plugins/binarytools/binarytools.json b/src/plugins/binarytools/binarytools.json index 89bd29ed8..076aa99fb 100644 --- a/src/plugins/binarytools/binarytools.json +++ b/src/plugins/binarytools/binarytools.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Utils", "Description" : "The binary compatibility tool plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=656d40c5bd766615b0b02e64&id=656d417abd766615b0b02e7e", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=656d40c5bd766615b0b02e64&id=656d417abd766615b0b02e7e", "Depends" : [ {"Name" : "core"} ] diff --git a/src/plugins/binarytools/configure/binarytoolsmanager.cpp b/src/plugins/binarytools/configure/binarytoolsmanager.cpp index 13b18b8b9..5a689c605 100644 --- a/src/plugins/binarytools/configure/binarytoolsmanager.cpp +++ b/src/plugins/binarytools/configure/binarytoolsmanager.cpp @@ -236,6 +236,9 @@ BinaryToolsManager::BinaryTools BinaryToolsManager::mergeTools(const BinaryTools QSharedPointer BinaryToolsManager::createToolProcess(const ToolInfo &tool) { + if (toolTaskMap.contains(tool.id)) + return nullptr; + using namespace std::placeholders; QSharedPointer toolProcess { new ToolProcess }; @@ -368,31 +371,27 @@ void BinaryToolsManager::executeTool(const QString &id) if (!checkCommandExists(tool.command)) return toolMissingHint(tool); - auto toolProcess = createToolProcess(tool); - toolProcess->setId(id); - toolProcess->setProgram(tool.command); - auto args = globalMacroExpander()->expandArguments(tool.arguments); - QStringList argList = args.split(" ", QString::SkipEmptyParts); - toolProcess->setArguments(argList); - auto workingDir = globalMacroExpander()->expand(tool.workingDirectory); - toolProcess->setWorkingDirectory(workingDir); - auto channelData = globalMacroExpander()->expand(tool.advSettings.channelData); - toolProcess->setChannelData(channelData); - QProcessEnvironment env; - auto iterator = tool.environment.begin(); - while (iterator != tool.environment.end()) { - env.insert(iterator.key(), iterator.value().toString()); - ++iterator; - } - toolProcess->setProcessEnvironment(env); - AppOutputPane::instance()->createApplicationPane(id, tool.name); auto stopHandler = std::bind(&BinaryToolsManager::stopTool, this, id); AppOutputPane::instance()->setStopHandler(id, stopHandler); - QString startMsg = tr("Start execute \"%1\": \"%2\" \"%3\" in workspace \"%4\".\n") - .arg(tool.name, tool.command, args, workingDir); - uiController.switchContext(tr("&Application Output")); + QString startMsg = tr("Start execute tool \"%1\".\n").arg(tool.name); printOutput(id, startMsg, OutputPane::NormalMessage); + uiController.switchContext(tr("&Application Output")); + + auto toolProcess = createToolProcess(tool); + if (!toolProcess) { + printOutput(id, tr("The tool is running. Please stop it before running.\n"), OutputPane::ErrorMessage); + return; + } + + QString errorMsg; + if (!checkAndSetProcessParams(toolProcess, tool, errorMsg)) { + printOutput(id, errorMsg, OutputPane::ErrorMessage); + stopTool(id); + toolTaskMap.remove(id); + AppOutputPane::instance()->setProcessFinished(id); + return; + } Q_EMIT execute(id); } @@ -445,8 +444,8 @@ void BinaryToolsManager::installTool(const QString &id) if (!terminalSrv) terminalSrv = dpfGetService(TerminalService); - uiController.switchContext(tr("&Console")); - terminalSrv->executeCommand(tool.advSettings.installCommand); + uiController.switchContext(TERMINAL_TAB_TEXT); + terminalSrv->sendCommand(tool.advSettings.installCommand); } void BinaryToolsManager::eventTriggered(EventType event, const QVariantList &args) @@ -640,3 +639,40 @@ void BinaryToolsManager::replaceCurrentDocument(const QString &id, int exitCode) if (!text.isEmpty()) editorSrv->setText(text); } + +bool BinaryToolsManager::checkAndSetProcessParams(QSharedPointer process, const ToolInfo &tool, QString &errorMsg) +{ + process->setId(tool.id); + process->setProgram(tool.command); + auto args = globalMacroExpander()->expandArguments(tool.arguments); + QStringList argList = args.split(" ", QString::SkipEmptyParts); + process->setArguments(argList); + + if (!tool.workingDirectory.isEmpty()) { + auto workingDir = globalMacroExpander()->expand(tool.workingDirectory); + if (workingDir.isEmpty()) { + errorMsg = tr("The tool has set the working directory, but the working directory parsing is empty. Please check and try again.\n"); + return false; + } + process->setWorkingDirectory(workingDir); + } + + if (!tool.advSettings.channelData.isEmpty()) { + auto channelData = globalMacroExpander()->expand(tool.advSettings.channelData); + if (channelData.isEmpty()) { + errorMsg = tr("The tool has set the channel data, but the channel data parsing is empty. Please check and try again.\n"); + return false; + } + process->setChannelData(channelData); + } + + QProcessEnvironment env; + auto iterator = tool.environment.begin(); + while (iterator != tool.environment.end()) { + env.insert(iterator.key(), iterator.value().toString()); + ++iterator; + } + process->setProcessEnvironment(env); + + return true; +} diff --git a/src/plugins/binarytools/configure/binarytoolsmanager.h b/src/plugins/binarytools/configure/binarytoolsmanager.h index a3762947d..4e5ab4792 100644 --- a/src/plugins/binarytools/configure/binarytoolsmanager.h +++ b/src/plugins/binarytools/configure/binarytoolsmanager.h @@ -101,6 +101,7 @@ private Q_SLOTS: void printOutput(const QString &id, const QString &content, OutputPane::OutputFormat format); void stopTool(const QString &id); void replaceCurrentDocument(const QString &id, int exitCode); + bool checkAndSetProcessParams(QSharedPointer process, const ToolInfo &tool, QString &errorMsg); private: QMap, QSharedPointer>> toolTaskMap; diff --git a/src/plugins/binarytools/configure/default_binarytools.json b/src/plugins/binarytools/configure/default_binarytools.json index 264cb0149..a71296bd9 100644 --- a/src/plugins/binarytools/configure/default_binarytools.json +++ b/src/plugins/binarytools/configure/default_binarytools.json @@ -18,8 +18,8 @@ "type": 0, "workingDirectory": "", "advance": { - "missingHint": "", - "installCommand": "", + "missingHint": "工具执行程序未找到,请安装后重试", + "installCommand": "sudo apt install dpkg-dev", "triggerEvent" : 0 } }, @@ -56,7 +56,7 @@ "type": 0, "workingDirectory": "", "advance": { - "missingHint": "", + "missingHint": "工具执行程序未找到,请通过浏览器访问“https://github.com/transifex”进行下载并配置", "installCommand": "", "triggerEvent" : 0 } @@ -75,7 +75,7 @@ "type": 0, "workingDirectory": "", "advance": { - "missingHint": "", + "missingHint": "工具执行程序未找到,请通过浏览器访问“https://github.com/transifex”进行下载并配置", "installCommand": "", "triggerEvent" : 0 } @@ -94,8 +94,8 @@ "type": 0, "workingDirectory": "", "advance": { - "missingHint": "", - "installCommand": "", + "missingHint": "工具执行程序未找到,请安装后重试", + "installCommand": "sudo apt install qtchooser", "triggerEvent" : 0 } }, @@ -113,8 +113,8 @@ "type": 0, "workingDirectory": "", "advance": { - "missingHint": "", - "installCommand": "", + "missingHint": "工具执行程序未找到,请安装后重试", + "installCommand": "sudo apt install xdg-utils", "triggerEvent" : 0 } }, @@ -165,7 +165,7 @@ "errorOutputOption": 1, "icon": "binarytools_flag", "id": "clang-format", - "name": "代码格式化", + "name": "C++代码格式化", "outputOption": 2, "type": 0, "workingDirectory": "", diff --git a/src/plugins/binarytools/mainframe/binarytoolsconfigview.cpp b/src/plugins/binarytools/mainframe/binarytoolsconfigview.cpp index f485b87ff..838889e57 100644 --- a/src/plugins/binarytools/mainframe/binarytoolsconfigview.cpp +++ b/src/plugins/binarytools/mainframe/binarytoolsconfigview.cpp @@ -411,7 +411,7 @@ void BinaryToolsConfigViewPrivate::handleRemove() void BinaryToolsConfigViewPrivate::handleSelectCommand() { QString dir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - QString filePath = DFileDialog::getOpenFileName(q, tr("Select Executabel Path"), dir); + QString filePath = DFileDialog::getOpenFileName(q, BinaryToolsConfigView::tr("Select Executabel Path"), dir); if (filePath.isEmpty() && !QFileInfo(filePath).exists()) return; @@ -421,7 +421,7 @@ void BinaryToolsConfigViewPrivate::handleSelectCommand() void BinaryToolsConfigViewPrivate::handleSelectWorkingDirectory() { QString dir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - QString filePath = DFileDialog::getExistingDirectory(q, tr("Select Working Directory"), dir); + QString filePath = DFileDialog::getExistingDirectory(q, BinaryToolsConfigView::tr("Select Working Directory"), dir); if (filePath.isEmpty() && !QFileInfo(filePath).exists()) return; diff --git a/src/plugins/binarytools/mainframe/environmentview.cpp b/src/plugins/binarytools/mainframe/environmentview.cpp index 6e060d092..ffbd84450 100644 --- a/src/plugins/binarytools/mainframe/environmentview.cpp +++ b/src/plugins/binarytools/mainframe/environmentview.cpp @@ -120,11 +120,12 @@ QModelIndex EnvironmentModel::append(const QString &key, const QVariant &value) if (index(row, 0).data() == key) return index(row, 0); } + return {}; } void EnvironmentModel::remove(QModelIndex &index) { - if (d->envs.keys().isEmpty() || index.row() < 0) + if (d->envs.keys().isEmpty() || index.row() < 0 || index.row() >= d->envs.count()) return; beginResetModel(); diff --git a/src/plugins/builder/builder.qrc b/src/plugins/builder/builder.qrc index 85ae0d938..4877ee361 100644 --- a/src/plugins/builder/builder.qrc +++ b/src/plugins/builder/builder.qrc @@ -5,5 +5,6 @@ texts/clearall_16px.svg texts/rebuild_16px.svg texts/filter_16px.svg + texts/clear_log_16px.svg diff --git a/src/plugins/builder/buildercore.json b/src/plugins/builder/buildercore.json index b1c399af6..2233b5b78 100644 --- a/src/plugins/builder/buildercore.json +++ b/src/plugins/builder/buildercore.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Core Plugins", "Description" : "The buildercore plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document2?dirid=656d40a9bd766615b0b02e5e", + "UrlLink" : "https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/builder/mainframe/buildmanager.cpp b/src/plugins/builder/mainframe/buildmanager.cpp index f51c716f3..88e9e4400 100644 --- a/src/plugins/builder/mainframe/buildmanager.cpp +++ b/src/plugins/builder/mainframe/buildmanager.cpp @@ -36,7 +36,7 @@ class BuildManagerPrivate { friend class BuildManager; - QAction* buildAction; + QAction* buildCancelAction; QAction* buildActionNoIcon; QAction* rebuildAction; QAction* cleanAction; @@ -111,8 +111,8 @@ void BuildManager::addMenu() return inputAction; }; - d->buildAction = new QAction(MWMBA_BUILD, this); - windowService->addTopToolItem(actionInit(d->buildAction, "Build.Build", + d->buildCancelAction = new QAction(MWMBA_BUILD, this); + windowService->addTopToolItem(actionInit(d->buildCancelAction, "Build.Build", QKeySequence(Qt::Modifier::CTRL | Qt::Key::Key_B), "build"), true, Priority::low); @@ -143,8 +143,8 @@ void BuildManager::addMenu() QKeySequence(Qt::Modifier::CTRL | Qt::Modifier::SHIFT | Qt::Key::Key_C)); - QObject::connect(d->buildAction, &QAction::triggered, - this, &BuildManager::buildProject, Qt::DirectConnection); + QObject::connect(d->buildCancelAction, &QAction::triggered, + this, &BuildManager::buildCancelProject, Qt::DirectConnection); QObject::connect(d->buildActionNoIcon, &QAction::triggered, this, &BuildManager::buildProject, Qt::DirectConnection); QObject::connect(d->rebuildAction, &QAction::triggered, @@ -169,7 +169,7 @@ void BuildManager::initCompileWidget() QSplitter *spl = new QSplitter(Qt::Horizontal); spl->addWidget(d->compileOutputWidget); spl->addWidget(d->issuesWidget); - spl->setHandleWidth(2); + spl->setHandleWidth(1); mainLayout->setSpacing(0); mainLayout->addWidget(spl); if (auto holder = createFindPlaceHolder()) @@ -219,10 +219,11 @@ void BuildManager::initIssueList() d->issuesWidget = new DWidget(d->compileWidget); QVBoxLayout *issuesListLayout = new QVBoxLayout(d->issuesWidget); - issuesListLayout->addWidget(issueTopWidget); + issuesListLayout->setSpacing(0); issuesListLayout->setContentsMargins(0, 0, 0, 0); + issuesListLayout->addWidget(issueTopWidget); + issuesListLayout->addWidget(new DHorizontalLine(d->issuesWidget)); issuesListLayout->addWidget(d->problemOutputPane); - issuesListLayout->setSpacing(2); connect(filterMenu, &DMenu::triggered, [=](QAction *action) { if (action == showAllAction) { @@ -263,11 +264,15 @@ void BuildManager::initCompileOutput() hOutputTopLayout->setContentsMargins(0, 0, 5, 0); hOutputTopLayout->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + auto createVLine = [this]{ + DVerticalLine *vLine = new DVerticalLine(d->compileWidget); + vLine->setFixedHeight(20); + return vLine; + }; + // init toolButton - DVerticalLine *vLine = new DVerticalLine(d->compileWidget); - vLine->setFixedHeight(20); hOutputTopLayout->addSpacing(10); - hOutputTopLayout->addWidget(vLine); + hOutputTopLayout->addWidget(createVLine()); hOutputTopLayout->addSpacing(10); auto btn = utils::createIconButton(d->cancelAction, d->compileWidget); @@ -280,6 +285,15 @@ void BuildManager::initCompileOutput() btn->setFixedSize(QSize(26, 26)); hOutputTopLayout->addWidget(btn); + DToolButton *clearLogBtn = new DToolButton(d->compileWidget); + clearLogBtn->setIconSize({ 16, 16 }); + clearLogBtn->setFixedSize({ 26, 26}); + clearLogBtn->setIcon(QIcon::fromTheme("clear_log")); + clearLogBtn->setToolTip(tr("Clear Output")); + connect(clearLogBtn, &DToolButton::clicked, d->compileOutputPane, &CompileOutputPane::clearContents); + hOutputTopLayout->addWidget(createVLine()); + hOutputTopLayout->addWidget(clearLogBtn); + DFrame *OutputTopWidget = new DFrame(d->compileWidget); DStyle::setFrameRadius(OutputTopWidget, 0); OutputTopWidget->setLineWidth(0); @@ -289,9 +303,10 @@ void BuildManager::initCompileOutput() d->compileOutputWidget = new DWidget(d->compileWidget); auto outputLayout = new QVBoxLayout(d->compileOutputWidget); outputLayout->setContentsMargins(0, 0, 0, 0); + outputLayout->setSpacing(0); outputLayout->addWidget(OutputTopWidget); + outputLayout->addWidget(new DHorizontalLine(d->compileOutputWidget)); outputLayout->addWidget(d->compileOutputPane); - outputLayout->setSpacing(2); } QWidget *BuildManager::createFindPlaceHolder() @@ -305,6 +320,14 @@ QWidget *BuildManager::createFindPlaceHolder() return windowService->createFindPlaceHolder(d->compileOutputPane, docFind); } +void BuildManager::buildCancelProject() +{ + if (d->currentState == BuildState::kBuilding) + cancelBuild(); + else + buildProject(); +} + void BuildManager::buildProject() { execBuildStep({Build}); @@ -395,18 +418,25 @@ void BuildManager::slotResetBuildUI() uiController.switchContext(tr("&Build")); } -void BuildManager::setActivedProjectInfo(const QString &kitName, const QString &workingDir) +void BuildManager::setActivatedProjectInfo(const QString &kitName, const QString &workingDir) { d->activedKitName = kitName; d->activedWorkingDir = workingDir; } -void BuildManager::clearActivedProjectInfo() +void BuildManager::clearActivatedProjectInfo() { d->activedKitName.clear(); d->activedWorkingDir.clear(); } +bool BuildManager::isActivatedProject(const ProjectInfo &info) +{ + if (info.kitName() == d->activedKitName && info.workspaceFolder() == d->activedWorkingDir) + return true; + return false; +} + bool BuildManager::handleCommand(const QList &commandInfo, bool isSynchronous) { if(!canStartBuild()) { @@ -579,14 +609,18 @@ void BuildManager::slotBuildState(const BuildState &buildState) switch (buildState) { case BuildState::kNoBuild: case BuildState::kBuildFailed: - d->buildAction->setEnabled(true); + d->buildCancelAction->setIcon(QIcon::fromTheme("build")); + d->buildCancelAction->setText(MWMBA_BUILD); + d->buildCancelAction->setShortcut(ActionManager::getInstance()->command("Build.Build")->keySequence()); d->buildActionNoIcon->setEnabled(true); d->rebuildAction->setEnabled(true); d->cleanAction->setEnabled(true); d->cancelAction->setEnabled(false); break; case BuildState::kBuilding: - d->buildAction->setEnabled(false); + d->buildCancelAction->setIcon(QIcon::fromTheme("cancel")); + d->buildCancelAction->setText(MWMBA_CANCEL); + d->buildCancelAction->setShortcut(d->cancelAction->shortcut()); d->buildActionNoIcon->setEnabled(true); d->rebuildAction->setEnabled(false); d->cleanAction->setEnabled(false); diff --git a/src/plugins/builder/mainframe/buildmanager.h b/src/plugins/builder/mainframe/buildmanager.h index e48277c44..6968491e8 100644 --- a/src/plugins/builder/mainframe/buildmanager.h +++ b/src/plugins/builder/mainframe/buildmanager.h @@ -33,8 +33,9 @@ class BuildManager : public QObject ProblemOutputPane *getProblemOutputPane() const; DTK_WIDGET_NAMESPACE::DWidget *getCompileWidget() const; - void setActivedProjectInfo(const QString &kitName, const QString &workingDir); - void clearActivedProjectInfo(); + void setActivatedProjectInfo(const QString &kitName, const QString &workingDir); + void clearActivatedProjectInfo(); + bool isActivatedProject(const dpfservice::ProjectInfo &info); bool handleCommand(const QList &info, bool isSynchronous); @@ -49,6 +50,7 @@ public slots: void slotOutputCompileInfo(const QString &content, const OutputPane::OutputFormat format); void slotOutputProblemInfo(const QString &content); void addOutput(const QString &content, const OutputPane::OutputFormat format); + void buildCancelProject(); void buildProject(); void rebuildProject(); void cleanProject(); diff --git a/src/plugins/builder/mainframe/compileoutputpane.cpp b/src/plugins/builder/mainframe/compileoutputpane.cpp index ea139a735..2bb4a09b7 100644 --- a/src/plugins/builder/mainframe/compileoutputpane.cpp +++ b/src/plugins/builder/mainframe/compileoutputpane.cpp @@ -8,6 +8,8 @@ CompileOutputPane::CompileOutputPane(QWidget *parent) : OutputPane(parent) { + setAutoFillBackground(true); + setBackgroundRole(DPalette::Base); } // Add more future here when you need. diff --git a/src/plugins/builder/mainframe/problemoutputpane.cpp b/src/plugins/builder/mainframe/problemoutputpane.cpp index 69f71ac65..16720876a 100644 --- a/src/plugins/builder/mainframe/problemoutputpane.cpp +++ b/src/plugins/builder/mainframe/problemoutputpane.cpp @@ -14,7 +14,7 @@ ProblemOutputPane::ProblemOutputPane(QWidget *parent) : DWidget (parent) { QVBoxLayout *layout = new QVBoxLayout(this); - layout->setContentsMargins(1, 1, 1, 1); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(TaskManager::instance()->getView()); } diff --git a/src/plugins/builder/texts/clear_log_16px.svg b/src/plugins/builder/texts/clear_log_16px.svg new file mode 100644 index 000000000..b685950fe --- /dev/null +++ b/src/plugins/builder/texts/clear_log_16px.svg @@ -0,0 +1,11 @@ + + + ICON / action /delete + + + + + + + + \ No newline at end of file diff --git a/src/plugins/builder/transceiver/builderreceiver.cpp b/src/plugins/builder/transceiver/builderreceiver.cpp index 164b93870..24c7e43e9 100644 --- a/src/plugins/builder/transceiver/builderreceiver.cpp +++ b/src/plugins/builder/transceiver/builderreceiver.cpp @@ -25,18 +25,22 @@ QStringList BuilderReceiver::topics() void BuilderReceiver::eventProcess(const dpf::Event &event) { - if (event.data() == project.activedProject.name) { - QVariant proInfoVar = event.property(project.activedProject.pKeys[0]); + if (event.data() == project.activatedProject.name) { + QVariant proInfoVar = event.property(project.activatedProject.pKeys[0]); dpfservice::ProjectInfo projectInfo = qvariant_cast(proInfoVar); - BuildManager::instance()->setActivedProjectInfo(projectInfo.kitName(), projectInfo.workspaceFolder()); + BuildManager::instance()->setActivatedProjectInfo(projectInfo.kitName(), projectInfo.workspaceFolder()); } else if (event.data() == project.createdProject.name) { QVariant proInfoVar = event.property(project.createdProject.pKeys[0]); dpfservice::ProjectInfo projectInfo = qvariant_cast(proInfoVar); - BuildManager::instance()->setActivedProjectInfo(projectInfo.kitName(), projectInfo.workspaceFolder()); + BuildManager::instance()->setActivatedProjectInfo(projectInfo.kitName(), projectInfo.workspaceFolder()); } else if (event.data() == project.deletedProject.name) { + auto builder = BuildManager::instance(); QVariant proInfoVar = event.property(project.deletedProject.pKeys[0]); dpfservice::ProjectInfo projectInfo = qvariant_cast(proInfoVar); - BuildManager::instance()->clearActivedProjectInfo(); + if (builder->isActivatedProject(projectInfo)) { + builder->clearActivatedProjectInfo(); + builder->cancelBuild(); + } } else if (event.data() == symbol.parseDone.name) { bool bSuccess = event.property("success").toBool(); if(!bSuccess) { diff --git a/src/plugins/codeeditor/codeeditor.cpp b/src/plugins/codeeditor/codeeditor.cpp index fc00894ec..d2d95796b 100644 --- a/src/plugins/codeeditor/codeeditor.cpp +++ b/src/plugins/codeeditor/codeeditor.cpp @@ -10,6 +10,8 @@ #include "gui/settings/editorsettingswidget.h" #include "lexer/lexermanager.h" #include "utils/editorutils.h" +#include "status/statusinfomanager.h" +#include "symbol/symbollocator.h" #include "base/abstractmenu.h" #include "base/abstractaction.h" @@ -21,6 +23,7 @@ #include "services/editor/editorservice.h" #include "services/option/optionservice.h" #include "services/option/optiondatastruct.h" +#include "services/locator/locatorservice.h" #include #include @@ -62,6 +65,7 @@ bool CodeEditor::start() initButtonBox(); initEditorService(); initOptionService(); + initLocator(); registerVariables(); return true; @@ -101,6 +105,7 @@ void CodeEditor::initButtonBox() layout->addWidget(backBtn); layout->addWidget(forwardBtn); layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); windowService->addWidgetToTopTool(new AbstractWidget(btnWidget), false, false, Priority::low); } @@ -166,6 +171,10 @@ void CodeEditor::initEditorService() editorService->registerWidget = std::bind(&WorkspaceWidget::registerWidget, workspaceWidget, _1, _2); editorService->switchWidget = std::bind(&WorkspaceWidget::switchWidget, workspaceWidget, _1); editorService->switchDefaultWidget = std::bind(&WorkspaceWidget::switchDefaultWidget, workspaceWidget); + editorService->openedFiles = std::bind(&WorkspaceWidget::openedFiles, workspaceWidget); + editorService->fileText = std::bind(&WorkspaceWidget::fileText, workspaceWidget, _1); + editorService->replaceAll = std::bind(&WorkspaceWidget::replaceAll, workspaceWidget, _1, _2, _3, _4, _5); + editorService->replaceRange = std::bind(&WorkspaceWidget::replaceRange, workspaceWidget, _1, _2, _3, _4, _5); LexerManager::instance()->init(editorService); } @@ -190,6 +199,8 @@ void CodeEditor::initWindowService() windowService->addAction(MWM_FILE, new AbstractAction(sep)); windowService->addContextWidget(QTabWidget::tr("Search &Results"), new AbstractWidget(CodeLens::instance()), true); + + StatusInfoManager::instance()->init(windowService); } } @@ -204,6 +215,17 @@ void CodeEditor::initOptionService() optionService->implGenerator(option::GROUP_GENERAL, EditorSettingsWidgetGenerator::kitName()); } +void CodeEditor::initLocator() +{ + auto locatorSrv = dpfGetService(LocatorService); + if (!locatorSrv) + return; + + SymbolLocator *locator = new SymbolLocator(workspaceWidget); + locator->setWorkspaceWidget(workspaceWidget); + locatorSrv->registerLocator(locator); +} + void CodeEditor::registerVariables() { globalMacroExpander()->registerFileVariables("CurrentDocument", diff --git a/src/plugins/codeeditor/codeeditor.h b/src/plugins/codeeditor/codeeditor.h index a055b1269..85f262a8c 100644 --- a/src/plugins/codeeditor/codeeditor.h +++ b/src/plugins/codeeditor/codeeditor.h @@ -27,6 +27,7 @@ class CodeEditor : public dpf::Plugin void initEditorService(); void initWindowService(); void initOptionService(); + void initLocator(); void registerVariables(); private: diff --git a/src/plugins/codeeditor/codeeditor.json b/src/plugins/codeeditor/codeeditor.json index 3946e1f0d..633e6befb 100644 --- a/src/plugins/codeeditor/codeeditor.json +++ b/src/plugins/codeeditor/codeeditor.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Core Plugins", "Description" : "The core plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=656d40c5bd766615b0b02e64&id=656d415bbd766615b0b02e78", + "UrlLink" : "https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e", "Depends" : [ {"Name" : "core"} ] diff --git a/src/plugins/codeeditor/codelens/codelens.cpp b/src/plugins/codeeditor/codelens/codelens.cpp index 51a792ce9..3659c63c8 100644 --- a/src/plugins/codeeditor/codelens/codelens.cpp +++ b/src/plugins/codeeditor/codelens/codelens.cpp @@ -4,6 +4,7 @@ #include "codelens.h" #include "codelenstree.h" +#include "transceiver/codeeditorreceiver.h" #include @@ -44,8 +45,7 @@ CodeLens::CodeLens(QWidget *parent) DStyle::setFrameRadius(mainFrame, 0); setLayout(mainLayout); - - QObject::connect(d->lens, &CodeLensTree::doubleClicked, this, &CodeLens::doubleClicked); + connect(d->lens, &CodeLensTree::doubleClicked, EditorCallProxy::instance(), &EditorCallProxy::reqGotoPosition); } CodeLens::~CodeLens() diff --git a/src/plugins/codeeditor/codelens/codelens.h b/src/plugins/codeeditor/codelens/codelens.h index f1045374c..d5d0f82a2 100644 --- a/src/plugins/codeeditor/codelens/codelens.h +++ b/src/plugins/codeeditor/codelens/codelens.h @@ -18,9 +18,6 @@ class CodeLens : public QWidget explicit CodeLens(QWidget *parent = nullptr); virtual ~CodeLens(); void displayReference(const lsp::References &data); - -signals: - void doubleClicked(const QString &filePath, const lsp::Range range); }; #endif // REFACTORWIDGET_H diff --git a/src/plugins/codeeditor/codelens/codelensdelegate.cpp b/src/plugins/codeeditor/codelens/codelensdelegate.cpp index 063a07a07..9d0d6676e 100644 --- a/src/plugins/codeeditor/codelens/codelensdelegate.cpp +++ b/src/plugins/codeeditor/codelens/codelensdelegate.cpp @@ -5,20 +5,46 @@ #include "codelensdelegate.h" #include "codelenstype.h" +#include #include +const int LinePadding = 4; +const int LineDigits = 6; + +DGUI_USE_NAMESPACE + +static std::pair lineNumberInfo(const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + int line = index.data(CodeLensItemRole::LineRole).toInt(); + if (line < 1) + return { 0, {} }; + + const QString lineText = QString::number(line + 1); + const int lineDigits = qMax(LineDigits, lineText.size()); + const int fontWidth = option.fontMetrics.horizontalAdvance(QString(lineDigits, QLatin1Char('0'))); + const QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + return { LinePadding + fontWidth + LinePadding + style->pixelMetric(QStyle::PM_FocusFrameHMargin), lineText }; +} + +static QString itemText(const QModelIndex &index) +{ + QString text = index.data(Qt::DisplayRole).toString(); + if (index.model()->hasChildren(index)) + text += " (" + QString::number(index.model()->rowCount(index)) + ')'; + + return text; +} + CodeLensDelegate::CodeLensDelegate(QObject *parent) - : QStyledItemDelegate (parent) - , characterStart(-1) - , characterEnd(-1) - ,color(QColor(Qt::yellow)) + : QItemDelegate(parent) { - color.setAlpha(95); + tabStr = QString(8, ' '); } QSize CodeLensDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - QSize size = QStyledItemDelegate::sizeHint(option, index); + QSize size = QItemDelegate::sizeHint(option, index); size.setHeight(24); return size; } @@ -26,76 +52,158 @@ QSize CodeLensDelegate::sizeHint(const QStyleOptionViewItem &option, const QMode void CodeLensDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - QString displayName = index.data().toString(); //从模型索引index中获取显示的文本。 - QFont font = painter->font(); //获取画笔的字体,并创建一个字体度量对象,用于后续计算文本的尺寸 - QFontMetrics fontMetrics(font); //使用字体创建一个字体度量对象,用于计算文本的布局信息。 - - if (index.parent().isValid()){ //如果是root item,就按默认绘制即可 - // 获取数据用于绘制 - lsp::Range range; - QString codeText; - QColor heightColor; - QVariant rangeVar = index.data(CodeLensItemRole::Range); - - // 从一个可能包含不同数据类型的 QVariant 对象中提取出数据 - if (rangeVar.canConvert()) { - range = rangeVar.value(); - } - QVariant codeTextVar = index.data(CodeLensItemRole::CodeText); - if (codeTextVar.canConvert()) { - codeText = codeTextVar.value(); - } - QVariant colorVar = index.data(CodeLensItemRole::HeightColor); - if (colorVar.canConvert()) { - heightColor = colorVar.value(); - } - - // 获取当前行号并计算相关尺寸 - QString lineNumCurr = QString::number(range.start.line + 1); // 展示时line从1开始 - int sepWidth = option.fontMetrics.horizontalAdvance(" "); // 计算一个空格字符的宽度 - int lineNumMaxWidth = option.fontMetrics.horizontalAdvance(QString("99999")); // 计算最大行号宽度 - int lineNumCurrWidth = option.fontMetrics.horizontalAdvance(lineNumCurr); // 计算当前行号字符串的宽度 - int lineNumOffset = lineNumMaxWidth - lineNumCurrWidth; // 计算行号的偏移量,对齐 - int textHeight = 24; // 设置每行的绘制高度为24 - - // 开始绘制行号 - QRect lineNumDrawRect = option.rect.adjusted(lineNumOffset, 0, 0, 0); - lineNumDrawRect.setHeight(textHeight); - painter->setPen(option.widget->palette().text().color()); - painter->drawText(lineNumDrawRect, lineNumCurr); - - // 计算高亮文本的位置和大小 - int startCharacter = range.start.character; - int endCharacter = range.end.character; - QString heightText = codeText.mid(startCharacter, endCharacter - startCharacter); - QString frontText = codeText.mid(0, startCharacter); - int frontTextWidth = option.fontMetrics.horizontalAdvance(frontText); - int heightTextWidth = option.fontMetrics.horizontalAdvance(heightText); - QRect heightTextRect = option.rect.adjusted(sepWidth + lineNumMaxWidth + frontTextWidth, 0, 0, 0); - heightTextRect.setSize(QSize(heightTextWidth, textHeight)); - - // 绘制高亮文本的背景 - painter->setBrush(CodeLensDelegate::color); - painter->setPen(Qt::NoPen); - painter->drawRect(heightTextRect); - - // 绘制全部代码文本 - painter->setPen(option.widget->palette().text().color()); - QRect codeTextDrawRect = option.rect.adjusted(sepWidth + lineNumMaxWidth, 0, 0, 0); - codeTextDrawRect.setHeight(textHeight); - painter->drawText(codeTextDrawRect, codeText); - } else { - QStyledItemDelegate::paint(painter, option, index); + painter->save(); + const LayoutInfo info = getLayoutInfo(option, index); + painter->setFont(info.option.font); + QItemDelegate::drawBackground(painter, info.option, index); + + // line numbers + drawLineNumber(painter, info.option, info.lineNumberRect, index); + + // text + drawText(painter, info.option, info.textRect, index); + QItemDelegate::drawFocus(painter, info.option, info.option.rect); + painter->restore(); +} + +LayoutInfo CodeLensDelegate::getLayoutInfo(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + auto op = option; + if (!option.state.testFlag(QStyle::State_HasFocus)) { + QColor color; + DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::LightType + ? color = Qt::black + : color = QColor("#c5c8c9"); + op.palette.setColor(QPalette::Text, color); } + + LayoutInfo info; + info.option = setOptions(index, op); + + // text + info.textRect = info.option.rect; + + // do basic layout + QRect checkRect, iconRect; + doLayout(info.option, &checkRect, &iconRect, &info.textRect, false); + + // adapt for line numbers + const int lineNumberWidth = lineNumberInfo(info.option, index).first; + info.lineNumberRect = info.textRect; + info.lineNumberRect.setWidth(lineNumberWidth); + info.textRect.adjust(lineNumberWidth, 0, 0, 0); + return info; } -void CodeLensDelegate::setHeightColor(const QColor &color) +int CodeLensDelegate::drawLineNumber(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QModelIndex &index) const { - CodeLensDelegate::color = color; + const bool isSelected = option.state & QStyle::State_Selected; + const std::pair numberInfo = lineNumberInfo(option, index); + if (numberInfo.first == 0) + return 0; + + QRect lineNumberAreaRect(rect); + lineNumberAreaRect.setWidth(numberInfo.first); + + QPalette::ColorGroup cg = QPalette::Normal; + if (!(option.state & QStyle::State_Active)) + cg = QPalette::Inactive; + else if (!(option.state & QStyle::State_Enabled)) + cg = QPalette::Disabled; + + painter->fillRect(lineNumberAreaRect, + QBrush(isSelected + ? option.palette.brush(cg, QPalette::Highlight) + : option.palette.color(cg, QPalette::Base).darker(111))); + + QStyleOptionViewItem opt = option; + opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter; + opt.palette.setColor(cg, QPalette::Text, Qt::darkGray); + + const QStyle *style = QApplication::style(); + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, nullptr) + 1; + + const QRect rowRect = lineNumberAreaRect.adjusted(-textMargin, 0, textMargin - LinePadding, 0); + QItemDelegate::drawDisplay(painter, opt, rowRect, numberInfo.second); + + return numberInfo.first; } -void CodeLensDelegate::setHeightRange(int characterStart, int characterEnd) +void CodeLensDelegate::drawText(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QModelIndex &index) const { - CodeLensDelegate::characterStart = characterStart; - CodeLensDelegate::characterEnd = characterEnd; + const auto text = itemText(index); + const int termStart = index.data(CodeLensItemRole::TermStartRole).toInt(); + const auto termEnd = index.data(CodeLensItemRole::TermEndRole).toInt(); + int termLength = termEnd - termStart; + if (termStart < 0 || termStart >= text.length() || termLength < 1) { + QItemDelegate::drawDisplay(painter, + option, + rect, + QString(text).replace(QLatin1Char('\t'), tabStr)); + return; + } + + // clip searchTermLength to end of line + termLength = qMin(termLength, text.length() - termStart); + const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; + const QString textBefore = text.left(termStart).replace(QLatin1Char('\t'), tabStr); + const QString textHighlight = text.mid(termStart, termLength).replace(QLatin1Char('\t'), tabStr); + const QString textAfter = text.mid(termStart + termLength).replace(QLatin1Char('\t'), tabStr); + int searchTermStartPixels = option.fontMetrics.horizontalAdvance(textBefore); + int searchTermLengthPixels = option.fontMetrics.horizontalAdvance(textHighlight); + int textAfterLengthPixels = option.fontMetrics.horizontalAdvance(textAfter); + + // rects + QRect beforeHighlightRect(rect); + beforeHighlightRect.setRight(beforeHighlightRect.left() + searchTermStartPixels); + + QRect resultHighlightRect(rect); + resultHighlightRect.setLeft(beforeHighlightRect.right()); + resultHighlightRect.setRight(resultHighlightRect.left() + searchTermLengthPixels); + + QRect afterHighlightRect(rect); + afterHighlightRect.setLeft(resultHighlightRect.right()); + afterHighlightRect.setRight(afterHighlightRect.left() + textAfterLengthPixels); + + // paint all highlight backgrounds + bool isSelected = option.state & QStyle::State_Selected; + QPalette::ColorGroup cg = option.state & QStyle::State_Enabled + ? QPalette::Normal + : QPalette::Disabled; + if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) + cg = QPalette::Inactive; + QStyleOptionViewItem baseOption = option; + baseOption.state &= ~QStyle::State_Selected; + if (isSelected) { + painter->fillRect(beforeHighlightRect.adjusted(textMargin, 0, textMargin, 0), + option.palette.brush(cg, QPalette::Highlight)); + painter->fillRect(afterHighlightRect.adjusted(textMargin, 0, textMargin, 0), + option.palette.brush(cg, QPalette::Highlight)); + } + + QColor highlightBackground("#ffef0b"); + if (DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::DarkType) + highlightBackground = QColor("#8a7f2c"); + painter->fillRect(resultHighlightRect.adjusted(textMargin, 0, textMargin - 1, 0), + QBrush(highlightBackground)); + + // Text before the highlighting + QColor textColor = baseOption.palette.color(QPalette::Text); + QStyleOptionViewItem noHighlightOpt = baseOption; + noHighlightOpt.rect = beforeHighlightRect; + noHighlightOpt.textElideMode = Qt::ElideNone; + if (isSelected) + noHighlightOpt.palette.setColor(QPalette::Text, noHighlightOpt.palette.color(cg, QPalette::HighlightedText)); + QItemDelegate::drawDisplay(painter, noHighlightOpt, beforeHighlightRect, textBefore); + + // Highlight text + QStyleOptionViewItem highlightOpt = noHighlightOpt; + highlightOpt.palette.setColor(QPalette::Text, textColor); + QItemDelegate::drawDisplay(painter, highlightOpt, resultHighlightRect, textHighlight); + + // Text after the Highlight + noHighlightOpt.rect = afterHighlightRect; + QItemDelegate::drawDisplay(painter, noHighlightOpt, afterHighlightRect, textAfter); } diff --git a/src/plugins/codeeditor/codelens/codelensdelegate.h b/src/plugins/codeeditor/codelens/codelensdelegate.h index 03dae3468..d175a7bfc 100644 --- a/src/plugins/codeeditor/codelens/codelensdelegate.h +++ b/src/plugins/codeeditor/codelens/codelensdelegate.h @@ -7,23 +7,32 @@ #include "common/common.h" -#include -#include +#include -class CodeLensDelegate : public QStyledItemDelegate +struct LayoutInfo +{ + QRect textRect; + QRect lineNumberRect; + QStyleOptionViewItem option; +}; + +class CodeLensDelegate : public QItemDelegate { Q_OBJECT - int characterStart; - int characterEnd; - QColor color; public: explicit CodeLensDelegate(QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - void setHeightColor(const QColor &color); - void setHeightRange(int characterStart, int characterEnd); QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; +private: + LayoutInfo getLayoutInfo(const QStyleOptionViewItem &option, const QModelIndex &index) const; + int drawLineNumber(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QModelIndex &index) const; + void drawText(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QModelIndex &index) const; + +private: + QString tabStr; }; -#endif // CODELENSDELEGATE_H +#endif // CODELENSDELEGATE_H diff --git a/src/plugins/codeeditor/codelens/codelenstree.cpp b/src/plugins/codeeditor/codelens/codelenstree.cpp index 77740edc3..746696f55 100644 --- a/src/plugins/codeeditor/codelens/codelenstree.cpp +++ b/src/plugins/codeeditor/codelens/codelenstree.cpp @@ -5,28 +5,16 @@ #include "codelenstree.h" #include "framework.h" #include "codelensdelegate.h" + +#include "services/editor/editorservice.h" + #include #include -QByteArray readLine(const QString &filePath, int line) -{ - QByteArray array{}; - QFile file(filePath); - if(!file.open(QFile::ReadOnly)) { - return array; - } - - for(int i = 0; i <= line; i++) { - array = file.readLine(); - if (i == line) - return array; - } - file.close(); - return array; -} +using namespace dpfservice; CodeLensTree::CodeLensTree(QWidget *parent) - : QTreeView (parent) + : QTreeView(parent) { setModel(new QStandardItemModel(this)); setEnabled(true); @@ -35,21 +23,18 @@ CodeLensTree::CodeLensTree(QWidget *parent) setHeaderHidden(true); setLineWidth(0); - QObject::connect(this, &QTreeView::doubleClicked, [=](const QModelIndex &index){ - if (!index.parent().isValid()) { //root return + QObject::connect(this, &QTreeView::doubleClicked, [=](const QModelIndex &index) { + if (!index.parent().isValid()) //root return return; - } - QVariant rangeVar = index.data(CodeLensItemRole::Range); - lsp::Range range; - if (rangeVar.canConvert()){ - range = rangeVar.value(); - } + + int line = index.data(CodeLensItemRole::LineRole).toInt(); + int column = index.data(CodeLensItemRole::TermStartRole).toInt(); QModelIndex parentIndex = index; while (parentIndex.parent().isValid()) { parentIndex = index.parent(); } QString filePath = parentIndex.data(Qt::DisplayRole).toString(); - emit CodeLensTree::doubleClicked(filePath, range); + emit CodeLensTree::doubleClicked(filePath, line, column); }); } @@ -60,31 +45,78 @@ QString codeDataFormat(int line, const QString &codeText) void CodeLensTree::setData(const lsp::References &refs) { - auto model = qobject_cast(CodeLensTree::model()); + onceReadFlag = false; + auto model = qobject_cast(CodeLensTree::model()); model->clear(); - QHash cache{}; - for(auto ref : refs) { - QString file = ref.fileUrl.toLocalFile(); + QHash cache {}; + for (auto ref : refs) { lsp::Range range = ref.range; - if (range.start.line == range.end.line) { - QString filePath = ref.fileUrl.toLocalFile(); - QStandardItem *fileItem = nullptr; - if (cache[filePath]) { - fileItem = cache[filePath]; - } else { - fileItem = new QStandardItem(filePath); - cache[filePath] = fileItem; - model->appendRow(fileItem); - } - QString codeText = readLine(file, range.start.line); - QString displayText = codeDataFormat(range.start.line, codeText); - QColor hColor(Qt::yellow); - QStandardItem *codeChild = new QStandardItem(displayText); - codeChild->setData(QVariant::fromValue(range), CodeLensItemRole::Range); - codeChild->setData(QVariant::fromValue(codeText), CodeLensItemRole::CodeText); - codeChild->setData(QVariant::fromValue(hColor), CodeLensItemRole::HeightColor); - codeChild->setTextAlignment(Qt::AlignVCenter); - fileItem->appendRow(codeChild); + if (range.start.line != range.end.line) + continue; + + QString filePath = ref.fileUrl.toLocalFile(); + QStandardItem *fileItem = nullptr; + if (cache.contains(filePath)) { + fileItem = cache[filePath]; + } else { + fileItem = new QStandardItem(filePath); + cache[filePath] = fileItem; + model->appendRow(fileItem); } + + QString text = readLine(filePath, range.start.line); + if (text.isEmpty()) + continue; + + QStandardItem *item = new QStandardItem(text); + item->setData(range.start.line, CodeLensItemRole::LineRole); + item->setData(range.start.character, CodeLensItemRole::TermStartRole); + item->setData(range.end.character, CodeLensItemRole::TermEndRole); + item->setTextAlignment(Qt::AlignVCenter); + fileItem->appendRow(item); + } +} + +QString CodeLensTree::readLine(const QString &filePath, int line) +{ + if (line < 0) + return {}; + + if (!editSrv) + editSrv = dpfGetService(EditorService); + + static QStringList openedFileList; + if (!onceReadFlag) { + openedFileList = editSrv->openedFiles(); + onceReadFlag = true; + } + + if (!openedFileList.contains(filePath)) + return readFileLine(filePath, line); + + auto fileText = editSrv->fileText(filePath); + auto textLines = fileText.split('\n'); + if (textLines.size() > line) + return textLines.at(line); + + return {}; +} + +QString CodeLensTree::readFileLine(const QString &filePath, int line) +{ + QFile file(filePath); + if (!file.open(QFile::ReadOnly | QFile::Text)) + return {}; + + QTextStream in(&file); + int lineCount = 0; + while (!in.atEnd()) { + QString text = in.readLine(); + if (line == lineCount) + return text; + lineCount++; } + + file.close(); + return {}; } diff --git a/src/plugins/codeeditor/codelens/codelenstree.h b/src/plugins/codeeditor/codelens/codelenstree.h index f35f6bd2b..b048a508d 100644 --- a/src/plugins/codeeditor/codelens/codelenstree.h +++ b/src/plugins/codeeditor/codelens/codelenstree.h @@ -11,6 +11,10 @@ #include +namespace dpfservice { +class EditorService; +} + class CodeLensTree : public QTreeView { Q_OBJECT @@ -19,6 +23,13 @@ class CodeLensTree : public QTreeView void setData(const lsp::References &refs); signals: - void doubleClicked(const QString &filePath, const lsp::Range &range); + void doubleClicked(const QString &filePath, int line, int column); + +private: + QString readLine(const QString &filePath, int line); + QString readFileLine(const QString &filePath, int line); + + dpfservice::EditorService *editSrv { nullptr }; + bool onceReadFlag { false }; }; -#endif // CODELENS_H +#endif // CODELENS_H diff --git a/src/plugins/codeeditor/codelens/codelenstype.h b/src/plugins/codeeditor/codelens/codelenstype.h index 8c899f32d..3b09b22aa 100644 --- a/src/plugins/codeeditor/codelens/codelenstype.h +++ b/src/plugins/codeeditor/codelens/codelenstype.h @@ -9,9 +9,9 @@ enum CodeLensItemRole { - Range = Qt::ItemDataRole::UserRole + 1, - CodeText, - HeightColor + LineRole = Qt::ItemDataRole::UserRole + 1, + TermStartRole, + TermEndRole }; diff --git a/src/plugins/codeeditor/find/editordocumentfind.cpp b/src/plugins/codeeditor/find/editordocumentfind.cpp index 8a59e11e4..3cac5e45a 100644 --- a/src/plugins/codeeditor/find/editordocumentfind.cpp +++ b/src/plugins/codeeditor/find/editordocumentfind.cpp @@ -30,7 +30,8 @@ class EditorDocumentFindPrivate void adjustFindStartPosition(TextEditor *editor); void dealWithZeroFound(TextEditor *editor); bool findStep(const QString &text, bool isForward); - void doReplaceAll(TextEditor *editor, const QString &findText, const QString &replaceText); + void doReplaceAll(TextEditor *editor, const QString &findText, + const QString &replaceText, bool caseSensitive = false, bool wholeWords = false); int buildSearchFlags(bool re, bool cs, bool wo, bool wrap, bool forward, FindNextType findNextType, bool posix, bool cxx11); public: @@ -115,13 +116,14 @@ bool EditorDocumentFindPrivate::findStep(const QString &text, bool isForward) return ret; } -void EditorDocumentFindPrivate::doReplaceAll(TextEditor *editor, const QString &findText, const QString &replaceText) +void EditorDocumentFindPrivate::doReplaceAll(TextEditor *editor, const QString &findText, + const QString &replaceText, bool caseSensitive, bool wholeWords) { int srcPosition = editor->cursorPosition(); int firstDisLineNum = editor->SendScintilla(TextEditor::SCI_GETFIRSTVISIBLELINE); editor->beginUndoAction(); - int flags = buildSearchFlags(false, false, false, false, true, FINDNEXTTYPE_REPLACENEXT, 0, 0); + int flags = buildSearchFlags(false, caseSensitive, wholeWords, false, true, FINDNEXTTYPE_REPLACENEXT, 0, 0); editor->SendScintilla(TextEditor::SCI_SETSEARCHFLAGS, flags); FindReplaceInfo findReplaceInfo; @@ -192,6 +194,11 @@ EditorDocumentFind::EditorDocumentFind(QObject *parent) { } +EditorDocumentFind::~EditorDocumentFind() +{ + delete d; +} + QString EditorDocumentFind::findString() const { auto w = d->autoAdjustCurrentEditor(); @@ -266,3 +273,16 @@ void EditorDocumentFind::findStringChanged() { d->isFindFirst = true; } + +void EditorDocumentFind::replaceAll(TextEditor *editor, const QString &before, + const QString &after, bool caseSensitive, bool wholeWords) +{ + if (before.isEmpty()) + return; + + if (!editor || editor->isReadOnly()) + return; + + d->doReplaceAll(editor, before, after, caseSensitive, wholeWords); + d->isFindFirst = true; +} diff --git a/src/plugins/codeeditor/find/editordocumentfind.h b/src/plugins/codeeditor/find/editordocumentfind.h index 5ebf71e7a..b778138fb 100644 --- a/src/plugins/codeeditor/find/editordocumentfind.h +++ b/src/plugins/codeeditor/find/editordocumentfind.h @@ -7,12 +7,14 @@ #include "common/find/abstractdocumentfind.h" +class TextEditor; class EditorDocumentFindPrivate; class EditorDocumentFind : public AbstractDocumentFind { Q_OBJECT public: explicit EditorDocumentFind(QObject *parent = nullptr); + ~EditorDocumentFind(); virtual QString findString() const override; virtual void findNext(const QString &txt) override; @@ -22,8 +24,11 @@ class EditorDocumentFind : public AbstractDocumentFind virtual void replaceAll(const QString &before, const QString &after) override; virtual void findStringChanged() override; + void replaceAll(TextEditor *editor, const QString &before, + const QString &after, bool caseSensitive, bool wholeWords); + private: EditorDocumentFindPrivate *const d; }; -#endif // EDITORDOCUMENTFIND_H +#endif // EDITORDOCUMENTFIND_H diff --git a/src/plugins/codeeditor/gui/completion/codecompletiondelegate.cpp b/src/plugins/codeeditor/gui/completion/codecompletiondelegate.cpp index faabd72bb..a68f23324 100644 --- a/src/plugins/codeeditor/gui/completion/codecompletiondelegate.cpp +++ b/src/plugins/codeeditor/gui/completion/codecompletiondelegate.cpp @@ -70,7 +70,7 @@ CodeCompletionView *CodeCompletionDelegate::view() const void CodeCompletionDelegate::paintItemText(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - QString text = index.sibling(index.row(), CodeCompletionModel::Name).data().toString(); + QString text = index.data(CodeCompletionModel::NameRole).toString(); // set formats QTextLayout textLayout(text, option.font, painter->device()); @@ -156,8 +156,6 @@ void CodeCompletionDelegate::paintItemBackground(QPainter *painter, const QStyle QRect CodeCompletionDelegate::paintItemIcon(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - Q_UNUSED(index) - if (!view()) return {}; @@ -169,7 +167,7 @@ QRect CodeCompletionDelegate::paintItemIcon(QPainter *painter, const QStyleOptio iconRect.moveTop(iconRect.top() + ((option.rect.bottom() - iconRect.bottom()) / 2)); bool isEnabled = option.state & QStyle::State_Enabled; - const QPixmap &px = getIconPixmap(option.icon, iconRect.size(), + const QPixmap &px = getIconPixmap(index.data(CodeCompletionModel::IconRole).value(), iconRect.size(), isEnabled ? QIcon::Normal : QIcon::Disabled, QIcon::Off); qreal x = iconRect.x(); qreal y = iconRect.y(); diff --git a/src/plugins/codeeditor/gui/completion/codecompletionextendwidget.cpp b/src/plugins/codeeditor/gui/completion/codecompletionextendwidget.cpp index 654f6ce0d..a06e5d822 100644 --- a/src/plugins/codeeditor/gui/completion/codecompletionextendwidget.cpp +++ b/src/plugins/codeeditor/gui/completion/codecompletionextendwidget.cpp @@ -102,7 +102,6 @@ class CodeCompletionExtendWidgetPrivate public: CodeCompletionExtendWidget *q; - TextEditor *editor { nullptr }; QKeySequence shortcutKey; DIconButton *iconButton { nullptr }; @@ -117,12 +116,12 @@ CodeCompletionExtendWidgetPrivate::CodeCompletionExtendWidgetPrivate(CodeComplet void CodeCompletionExtendWidgetPrivate::initUI() { - auto fromLabel = new QLabel(q->tr("From:"), q); + auto fromLabel = new QLabel(CodeCompletionExtendWidget::tr("From:"), q); iconButton = new DIconButton(q); iconButton->setIconSize({ 24, 24 }); iconButton->setFlat(true); - auto shortcutLabel = new QLabel(q->tr("Shortcut:"), q); + auto shortcutLabel = new QLabel(CodeCompletionExtendWidget::tr("Shortcut:"), q); shortcutKeyLabel = new KeyLabel("", q); messageEdit = new QPlainTextEdit(q); diff --git a/src/plugins/codeeditor/gui/completion/codecompletionmodel.cpp b/src/plugins/codeeditor/gui/completion/codecompletionmodel.cpp index fe7f4e459..7e8660f4e 100644 --- a/src/plugins/codeeditor/gui/completion/codecompletionmodel.cpp +++ b/src/plugins/codeeditor/gui/completion/codecompletionmodel.cpp @@ -4,25 +4,60 @@ #include "codecompletionmodel.h" #include "gui/texteditor.h" -#include "lsp/lspstyle.h" +#include "lsp/languageclienthandler.h" + +CompletionSortFilterProxyModel::CompletionSortFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} + +bool CompletionSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + const QModelIndex index = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent); + if (!index.isValid()) + return false; + + const QRegExp regexp = filterRegExp(); + if (regexp.pattern().isEmpty() || sourceModel()->rowCount(index) > 0) + return true; + + const QString filterText = index.data(CodeCompletionModel::FilterTextRole).toString(); + return filterText.contains(regexp); +} + +bool CompletionSortFilterProxyModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const +{ + auto leftStr = sourceLeft.data(CodeCompletionModel::SortTextRole).toString(); + auto rightStr = sourceRight.data(CodeCompletionModel::SortTextRole).toString(); + bool ret = leftStr < rightStr; + + const QRegExp regexp = filterRegExp(); + if (regexp.pattern().isEmpty()) + return ret; + + auto leftTextStr = sourceLeft.data(CodeCompletionModel::FilterTextRole).toString(); + auto rightTextStr = sourceRight.data(CodeCompletionModel::FilterTextRole).toString(); + bool leftStartsWith = leftTextStr.startsWith(regexp.pattern(), Qt::CaseInsensitive); + bool rightStartsWith = rightTextStr.startsWith(regexp.pattern(), Qt::CaseInsensitive); + if (leftStartsWith && !rightStartsWith) + return true; + else if (!leftStartsWith && rightStartsWith) + return false; + + return ret; +} class CodeCompletionModelPrivate { public: explicit CodeCompletionModelPrivate() {} - bool isFunctionKind(lsp::CompletionItem::Kind k); QIcon iconForKind(lsp::CompletionItem::Kind k); QList completionDatas; bool hasGroups = false; }; -bool CodeCompletionModelPrivate::isFunctionKind(lsp::CompletionItem::Kind k) -{ - return k == lsp::CompletionItem::Function || k == lsp::CompletionItem::Method; -} - QIcon CodeCompletionModelPrivate::iconForKind(lsp::CompletionItem::Kind k) { switch (k) { @@ -74,64 +109,44 @@ void CodeCompletionModel::clear() void CodeCompletionModel::completionInvoked(TextEditor *editor, int position) { - connect(editor->lspStyle(), &LSPStyle::completeFinished, this, &CodeCompletionModel::onCompleteFinished, Qt::UniqueConnection); + connect(editor->languageClient(), &LanguageClientHandler::completeFinished, this, &CodeCompletionModel::onCompleteFinished, Qt::UniqueConnection); beginResetModel(); d->completionDatas.clear(); int line = 0, col = 0; editor->lineIndexFromPosition(position, &line, &col); - editor->lspStyle()->requestCompletion(line, col); + editor->languageClient()->requestCompletion(line, col); endResetModel(); } -void CodeCompletionModel::executeCompletionItem(TextEditor *editor, int start, int end, const QModelIndex &index) +lsp::Range CodeCompletionModel::range() const { - if (index.row() >= d->completionDatas.size()) - return; + if (d->completionDatas.isEmpty()) + return {}; - int line = 0, col = 0; - editor->lineIndexFromPosition(end, &line, &col); - int lineEndPos = editor->SendScintilla(TextEditor::SCI_GETLINEENDPOSITION, line); - - QString next; - if (end >= lineEndPos) - next = editor->text(lineEndPos - 1, lineEndPos); - else - next = editor->text(end, end + 1); - - QString matching = d->completionDatas.at(index.row()).insertText; - if ((next == QLatin1Char('"') && matching.endsWith(QLatin1Char('"'))) - || (next == QLatin1Char('>') && matching.endsWith(QLatin1Char('>')))) - matching.chop(1); - - auto kind = d->completionDatas.at(index.row()).kind; - bool addParens = next != QLatin1Char('(') && d->isFunctionKind(kind); - if (addParens) - matching += QStringLiteral("()"); - - editor->replaceRange(start, end, matching); - if (addParens) { - int curLine = 0, curIndex = 0; - editor->lineIndexFromPosition(editor->cursorPosition(), &curLine, &curIndex); - editor->setCursorPosition(curLine, curIndex - 1); - } + return d->completionDatas.first().textEdit.range; +} + +lsp::CompletionItem *CodeCompletionModel::item(const QModelIndex &index) const +{ + return static_cast(index.internalPointer()); } int CodeCompletionModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) - return ColumnCount; + return 2; } QModelIndex CodeCompletionModel::index(int row, int column, const QModelIndex &parent) const { - if (row < 0 || row >= rowCount(parent) || column < 0 || column >= ColumnCount || parent.isValid()) + if (row < 0 || row >= rowCount(parent) || column < 0 || column >= 2 || parent.isValid()) return QModelIndex(); - return createIndex(row, column); + return createIndex(row, column, &d->completionDatas[row]); } QModelIndex CodeCompletionModel::parent(const QModelIndex &child) const @@ -156,12 +171,18 @@ QVariant CodeCompletionModel::data(const QModelIndex &index, int role) const const auto &item = d->completionDatas.at(index.row()); switch (role) { - case Qt::DisplayRole: - if (index.column() == Name) - return item.label; - break; - case Qt::DecorationRole: + case NameRole: + return item.label; + case IconRole: return d->iconForKind(item.kind); + case InsertTextRole: + return item.insertText; + case KindRole: + return item.kind; + case SortTextRole: + return item.sortText; + case FilterTextRole: + return item.filterText; default: break; } @@ -172,13 +193,6 @@ QVariant CodeCompletionModel::data(const QModelIndex &index, int role) const void CodeCompletionModel::onCompleteFinished(const lsp::CompletionProvider &provider) { beginResetModel(); - d->completionDatas = provider.items; - - qSort(d->completionDatas.begin(), d->completionDatas.end(), - [](const lsp::CompletionItem &item1, const lsp::CompletionItem &item2) { - return item1.sortText < item2.sortText; - }); - endResetModel(); } diff --git a/src/plugins/codeeditor/gui/completion/codecompletionmodel.h b/src/plugins/codeeditor/gui/completion/codecompletionmodel.h index a345a0ba0..90b2f6ac3 100644 --- a/src/plugins/codeeditor/gui/completion/codecompletionmodel.h +++ b/src/plugins/codeeditor/gui/completion/codecompletionmodel.h @@ -8,7 +8,17 @@ #include "common/common.h" #include -#include +#include + +class CompletionSortFilterProxyModel : public QSortFilterProxyModel +{ +public: + explicit CompletionSortFilterProxyModel(QObject *parent = nullptr); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override; +}; class TextEditor; class CodeCompletionModelPrivate; @@ -19,15 +29,19 @@ class CodeCompletionModel : public QAbstractItemModel explicit CodeCompletionModel(QObject *parent = nullptr); ~CodeCompletionModel() override; - enum Columns { - Icon, - Name + enum ItemRole { + IconRole = Qt::UserRole + 1, + NameRole, + InsertTextRole, + KindRole, + SortTextRole, + FilterTextRole }; - static const int ColumnCount = Name + 1; void clear(); void completionInvoked(TextEditor *editor, int position); - void executeCompletionItem(TextEditor *editor, int start, int end, const QModelIndex &index); + lsp::Range range() const; + lsp::CompletionItem *item(const QModelIndex &index) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; diff --git a/src/plugins/codeeditor/gui/completion/codecompletionwidget.cpp b/src/plugins/codeeditor/gui/completion/codecompletionwidget.cpp index c9ba52e13..0e301971f 100644 --- a/src/plugins/codeeditor/gui/completion/codecompletionwidget.cpp +++ b/src/plugins/codeeditor/gui/completion/codecompletionwidget.cpp @@ -33,7 +33,11 @@ void CodeCompletionWidget::initUI() completionView = new CodeCompletionView(this); completionModel = new CodeCompletionModel(this); - completionView->setModel(completionModel); + proxyModel = new CompletionSortFilterProxyModel(this); + proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + proxyModel->sort(0); + proxyModel->setSourceModel(completionModel); + completionView->setModel(proxyModel); completionView->setFixedWidth(500); completionView->setVisible(false); @@ -52,7 +56,11 @@ void CodeCompletionWidget::initUI() void CodeCompletionWidget::initConnection() { connect(completionView, &CodeCompletionView::doubleClicked, this, &CodeCompletionWidget::execute); - connect(completionModel, &CodeCompletionModel::modelReset, this, &CodeCompletionWidget::modelContentChanged); + connect(proxyModel, &CodeCompletionModel::modelReset, this, [this] { + proxyModel->setFilterRegExp(filterString()); + modelContentChanged(); + }); + connect(proxyModel, &CodeCompletionModel::layoutChanged, this, &CodeCompletionWidget::modelContentChanged); connect(completionExtWidget, &CodeCompletionExtendWidget::completionChanged, this, &CodeCompletionWidget::onCompletionChanged); connect(automaticInvocationTimer, &QTimer::timeout, this, &CodeCompletionWidget::automaticInvocation); @@ -104,12 +112,12 @@ void CodeCompletionWidget::updateAndShow() bool CodeCompletionWidget::hasAtLeastNRows(int rows) { int count = 0; - for (int row = 0; row < completionModel->rowCount(); ++row) { + for (int row = 0; row < proxyModel->rowCount(); ++row) { ++count; - QModelIndex index(completionModel->index(row, 0)); + QModelIndex index(proxyModel->index(row, 0)); if (index.isValid()) - count += completionModel->rowCount(index); + count += proxyModel->rowCount(index); if (count > rows) return true; @@ -118,26 +126,104 @@ bool CodeCompletionWidget::hasAtLeastNRows(int rows) return false; } +QString CodeCompletionWidget::filterString() +{ + const auto &word = editor()->wordAtPosition(editor()->cursorPosition()); + if (word.isEmpty()) + return {}; + + auto range = completionModel->range(); + if (range.start.line == -1 || range.start.character == -1) + return {}; + + int pos = editor()->wordStartPositoin(editor()->cursorPosition()); + if (pos != editor()->positionFromLineIndex(range.start.line, range.start.character)) + return {}; + + int startPos = editor()->positionFromLineIndex(range.start.line, range.start.character); + return editor()->text(startPos, automaticInvocationAt); +} + +bool CodeCompletionWidget::isFunctionKind(int kind) +{ + return kind == lsp::CompletionItem::Function || kind == lsp::CompletionItem::Method + || kind == lsp::CompletionItem::Constructor; +} + +void CodeCompletionWidget::executeCompletionItem(const QModelIndex &index) +{ + if (!index.isValid() || index.row() >= proxyModel->rowCount()) + return; + + auto srcIndex = proxyModel->mapToSource(index); + auto item = completionModel->item(srcIndex); + if (!item) + return; + + int labelOpenParenOffset = item->label.indexOf('('); + int labelClosingParenOffset = item->label.indexOf(')'); + bool isMacroCall = item->kind == lsp::CompletionItem::Text && labelOpenParenOffset != -1 + && labelClosingParenOffset > labelOpenParenOffset; + bool isFunctionLike = isFunctionKind(item->kind) || isMacroCall; + QString rawInsertText = item->textEdit.newText; + if (isFunctionLike && !rawInsertText.contains('(')) { + if (labelOpenParenOffset != -1) { + // function takes no arguments + if (labelClosingParenOffset == labelOpenParenOffset + 1) + rawInsertText += "()"; + else // function takes arguments + rawInsertText += "( )"; + } + } + + int firstParenOffset = rawInsertText.indexOf('('); + int lastParenOffset = rawInsertText.lastIndexOf(')'); + QString textToBeInserted = rawInsertText.left(firstParenOffset); + QString extraCharacters; + int cursorOffset = 0; + if (isFunctionLike) { + extraCharacters += '('; + // If the function takes no arguments, automatically place the closing parenthesis + if (firstParenOffset + 1 == lastParenOffset) { + extraCharacters += QLatin1Char(')'); + } else { + extraCharacters += ')'; + --cursorOffset; + } + } + + int curLine = 0, curIndex = 0; + editor()->lineIndexFromPosition(editor()->cursorPosition(), &curLine, &curIndex); + textToBeInserted += extraCharacters; + auto range = item->textEdit.range; + editor()->replaceRange(range.start.line, range.start.character, + curLine, curIndex, textToBeInserted); + if (cursorOffset) { + editor()->lineIndexFromPosition(editor()->cursorPosition(), &curLine, &curIndex); + editor()->setCursorPosition(curLine, curIndex + cursorOffset); + } +} + void CodeCompletionWidget::modelContentChanged() { if (!editor()->hasFocus()) return; - int realItemCount = completionModel->rowCount(); - if ((completionView->isHidden() || needShow) && realItemCount != 0) { + if ((completionView->isHidden() || needShow) && proxyModel->rowCount() != 0) { needShow = false; completionView->setVisible(true); updateAndShow(); } - if (completionModel->rowCount() == 0) { + if (proxyModel->rowCount() == 0) { completionView->setVisible(false); if (!completionExtWidget->isVisible()) hide(); else updateAndShow(); } else { - completionView->setCurrentIndex(completionModel->index(0, 0)); + updateHeight(); + completionView->setCurrentIndex(proxyModel->index(0, 0)); } } @@ -151,11 +237,13 @@ void CodeCompletionWidget::onCompletionChanged() if (completionView->isHidden()) hide(); } else { - if (completionModel->rowCount() == 0) + if (proxyModel->rowCount() == 0) completionView->setVisible(false); - completionExtWidget->setVisible(true); - updateAndShow(); + if (!completionExtWidget->isVisible()) { + completionExtWidget->setVisible(true); + updateAndShow(); + } } } @@ -166,7 +254,7 @@ TextEditor *CodeCompletionWidget::editor() const bool CodeCompletionWidget::isCompletionActive() const { - return (!isHidden() && isVisible()) || (!completionView->isHidden() && completionView->isVisible()); + return isVisible(); } void CodeCompletionWidget::startCompletion() @@ -188,15 +276,15 @@ void CodeCompletionWidget::updateHeight() baseHeight = maxBaseHeight; } else { // Calculate size-hints to determine the best height - for (int row = 0; row < completionModel->rowCount(); ++row) { + for (int row = 0; row < proxyModel->rowCount(); ++row) { baseHeight += completionView->sizeHintForRow(row); - QModelIndex index(completionModel->index(row, 0)); + QModelIndex index(proxyModel->index(row, 0)); if (index.isValid()) { - for (int row2 = 0; row2 < completionModel->rowCount(index); ++row2) { + for (int row2 = 0; row2 < proxyModel->rowCount(index); ++row2) { int h = 0; - for (int a = 0; a < completionModel->columnCount(index); ++a) { - const QModelIndex child = completionModel->index(row2, a, index); + for (int a = 0; a < proxyModel->columnCount(index); ++a) { + const QModelIndex child = proxyModel->index(row2, a, index); int localHeight = completionView->sizeHintForIndex(child).height(); if (localHeight > h) h = localHeight; @@ -312,11 +400,7 @@ bool CodeCompletionWidget::execute() } isCompletionInput = true; - auto pos = editor()->cursorPosition(); - auto startPos = editor()->wordStartPositoin(pos); - auto endPos = editor()->wordEndPosition(pos); - - completionModel->executeCompletionItem(editor(), startPos, endPos, index); + executeCompletionItem(index); abortCompletion(); isCompletionInput = false; return true; @@ -344,7 +428,15 @@ void CodeCompletionWidget::automaticInvocation() return; } - startCompletion(); + const auto &word = editor()->wordAtPosition(editor()->cursorPosition()); + if (!completionView->isVisible() || completionModel->rowCount() == 0 || word.isEmpty()) { + startCompletion(); + } else if (!word.isEmpty()) { + int pos = editor()->wordStartPositoin(editor()->cursorPosition()); + const auto &range = completionModel->range(); + if (pos != editor()->positionFromLineIndex(range.start.line, range.start.character)) + startCompletion(); + } } void CodeCompletionWidget::focusOutEvent(QFocusEvent *event) @@ -399,5 +491,12 @@ void CodeCompletionWidget::cursorPositionChanged() return; if (editor()->cursorPosition() != automaticInvocationAt) - abortCompletion(); + return abortCompletion(); + + QString filter = filterString(); + if (filter.isEmpty()) + return; + + proxyModel->setFilterRegExp(filter); + proxyModel->invalidate(); } diff --git a/src/plugins/codeeditor/gui/completion/codecompletionwidget.h b/src/plugins/codeeditor/gui/completion/codecompletionwidget.h index 5bb91ac0c..69fcb270b 100644 --- a/src/plugins/codeeditor/gui/completion/codecompletionwidget.h +++ b/src/plugins/codeeditor/gui/completion/codecompletionwidget.h @@ -10,6 +10,7 @@ class TextEditor; class CodeCompletionView; class CodeCompletionModel; +class CompletionSortFilterProxyModel; class CodeCompletionExtendWidget; class CodeCompletionWidget : public QFrame @@ -44,6 +45,9 @@ public slots: bool shouldStartCompletion(const QString &insertedText); void updateAndShow(); bool hasAtLeastNRows(int rows); + QString filterString(); + bool isFunctionKind(int kind); + void executeCompletionItem(const QModelIndex &index); private slots: void modelContentChanged(); @@ -56,6 +60,7 @@ private slots: private: CodeCompletionView *completionView { nullptr }; CodeCompletionModel *completionModel { nullptr }; + CompletionSortFilterProxyModel *proxyModel { nullptr }; CodeCompletionExtendWidget *completionExtWidget { nullptr }; QTimer *automaticInvocationTimer { nullptr }; diff --git a/src/plugins/codeeditor/gui/private/tabbar_p.h b/src/plugins/codeeditor/gui/private/tabbar_p.h index a2748c4cc..a62869cd9 100644 --- a/src/plugins/codeeditor/gui/private/tabbar_p.h +++ b/src/plugins/codeeditor/gui/private/tabbar_p.h @@ -21,6 +21,11 @@ class TabBarPrivate : public QObject void initUI(); void initConnection(); + void updateBackgroundColor(); + bool isModified(int index) const; + int showConfirmDialog(const QString &filePath); + void closeAllTab(const QStringList &exceptList); + public slots: void onCurrentTabChanged(int index); void onTabColseRequested(int index); diff --git a/src/plugins/codeeditor/gui/private/tabwidget_p.h b/src/plugins/codeeditor/gui/private/tabwidget_p.h index f772fb0d4..17e95ba70 100644 --- a/src/plugins/codeeditor/gui/private/tabwidget_p.h +++ b/src/plugins/codeeditor/gui/private/tabwidget_p.h @@ -8,6 +8,9 @@ #include "gui/tabwidget.h" #include "gui/tabbar.h" #include "gui/texteditor.h" +#include "gui/recent/recentopenwidget.h" +#include "find/editordocumentfind.h" +#include "symbol/symbolbar.h" #include "common/util/eventdefinitions.h" @@ -65,11 +68,16 @@ public slots: QStackedLayout *editorLayout { nullptr }; TabBar *tabBar { nullptr }; + SymbolBar *symbolBar { nullptr }; QHash editorMng; PosRecord curPosRecord; QList prePosRecord; QList nextPosRecord; + + QVector recentOpenedFiles; + RecentOpenWidget *openedWidget { nullptr }; + EditorDocumentFind *docFind { nullptr }; }; #endif // TABWIDGET_P_H diff --git a/src/plugins/codeeditor/gui/private/texteditor_p.cpp b/src/plugins/codeeditor/gui/private/texteditor_p.cpp index 99d9b3924..45b4cd076 100644 --- a/src/plugins/codeeditor/gui/private/texteditor_p.cpp +++ b/src/plugins/codeeditor/gui/private/texteditor_p.cpp @@ -22,6 +22,7 @@ #include #include #include +#include static constexpr char DEFAULT_FONT_NAME[] { "Noto Mono" }; @@ -50,13 +51,10 @@ TextEditorPrivate::TextEditorPrivate(TextEditor *qq) void TextEditorPrivate::init() { q->setFrameShape(QFrame::NoFrame); - q->SendScintilla(TextEditor::SCI_SETMOUSEDWELLTIME, 20); q->setAnnotationDisplay(TextEditor::AnnotationStandard); q->SendScintilla(TextEditor::SCI_AUTOCSETCASEINSENSITIVEBEHAVIOUR, TextEditor::SC_CASEINSENSITIVEBEHAVIOUR_IGNORECASE); - hoverTimer.setSingleShot(true); - initMargins(); updateColorTheme(); updateSettings(); @@ -66,6 +64,10 @@ void TextEditorPrivate::initConnection() { connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, this, &TextEditorPrivate::resetThemeColor); connect(EditorSettings::instance(), &EditorSettings::valueChanged, this, &TextEditorPrivate::updateSettings); + connect(qApp, &QApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { + if (state == Qt::ApplicationState::ApplicationInactive) + q->cancelTips(); + }); connect(q, &TextEditor::SCN_ZOOM, q, &TextEditor::zoomValueChanged); connect(q, &TextEditor::SCN_DWELLSTART, this, &TextEditorPrivate::onDwellStart); @@ -109,6 +111,7 @@ void TextEditorPrivate::updateColorTheme() auto rtIcon = QIcon::fromTheme("arrow"); q->markerDefine(rtIcon.pixmap(14, 14), Runtime); + q->setColor(q->palette().color(QPalette::WindowText)); auto palette = QToolTip::palette(); if (DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::DarkType) { // editor @@ -171,7 +174,11 @@ void TextEditorPrivate::updateSettings() int fontZoom = EditorSettings::instance()->value(Node::FontColor, Group::FontGroup, Key::FontZoom, 100).toInt(); int realFontZoom = (fontZoom - 100) / 10; - q->zoomTo(realFontZoom); + { + // Avoid triggering the `zoomChanged` signal. + QSignalBlocker blk(q); + q->zoomTo(realFontZoom); + } // Indentation auto tabPolicy = EditorSettings::instance()->value(Node::Behavior, Group::TabGroup, Key::TabPolicy, 0).toInt(); @@ -182,6 +189,9 @@ void TextEditorPrivate::updateSettings() q->setWhitespaceSize(3); q->setAutoIndent(autoIndent); + int hoverTime = EditorSettings::instance()->value(Node::Behavior, Group::TipGroup, Key::TipActiveTime, 500).toInt(); + q->SendScintilla(TextEditor::SCI_SETMOUSEDWELLTIME, hoverTime); + // Highlight the current line q->setCaretLineVisible(true); @@ -216,14 +226,12 @@ void TextEditorPrivate::loadLexer() } } -void TextEditorPrivate::loadLSPStyle() +void TextEditorPrivate::initLanguageClient() { - if (!lspStyle) { - lspStyle = new LSPStyle(q); - lspStyle->initLspConnection(); - } + if (!languageClient) + languageClient = new LanguageClientHandler(q); - lspStyle->updateTokens(); + languageClient->updateTokens(); } int TextEditorPrivate::cursorPosition() const @@ -325,7 +333,7 @@ void TextEditorPrivate::showMarginMenu() menu.addAction(tr("Enable Breakpoint"), q, [this, line] { q->setBreakpointEnabled(line, true); }); menu.addAction(tr("Add Condition"), q, [this, line] { q->setBreakpointCondition(line); }); } else { - static QString text("Add a breakpoint on line %1"); + static QString text(tr("Add a breakpoint on line %1")); menu.addAction(text.arg(line + 1), q, [this, line] { q->addBreakpoint(line); }); } @@ -422,6 +430,33 @@ void TextEditorPrivate::adjustScrollBar() scrollbar->setValue(scrollbar->value() + currentLine - halfEditorLines); } +QMap TextEditorPrivate::allMarkers() +{ + QMap markers; + for (int line = 0; line < q->lines(); ++line) { + int mask = static_cast(q->markersAtLine(line)); + if (mask != 0) + markers.insert(line, mask); + } + + return markers; +} + +void TextEditorPrivate::setMarkers(const QMap &maskMap) +{ + int totalLine = q->lines(); + for (auto iter = maskMap.begin(); iter != maskMap.end(); ++iter) { + if (iter.key() >= totalLine) + break; + + if (iter.value() & (1 << Breakpoint)) { + q->addBreakpoint(iter.key(), true); + } else if (iter.value() & (1 << BreakpointDisabled)) { + q->addBreakpoint(iter.key(), false); + } + } +} + void TextEditorPrivate::resetThemeColor() { if (q->lexer()) { @@ -429,8 +464,8 @@ void TextEditorPrivate::resetThemeColor() q->setLexer(q->lexer()); } - if (lspStyle) - lspStyle->refreshTokens(); + if (languageClient) + languageClient->refreshTokens(); updateColorTheme(); } @@ -443,11 +478,7 @@ void TextEditorPrivate::onDwellStart(int position, int x, int y) if (pos == -1) return; - bool isKeyCtrl = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier); - if (isKeyCtrl) - emit q->documentHoveredWithCtrl(pos); - else - emit q->documentHovered(pos); + emit q->documentHovered(pos); } void TextEditorPrivate::onDwellEnd(int position, int x, int y) @@ -472,6 +503,12 @@ void TextEditorPrivate::onModified(int pos, int mtype, const QString &text, int contentsChanged = true; if (isAutoCompletionEnabled && !text.isEmpty()) editor.textChanged(); + + if (added != 0) { + int line = 0, index = 0; + q->lineIndexFromPosition(pos, &line, &index); + editor.lineChanged(fileName, line + 1, added); + } if (mtype & TextEditor::SC_MOD_INSERTTEXT) { emit q->textAdded(pos, len, added, text, line); diff --git a/src/plugins/codeeditor/gui/private/texteditor_p.h b/src/plugins/codeeditor/gui/private/texteditor_p.h index 81c6ee1cd..fcf14b89f 100644 --- a/src/plugins/codeeditor/gui/private/texteditor_p.h +++ b/src/plugins/codeeditor/gui/private/texteditor_p.h @@ -7,7 +7,7 @@ #include "gui/texteditor.h" #include "common/util/eventdefinitions.h" -#include "lsp/lspstyle.h" +#include "lsp/languageclienthandler.h" #include "gui/completion/codecompletionwidget.h" #include @@ -39,7 +39,7 @@ class TextEditorPrivate : public QObject void initMargins(); void updateColorTheme(); void loadLexer(); - void loadLSPStyle(); + void initLanguageClient(); int cursorPosition() const; int marginsWidth(); @@ -53,6 +53,8 @@ class TextEditorPrivate : public QObject void gotoPreviousMark(uint mask); QsciStyle createAnnotationStyle(int type); void adjustScrollBar(); + QMap allMarkers(); + void setMarkers(const QMap &maskMap); public slots: void resetThemeColor(); @@ -69,13 +71,11 @@ public slots: int preFirstLineNum { 0 }; int lastCursorPos { 0 }; QMultiHash annotationRecords; - - QTimer hoverTimer; - int hoverPos { -1 }; - LSPStyle *lspStyle { nullptr }; - bool isSaved { false }; + + LanguageClientHandler *languageClient { nullptr }; bool isAutoCompletionEnabled { false }; + bool tipsDisplayable { true }; bool contentsChanged { false }; bool lastCursorNeedRecord { true }; bool postionChangedByGoto { false }; diff --git a/src/plugins/codeeditor/gui/private/workspacewidget_p.h b/src/plugins/codeeditor/gui/private/workspacewidget_p.h index dc44f17c9..24ea3330b 100644 --- a/src/plugins/codeeditor/gui/private/workspacewidget_p.h +++ b/src/plugins/codeeditor/gui/private/workspacewidget_p.h @@ -33,7 +33,6 @@ class WorkspaceWidgetPrivate : public QObject void handleFileChanged(); void handleFileRemoved(); - bool checkAndResetSaveState(const QString &fileName); public slots: void checkFileState(); @@ -45,6 +44,7 @@ public slots: void onFileModified(const QString &fileName); void handleOpenFile(const QString &workspace, const QString &fileName); + void handleCloseFile(const QString &fileName); void handleAddBreakpoint(const QString &fileName, int line, bool enabled); void handleRemoveBreakpoint(const QString &fileName, int line); void handleSetBreakpointEnabled(const QString &fileName, int line, bool enabled); @@ -62,6 +62,7 @@ public slots: void handleRenameSymbol(); void handleSetModifiedAutoReload(const QString &fileName, bool flag); void handleSetComment(); + void handleShowOpenedFiles(); public: WorkspaceWidget *q; diff --git a/src/plugins/codeeditor/gui/recent/recentopenlistdelegate.cpp b/src/plugins/codeeditor/gui/recent/recentopenlistdelegate.cpp new file mode 100644 index 000000000..f4c8cab23 --- /dev/null +++ b/src/plugins/codeeditor/gui/recent/recentopenlistdelegate.cpp @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "recentopenlistdelegate.h" + +#include + +#include +#include + +DWIDGET_USE_NAMESPACE +DGUI_USE_NAMESPACE + +RecentOpenListDelegate::RecentOpenListDelegate(QAbstractItemView *parent) : + QStyledItemDelegate(parent) +{ +} + +void RecentOpenListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + painter->setRenderHint(QPainter::Antialiasing); + painter->save(); + if (option.widget) { + DPalette pl = DGuiApplicationHelper::instance()->applicationPalette(); + QColor baseColor = pl.color(DPalette::ColorGroup::Active, DPalette::ColorType::ItemBackground); + QColor adjustColor = baseColor; + + bool isSelected = (option.state & QStyle::State_Selected) && option.showDecorationSelected; + + if (isSelected) { + adjustColor = option.palette.color(DPalette::ColorGroup::Active, QPalette::Highlight); + } else if (option.state & QStyle::StateFlag::State_MouseOver) { + // hover color + adjustColor = DGuiApplicationHelper::adjustColor(baseColor, 0, 0, 0, 0, 0, 0, +10); + } else { + // alternately background color + painter->setOpacity(0); + if (index.row() % 2 == 0) { + adjustColor = DGuiApplicationHelper::adjustColor(baseColor, 0, 0, 0, 0, 0, 0, +5); + painter->setOpacity(1); + } + } + + // set paint path + QPainterPath path; + path.addRoundedRect(option.rect, 8, 8); + painter->fillPath(path, adjustColor); + } + painter->restore(); + QStyledItemDelegate::paint(painter, option, index); +} + +QSize RecentOpenListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QSize s; + s.setWidth(option.rect.width()); + s.setHeight(30); + return s; +} diff --git a/src/plugins/codeeditor/gui/recent/recentopenlistdelegate.h b/src/plugins/codeeditor/gui/recent/recentopenlistdelegate.h new file mode 100644 index 000000000..b7e4ba0f2 --- /dev/null +++ b/src/plugins/codeeditor/gui/recent/recentopenlistdelegate.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RECENTOPENLISTDELEGATE_H +#define RECENTOPENLISTDELEGATE_H + +#include +#include + +#include +#include + +class RecentOpenListDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit RecentOpenListDelegate(QAbstractItemView *parent = nullptr); + +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +#endif // RECENTOPENLISTDELEGATE_H diff --git a/src/plugins/codeeditor/gui/recent/recentopenlistview.cpp b/src/plugins/codeeditor/gui/recent/recentopenlistview.cpp new file mode 100644 index 000000000..ea03166be --- /dev/null +++ b/src/plugins/codeeditor/gui/recent/recentopenlistview.cpp @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "recentopenlistview.h" + +#include + +RecentOpenListView::RecentOpenListView(QWidget *parent) + : DListView(parent) +{ +} + +RecentOpenListView::~RecentOpenListView() +{ +} + +void RecentOpenListView::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Tab) { + QModelIndex currentIndex = this->currentIndex(); + if (currentIndex.isValid()) { + QModelIndex nextIndex = model()->index(currentIndex.row() + 1, currentIndex.column(), currentIndex.parent()); + if (nextIndex.isValid()) { + setCurrentIndex(nextIndex); + } else { + setCurrentIndex(model()->index(0, 0)); + } + } + } else { + DListView::keyPressEvent(event); + } +} diff --git a/src/plugins/codeeditor/gui/recent/recentopenlistview.h b/src/plugins/codeeditor/gui/recent/recentopenlistview.h new file mode 100644 index 000000000..7d82e5db0 --- /dev/null +++ b/src/plugins/codeeditor/gui/recent/recentopenlistview.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RECENTOPENLISTVIEW_H +#define RECENTOPENLISTVIEW_H + +#include + +class RecentOpenListView : public Dtk::Widget::DListView +{ + Q_OBJECT +public: + explicit RecentOpenListView(QWidget *parent = nullptr); + ~RecentOpenListView(); + +protected: + void keyPressEvent(QKeyEvent *event) override; +}; + +#endif // RECENTOPENLISTVIEW_H diff --git a/src/plugins/codeeditor/gui/recent/recentopenwidget.cpp b/src/plugins/codeeditor/gui/recent/recentopenwidget.cpp new file mode 100644 index 000000000..acceb3f78 --- /dev/null +++ b/src/plugins/codeeditor/gui/recent/recentopenwidget.cpp @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "recentopenwidget.h" +#include "common/util/customicons.h" + +#include + +#include +#include +#include + +DWIDGET_USE_NAMESPACE + +RecentOpenWidget::RecentOpenWidget(QWidget *parent) + : DFrame(parent) +{ + initUI(); +} + +RecentOpenWidget::~RecentOpenWidget() +{ +} + +void RecentOpenWidget::keyReleaseEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Control) { + if (this->listView->selectionModel()->hasSelection()) { + this->triggered(listView->selectionModel()->selectedIndexes()[0]); + this->close(); + } + } + DFrame::keyReleaseEvent(event); +} + +void RecentOpenWidget::initUI() +{ + QHBoxLayout *mainLayout = new QHBoxLayout(); + + listView = new RecentOpenListView(this); + listView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + listView->setLineWidth(0); + listView->setEditTriggers(QAbstractItemView::NoEditTriggers); + delegate = new RecentOpenListDelegate(listView); + listView->setItemDelegate(delegate); + listView->setSelectionMode(QAbstractItemView::SingleSelection); + + model = new QStandardItemModel(this); + listView->setModel(model); + + connect(listView, &RecentOpenListView::clicked, this, &RecentOpenWidget::triggered); + + mainLayout->addWidget(listView); + mainLayout->setContentsMargins(10, 10, 10, 0); + this->setLineWidth(0); + this->setLayout(mainLayout); + this->setMinimumSize(400, 265); + DStyle::setFrameRadius(this, 16); +} + +void RecentOpenWidget::setOpenedFiles(const QVector &list) +{ + model->clear(); + QSet fileNamesSet; + + for (const QString &absolutePath : list) { + QFileInfo info(absolutePath); + QString fileName = info.fileName(); + QStandardItem *item = new QStandardItem(fileName); + item->setData(absolutePath, RecentOpenedUserRole::FilePathRole); + item->setIcon(CustomIcons::icon(info)); + model->appendRow(item); + } + + for (int i = 0; i < model->rowCount(); ++i) { + QStandardItem *item = model->item(i); + QString fileName = item->text(); + if (fileNamesSet.contains(fileName)) { + for (int j = 0; j < model->rowCount(); ++j) { + QStandardItem *nextItem = model->item(j); + if (nextItem->text() == fileName) { + QString dirName = QFileInfo(nextItem->data(RecentOpenedUserRole::FilePathRole).toString()).absolutePath(); + dirName = dirName.mid(dirName.lastIndexOf('/') + 1); + QString newName = dirName + "/" + fileName; + nextItem->setText(newName); + } + } + } else { + fileNamesSet.insert(fileName); + } + } +} + +void RecentOpenWidget::setListViewSelection(int num) +{ + if (num < 0 || !listView || !listView->model()) { + return; + } + + int rowCount = listView->model()->rowCount(); + + if (num >= rowCount) { + return; + } + listView->setCurrentIndex(listView->model()->index(num, 0)); +} + +void RecentOpenWidget::setFocusListView() +{ + listView->setFocus(); +} diff --git a/src/plugins/codeeditor/gui/recent/recentopenwidget.h b/src/plugins/codeeditor/gui/recent/recentopenwidget.h new file mode 100644 index 000000000..c65d1df08 --- /dev/null +++ b/src/plugins/codeeditor/gui/recent/recentopenwidget.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RECENTOPENWIDGET_H +#define RECENTOPENWIDGET_H + +#include "recentopenlistview.h" +#include "recentopenlistdelegate.h" + +#include + +#include +#include + +class RecentOpenWidget : public Dtk::Widget::DFrame +{ + Q_OBJECT + +public: + enum RecentOpenedUserRole{ + FilePathRole = Qt::UserRole + 1 + }; + explicit RecentOpenWidget(QWidget *parent = nullptr); + ~RecentOpenWidget(); + + void initUI(); + void setOpenedFiles(const QVector &list); + void setListViewSelection(int index); + void setFocusListView(); + +signals: + void triggered(const QModelIndex &index); + +protected: + void keyReleaseEvent(QKeyEvent *event) override; + +private: + RecentOpenListView *listView = nullptr; + QStandardItemModel *model = nullptr; + RecentOpenListDelegate *delegate = nullptr; +}; + +#endif // RECENTOPENWIDGET_H diff --git a/src/plugins/codeeditor/gui/settings/behaviorwidget.cpp b/src/plugins/codeeditor/gui/settings/behaviorwidget.cpp index fca910805..16dc5b575 100644 --- a/src/plugins/codeeditor/gui/settings/behaviorwidget.cpp +++ b/src/plugins/codeeditor/gui/settings/behaviorwidget.cpp @@ -29,6 +29,7 @@ class BehaviorWidgetPrivate QStringList policyList; QComboBox *tabPolicyCB { nullptr }; DSpinBox *tabSizeSB { nullptr }; + DSpinBox *tipActiveTime { nullptr }; QCheckBox *autoIndentCB { nullptr }; }; @@ -60,9 +61,19 @@ void BehaviorWidgetPrivate::initUI() itemLayout->addWidget(createItem(BehaviorWidget::tr("Tab size:"), tabSizeSB)); itemLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding)); + QLabel *tipConfigLabel = new QLabel(BehaviorWidget::tr("Editor Tip"), q); + QHBoxLayout *tipLayout = new QHBoxLayout; + tipLayout->setSpacing(15); + tipActiveTime = new DSpinBox(q); + tipActiveTime->setRange(0, 2000); + tipLayout->addWidget(createItem(BehaviorWidget::tr("Tip Active Time(ms):"), tipActiveTime)); + tipLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding)); + mainLayout->addWidget(titleLabel); mainLayout->addLayout(itemLayout); mainLayout->addWidget(autoIndentCB); + mainLayout->addWidget(tipConfigLabel); + mainLayout->addLayout(tipLayout); } QWidget *BehaviorWidgetPrivate::createItem(const QString &name, QWidget *widget) @@ -98,15 +109,21 @@ void BehaviorWidget::setUserConfig(const QMap &map) auto tabPolicy = EditorSettings::instance()->value(Node::Behavior, Group::TabGroup, Key::TabPolicy, 0).toInt(); auto tabSize = EditorSettings::instance()->value(Node::Behavior, Group::TabGroup, Key::TabSize, 4).toInt(); auto enableIndentation = EditorSettings::instance()->value(Node::Behavior, Group::TabGroup, Key::EnableAutoIndentation, true).toBool(); + auto tipActiveTime = EditorSettings::instance()->value(Node::Behavior, Group::TipGroup, Key::TipActiveTime, 500).toInt(); if (tabSize > d->tabSizeSB->maximum()) tabSize = d->tabSizeSB->maximum(); else if (tabSize < d->tabSizeSB->minimum()) tabSize = d->tabSizeSB->minimum(); + if (tipActiveTime > d->tipActiveTime->maximum()) + tipActiveTime = d->tipActiveTime->maximum(); + else if (tipActiveTime < d->tipActiveTime->minimum()) + tipActiveTime = d->tipActiveTime->minimum(); d->tabPolicyCB->setCurrentIndex(tabPolicy); d->tabSizeSB->setValue(tabSize); d->autoIndentCB->setChecked(enableIndentation); + d->tipActiveTime->setValue(tipActiveTime); } void BehaviorWidget::getUserConfig(QMap &map) @@ -115,10 +132,12 @@ void BehaviorWidget::getUserConfig(QMap &map) fontMap.insert(Key::TabPolicy, d->tabPolicyCB->currentIndex()); fontMap.insert(Key::TabSize, d->tabSizeSB->value()); fontMap.insert(Key::EnableAutoIndentation, d->autoIndentCB->isChecked()); + fontMap.insert(Key::TipActiveTime, d->tipActiveTime->value()); map.insert(Group::TabGroup, fontMap); EditorSettings::instance()->setValue(Node::Behavior, Group::TabGroup, Key::TabPolicy,d->tabPolicyCB->currentIndex()); EditorSettings::instance()->setValue(Node::Behavior, Group::TabGroup, Key::TabSize, d->tabSizeSB->value()); EditorSettings::instance()->setValue(Node::Behavior, Group::TabGroup, Key::EnableAutoIndentation, d->autoIndentCB->isChecked()); + EditorSettings::instance()->setValue(Node::Behavior, Group::TipGroup, Key::TipActiveTime, d->tipActiveTime->value()); } diff --git a/src/plugins/codeeditor/gui/settings/fontcolorwidget.cpp b/src/plugins/codeeditor/gui/settings/fontcolorwidget.cpp index ffa5fa74a..c39284e80 100644 --- a/src/plugins/codeeditor/gui/settings/fontcolorwidget.cpp +++ b/src/plugins/codeeditor/gui/settings/fontcolorwidget.cpp @@ -16,6 +16,7 @@ const char DefaultFontFamily[] { "Noto Mono" }; const int DefaultFontSize { 10 }; const int DefaultFontZoom { 100 }; +const int FontMiniZoomSize { 5 }; DWIDGET_USE_NAMESPACE @@ -30,6 +31,7 @@ class FontColorWidgetPrivate QList pointSizesForSelectedFont() const; void updatePointSizes(); + void updateZoomRange(int fontSize); FontColorWidget *q; DFontComboBox *fontComboBox { nullptr }; @@ -58,7 +60,7 @@ void FontColorWidgetPrivate::initUI() zoomSpinBox = new DSpinBox(q); zoomSpinBox->setSuffix("%"); - zoomSpinBox->setRange(10, 3000); + zoomSpinBox->setRange(10, 300); zoomSpinBox->setSingleStep(10); QHBoxLayout *itemLayout = new QHBoxLayout; @@ -127,6 +129,14 @@ void FontColorWidgetPrivate::updatePointSizes() fontSizeComboBox->setCurrentIndex(idx); } +void FontColorWidgetPrivate::updateZoomRange(int fontSize) +{ + auto ratio = (double)FontMiniZoomSize / fontSize; + int zoomMiniValue = qRound(ratio * 10) * 10; + + zoomSpinBox->setRange(zoomMiniValue, 300); +} + FontColorWidget::FontColorWidget(QWidget *parent) : PageWidget(parent), d(new FontColorWidgetPrivate(this)) @@ -151,6 +161,7 @@ void FontColorWidget::setUserConfig(const QMap &map) d->fontSizeComboBox->setCurrentText(QString::number(d->fontSize)); d->fontComboBox->setCurrentText(fontFamily); d->zoomSpinBox->setValue(fontZoom); + d->updatePointSizes(); } void FontColorWidget::getUserConfig(QMap &map) @@ -178,6 +189,8 @@ void FontColorWidget::fontSizeSelected(int index) const auto sizeStr = d->fontSizeComboBox->itemText(index); bool ok = true; const int size = sizeStr.toInt(&ok); - if (ok) + if (ok) { d->fontSize = size; + d->updateZoomRange(size); + } } diff --git a/src/plugins/codeeditor/gui/settings/settingsdefine.h b/src/plugins/codeeditor/gui/settings/settingsdefine.h index 710e6e0a8..b5a2d825a 100644 --- a/src/plugins/codeeditor/gui/settings/settingsdefine.h +++ b/src/plugins/codeeditor/gui/settings/settingsdefine.h @@ -19,6 +19,7 @@ constexpr char MimeTypeConfig[] { "MimeType & Comments" }; namespace Group { constexpr char FontGroup[] { "Font" }; constexpr char TabGroup[] { "Tabs And Indentation" }; +constexpr char TipGroup[] { "Editor Tip" }; } namespace Key { @@ -30,6 +31,8 @@ constexpr char TabPolicy[] { "tabPolicy" }; constexpr char TabSize[] { "tabSize" }; constexpr char EnableAutoIndentation[] { "enableAutoIndentation" }; +constexpr char TipActiveTime[] { "tipActiveTime" }; + constexpr char MimeTypeGroupName[] { "groupName" }; constexpr char MimeType[] { "mimeType" }; constexpr char SingleLineComment[] { "singleLineComment" }; diff --git a/src/plugins/codeeditor/gui/tabbar.cpp b/src/plugins/codeeditor/gui/tabbar.cpp index bdc93d474..82516faf3 100644 --- a/src/plugins/codeeditor/gui/tabbar.cpp +++ b/src/plugins/codeeditor/gui/tabbar.cpp @@ -5,17 +5,18 @@ #include "tabbar.h" #include "private/tabbar_p.h" -#include "common/common.h" -#include "services/editor/editorservice.h" +#include "common/util/eventdefinitions.h" #include -#include +#include +#include #include #include #include #include #include +#include DWIDGET_USE_NAMESPACE @@ -40,11 +41,67 @@ void TabBarPrivate::initUI() closeBtn->setIcon(QIcon::fromTheme("edit-closeBtn")); QHBoxLayout *mainLayout = new QHBoxLayout(q); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addWidget(tabBar, 1, Qt::AlignLeft); mainLayout->addWidget(hSplitBtn, 0, Qt::AlignRight); mainLayout->addWidget(vSplitBtn, 0, Qt::AlignRight); mainLayout->addWidget(closeBtn, 0, Qt::AlignRight); + + updateBackgroundColor(); +} + +void TabBarPrivate::updateBackgroundColor() +{ + q->setAutoFillBackground(true); + auto p = q->palette(); + if (Dtk::Gui::DGuiApplicationHelper::instance()->themeType() == Dtk::Gui::DGuiApplicationHelper::LightType) + p.setColor(QPalette::Window, QColor(225, 225, 225)); + else + p.setColor(QPalette::Window, QColor(47, 47, 47)); + q->setPalette(p); +} + +bool TabBarPrivate::isModified(int index) const +{ + QString text = tabBar->tabText(index); + return text.length() > 0 && text.at(0) == "*"; +} + +int TabBarPrivate::showConfirmDialog(const QString &filePath) +{ + DDialog dialog(q); + dialog.setWindowTitle(tr("Save Changes")); + dialog.setIcon(QIcon::fromTheme("dialog-warning")); + dialog.setMessage(tr("The file %1 has unsaved changes, will save?").arg(QFileInfo(filePath).fileName())); + dialog.addButton(tr("Save", "button"), true, DDialog::ButtonRecommend); + dialog.addButton(tr("Do Not Save", "button")); + dialog.addButton(tr("Cancel", "button")); + + return dialog.exec(); +} + +void TabBarPrivate::closeAllTab(const QStringList &exceptList) +{ + QStringList tabList; + for (int i = 0; i < tabBar->count(); ++i) { + auto file = tabBar->tabToolTip(i); + if (exceptList.contains(file)) + continue; + + if (isModified(i)) { + int ret = showConfirmDialog(file); + if (ret == 0) // save + emit q->saveFileRequested(file); + else if (ret == 2 || ret == -1) // cancel or close + return; + } + + tabList << file; + } + + for (const auto &tab : tabList) + q->removeTab(tab, true); } void TabBarPrivate::initConnection() @@ -55,6 +112,7 @@ void TabBarPrivate::initConnection() connect(hSplitBtn, &DToolButton::clicked, this, [this] { emit q->spliterClicked(Qt::Horizontal); }); connect(vSplitBtn, &DToolButton::clicked, this, [this] { emit q->spliterClicked(Qt::Vertical); }); connect(closeBtn, &DToolButton::clicked, q, &TabBar::closeRequested); + connect(Dtk::Gui::DGuiApplicationHelper::instance(), &Dtk::Gui::DGuiApplicationHelper::themeTypeChanged, this, &TabBarPrivate::updateBackgroundColor); } void TabBarPrivate::onCurrentTabChanged(int index) @@ -91,27 +149,19 @@ void TabBarPrivate::showMenu(QPoint pos) q->removeTab(file); }); menu.addAction(tr("Close All Files"), [=]() { - while (tabBar->count() > 0) { - auto file = tabBar->tabToolTip(0); - q->removeTab(file); - }; + closeAllTab({}); }); menu.addAction(tr("Close All Files Except This"), [=]() { auto curFile = tabBar->tabToolTip(curIndex); - int index = 0; - while (tabBar->count() > 1) { - auto file = tabBar->tabToolTip(index); - if (file != curFile) - q->removeTab(file); - else - index++; - }; + QStringList exceptList { curFile }; + closeAllTab(exceptList); }); menu.addSeparator(); - menu.addAction(tr("Open File Location"), [=]() { + menu.addAction(tr("Show Containing Folder"), [=]() { auto file = tabBar->tabToolTip(curIndex); - DDesktopServices::showFileItem(file); + QFileInfo info(file); + QDesktopServices::openUrl(QUrl::fromLocalFile(info.absolutePath())); }); menu.exec(QCursor::pos()); @@ -192,29 +242,25 @@ void TabBar::switchTab(const QString &fileName) d->tabBar->setCurrentIndex(index); } -void TabBar::removeTab(const QString &fileName) +void TabBar::removeTab(const QString &fileName, bool silent) { int index = indexOf(fileName); if (-1 == index) return; - QString text = d->tabBar->tabText(index); QFileInfo info(fileName); - if (info.exists() && text.length() > 0 && text.at(0) == "*") { - int ret = QMessageBox::question(this, QMessageBox::tr("Save Changes"), - QMessageBox::tr("The file has unsaved changes, will save?"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Cancel); - if (QMessageBox::Yes != ret && QMessageBox::No != ret) { - return; - } else if (QMessageBox::Yes == ret) { + if (!silent && info.exists() && d->isModified(index)) { + int ret = d->showConfirmDialog(fileName); + if (ret == 0) // save emit saveFileRequested(fileName); - } + else if (ret == 2 || ret == -1) // cancel or close + return; } emit tabClosed(fileName); editor.fileClosed(fileName); d->tabBar->removeTab(index); + editor.switchedFile(this->currentFileName()); } void TabBar::setCloseButtonVisible(bool visible) diff --git a/src/plugins/codeeditor/gui/tabbar.h b/src/plugins/codeeditor/gui/tabbar.h index 89cdde9e2..c5a3c2d68 100644 --- a/src/plugins/codeeditor/gui/tabbar.h +++ b/src/plugins/codeeditor/gui/tabbar.h @@ -23,7 +23,7 @@ class TabBar : public QWidget void setCurrentIndex(int index); void switchTab(const QString &fileName); - void removeTab(const QString &fileName); + void removeTab(const QString &fileName, bool silent = false); void setCloseButtonVisible(bool visible); void setSplitButtonVisible(bool visible); diff --git a/src/plugins/codeeditor/gui/tabwidget.cpp b/src/plugins/codeeditor/gui/tabwidget.cpp index 3e21484d4..d13004efb 100644 --- a/src/plugins/codeeditor/gui/tabwidget.cpp +++ b/src/plugins/codeeditor/gui/tabwidget.cpp @@ -5,10 +5,10 @@ #include "tabwidget.h" #include "private/tabwidget_p.h" #include "transceiver/codeeditorreceiver.h" -#include "find/editordocumentfind.h" #include "common/common.h" #include "settings/settingsdefine.h" #include "base/abstractaction.h" +#include "recent/recentopenwidget.h" #include "services/window/windowservice.h" @@ -120,10 +120,15 @@ void TabWidgetPrivate::initUI() editorLayout->addWidget(spaceWidget); tabBar = new TabBar(q); + symbolBar = new SymbolBar(q); mainLayout->addWidget(tabBar, 0, Qt::AlignTop); + mainLayout->addWidget(symbolBar, 0, Qt::AlignTop); mainLayout->addLayout(editorLayout); + openedWidget = new RecentOpenWidget(q); + openedWidget->hide(); + auto holder = createFindPlaceHolder(); if (holder) mainLayout->addWidget(holder); @@ -135,6 +140,7 @@ void TabWidgetPrivate::initConnection() connect(tabBar, &TabBar::tabClosed, this, &TabWidgetPrivate::onTabClosed); connect(tabBar, &TabBar::spliterClicked, this, &TabWidgetPrivate::onSpliterClicked); connect(tabBar, &TabBar::closeRequested, q, &TabWidget::closeRequested); + connect(tabBar, &TabBar::saveFileRequested, q, &TabWidget::saveFile); connect(EditorCallProxy::instance(), &EditorCallProxy::reqAddAnnotation, this, &TabWidgetPrivate::handleAddAnnotation); connect(EditorCallProxy::instance(), &EditorCallProxy::reqRemoveAnnotation, this, &TabWidgetPrivate::handleRemoveAnnotation); @@ -151,6 +157,28 @@ void TabWidget::handleSetComment() editor->commentOperation(); } +void TabWidget::handleShowOpenedFiles(const int &x, const int &y, const QSize &size) +{ + int count = d->tabBar->tabCount(); + if (count < 2) + return; + + d->openedWidget->setWindowFlags(Qt::Popup); + QSize popupSize = d->openedWidget->size(); + + int posX = (size.width() - popupSize.width()) / 2 + x; + int posY = (size.height() - popupSize.height()) / 2 + y; + + d->openedWidget->move(posX, posY); + d->openedWidget->setOpenedFiles(d->recentOpenedFiles); + d->openedWidget->setListViewSelection(1); + connect(d->openedWidget, &RecentOpenWidget::triggered, this, [=](const QModelIndex &index) { + d->tabBar->setCurrentIndex(d->tabBar->indexOf(index.data(RecentOpenWidget::RecentOpenedUserRole::FilePathRole).toString())); + }); + d->openedWidget->show(); + d->openedWidget->setFocusListView(); +} + QWidget *TabWidgetPrivate::createSpaceWidget() { QWidget *widget = new QWidget(q); @@ -191,12 +219,12 @@ QWidget *TabWidgetPrivate::createSpaceWidget() QWidget *TabWidgetPrivate::createFindPlaceHolder() { + docFind = new EditorDocumentFind(q); auto &ctx = dpfInstance.serviceContext(); WindowService *windowService = ctx.service(WindowService::name()); if (!windowService) return nullptr; - auto docFind = new EditorDocumentFind(q); return windowService->createFindPlaceHolder(q, docFind); } @@ -210,6 +238,7 @@ TextEditor *TabWidgetPrivate::createEditor(const QString &fileName) connect(editor, &TextEditor::cursorRecordChanged, this, &TabWidgetPrivate::onCursorRecordChanged); connect(editor, &TextEditor::fileSaved, tabBar, &TabBar::onFileSaved); connect(editor, &TextEditor::requestOpenFiles, this, &TabWidgetPrivate::handleOpenFiles); + connect(editor, &TextEditor::cursorPositionChanged, symbolBar, &SymbolBar::updateSymbol); editor->setFile(fileName); editor->setCursorPosition(0, 0); @@ -220,6 +249,7 @@ TextEditor *TabWidgetPrivate::createEditor(const QString &fileName) }); editorMng.insert(fileName, editor); + recentOpenedFiles.prepend(fileName); return editor; } @@ -309,7 +339,7 @@ void TabWidgetPrivate::replaceRange(const QString &fileName, const newlsp::Range void TabWidgetPrivate::doSave() { if (auto editor = currentTextEditor()) - editor->save(); + q->saveFile(editor->getFile()); } void TabWidgetPrivate::removePositionRecord(const QString &fileName) @@ -332,7 +362,10 @@ void TabWidgetPrivate::onTabSwitched(const QString &fileName) if (!editorMng.contains(fileName)) return; + symbolBar->setPath(fileName); editorLayout->setCurrentWidget(editorMng[fileName]); + recentOpenedFiles.removeOne(fileName); + recentOpenedFiles.prepend(fileName); changeFocusProxy(); } @@ -345,6 +378,7 @@ void TabWidgetPrivate::onTabClosed(const QString &fileName) Inotify::globalInstance()->removePath(fileName); removePositionRecord(fileName); editorMng.remove(fileName); + recentOpenedFiles.removeOne(fileName); editorLayout->removeWidget(editor); changeFocusProxy(); @@ -352,6 +386,7 @@ void TabWidgetPrivate::onTabClosed(const QString &fileName) editor->deleteLater(); if (editorMng.isEmpty()) { + symbolBar->clear(); q->setSplitButtonVisible(false); emit q->closeRequested(); } @@ -563,10 +598,37 @@ void TabWidget::setText(const QString &text) } } +QString TabWidget::fileText(const QString &fileName, bool *success) +{ + if (auto editor = d->findEditor(fileName)) { + if (success) *success = true; + return editor->text(); + } + + if (success) *success = false; + return {}; +} + +void TabWidget::replaceAll(const QString &fileName, const QString &oldText, + const QString &newText, bool caseSensitive, bool wholeWords) +{ + if (auto editor = d->findEditor(fileName)) + d->docFind->replaceAll(editor, oldText, newText, caseSensitive, wholeWords); +} + +void TabWidget::replaceRange(const QString &fileName, int line, int index, int length, const QString &after) +{ + if (auto editor = d->findEditor(fileName)) + editor->replaceRange(line, index, line, index + length, after); +} + void TabWidget::saveAll() const { - for (auto editor : d->editorMng.values()) + for (auto editor : d->editorMng.values()) { + Inotify::globalInstance()->removePath(editor->getFile()); editor->save(); + Inotify::globalInstance()->addPath(editor->getFile()); + } } bool TabWidget::saveAs(const QString &from, const QString &to) @@ -720,19 +782,6 @@ void TabWidget::gotoPreviousPosition() editor->gotoPosition(record.pos); } -bool TabWidget::checkAndResetSaveState(const QString &fileName) -{ - if (auto editor = d->findEditor(fileName)) { - bool ret = editor->isSaved(); - if (ret) - editor->resetSaveState(); - - return ret; - } - - return false; -} - void TabWidget::setEditorCursorPosition(int pos) { if (auto editor = d->currentTextEditor()) @@ -837,6 +886,7 @@ void TabWidget::openFile(const QString &fileName) // add file monitor Inotify::globalInstance()->addPath(fileName); + d->symbolBar->setPath(fileName); d->tabBar->setFileName(fileName); TextEditor *editor = d->createEditor(fileName); @@ -872,6 +922,15 @@ void TabWidget::gotoPosition(int line, int column) editor->gotoPosition(editor->positionFromLineIndex(line, column)); } +void TabWidget::saveFile(const QString &fileName) +{ + if (auto editor = d->findEditor(fileName)) { + Inotify::globalInstance()->removePath(fileName); + editor->save(); + Inotify::globalInstance()->addPath(fileName); + } +} + void TabWidget::dragEnterEvent(QDragEnterEvent *event) { if (!event->mimeData()->hasUrls()) diff --git a/src/plugins/codeeditor/gui/tabwidget.h b/src/plugins/codeeditor/gui/tabwidget.h index 7936a3a01..a64f3b783 100644 --- a/src/plugins/codeeditor/gui/tabwidget.h +++ b/src/plugins/codeeditor/gui/tabwidget.h @@ -25,6 +25,10 @@ class TabWidget : public QWidget QStringList modifiedFiles() const; QStringList openedFiles() const; void setText(const QString &text); + QString fileText(const QString &fileName, bool *success = nullptr); + void replaceAll(const QString &fileName, const QString &oldText, + const QString &newText, bool caseSensitive, bool wholeWords); + void replaceRange(const QString &fileName, int line, int index, int length, const QString &after); void saveAll() const; bool saveAs(const QString &from, const QString &to); void reloadFile(const QString &fileName); @@ -42,7 +46,6 @@ class TabWidget : public QWidget Q_INVOKABLE void setCompletion(const QString &info, const QIcon &icon, const QKeySequence &key); void gotoNextPosition(); void gotoPreviousPosition(); - bool checkAndResetSaveState(const QString &fileName); void setEditorCursorPosition(int pos); int editorCursorPosition(); @@ -55,6 +58,7 @@ class TabWidget : public QWidget void toggleBreakpoint(); void clearAllBreakpoints(); void handleSetComment(); + void handleShowOpenedFiles(const int &x, const int &y, const QSize &size); int zoomValue(); void updateZoomValue(int value); @@ -67,7 +71,8 @@ public slots: void removeDebugLine(); void gotoLine(int line); void gotoPosition(int line, int column); - + void saveFile(const QString &fileName); + signals: void closeRequested(); void splitRequested(Qt::Orientation ori, const QString &fileName); diff --git a/src/plugins/codeeditor/gui/texteditor.cpp b/src/plugins/codeeditor/gui/texteditor.cpp index 7df9df61f..524ae1072 100644 --- a/src/plugins/codeeditor/gui/texteditor.cpp +++ b/src/plugins/codeeditor/gui/texteditor.cpp @@ -11,6 +11,8 @@ #include "Qsci/qscistyledtext.h" +#include + #include #include #include @@ -18,6 +20,8 @@ #include #include +DWIDGET_USE_NAMESPACE + TextEditor::TextEditor(QWidget *parent) : QsciScintilla(parent) { @@ -57,7 +61,7 @@ void TextEditor::setFile(const QString &fileName) setModified(false); editor.fileOpened(fileName); d->loadLexer(); - d->loadLSPStyle(); + d->initLanguageClient(); d->isAutoCompletionEnabled = true; endUndoAction(); } @@ -76,12 +80,19 @@ void TextEditor::save() if (!file.exists()) return; - if (!file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate)) + if (!file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate)) { + DDialog dialog; + dialog.setIcon(QIcon::fromTheme("dialog-warning")); + dialog.setWindowTitle(tr("Save File")); + QString msg(tr("The file \"%1\" has no write permission. Please add write permission and try again")); + dialog.setMessage(msg.arg(d->fileName)); + dialog.addButton(tr("Ok", "button"), true, DDialog::ButtonRecommend); + dialog.exec(); return; + } file.write(text().toUtf8()); file.close(); - d->isSaved = true; setModified(false); emit fileSaved(d->fileName); editor.fileSaved(d->fileName); @@ -105,7 +116,6 @@ void TextEditor::saveAs(const QString &fileName) d->fileName = fileName; file.write(text().toUtf8()); file.close(); - d->isSaved = true; setModified(false); } @@ -113,7 +123,8 @@ void TextEditor::reload() { int line = 0, index = 0; getCursorPosition(&line, &index); - + const auto &markers = d->allMarkers(); + QString text; QFile file(d->fileName); if (file.open(QFile::OpenModeFlag::ReadOnly)) { @@ -123,6 +134,7 @@ void TextEditor::reload() setText(text.toUtf8()); setModified(false); + d->setMarkers(markers); setCursorPosition(line, index); } @@ -133,10 +145,11 @@ void TextEditor::addBreakpoint(int line, bool enabled) if (enabled) { markerAdd(line, TextEditorPrivate::Breakpoint); - editor.breakpointAdded(d->fileName, line + 1); } else { markerAdd(line, TextEditorPrivate::BreakpointDisabled); } + + editor.breakpointAdded(d->fileName, line + 1, enabled); } void TextEditor::removeBreakpoint(int line) @@ -326,7 +339,11 @@ void TextEditor::showTips(const QString &tips) void TextEditor::showTips(int pos, const QString &tips) { - if (!hasFocus()) + if (!hasFocus() || !d->tipsDisplayable) + return; + + bool isCtrlPressed = qApp->queryKeyboardModifiers().testFlag(Qt::ControlModifier); + if (isCtrlPressed) return; auto point = pointFromPosition(pos); @@ -578,19 +595,9 @@ void TextEditor::insertText(const QString &text) SendScintilla(SCI_SETEMPTYSELECTION, d->cursorPosition() + textData.size()); } -bool TextEditor::isSaved() const -{ - return d->isSaved; -} - -void TextEditor::resetSaveState() -{ - d->isSaved = false; -} - -LSPStyle *TextEditor::lspStyle() const +LanguageClientHandler *TextEditor::languageClient() const { - return d->lspStyle; + return d->languageClient; } int TextEditor::wordStartPositoin(int position) @@ -605,34 +612,34 @@ int TextEditor::wordEndPosition(int position) void TextEditor::switchHeaderSource() { - if (!d->lspStyle) + if (!d->languageClient) return; - - d->lspStyle->switchHeaderSource(d->fileName); + + d->languageClient->switchHeaderSource(d->fileName); } void TextEditor::followSymbolUnderCursor() { - if (!d->lspStyle) + if (!d->languageClient) return; - - d->lspStyle->followSymbolUnderCursor(); + + d->languageClient->followSymbolUnderCursor(); } void TextEditor::findUsage() { - if (!d->lspStyle) + if (!d->languageClient) return; - - d->lspStyle->findUsagesActionTriggered(); + + d->languageClient->findUsagesActionTriggered(); } void TextEditor::renameSymbol() { - if (!d->lspStyle) + if (!d->languageClient) return; - - d->lspStyle->renameActionTriggered(); + + d->languageClient->renameActionTriggered(); } void TextEditor::setCompletion(const QString &info, const QIcon &icon, const QKeySequence &key) @@ -741,7 +748,8 @@ void TextEditor::onCursorPositionChanged(int line, int index) void TextEditor::focusOutEvent(QFocusEvent *event) { - emit focusOut(); + Q_EMIT focusOut(); + Q_EMIT followTypeEnd(); QsciScintilla::focusOutEvent(event); } @@ -753,6 +761,18 @@ void TextEditor::keyPressEvent(QKeyEvent *event) QsciScintilla::keyPressEvent(event); } +void TextEditor::mouseMoveEvent(QMouseEvent *event) +{ + bool isCtrlPressed = qApp->queryKeyboardModifiers().testFlag(Qt::ControlModifier); + if (isCtrlPressed) { + auto point = event->pos(); + auto pos = positionFromPoint(point.x(), point.y()); + Q_EMIT requestFollowType(pos); + } + + QsciScintilla::mouseMoveEvent(event); +} + bool TextEditor::event(QEvent *event) { if (!d) @@ -761,6 +781,15 @@ bool TextEditor::event(QEvent *event) if (event->type() != QEvent::InputMethodQuery) d->contentsChanged = false; + if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { + auto keyEvent = static_cast(event); + if (keyEvent) { + bool isCtrlPressed = keyEvent->modifiers().testFlag(Qt::ControlModifier); + if (!isCtrlPressed) + Q_EMIT followTypeEnd(); + } + } + return QsciScintilla::event(event); } @@ -769,12 +798,14 @@ void TextEditor::contextMenuEvent(QContextMenuEvent *event) if (!contextMenuNeeded(event->pos().x(), event->pos().y())) return; + d->tipsDisplayable = false; int xPos = event->pos().x(); if (xPos <= d->marginsWidth()) { d->showMarginMenu(); } else { d->showContextMenu(); } + d->tipsDisplayable = true; } void TextEditor::dropEvent(QDropEvent *event) diff --git a/src/plugins/codeeditor/gui/texteditor.h b/src/plugins/codeeditor/gui/texteditor.h index a31d98068..44cea6278 100644 --- a/src/plugins/codeeditor/gui/texteditor.h +++ b/src/plugins/codeeditor/gui/texteditor.h @@ -5,6 +5,8 @@ #ifndef TEXTEDITOR_H #define TEXTEDITOR_H +#include "common/lsp/protocol/new/languagefeatures.h" + #include enum CommentSettings { @@ -14,7 +16,7 @@ enum CommentSettings { }; class CodeCompletionWidget; -class LSPStyle; +class LanguageClientHandler; class TextEditorPrivate; class TextEditor : public QsciScintilla { @@ -86,9 +88,7 @@ class TextEditor : public QsciScintilla void replaceRange(int lineFrom, int indexFrom, int lineTo, int indexTo, const QString &text); void replaceRange(int startPosition, int endPosition, const QString &text); void insertText(const QString &text); - bool isSaved() const; - void resetSaveState(); - LSPStyle *lspStyle() const; + LanguageClientHandler *languageClient() const; int wordStartPositoin(int position); int wordEndPosition(int position); void switchHeaderSource(); @@ -112,6 +112,7 @@ public slots: protected: virtual void focusOutEvent(QFocusEvent *event) override; virtual void keyPressEvent(QKeyEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; virtual bool event(QEvent *event) override; signals: @@ -121,8 +122,9 @@ public slots: void textRemoved(int pos, int len, int removed, const QString &text, int line); void zoomValueChanged(); void documentHovered(int pos); - void documentHoveredWithCtrl(int pos); void documentHoverEnd(int pos); + void requestFollowType(int pos); + void followTypeEnd(); void contextMenuRequested(QMenu *menu); void focusOut(); void cursorRecordChanged(int pos); diff --git a/src/plugins/codeeditor/gui/workspacewidget.cpp b/src/plugins/codeeditor/gui/workspacewidget.cpp index 247dde2fc..a7e215635 100644 --- a/src/plugins/codeeditor/gui/workspacewidget.cpp +++ b/src/plugins/codeeditor/gui/workspacewidget.cpp @@ -63,14 +63,23 @@ void WorkspaceWidgetPrivate::initActions() if (!windowService) return; + // add/del comment QAction *commentAction = new QAction(tr("Add/Delete Comment"), q); auto abstractCommentAction = new AbstractAction(commentAction, q); abstractCommentAction->setShortCutInfo("Editor.addAndRemoveComment", tr("Add/Remove Comment"), QKeySequence(Qt::Modifier::CTRL | Qt::Key_Slash)); - windowService->addAction(tr("&Add/Remove Comment"), abstractCommentAction); connect(commentAction, &QAction::triggered, this, &WorkspaceWidgetPrivate::handleSetComment); + + // show opened files + QAction *showOpenedAction = new QAction(tr("Show opened files"), q); + + auto abstractShowOpenedAction = new AbstractAction(showOpenedAction, q); + abstractShowOpenedAction->setShortCutInfo("Editor.showOpened", + tr("Show opened files"), QKeySequence(Qt::CTRL | Qt::Key_Tab)); + windowService->addAction(tr("&Show open files"), abstractShowOpenedAction); + connect(showOpenedAction, &QAction::triggered, this, &WorkspaceWidgetPrivate::handleShowOpenedFiles); } void WorkspaceWidgetPrivate::handleSetComment() @@ -81,6 +90,14 @@ void WorkspaceWidgetPrivate::handleSetComment() currentTabWidget()->handleSetComment(); } +void WorkspaceWidgetPrivate::handleShowOpenedFiles() +{ + if (!currentTabWidget()) + return; + + currentTabWidget()->handleShowOpenedFiles(q->pos().x() - q->mapFromGlobal(q->pos()).x(), q->pos().y() + q->mapToGlobal(q->pos()).y() - 100, q->size()); +} + void WorkspaceWidgetPrivate::initConnection() { connect(&fileCheckTimer, &QTimer::timeout, this, &WorkspaceWidgetPrivate::checkFileState); @@ -90,6 +107,7 @@ void WorkspaceWidgetPrivate::initConnection() connect(Inotify::globalInstance(), &Inotify::modified, this, &WorkspaceWidgetPrivate::onFileModified); connect(EditorCallProxy::instance(), &EditorCallProxy::reqOpenFile, this, &WorkspaceWidgetPrivate::handleOpenFile); + connect(EditorCallProxy::instance(), &EditorCallProxy::reqCloseFile, this, &WorkspaceWidgetPrivate::handleCloseFile); connect(EditorCallProxy::instance(), &EditorCallProxy::reqAddBreakpoint, this, &WorkspaceWidgetPrivate::handleAddBreakpoint); connect(EditorCallProxy::instance(), &EditorCallProxy::reqRemoveBreakpoint, this, &WorkspaceWidgetPrivate::handleRemoveBreakpoint); connect(EditorCallProxy::instance(), &EditorCallProxy::reqSetBreakpointEnabled, this, &WorkspaceWidgetPrivate::handleSetBreakpointEnabled); @@ -205,9 +223,6 @@ void WorkspaceWidgetPrivate::handleFileChanged() return; auto fileName = modifiedFileList.takeFirst(); - if (checkAndResetSaveState(fileName)) - return handleFileChanged(); - int ret = showFileChangedConfirmDialog(fileName); switch (ret) { case 0: // yes @@ -233,7 +248,7 @@ void WorkspaceWidgetPrivate::handleFileChanged() } break; case 4: // close - q->closeFileEditor(fileName); + handleCloseFile(fileName); handleFileChanged(); break; default: @@ -258,14 +273,14 @@ void WorkspaceWidgetPrivate::handleFileRemoved() handleFileRemoved(); break; case 2: // close - q->closeFileEditor(fileName); + handleCloseFile(fileName); handleFileRemoved(); break; case 3: // close all - q->closeFileEditor(fileName); + handleCloseFile(fileName); while (!removedFileList.isEmpty()) { fileName = removedFileList.takeFirst(); - q->closeFileEditor(fileName); + handleCloseFile(fileName); } break; default: @@ -273,18 +288,17 @@ void WorkspaceWidgetPrivate::handleFileRemoved() } } -bool WorkspaceWidgetPrivate::checkAndResetSaveState(const QString &fileName) +void WorkspaceWidgetPrivate::checkFileState() { - auto iter = std::find_if(tabWidgetList.begin(), tabWidgetList.end(), - [&fileName](TabWidget *w) { - return w->checkAndResetSaveState(fileName); - }); + for (const auto &file : removedFileList) { + if (!QFile::exists(file)) + continue; - return iter != tabWidgetList.end(); -} + removedFileList.removeOne(file); + Inotify::globalInstance()->addPath(file); + modifiedFileList.append(file); + } -void WorkspaceWidgetPrivate::checkFileState() -{ handleFileChanged(); handleFileRemoved(); } @@ -338,6 +352,11 @@ void WorkspaceWidgetPrivate::onCloseRequested() tabWidgetList.removeOne(tabWidget); tabWidget->deleteLater(); + if (!tabWidgetList.isEmpty()) { + tabWidgetList.last()->setFocus(); + editor.switchedFile(tabWidgetList.last()->currentFile()); + } + if (tabWidgetList.size() == 1) tabWidgetList.first()->setCloseButtonVisible(false); } @@ -353,6 +372,12 @@ void WorkspaceWidgetPrivate::handleOpenFile(const QString &workspace, const QStr tabWidget->openFile(fileName); } +void WorkspaceWidgetPrivate::handleCloseFile(const QString &fileName) +{ + if (auto tabWidget = currentTabWidget()) + tabWidget->closeFileEditor(fileName); +} + void WorkspaceWidgetPrivate::handleAddBreakpoint(const QString &fileName, int line, bool enabled) { for (auto tabWidget : tabWidgetList) @@ -497,6 +522,7 @@ void WorkspaceWidgetPrivate::onFocusChanged(QWidget *old, QWidget *now) return; focusTabWidget = tabWidget; + editor.switchedFile(focusTabWidget->currentFile()); } void WorkspaceWidgetPrivate::onZoomValueChanged() @@ -702,10 +728,47 @@ void WorkspaceWidget::setFileModified(const QString &fileName, bool isModified) tabWidget->setFileModified(fileName, isModified); } -void WorkspaceWidget::closeFileEditor(const QString &fileName) +QStringList WorkspaceWidget::openedFiles() const { + QStringList files; for (auto tabWidget : d->tabWidgetList) - tabWidget->closeFileEditor(fileName); + files << tabWidget->openedFiles(); + + // Delete duplicates + auto tmp = files.toSet(); + return tmp.toList(); +} + +QString WorkspaceWidget::fileText(const QString &fileName) const +{ + for (auto tabWidget : d->tabWidgetList) { + bool success = false; + const auto &text = tabWidget->fileText(fileName, &success); + if (success) + return text; + } + + return {}; +} + +void WorkspaceWidget::replaceAll(const QString &fileName, const QString &oldText, + const QString &newText, bool caseSensitive, bool wholeWords) +{ + for (auto tabWidget : d->tabWidgetList) { + tabWidget->replaceAll(fileName, oldText, newText, caseSensitive, wholeWords); + } +} + +void WorkspaceWidget::replaceRange(const QString &fileName, int line, int index, int length, const QString &after) +{ + for (auto tabWidget : d->tabWidgetList) { + tabWidget->replaceRange(fileName, line, index, length, after); + } +} + +TabWidget *WorkspaceWidget::currentTabWidget() const +{ + return d->currentTabWidget(); } void WorkspaceWidget::registerWidget(const QString &id, AbstractEditWidget *widget) diff --git a/src/plugins/codeeditor/gui/workspacewidget.h b/src/plugins/codeeditor/gui/workspacewidget.h index ef1c2da5b..76773fbca 100644 --- a/src/plugins/codeeditor/gui/workspacewidget.h +++ b/src/plugins/codeeditor/gui/workspacewidget.h @@ -8,6 +8,7 @@ #include #include +class TabWidget; class AbstractEditWidget; class WorkspaceWidgetPrivate; class WorkspaceWidget : public QWidget @@ -32,7 +33,11 @@ class WorkspaceWidget : public QWidget void undo(); void reloadFile(const QString &fileName); void setFileModified(const QString &fileName, bool isModified); - void closeFileEditor(const QString &fileName); + QStringList openedFiles() const; + QString fileText(const QString &fileName) const; + void replaceAll(const QString &fileName, const QString &oldText, const QString &newText, bool caseSensitive, bool wholeWords); + void replaceRange(const QString &fileName, int line, int index, int length, const QString &after); + TabWidget *currentTabWidget() const; void registerWidget(const QString &id, AbstractEditWidget *widget); void switchWidget(const QString &id); diff --git a/src/plugins/codeeditor/lexer/lexermanager.cpp b/src/plugins/codeeditor/lexer/lexermanager.cpp index 4d533d550..ae2586db4 100644 --- a/src/plugins/codeeditor/lexer/lexermanager.cpp +++ b/src/plugins/codeeditor/lexer/lexermanager.cpp @@ -86,8 +86,8 @@ QsciLexer *LexerManager::defaultSciLexer(const QString &language) lexer = new QsciLexerPython(); } else if (language.compare("js", Qt::CaseInsensitive) == 0) { lexer = new QsciLexerJavaScript(); - } else { - lexer = new QsciLexerCPP(); + } else if (language.compare("shell", Qt::CaseInsensitive) == 0) { + lexer = new QsciLexerBash(); } // The `lexer` is structured by `TextEditor` diff --git a/src/plugins/codeeditor/lsp/languageclienthandler.cpp b/src/plugins/codeeditor/lsp/languageclienthandler.cpp new file mode 100644 index 000000000..d990cbd8c --- /dev/null +++ b/src/plugins/codeeditor/lsp/languageclienthandler.cpp @@ -0,0 +1,769 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "languageclienthandler.h" +#include "private/languageclienthandler_p.h" +#include "gui/texteditor.h" +#include "gui/settings/editorsettings.h" +#include "gui/settings/settingsdefine.h" +#include "lspclientmanager.h" +#include "transceiver/codeeditorreceiver.h" +#include "codelens/codelens.h" +#include "symbol/symbolmanager.h" + +#include "services/project/projectservice.h" + +#include "Qsci/qscilexer.h" + +#include +#include + +#include + +DGUI_USE_NAMESPACE + +LanguageClientHandlerPrivate::LanguageClientHandlerPrivate(TextEditor *edit, LanguageClientHandler *qq) + : q(qq), + editor(edit) +{ +} + +LanguageClientHandlerPrivate::~LanguageClientHandlerPrivate() +{ + languageWorker->stop(); + thread.quit(); + thread.wait(); + languageWorker->deleteLater(); +} + +void LanguageClientHandlerPrivate::init() +{ + diagnosticFormat = "%1\n%2:%3"; + textChangedTimer.setSingleShot(true); + textChangedTimer.setInterval(200); + + positionChangedTimer.setSingleShot(true); + positionChangedTimer.setInterval(250); + + languageWorker = new LanguageWorker(q); + languageWorker->setTextEditor(editor); + languageWorker->moveToThread(&thread); + thread.start(); + + initConnection(); + initLspConnection(); + initIndicStyle(); +} + +void LanguageClientHandlerPrivate::initConnection() +{ + connect(editor, &TextEditor::textChanged, this, [this] { textChangedTimer.start(); }); + connect(editor, &TextEditor::cursorPositionChanged, this, [this] { positionChangedTimer.start(); }); + connect(editor, &TextEditor::documentHovered, this, &LanguageClientHandlerPrivate::handleHoveredStart); + connect(editor, &TextEditor::documentHoverEnd, this, &LanguageClientHandlerPrivate::handleHoverEnd); + connect(editor, &TextEditor::requestFollowType, this, &LanguageClientHandlerPrivate::handleFollowTypeStart); + connect(editor, &TextEditor::followTypeEnd, this, &LanguageClientHandlerPrivate::handleFollowTypeEnd); + connect(editor, &TextEditor::indicatorClicked, this, &LanguageClientHandlerPrivate::handleIndicClicked); + connect(editor, &TextEditor::contextMenuRequested, this, &LanguageClientHandlerPrivate::handleShowContextMenu); + connect(editor, &TextEditor::fileClosed, this, &LanguageClientHandlerPrivate::handleFileClosed); + connect(&renamePopup, &RenamePopup::editingFinished, this, &LanguageClientHandlerPrivate::handleRename); + connect(&textChangedTimer, &QTimer::timeout, this, &LanguageClientHandlerPrivate::delayTextChanged); + connect(&positionChangedTimer, &QTimer::timeout, this, &LanguageClientHandlerPrivate::delayPositionChanged); + connect(languageWorker, &LanguageWorker::highlightToken, this, &LanguageClientHandlerPrivate::handleHighlightToken); +} + +void LanguageClientHandlerPrivate::initLspConnection() +{ + auto client = getClient(); + if (!editor || !client) + return; + + auto referencesResult = qOverload(&newlsp::Client::requestResult); + connect(client, referencesResult, CodeLens::instance(), &CodeLens::displayReference, Qt::UniqueConnection); + connect(client, &newlsp::Client::switchHeaderSourceResult, this, &LanguageClientHandlerPrivate::handleSwitchHeaderSource, Qt::UniqueConnection); + + //bind signals to file diagnostics + connect(client, &newlsp::Client::publishDiagnostics, this, &LanguageClientHandlerPrivate::handleDiagnostics); + + auto tokenResult = qOverload &, const QString &>(&newlsp::Client::requestResult); + connect(client, tokenResult, this, &LanguageClientHandlerPrivate::handleTokenFull); + + auto completeResult = qOverload(&newlsp::Client::requestResult); + connect(client, completeResult, q, &LanguageClientHandler::completeFinished); + + connect(client, &newlsp::Client::hoverRes, this, &LanguageClientHandlerPrivate::handleShowHoverInfo); + connect(client, &newlsp::Client::renameRes, EditorCallProxy::instance(), &EditorCallProxy::reqDoRename, Qt::UniqueConnection); + connect(client, &newlsp::Client::rangeFormattingRes, this, &LanguageClientHandlerPrivate::handleRangeFormattingReplace); + connect(client, &newlsp::Client::documentHighlightResult, this, &LanguageClientHandlerPrivate::handleDocumentHighlight); + + /* to use QOverload cast virtual slot can't working */ + connect(client, qOverload(&newlsp::Client::definitionRes), + this, qOverload(&LanguageClientHandlerPrivate::handleCodeDefinition)); + connect(client, qOverload &, const QString &>(&newlsp::Client::definitionRes), + this, qOverload &, const QString &>(&LanguageClientHandlerPrivate::handleCodeDefinition)); + connect(client, qOverload &, const QString &>(&newlsp::Client::definitionRes), + this, qOverload &, const QString &>(&LanguageClientHandlerPrivate::handleCodeDefinition)); + + // symbol + auto docSymbolResult = qOverload &, const QString &>(&newlsp::Client::symbolResult); + connect(client, docSymbolResult, this, &LanguageClientHandlerPrivate::handleDocumentSymbolResult); + auto symbolInfoResult = qOverload &, const QString &>(&newlsp::Client::symbolResult); + connect(client, symbolInfoResult, this, &LanguageClientHandlerPrivate::handleSymbolInfomationResult); +} + +void LanguageClientHandlerPrivate::initIndicStyle() +{ + editor->indicatorDefine(TextEditor::PlainIndicator, TextEditor::INDIC_PLAIN); + editor->indicatorDefine(TextEditor::SquiggleIndicator, TextEditor::INDIC_SQUIGGLE); + editor->indicatorDefine(TextEditor::TTIndicator, TextEditor::INDIC_TT); + editor->indicatorDefine(TextEditor::DiagonalIndicator, TextEditor::INDIC_DIAGONAL); + editor->indicatorDefine(TextEditor::StrikeIndicator, TextEditor::INDIC_STRIKE); + editor->indicatorDefine(TextEditor::HiddenIndicator, TextEditor::INDIC_HIDDEN); + editor->indicatorDefine(TextEditor::BoxIndicator, TextEditor::INDIC_BOX); + editor->indicatorDefine(TextEditor::RoundBoxIndicator, TextEditor::INDIC_ROUNDBOX); + editor->indicatorDefine(TextEditor::StraightBoxIndicator, TextEditor::INDIC_STRAIGHTBOX); + editor->indicatorDefine(TextEditor::FullBoxIndicator, TextEditor::INDIC_FULLBOX); + editor->indicatorDefine(TextEditor::DashesIndicator, TextEditor::INDIC_DASH); + editor->indicatorDefine(TextEditor::DotsIndicator, TextEditor::INDIC_DOTS); + editor->indicatorDefine(TextEditor::SquiggleLowIndicator, TextEditor::INDIC_SQUIGGLELOW); + editor->indicatorDefine(TextEditor::DotBoxIndicator, TextEditor::INDIC_DOTBOX); + editor->indicatorDefine(TextEditor::GradientIndicator, TextEditor::INDIC_GRADIENT); + editor->indicatorDefine(TextEditor::GradientIndicator, TextEditor::INDIC_GRADIENTCENTRE); + editor->indicatorDefine(TextEditor::SquigglePixmapIndicator, TextEditor::INDIC_SQUIGGLEPIXMAP); + editor->indicatorDefine(TextEditor::ThickCompositionIndicator, TextEditor::INDIC_COMPOSITIONTHICK); + editor->indicatorDefine(TextEditor::ThinCompositionIndicator, TextEditor::INDIC_COMPOSITIONTHIN); + editor->indicatorDefine(TextEditor::TextColorIndicator, TextEditor::INDIC_TEXTFORE); + editor->indicatorDefine(TextEditor::TriangleIndicator, TextEditor::INDIC_POINT); + editor->indicatorDefine(TextEditor::TriangleCharacterIndicator, TextEditor::INDIC_POINTCHARACTER); +} + +QString LanguageClientHandlerPrivate::formatDiagnosticMessage(const QString &message, int type) +{ + auto result = message; + switch (type) { + case AnnotationType::ErrorAnnotation: + result = diagnosticFormat.arg("Parse Issue", "Error", result); + break; + default: + break; + } + + return result; +} + +bool LanguageClientHandlerPrivate::shouldStartCompletion(const QString &insertedText) +{ + if (insertedText.isEmpty()) + return false; + + QChar lastChar = insertedText.at(insertedText.count() - 1); + if (lastChar.isLetter() || lastChar.isNumber() || lastChar == QLatin1Char('_')) + return true; + + if (editor && editor->lexer()) { + auto spList = editor->lexer()->autoCompletionWordSeparators(); + auto iter = std::find_if(spList.begin(), spList.end(), + [&insertedText](const QString &sp) { + return insertedText.endsWith(sp); + }); + return iter != spList.end(); + } + + return false; +} + +int LanguageClientHandlerPrivate::wordPostion() +{ + int pos = editor->cursorPosition(); + if (editor->hasSelectedText()) { + int startPos = editor->wordStartPositoin(pos); + int endPos = editor->wordEndPosition(pos); + return (startPos + endPos) / 2; + } + + return pos; +} + +newlsp::Client *LanguageClientHandlerPrivate::getClient() +{ + if (prjectKey.isValid()) + return LSPClientManager::instance()->get(prjectKey); + + auto prjSrv = dpfGetService(dpfservice::ProjectService); + const auto &filePath = editor->getFile(); + const auto &allProject = prjSrv->getAllProjectInfo(); + for (const auto &prj : allProject) { + const auto &files = prj.sourceFiles(); + if (!files.contains(filePath)) + continue; + + prjectKey.language = prj.language().toStdString(); + prjectKey.workspace = prj.workspaceFolder().toStdString(); + break; + } + + if (!prjectKey.isValid()) { + auto prj = prjSrv->getActiveProjectInfo(); + prjectKey.language = prj.language().toStdString(); + prjectKey.workspace = prj.workspaceFolder().toStdString(); + } + + auto fileLangId = support_file::Language::id(filePath); + if (fileLangId != prjectKey.language.c_str()) { + fileLangId = support_file::Language::idAlias(fileLangId); + if (fileLangId != prjectKey.language.c_str()) + return nullptr; + } + + return LSPClientManager::instance()->get(prjectKey); +} + +void LanguageClientHandlerPrivate::handleDiagnostics(const newlsp::PublishDiagnosticsParams &data) +{ + if (!editor) + return; + + if (QUrl(QString::fromStdString(data.uri)).toLocalFile() != editor->getFile()) + return; + + // clear all flags of diagnostics + editor->SendScintilla(TextEditor::SCI_SETINDICATORCURRENT, TextEditor::INDIC_SQUIGGLE); + editor->SendScintilla(TextEditor::SCI_INDICATORCLEARRANGE, 0, editor->length()); + this->cleanDiagnostics(); + for (auto val : data.diagnostics) { + if (newlsp::Enum::DiagnosticSeverity::get()->Error == val.severity.value()) { // error + newlsp::Position start { val.range.start.line, val.range.start.character }; + newlsp::Position end { val.range.end.line, val.range.end.character }; + int startPos = editor->positionFromLineIndex(start.line, start.character); + int endPos = editor->positionFromLineIndex(end.line, end.character); + + editor->SendScintilla(TextEditor::SCI_SETINDICATORCURRENT, TextEditor::INDIC_SQUIGGLE); + editor->SendScintilla(TextEditor::SCI_INDICSETFORE, TextEditor::INDIC_SQUIGGLE, QColor(Qt::red)); + editor->SendScintilla(TextEditor::SCI_INDICATORFILLRANGE, static_cast(startPos), endPos - startPos); + + std::string message = val.message.value(); + diagnosticCache.append({ startPos, endPos, message.c_str(), AnnotationType::ErrorAnnotation }); + } + } +} + +void LanguageClientHandlerPrivate::cleanDiagnostics() +{ + diagnosticCache.clear(); +} + +void LanguageClientHandlerPrivate::handleTokenFull(const QList &tokens, const QString &filePath) +{ + if (!editor || editor->getFile() != filePath || !editor->lexer()) + return; + + metaObject()->invokeMethod(languageWorker, + "handleDocumentSemanticTokens", + Qt::QueuedConnection, + Q_ARG(QList, tokens)); +} + +void LanguageClientHandlerPrivate::handleShowHoverInfo(const newlsp::Hover &hover) +{ + if (!editor || hoverCache.getPosition() == -1) + return; + + std::string showText; + if (newlsp::any_contrast>(hover.contents)) { + auto markupStrings = std::any_cast>(hover.contents); + for (auto one : markupStrings) { + if (!showText.empty()) showText += "\n"; + + if (!one.value.empty()) // markedString value append + showText += one.value; + else if (!std::string(one).empty()) // markedString self is String append + showText += one; + }; + } else if (newlsp::any_contrast(hover.contents)) { + auto markupContent = std::any_cast(hover.contents); + showText = markupContent.value; + } else if (newlsp::any_contrast(hover.contents)) { + auto markedString = std::any_cast(hover.contents); + if (!std::string(markedString).empty()) { + showText = std::string(markedString); + } else { + showText = markedString.value; + } + } + + if (!showText.empty()) + editor->showTips(hoverCache.getPosition(), showText.c_str()); +} + +void LanguageClientHandlerPrivate::handleCodeDefinition(const newlsp::Location &data, const QString &filePath) +{ + if (!editor || editor->getFile() != filePath) + return; + + definitionCache.set(data); + if (definitionCache.switchMode() == DefinitionCache::ClickMode) { + auto textRange = definitionCache.getTextRange(); + setDefinitionSelectedStyle(textRange.getStart(), textRange.getEnd()); + } else { + gotoDefinition(); + definitionCache.clean(); + } +} + +void LanguageClientHandlerPrivate::handleCodeDefinition(const std::vector &data, const QString &filePath) +{ + if (!editor || data.empty() || editor->getFile() != filePath) + return; + + definitionCache.set(data); + if (definitionCache.switchMode() == DefinitionCache::ClickMode) { + auto textRange = definitionCache.getTextRange(); + if (!textRange.isEmpty()) + setDefinitionSelectedStyle(textRange.getStart(), textRange.getEnd()); + } else { + gotoDefinition(); + definitionCache.clean(); + } +} + +void LanguageClientHandlerPrivate::handleCodeDefinition(const std::vector &data, const QString &filePath) +{ + if (!editor || data.empty() || editor->getFile() != filePath) + return; + + definitionCache.set(data); + if (definitionCache.switchMode() == DefinitionCache::ClickMode) { + auto textRange = definitionCache.getTextRange(); + setDefinitionSelectedStyle(textRange.getStart(), textRange.getEnd()); + } else { + gotoDefinition(); + definitionCache.clean(); + } +} + +void LanguageClientHandlerPrivate::cleanDefinition(int pos) +{ + auto data = editor->SendScintilla(TextEditor::SCI_INDICATORALLONFOR, pos); + std::bitset<32> flags(static_cast(data)); + if (flags[TextEditor::INDIC_COMPOSITIONTHICK]) { + editor->SendScintilla(TextEditor::SCI_SETCURSOR, definitionCache.getCursor()); + editor->SendScintilla(TextEditor::SCI_INDICATORCLEARRANGE, 0, editor->length()); + } +} + +void LanguageClientHandlerPrivate::handleRangeFormattingReplace(const std::vector &edits, const QString &filePath) +{ + if (edits.empty() || !editor || editor->getFile() != filePath) + return; + + for (auto itera = edits.rbegin(); itera != edits.rend(); itera++) { + editor->replaceRange(itera->range.start.line, itera->range.start.character, + itera->range.end.line, itera->range.end.character, + QString::fromStdString(itera->newText)); + } +} + +void LanguageClientHandlerPrivate::setDefinitionSelectedStyle(int start, int end) +{ + editor->SendScintilla(TextEditor::SCI_SETINDICATORCURRENT, TextEditor::INDIC_COMPOSITIONTHICK); + editor->SendScintilla(TextEditor::SCI_INDICATORFILLRANGE, static_cast(start), end - start); + + auto cursor = editor->SendScintilla(TextEditor::SCI_GETCURSOR); + if (cursor != 8) { + definitionCache.setCursor(static_cast(cursor)); + editor->SendScintilla(TextEditor::SCI_SETCURSOR, 8); // hand from Scintilla platfrom.h + } +} + +void LanguageClientHandlerPrivate::delayTextChanged() +{ + if (!editor) + return; + + cleanDiagnostics(); + if (auto client = getClient()) { + const auto &content = editor->text(); + client->changeRequest(editor->getFile(), content.toUtf8()); + client->docSemanticTokensFull(editor->getFile()); + client->symbolRequest(editor->getFile()); + } +} + +void LanguageClientHandlerPrivate::delayPositionChanged() +{ + if (!editor || !getClient()) + return; + + lsp::Position pos; + editor->lineIndexFromPosition(wordPostion(), &pos.line, &pos.character); + getClient()->docHighlightRequest(editor->getFile(), pos); +} + +void LanguageClientHandlerPrivate::handleHoveredStart(int position) +{ + if (!editor || !getClient()) + return; + + if (!diagnosticCache.isEmpty()) { + auto iter = std::find_if(diagnosticCache.begin(), diagnosticCache.end(), + [position](const DiagnosticCache &cache) { + return cache.contains(position); + }); + if (iter != diagnosticCache.end()) { + const auto &msg = formatDiagnosticMessage(iter->message, iter->type); + editor->showTips(position, msg); + return; + } + } + + hoverCache.setPosition(position); + auto textRange = hoverCache.getTextRange(); + if (!textRange.isEmpty() && textRange.contaions(position)) + return; + + auto startPos = editor->SendScintilla(TextEditor::SCI_WORDSTARTPOSITION, static_cast(position), true); + auto endPos = editor->SendScintilla(TextEditor::SCI_WORDENDPOSITION, static_cast(position), true); + // hover in the empty area + if (startPos == endPos) + return; + + hoverCache.setTextRange(static_cast(startPos), static_cast(endPos)); + lsp::Position pos; + editor->lineIndexFromPosition(position, &pos.line, &pos.character); + getClient()->docHoverRequest(editor->getFile(), pos); +} + +void LanguageClientHandlerPrivate::handleHoverEnd(int position) +{ + if (!editor) + return; + + auto textRange = hoverCache.getTextRange(); + if (!textRange.isEmpty() && !hoverCache.getTextRange().contaions(position)) { + editor->cancelTips(); + hoverCache.clean(); + } +} + +void LanguageClientHandlerPrivate::handleFollowTypeStart(int position) +{ + if (!editor || editor->wordAtPosition(position).isEmpty()) { + handleFollowTypeEnd(); + return; + } + + auto startPos = editor->SendScintilla(TextEditor::SCI_WORDSTARTPOSITION, static_cast(position), true); + auto endPos = editor->SendScintilla(TextEditor::SCI_WORDENDPOSITION, static_cast(position), true); + RangeCache textRange { static_cast(startPos), static_cast(endPos) }; + + if (definitionCache.getTextRange() == textRange) + return; + + handleFollowTypeEnd(); + definitionCache.setPosition((startPos + endPos) / 2); + definitionCache.setTextRange(textRange); + definitionCache.cleanFromLsp(); + definitionCache.setSwitchMode(DefinitionCache::ClickMode); + + lsp::Position pos; + editor->lineIndexFromPosition(position, &pos.line, &pos.character); + if (getClient()) + getClient()->definitionRequest(editor->getFile(), pos); +} + +void LanguageClientHandlerPrivate::handleFollowTypeEnd() +{ + if (!editor || definitionCache.getTextRange().isEmpty()) + return; + + cleanDefinition(definitionCache.getPosition()); + definitionCache.clean(); +} + +void LanguageClientHandlerPrivate::handleIndicClicked(int line, int index) +{ + if (!editor) + return; + + auto pos = editor->positionFromLineIndex(line, index); + auto data = editor->SendScintilla(TextEditor::SCI_INDICATORALLONFOR, pos); + std::bitset<32> flags(static_cast(data)); + if (flags[TextEditor::INDIC_COMPOSITIONTHICK]) { + gotoDefinition(); + cleanDefinition(pos); + } +} + +void LanguageClientHandlerPrivate::handleShowContextMenu(QMenu *menu) +{ + if (!editor) + return; + + auto actionList = menu->actions(); + for (auto act : actionList) { + if (act->text() == tr("Refactor")) { + QMenu *subMenu = new QMenu(menu); + subMenu->addAction(tr("Rename Symbol Under Cursor"), q, &LanguageClientHandler::renameActionTriggered); + act->setMenu(subMenu); + break; + } + } + + auto act = menu->addAction(tr("Switch Header/Source"), q, std::bind(&LanguageClientHandler::switchHeaderSource, q, editor->getFile())); + menu->insertAction(actionList.first(), act); + + act = menu->addAction(tr("Follow Symbol Under Cursor"), q, &LanguageClientHandler::followSymbolUnderCursor); + menu->insertAction(actionList.first(), act); + + act = menu->addAction(tr("Find Usages"), q, &LanguageClientHandler::findUsagesActionTriggered); + menu->insertAction(actionList.first(), act); + + act = menu->addAction(tr("Format Selection"), q, &LanguageClientHandler::formatSelections); + menu->insertAction(actionList.first(), act); + menu->insertSeparator(actionList.first()); +} + +void LanguageClientHandlerPrivate::handleFileClosed(const QString &file) +{ + if (getClient()) + getClient()->closeRequest(file); +} + +void LanguageClientHandlerPrivate::handleRename(const QString &text) +{ + if (!editor || !getClient() || !renameCache.isValid()) + return; + + lsp::Position pos { renameCache.line, renameCache.column }; + getClient()->renameRequest(editor->getFile(), pos, text); + renameCache.clear(); +} + +void LanguageClientHandlerPrivate::gotoDefinition() +{ + if (definitionCache.getLocations().size() > 0) { + Q_EMIT editor->cursorRecordChanged(editor->cursorLastPosition()); + auto one = definitionCache.getLocations().front(); + EditorCallProxy::instance()->reqGotoPosition(QUrl(QString::fromStdString(one.uri)).toLocalFile(), + one.range.start.line, one.range.start.character); + } else if (definitionCache.getLocationLinks().size() > 0) { + Q_EMIT editor->cursorRecordChanged(editor->cursorLastPosition()); + auto one = definitionCache.getLocationLinks().front(); + EditorCallProxy::instance()->reqGotoPosition(QUrl(QString::fromStdString(one.targetUri)).toLocalFile(), + one.targetRange.end.line, one.targetRange.end.character); + } else { + Q_EMIT editor->cursorRecordChanged(editor->cursorLastPosition()); + auto one = definitionCache.getLocation(); + EditorCallProxy::instance()->reqGotoPosition(QUrl(QString::fromStdString(one.uri)).toLocalFile(), + one.range.start.line, one.range.start.character); + } +} + +void LanguageClientHandlerPrivate::handleSwitchHeaderSource(const QString &file) +{ + if (file.isEmpty()) + return; + + emit EditorCallProxy::instance()->reqOpenFile("", file); +} + +void LanguageClientHandlerPrivate::handleDocumentSymbolResult(const QList &docSymbols, + const QString &filePath) +{ + if (!editor || editor->getFile() != filePath) + return; + + SymbolManager::instance()->setDocumentSymbols(filePath, docSymbols); +} + +void LanguageClientHandlerPrivate::handleSymbolInfomationResult(const QList &symbolInfos, + const QString &filePath) +{ + if (!editor || editor->getFile() != filePath) + return; + + SymbolManager::instance()->setSymbolInformations(filePath, symbolInfos); +} + +void LanguageClientHandlerPrivate::handleDocumentHighlight(const QList &docHighlightList, + const QString &filePath) +{ + if (!editor || editor->getFile() != filePath) + return; + + editor->SendScintilla(TextEditor::SCI_SETINDICATORCURRENT, TextEditor::INDIC_FULLBOX); + editor->SendScintilla(TextEditor::SCI_INDICATORCLEARRANGE, 0, editor->length()); + QColor forgColor(0, 0, 0, 30); + QColor lineColor(0, 0, 0, 100); + if (DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::DarkType) { + forgColor.setRgb(200, 200, 200, 40); + lineColor.setRgb(200, 200, 200, 100); + } + editor->setIndicatorForegroundColor(forgColor, TextEditor::INDIC_FULLBOX); + editor->setIndicatorOutlineColor(lineColor, TextEditor::INDIC_FULLBOX); + for (const auto &dh : docHighlightList) { + int startPos = editor->positionFromLineIndex(dh.range.start.line, dh.range.start.character); + int endPos = editor->positionFromLineIndex(dh.range.end.line, dh.range.end.character); + editor->SendScintilla(TextEditor::SCI_INDICATORFILLRANGE, static_cast(startPos), endPos - startPos); + } +} + +void LanguageClientHandlerPrivate::handleHighlightToken(const QList &tokenList) +{ + // clear all text color + editor->SendScintilla(TextEditor::SCI_SETINDICATORCURRENT, TextEditor::INDIC_TEXTFORE); + editor->SendScintilla(TextEditor::SCI_INDICATORCLEARRANGE, 0, editor->length()); + for (const auto &token : tokenList) { + editor->SendScintilla(TextEditor::SCI_SETINDICATORCURRENT, TextEditor::INDIC_TEXTFORE); + editor->SendScintilla(TextEditor::SCI_INDICSETFLAGS, TextEditor::INDIC_TEXTFORE, 1); + editor->SendScintilla(TextEditor::SCI_SETINDICATORVALUE, token.color); + editor->SendScintilla(TextEditor::SCI_INDICATORFILLRANGE, + static_cast(token.startPostion), + token.field.length()); + } +} + +LanguageClientHandler::LanguageClientHandler(TextEditor *parent) + : QObject(parent), + d(new LanguageClientHandlerPrivate(parent, this)) +{ + d->init(); +} + +LanguageClientHandler::~LanguageClientHandler() +{ +} + +void LanguageClientHandler::requestCompletion(int line, int column) +{ + if (!d->getClient()) + return; + + lsp::CompletionContext context; + if (d->editor->lexer()) { + int pos = d->editor->positionFromLineIndex(line, column); + if (pos > 0) { + char ch = d->editor->SendScintilla(TextEditor::SCI_GETCHARAT, --pos); + auto spList = d->editor->lexer()->autoCompletionWordSeparators(); + auto iter = std::find_if(spList.begin(), spList.end(), + [&ch](const QString &sp) { + return sp.endsWith(ch); + }); + if (iter != spList.end()) { + context.kind = lsp::CompletionTriggerKind::TriggerCharacter; + context.triggerCharacter = ch; + } + } + } + + lsp::Position pos { line, column }; + d->getClient()->completionRequest(d->editor->getFile(), pos, context); +} + +void LanguageClientHandler::updateTokens() +{ + if (auto client = d->getClient()) { + client->openRequest(d->editor->getFile()); + client->docSemanticTokensFull(d->editor->getFile()); + client->symbolRequest(d->editor->getFile()); + } +} + +lsp::SemanticTokenType::type_value LanguageClientHandler::tokenToDefine(int token) +{ + auto client = d->getClient(); + if (!client) + return {}; + auto initSecTokensProvider = client->initSecTokensProvider(); + if (0 <= token && token < initSecTokensProvider.legend.tokenTypes.size()) + return initSecTokensProvider.legend.tokenTypes[token]; + return {}; +} + +QColor LanguageClientHandler::symbolIndicColor(lsp::SemanticTokenType::type_value token, + QList modifier) +{ + Q_UNUSED(modifier); + QMap result; + + const auto &filePath = d->editor->getFile(); + auto langId = support_file::Language::id(filePath); + + return LSPClientManager::instance()->highlightColor(langId, token); +} + +void LanguageClientHandler::refreshTokens() +{ + if (!d->editor || !d->getClient()) + return; + + d->getClient()->docSemanticTokensFull(d->editor->getFile()); +} + +void LanguageClientHandler::switchHeaderSource(const QString &file) +{ + if (!d->getClient()) + return; + + d->getClient()->switchHeaderSource(file); +} + +void LanguageClientHandler::followSymbolUnderCursor() +{ + if (!d->editor || !d->editor->hasFocus() || !d->getClient()) + return; + + d->definitionCache.setSwitchMode(DefinitionCache::ActionMode); + + lsp::Position pos; + d->editor->lineIndexFromPosition(d->wordPostion(), &pos.line, &pos.character); + d->getClient()->definitionRequest(d->editor->getFile(), pos); +} + +void LanguageClientHandler::findUsagesActionTriggered() +{ + if (!d->editor || !d->getClient()) + return; + + lsp::Position pos; + d->editor->lineIndexFromPosition(d->wordPostion(), &pos.line, &pos.character); + d->getClient()->referencesRequest(d->editor->getFile(), pos); +} + +void LanguageClientHandler::renameActionTriggered() +{ + if (!d->editor) + return; + + int pos = d->editor->cursorPosition(); + const auto &symbol = d->editor->wordAtPosition(pos); + if (symbol.isEmpty()) + return; + + d->editor->lineIndexFromPosition(pos, &d->renameCache.line, &d->renameCache.column); + auto point = d->editor->pointFromPosition(pos); + point = d->editor->mapToGlobal(point); + + d->renamePopup.setOldName(symbol); + d->renamePopup.exec(point); +} + +void LanguageClientHandler::formatSelections() +{ + if (!d->getClient() || !d->editor || !d->editor->hasSelectedText()) + return; + + int lineFrom, indexFrom, lineTo, indexTo; + d->editor->getSelection(&lineFrom, &indexFrom, &lineTo, &indexTo); + + newlsp::Position selStart { lineFrom, indexFrom }; + newlsp::Position selEnd { lineTo, indexTo }; + newlsp::DocumentRangeFormattingParams params; + params.textDocument.uri = QUrl::fromLocalFile(d->editor->getFile()).toString().toStdString(); + params.range = { selStart, selEnd }; + params.options.tabSize = EditorSettings::instance()->value(Node::Behavior, Group::TabGroup, Key::TabSize, 4).toInt(); + params.options.insertSpaces = true; + + d->getClient()->rangeFormatting(d->editor->getFile(), params); +} diff --git a/src/plugins/codeeditor/lsp/languageclienthandler.h b/src/plugins/codeeditor/lsp/languageclienthandler.h new file mode 100644 index 000000000..ca869de8f --- /dev/null +++ b/src/plugins/codeeditor/lsp/languageclienthandler.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LANGUAGECLIENTHANDLER_H +#define LANGUAGECLIENTHANDLER_H + +#include "common/common.h" + +#include + +class TextEditor; +class LanguageClientHandlerPrivate; +class LanguageClientHandler : public QObject +{ + Q_OBJECT +public: + explicit LanguageClientHandler(TextEditor *parent); + ~LanguageClientHandler(); + + void requestCompletion(int line, int column); + void updateTokens(); + void refreshTokens(); + lsp::SemanticTokenType::type_value tokenToDefine(int token); + QColor symbolIndicColor(lsp::SemanticTokenType::type_value token, + QList modifier); + +public slots: + void switchHeaderSource(const QString &file); + void followSymbolUnderCursor(); + void findUsagesActionTriggered(); + void renameActionTriggered(); + void formatSelections(); + +signals: + void completeFinished(const lsp::CompletionProvider &provider); + +private: + QSharedPointer d { nullptr }; +}; + +#endif // LANGUAGECLIENTHANDLER_H diff --git a/src/plugins/codeeditor/lsp/languageworker.cpp b/src/plugins/codeeditor/lsp/languageworker.cpp new file mode 100644 index 000000000..0758628e5 --- /dev/null +++ b/src/plugins/codeeditor/lsp/languageworker.cpp @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "languageworker.h" +#include "gui/texteditor.h" + +LanguageWorker::LanguageWorker(LanguageClientHandler *handler) + : clientHandler(handler) +{ + qRegisterMetaType>("QList"); +} + +void LanguageWorker::stop() +{ + isStop = true; +} + +void LanguageWorker::setTextEditor(TextEditor *edit) +{ + textEditor = edit; +} + +void LanguageWorker::handleDocumentSemanticTokens(const QList &tokens) +{ + isStop = false; + QList docTokenList; + int cacheLine = 0; + int cacheColumn = 0; + for (auto val : tokens) { + if (isStop) + return; + + cacheLine += val.start.line; + if (val.start.line != 0) + cacheColumn = 0; + + cacheColumn += val.start.character; + auto startPos = textEditor->positionFromLineIndex(cacheLine, cacheColumn); + auto wordEndPos = textEditor->SendScintilla(TextEditor::SCI_WORDENDPOSITION, static_cast(startPos), true); + auto wordStartPos = textEditor->SendScintilla(TextEditor::SCI_WORDSTARTPOSITION, static_cast(startPos), true); + if (startPos == 0 || wordEndPos == textEditor->length() || wordStartPos != startPos) + continue; + + QString sourceText = textEditor->text(static_cast(wordStartPos), static_cast(wordEndPos)); + if (!sourceText.isEmpty() && sourceText.length() == val.length) { + QString tokenValue = clientHandler->tokenToDefine(val.tokenType); + QColor color = clientHandler->symbolIndicColor(tokenValue, {}); +#if 0 + qInfo() << "line:" << cacheLine; + qInfo() << "charStart:" << val.start.character; + qInfo() << "charLength:" << val.length; + qInfo() << "text:" << sourceText; + qInfo() << "tokenType:" << val.tokenType; + qInfo() << "tokenModifiers:" << val.tokenModifiers; + qInfo() << "tokenValue:" << tokenValue; +#endif + docTokenList.append({ startPos, sourceText, color }); + } + } + + Q_EMIT highlightToken(docTokenList); +} diff --git a/src/plugins/codeeditor/lsp/languageworker.h b/src/plugins/codeeditor/lsp/languageworker.h new file mode 100644 index 000000000..ee76e2017 --- /dev/null +++ b/src/plugins/codeeditor/lsp/languageworker.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LANGUAGEWORKER_H +#define LANGUAGEWORKER_H + +#include "languageclienthandler.h" + +#include "common/lsp/protocol/protocol.h" + +#include + +class TextEditor; +class LanguageWorker : public QObject +{ + Q_OBJECT +public: + struct DocumentToken + { + int startPostion = -1; + QString field; + QColor color; + }; + + explicit LanguageWorker(LanguageClientHandler *handler); + + void stop(); + void setTextEditor(TextEditor *edit); + +public Q_SLOTS: + void handleDocumentSemanticTokens(const QList &tokens); + +Q_SIGNALS: + void highlightToken(const QList &tokenList); + +private: + LanguageClientHandler *clientHandler { nullptr }; + TextEditor *textEditor { nullptr }; + QAtomicInteger isStop {false}; +}; + +Q_DECLARE_METATYPE(LanguageWorker::DocumentToken) +#endif // LANGUAGEWORKER_H diff --git a/src/plugins/codeeditor/lsp/lspclientmanager.cpp b/src/plugins/codeeditor/lsp/lspclientmanager.cpp index 3fe4c8c73..f4c13c1f0 100644 --- a/src/plugins/codeeditor/lsp/lspclientmanager.cpp +++ b/src/plugins/codeeditor/lsp/lspclientmanager.cpp @@ -14,8 +14,7 @@ LSPClientManager::LSPClientManager() LSPClientManager::~LSPClientManager() { - if (client) - delete client; + qDeleteAll(clientHash.values()); } LSPClientManager *LSPClientManager::instance() @@ -29,20 +28,17 @@ newlsp::Client *LSPClientManager::get(const newlsp::ProjectKey &key) if (!key.isValid()) return nullptr; - if (client) { - qApp->metaObject()->invokeMethod(client, "selectLspServer", Q_ARG(const newlsp::ProjectKey &, key)); + if (clientHash.contains(key)) { + qApp->metaObject()->invokeMethod(clientHash[key], "selectLspServer", Q_ARG(const newlsp::ProjectKey &, key)); } else { - client = new newlsp::Client(); + auto client = new newlsp::Client(); qApp->metaObject()->invokeMethod(client, "selectLspServer", Q_ARG(const newlsp::ProjectKey &, key)); - } - - if (!projectKeys.contains(key)) { QString complieDB_Path = QString::fromStdString(key.workspace) + QDir::separator() + ".unioncode"; qApp->metaObject()->invokeMethod(client, "initRequest", Q_ARG(const QString &, complieDB_Path)); - projectKeys.append(key); + clientHash.insert(key, client); } - return client; + return clientHash[key]; } QColor LSPClientManager::highlightColor(const QString &langId, lsp::SemanticTokenType::type_value token) diff --git a/src/plugins/codeeditor/lsp/lspclientmanager.h b/src/plugins/codeeditor/lsp/lspclientmanager.h index 5967b309e..2791fdefa 100644 --- a/src/plugins/codeeditor/lsp/lspclientmanager.h +++ b/src/plugins/codeeditor/lsp/lspclientmanager.h @@ -20,9 +20,7 @@ class LSPClientManager explicit LSPClientManager(); ~LSPClientManager(); - QList projectKeys; - newlsp::Client *client { nullptr }; - + QHash clientHash; QMap styleMap; }; diff --git a/src/plugins/codeeditor/lsp/private/languageclienthandler_p.h b/src/plugins/codeeditor/lsp/private/languageclienthandler_p.h new file mode 100644 index 000000000..6dd73dca3 --- /dev/null +++ b/src/plugins/codeeditor/lsp/private/languageclienthandler_p.h @@ -0,0 +1,231 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LANGUAGECLIENTHANDLER_P_H +#define LANGUAGECLIENTHANDLER_P_H + +#include "lsp/languageclienthandler.h" +#include "renamepopup/renamepopup.h" +#include "lsp/languageworker.h" + +class RangeCache +{ + int start = 0; + int end = 0; + +public: + RangeCache(int start, int end) + : start(start), end(end) {} + RangeCache() {} + void clean() + { + start = 0; + end = 0; + } + bool isEmpty() { return !(start && end); } + bool contaions(int pos) { return pos >= start && pos <= end; } + int getStart() const { return start; } + void setStart(int value) { start = value; } + int getEnd() const { return end; } + void setEnd(int value) { end = value; } + bool operator==(const RangeCache &other) + { + return start == other.start && end == other.end; + } + bool operator!=(const RangeCache &other) + { + return !(operator==(other)); + } +}; + +class PositionCache +{ + int position = -1; + RangeCache textRange; + +public: + PositionCache() {} + PositionCache(int pos) + : position(pos) {} + void clean() + { + position = -1; + textRange.clean(); + } + bool isEmpty() { return position == -1 || textRange.isEmpty(); } + int getPosition() const { return position; } + void setPosition(int value) { position = value; } + RangeCache getTextRange() const { return textRange; } + void setTextRange(int start, int end) + { + textRange.setStart(start); + textRange.setEnd(end); + } +}; + +class DefinitionCache : public PositionCache +{ +public: + enum SwitchMode { + ActionMode, // menu + ClickMode // click with Ctrl + }; + + void clean() + { + cleanFromLsp(); + cursor = -1; + PositionCache::clean(); + textRange.clean(); + mode = ClickMode; + } + void cleanFromLsp() + { + if (locations) location.reset(); + if (location) location.reset(); + if (locationLinks) locationLinks.reset(); + } + + bool isEmpty() + { + return locations->empty() && !location.has_value() && locationLinks->empty() + && cursor == -1 && PositionCache::isEmpty() + && textRange.isEmpty(); + } + std::vector getLocations() const + { + if (locations.has_value()) + return locations.value(); + return {}; + } + newlsp::Location getLocation() const { return location.value(); } + std::vector getLocationLinks() const { return locationLinks.value(); } + void set(const std::vector &value) { locations = value; } + void set(const newlsp::Location &value) { location = value; } + void set(const std::vector &value) { locationLinks = value; } + int getCursor() const { return cursor; } + void setCursor(int value) { cursor = value; } + RangeCache getTextRange() const { return textRange; } + void setTextRange(const RangeCache &value) { textRange = value; } + void setSwitchMode(SwitchMode mode) { this->mode = mode; } + SwitchMode switchMode() const { return mode; } + +private: + std::optional> locations {}; + std::optional> locationLinks {}; + std::optional location {}; + RangeCache textRange {}; + int cursor = 0; //Invalid + SwitchMode mode { ClickMode }; +}; + +class HoverCache : public PositionCache +{ +public: + void clean() { PositionCache::clean(); } + bool isEmpty() { return PositionCache::isEmpty(); } +}; + +class CompletionCache +{ +public: + lsp::CompletionProvider provider; +}; + +struct RenamePositionCache +{ + int line = -1; + int column = -1; + + void clear() + { + line = -1; + column = -1; + } + + bool isValid() { return line != -1 && column != -1; } +}; + +struct DiagnosticCache +{ + int startPos = 0; + int endPos = 0; + QString message; + int type; + +public: + bool contains(int pos) const + { + return (pos >= startPos && pos <= endPos); + } +}; + +class LanguageClientHandlerPrivate : public QObject +{ + Q_OBJECT +public: + explicit LanguageClientHandlerPrivate(TextEditor *edit, LanguageClientHandler *qq); + ~LanguageClientHandlerPrivate(); + + void init(); + void initConnection(); + void initLspConnection(); + void initIndicStyle(); + + QString formatDiagnosticMessage(const QString &message, int type); + bool shouldStartCompletion(const QString &insertedText); + int wordPostion(); + newlsp::Client *getClient(); + + void cleanDiagnostics(); + void cleanDefinition(int pos); + void setDefinitionSelectedStyle(int start, int end); + void gotoDefinition(); + +public slots: + void handleTokenFull(const QList &tokens, const QString &filePath); + void handleShowHoverInfo(const newlsp::Hover &hover); + void handleCodeDefinition(const newlsp::Location &data, const QString &filePath); + void handleCodeDefinition(const std::vector &data, const QString &filePath); + void handleCodeDefinition(const std::vector &data, const QString &filePath); + void handleDiagnostics(const newlsp::PublishDiagnosticsParams &data); + void handleRangeFormattingReplace(const std::vector &edits, const QString &filePath); + void delayTextChanged(); + void delayPositionChanged(); + void handleHoveredStart(int position); + void handleHoverEnd(int position); + void handleFollowTypeStart(int position); + void handleFollowTypeEnd(); + void handleIndicClicked(int line, int index); + void handleShowContextMenu(QMenu *menu); + void handleFileClosed(const QString &file); + void handleRename(const QString &text); + void handleSwitchHeaderSource(const QString &file); + void handleDocumentSymbolResult(const QList &docSymbols, const QString &filePath); + void handleSymbolInfomationResult(const QList &symbolInfos, const QString &filePath); + void handleDocumentHighlight(const QList &docHighlightList, const QString &filePath); + void handleHighlightToken(const QList &tokenList); + +public: + LanguageClientHandler *q; + + CompletionCache completionCache; + DefinitionCache definitionCache; + HoverCache hoverCache; + RenamePopup renamePopup; + RenamePositionCache renameCache; + TextEditor *editor { nullptr }; + QList tokensCache; + QList diagnosticCache; + QString diagnosticFormat; + newlsp::ProjectKey prjectKey; + + QTimer textChangedTimer; + QTimer positionChangedTimer; + + QThread thread; + LanguageWorker *languageWorker { nullptr }; +}; + +#endif // LANGUAGECLIENTHANDLER_P_H diff --git a/src/plugins/codeeditor/status/editorlabel.cpp b/src/plugins/codeeditor/status/editorlabel.cpp new file mode 100644 index 000000000..ba4018297 --- /dev/null +++ b/src/plugins/codeeditor/status/editorlabel.cpp @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "editorlabel.h" + +#include +#include + +EditorLabel::EditorLabel(QWidget *parent) + : QWidget(parent) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + cursorlabel = new QLabel(this); + layout->addWidget(cursorlabel); +} + +void EditorLabel::updateCursor(int line, int column) +{ + QString format(tr("Line %1 Column %2")); + cursorlabel->setText(format.arg(line + 1).arg(column + 1)); +} diff --git a/src/plugins/codeeditor/status/editorlabel.h b/src/plugins/codeeditor/status/editorlabel.h new file mode 100644 index 000000000..bb890e8af --- /dev/null +++ b/src/plugins/codeeditor/status/editorlabel.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef EDITORLABEL_H +#define EDITORLABEL_H + +#include + +class QLabel; + +class EditorLabel : public QWidget +{ + Q_OBJECT +public: + explicit EditorLabel(QWidget *parent = nullptr); + +public Q_SLOTS: + void updateCursor(int line, int column); + +private: + QLabel *cursorlabel { nullptr }; +}; + +#endif // EDITORLABEL_H diff --git a/src/plugins/codeeditor/status/statusinfomanager.cpp b/src/plugins/codeeditor/status/statusinfomanager.cpp new file mode 100644 index 000000000..6850924e7 --- /dev/null +++ b/src/plugins/codeeditor/status/statusinfomanager.cpp @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "statusinfomanager.h" +#include "editorlabel.h" +#include "gui/texteditor.h" + +#include "services/window/windowservice.h" + +#include + +using namespace dpfservice; + +class StatusInfoManagerPrivate : public QObject +{ +public: + void init(WindowService *winSrv); + void initConnect(); + + void resetEditor(TextEditor *edit); + void updateLabelInfo(); + void handleFocusChanged(QWidget *old, QWidget *now); + +public: + TextEditor *currentEditor { nullptr }; + EditorLabel *editorLabel { nullptr }; +}; + +void StatusInfoManagerPrivate::init(WindowService *winSrv) +{ + editorLabel = new EditorLabel(); + winSrv->addStatusBarItem(editorLabel); +} + +void StatusInfoManagerPrivate::initConnect() +{ + connect(qApp, &QApplication::focusChanged, this, &StatusInfoManagerPrivate::handleFocusChanged); +} + +void StatusInfoManagerPrivate::resetEditor(TextEditor *edit) +{ + if (currentEditor == edit) + return; + + if (currentEditor) { + disconnect(currentEditor, &TextEditor::destroyed, this, 0); + disconnect(currentEditor, &TextEditor::cursorPositionChanged, editorLabel, &EditorLabel::updateCursor); + } + + currentEditor = edit; + connect(edit, &TextEditor::destroyed, this, [this] { currentEditor = nullptr; }); + connect(currentEditor, &TextEditor::cursorPositionChanged, editorLabel, &EditorLabel::updateCursor); + updateLabelInfo(); +} + +void StatusInfoManagerPrivate::updateLabelInfo() +{ + int line = 0, col = 0; + currentEditor->getCursorPosition(&line, &col); + editorLabel->updateCursor(line, col); +} + +void StatusInfoManagerPrivate::handleFocusChanged(QWidget *old, QWidget *now) +{ + Q_UNUSED(old) + + auto edit = qobject_cast(now); + if (!edit) + return; + + resetEditor(edit); +} + +StatusInfoManager::StatusInfoManager(QObject *parent) + : QObject(parent), + d(new StatusInfoManagerPrivate) +{ +} + +StatusInfoManager::~StatusInfoManager() +{ + delete d; +} + +StatusInfoManager *StatusInfoManager::instance() +{ + static StatusInfoManager ins; + return &ins; +} + +void StatusInfoManager::init(WindowService *winSrv) +{ + d->init(winSrv); + d->initConnect(); +} diff --git a/src/plugins/codeeditor/status/statusinfomanager.h b/src/plugins/codeeditor/status/statusinfomanager.h new file mode 100644 index 000000000..a6ff093cd --- /dev/null +++ b/src/plugins/codeeditor/status/statusinfomanager.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef STATUSINFOMANAGER_H +#define STATUSINFOMANAGER_H + +#include + +namespace dpfservice { +class WindowService; +} + +class StatusInfoManagerPrivate; +class StatusInfoManager : public QObject +{ + Q_OBJECT +public: + static StatusInfoManager *instance(); + + void init(dpfservice::WindowService *winSrv); + +private: + explicit StatusInfoManager(QObject *parent = nullptr); + ~StatusInfoManager(); + + StatusInfoManagerPrivate *const d; +}; + +#endif // STATUSINFOMANAGER_H diff --git a/src/plugins/codeeditor/symbol/symbolbar.cpp b/src/plugins/codeeditor/symbol/symbolbar.cpp new file mode 100644 index 000000000..02f75547f --- /dev/null +++ b/src/plugins/codeeditor/symbol/symbolbar.cpp @@ -0,0 +1,271 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "symbolbar.h" +#include "symbolview.h" +#include "symbolmanager.h" +#include "gui/texteditor.h" + +#include "services/project/projectservice.h" + +#include +#include + +#include +#include +#include + +inline constexpr int IconSize { 16 }; +inline constexpr int SeparatorSize { 12 }; + +using namespace dpfservice; +DWIDGET_USE_NAMESPACE + +CurmbItem::CurmbItem(CurmbType type, int index, QWidget *parent) + : QWidget(parent) +{ + this->type = type; + this->index = index; + setCursor(Qt::PointingHandCursor); +} + +CurmbItem::CurmbType CurmbItem::curmbType() const +{ + return type; +} + +void CurmbItem::setText(const QString &text) +{ + displayText = text; + auto rect = fontMetrics().boundingRect(text); + int w = rect.width() + qCeil(font().pixelSize() / 10); + if (!isRoot()) + w += spacing + SeparatorSize; + + setFixedWidth(w); + setFixedHeight(rect.height()); + update(); +} + +void CurmbItem::setIcon(const QIcon &icon) +{ + if (!icon.isNull()) + setFixedWidth(width() + spacing + IconSize); + + itemIcon = icon; +} + +void CurmbItem::setUserData(const QVariant &data) +{ + this->data = data; +} + +QVariant CurmbItem::userData() const +{ + return data; +} + +void CurmbItem::setSelected(bool selected) +{ + hasSelected = selected; + update(); +} + +bool CurmbItem::isSelected() const +{ + return hasSelected; +} + +bool CurmbItem::isRoot() const +{ + return 0 == index; +} + +void CurmbItem::mousePressEvent(QMouseEvent *event) +{ + setSelected(true); + Q_EMIT clicked(); + QWidget::mousePressEvent(event); +} + +void CurmbItem::enterEvent(QEvent *event) +{ + isHover = true; + update(); + QWidget::enterEvent(event); +} + +void CurmbItem::leaveEvent(QEvent *event) +{ + isHover = false; + update(); + QWidget::leaveEvent(event); +} + +void CurmbItem::paintEvent(QPaintEvent *event) +{ + QPainter p(this); + p.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing); + p.setPen(Qt::NoPen); + + int sw = 0; + // draw seperator + if (!isRoot()) { + sw = spacing + SeparatorSize; + auto rect = QRect(0, 0, SeparatorSize, SeparatorSize); + rect.moveTop((height() - rect.height()) / 2); + auto icon = QIcon::fromTheme("edit-forward"); + icon.paint(&p, rect); + } + + // draw icon + if (!itemIcon.isNull()) { + auto rect = QRect(sw, 0, IconSize, IconSize); + rect.moveTop((height() - rect.height()) / 2); + itemIcon.paint(&p, rect); + sw += spacing + IconSize; + } + + // draw text + auto palette = this->palette(); + if (hasSelected) + p.setPen(palette.color(QPalette::Highlight)); + else if (isHover) + p.setPen(palette.color(QPalette::BrightText)); + else + p.setPen(palette.color(QPalette::Text)); + + QRect rect = this->rect(); + rect.setLeft(rect.left() + sw); + + p.drawText(rect, Qt::AlignVCenter | Qt::AlignLeft, displayText); +} + +SymbolBar::SymbolBar(QWidget *parent) + : QWidget(parent) +{ + setAutoFillBackground(true); + setBackgroundRole(QPalette::Base); + + auto layout = new QHBoxLayout(this); + layout->setContentsMargins(10, 0, 0, 0); + layout->setSpacing(4); +} + +void SymbolBar::setPath(const QString &path) +{ + clear(); + + QString displayPath = path; + auto prjSrv = dpfGetService(ProjectService); + const auto &allPrj = prjSrv->getAllProjectInfo(); + + QString workspaceDir; + for (const auto &prj : allPrj) { + const auto &dir = prj.workspaceFolder(); + if (displayPath.startsWith(dir)) { + displayPath.remove(dir); + workspaceDir = dir; + break; + } + } + + auto layout = qobject_cast(this->layout()); + auto itemList = displayPath.split(QDir::separator(), QString::SkipEmptyParts); + for (int i = 0; i < itemList.size(); ++i) { + CurmbItem *item = new CurmbItem(CurmbItem::FilePath, i, this); + item->setText(itemList[i]); + item->setToolTip(path); + if (i == itemList.size() - 1) + item->setIcon(DFileIconProvider::globalProvider()->icon(path)); + + QString absolutePath = workspaceDir + QDir::separator() + itemList.mid(0, i).join(QDir::separator()); + item->setUserData(absolutePath); + layout->addWidget(item, 0, Qt::AlignVCenter | Qt::AlignLeft); + connect(item, &CurmbItem::clicked, this, &SymbolBar::curmbItemClicked); + } + layout->addStretch(1); +} + +void SymbolBar::clear() +{ + auto layout = qobject_cast(this->layout()); + while (QLayoutItem *item = layout->takeAt(0)) { + if (QWidget *widget = item->widget()) + delete widget; + delete item; + } + + symbolItem = nullptr; +} + +void SymbolBar::updateSymbol(int line, int index) +{ + auto editor = qobject_cast(sender()); + if (!editor) + return; + + auto info = SymbolManager::instance()->findSymbol(editor->getFile(), line, index); + if (info.first.isEmpty()) + return; + + if (!symbolItem) { + auto layout = qobject_cast(this->layout()); + if (layout->count() < 1) + return; + + symbolItem = new CurmbItem(CurmbItem::Symbol, layout->count() - 1, this); + layout->insertWidget(layout->count() - 1, symbolItem); + connect(symbolItem, &CurmbItem::clicked, this, &SymbolBar::curmbItemClicked); + } + + symbolItem->setUserData(editor->getFile()); + symbolItem->setText(info.first); + symbolItem->setToolTip(info.first); + symbolItem->setIcon(SymbolManager::instance()->iconFromKind(static_cast(info.second))); +} + +void SymbolBar::curmbItemClicked() +{ + CurmbItem *item = qobject_cast(sender()); + if (!item) + return; + + if (!symbolView) { + symbolView = new SymbolView(this); + connect(symbolView, &SymbolView::hidden, this, &SymbolBar::resetCurmbItemState); + } + + auto rect = item->geometry(); + auto pos = mapToGlobal(rect.bottomLeft()); + + switch (item->curmbType()) { + case CurmbItem::FilePath: { + const auto &path = item->userData().toString(); + symbolView->setRootPath(path); + symbolView->show(pos); + } break; + case CurmbItem::Symbol: { + const auto &path = item->userData().toString(); + if (symbolView->setSymbolPath(path)) + symbolView->show(pos); + else + item->setSelected(false); + } break; + } +} + +void SymbolBar::resetCurmbItemState() +{ + auto layout = qobject_cast(this->layout()); + for (int i = 0; i < layout->count(); ++i) { + auto item = layout->itemAt(i); + if (!item) + continue; + + auto curmb = qobject_cast(item->widget()); + if (curmb && curmb->isSelected()) + curmb->setSelected(false); + } +} diff --git a/src/plugins/codeeditor/symbol/symbolbar.h b/src/plugins/codeeditor/symbol/symbolbar.h new file mode 100644 index 000000000..f450d9adc --- /dev/null +++ b/src/plugins/codeeditor/symbol/symbolbar.h @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SYMBOLBAR_H +#define SYMBOLBAR_H + +#include +#include +#include + +class CurmbItem : public QWidget +{ + Q_OBJECT +public: + enum CurmbType { + FilePath, + Symbol + }; + + explicit CurmbItem(CurmbType type, int index, QWidget *parent = nullptr); + + CurmbType curmbType() const; + void setText(const QString &text); + void setIcon(const QIcon &icon); + void setSelected(bool selected); + bool isSelected() const; + bool isRoot() const; + void setUserData(const QVariant &data); + QVariant userData() const; + +Q_SIGNALS: + void clicked(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +private: + QString displayText; + QIcon itemIcon; + int index { 0 }; + int spacing { 5 }; + bool hasSelected { false }; + bool isHover { false }; + QVariant data; + CurmbType type; +}; + +class SymbolView; +class SymbolBar : public QWidget +{ + Q_OBJECT +public: + explicit SymbolBar(QWidget *parent = nullptr); + + void setPath(const QString &path); + void clear(); + +public Q_SLOTS: + void updateSymbol(int line, int index); + void curmbItemClicked(); + void resetCurmbItemState(); + +private: + CurmbItem *symbolItem { nullptr }; + SymbolView *symbolView { nullptr }; +}; + +#endif // SYMBOLBAR_H diff --git a/src/plugins/codeeditor/symbol/symbollocator.cpp b/src/plugins/codeeditor/symbol/symbollocator.cpp new file mode 100644 index 000000000..9661206d3 --- /dev/null +++ b/src/plugins/codeeditor/symbol/symbollocator.cpp @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "symbollocator.h" +#include "symbolmanager.h" +#include "gui/workspacewidget.h" +#include "gui/tabwidget.h" +#include "gui/texteditor.h" + +#include +#include + +struct SymbolLocatorItem : public baseLocatorItem +{ + explicit SymbolLocatorItem(abstractLocator *parentLocator) + : baseLocatorItem(parentLocator) {} + + int line = -1; + int column = -1; + QString name; +}; + +class SymbolLocatorPrivate +{ +public: + explicit SymbolLocatorPrivate(SymbolLocator *qq); + + TextEditor *currentEditor(); + void createSymbolItem(const newlsp::DocumentSymbol &symbol); + void createSymbolItem(const newlsp::SymbolInformation &info); + +public: + SymbolLocator *q; + WorkspaceWidget *workspace { nullptr }; + QList itemList; +}; + +SymbolLocatorPrivate::SymbolLocatorPrivate(SymbolLocator *qq) + : q(qq) +{ +} + +TextEditor *SymbolLocatorPrivate::currentEditor() +{ + auto tabWidget = workspace->currentTabWidget(); + if (!tabWidget) + return nullptr; + + return qobject_cast(tabWidget->currentWidget()); +} + +void SymbolLocatorPrivate::createSymbolItem(const newlsp::DocumentSymbol &symbol) +{ + SymbolLocatorItem item(q); + item.id = QUuid::createUuid().toString(); + item.name = symbol.name; + item.displayName = SymbolManager::instance()->displayNameFromDocumentSymbol(static_cast(symbol.kind), + symbol.name, + symbol.detail.value_or(QString())); + item.tooltip = item.displayName; + item.line = symbol.range.start.line; + item.column = symbol.range.start.character; + item.icon = SymbolManager::instance()->iconFromKind(static_cast(symbol.kind)); + itemList.append(item); + + auto children = symbol.children.value_or(QList()); + for (const auto &child : children) { + createSymbolItem(child); + } +} + +void SymbolLocatorPrivate::createSymbolItem(const newlsp::SymbolInformation &info) +{ + SymbolLocatorItem item(q); + item.id = QUuid::createUuid().toString(); + item.name = item.displayName = item.tooltip = info.name; + item.line = info.location.range.start.line; + item.column = info.location.range.start.character; + item.icon = SymbolManager::instance()->iconFromKind(static_cast(info.kind)); + + itemList.append(item); +} + +SymbolLocator::SymbolLocator(QObject *parent) + : abstractLocator(parent), + d(new SymbolLocatorPrivate(this)) +{ + setDisplayName("@"); + setDescription(tr("Symbols in Current Document")); + setIncludedDefault(false); +} + +SymbolLocator::~SymbolLocator() +{ + delete d; +} + +void SymbolLocator::setWorkspaceWidget(WorkspaceWidget *workspace) +{ + d->workspace = workspace; +} + +void SymbolLocator::prepareSearch(const QString &searchText) +{ + Q_UNUSED(searchText) + + d->itemList.clear(); + auto editor = d->currentEditor(); + if (!editor) + return; + + const auto &symbolList = SymbolManager::instance()->documentSymbols(editor->getFile()); + if (!symbolList.isEmpty()) { + for (const auto &symbol : symbolList) { + d->createSymbolItem(symbol); + } + } else { + const auto &infoList = SymbolManager::instance()->symbolInformations(editor->getFile()); + for (const auto &info : infoList) { + d->createSymbolItem(info); + } + } +} + +QList SymbolLocator::matchesFor(const QString &inputText) +{ + QList matchedResults; + auto regexp = createRegExp(inputText); + + for (auto item : d->itemList) { + auto match = regexp.match(item.name); + if (match.hasMatch()) + matchedResults.append(item); + } + + return matchedResults; +} + +void SymbolLocator::accept(baseLocatorItem item) +{ + auto iter = std::find_if(d->itemList.begin(), d->itemList.end(), + [&](const SymbolLocatorItem &symbItem) { + return symbItem.id == item.id; + }); + if (iter == d->itemList.end()) + return; + + if (auto editor = d->currentEditor()) { + int pos = editor->positionFromLineIndex(iter->line, iter->column); + editor->gotoPosition(pos); + } +} diff --git a/src/plugins/codeeditor/symbol/symbollocator.h b/src/plugins/codeeditor/symbol/symbollocator.h new file mode 100644 index 000000000..73b29ba71 --- /dev/null +++ b/src/plugins/codeeditor/symbol/symbollocator.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SYMBOLLOCATOR_H +#define SYMBOLLOCATOR_H + +#include "base/abstractlocator.h" + +class WorkspaceWidget; +class SymbolLocatorPrivate; +class SymbolLocator : public abstractLocator +{ + Q_OBJECT +public: + explicit SymbolLocator(QObject *parent = nullptr); + ~SymbolLocator(); + + void setWorkspaceWidget(WorkspaceWidget *workspace); + void prepareSearch(const QString &searchText) override; + QList matchesFor(const QString &inputText) override; + void accept(baseLocatorItem item) override; + +private: + SymbolLocatorPrivate *const d; +}; + +#endif // SYMBOLLOCATOR_H diff --git a/src/plugins/codeeditor/symbol/symbolmanager.cpp b/src/plugins/codeeditor/symbol/symbolmanager.cpp new file mode 100644 index 000000000..f86b0d2b9 --- /dev/null +++ b/src/plugins/codeeditor/symbol/symbolmanager.cpp @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "symbolmanager.h" + +#include + +class SymbolManagerPrivate +{ +public: + explicit SymbolManagerPrivate(SymbolManager *qq); + + QPair findSymbol(const newlsp::DocumentSymbol &symbol, int line, int column); + +public: + SymbolManager *q; + + QHash> docSymbolHash; + QHash> symbolInfoHash; +}; + +SymbolManagerPrivate::SymbolManagerPrivate(SymbolManager *qq) + : q(qq) +{ +} + +QPair SymbolManagerPrivate::findSymbol(const newlsp::DocumentSymbol &symbol, int line, int column) +{ + auto children = symbol.children.value_or(QList()); + auto iter = std::find_if(children.cbegin(), children.cend(), + [&](const newlsp::DocumentSymbol &child) { + return child.range.contains({ line, column }); + }); + if (iter != children.cend()) + return findSymbol(*iter, line, column); + + QString name = q->displayNameFromDocumentSymbol(static_cast(symbol.kind), + symbol.name, + symbol.detail.value_or(QString())); + return qMakePair(name, symbol.kind); +} + +SymbolManager::SymbolManager() + : d(new SymbolManagerPrivate(this)) +{ +} + +SymbolManager::~SymbolManager() +{ + delete d; +} + +SymbolManager *SymbolManager::instance() +{ + static SymbolManager ins; + return &ins; +} + +void SymbolManager::setDocumentSymbols(const QString &file, const QList &docSymbols) +{ + d->docSymbolHash.insert(file, docSymbols); +} + +QList SymbolManager::documentSymbols(const QString &file) const +{ + return d->docSymbolHash.value(file, {}); +} + +void SymbolManager::setSymbolInformations(const QString &file, const QList &symbolInfos) +{ + d->symbolInfoHash.insert(file, symbolInfos); +} + +QList SymbolManager::symbolInformations(const QString &file) const +{ + return d->symbolInfoHash.value(file, {}); +} + +QPair SymbolManager::findSymbol(const QString &file, int line, int column) +{ + if (d->docSymbolHash.contains(file)) { + const auto &symbolList = d->docSymbolHash[file]; + auto iter = std::find_if(symbolList.cbegin(), symbolList.cend(), + [&](const newlsp::DocumentSymbol &symbol) { + return symbol.range.contains({ line, column }); + }); + if (iter != symbolList.cend()) + return d->findSymbol(*iter, line, column); + } else if (d->symbolInfoHash.contains(file)) { + const auto &infoList = SymbolManager::instance()->symbolInformations(file); + auto iter = std::find_if(infoList.cbegin(), infoList.cend(), + [&](const newlsp::SymbolInformation &info) { + return info.location.range.contains({ line, column }); + }); + if (iter != infoList.cend()) + return qMakePair(iter->name, iter->kind); + } + + return {}; +} + +QIcon SymbolManager::iconFromKind(SymbolKind kind) +{ + switch (kind) { + case SymbolKind::File: + return QIcon::fromTheme("text-plain"); + case SymbolKind::Module: + case SymbolKind::Namespace: + case SymbolKind::Package: + return QIcon::fromTheme("namespace"); + case SymbolKind::Class: + case SymbolKind::Constructor: + case SymbolKind::Interface: + case SymbolKind::Object: + case SymbolKind::Struct: + return QIcon::fromTheme("class"); + case SymbolKind::Method: + case SymbolKind::Function: + case SymbolKind::Event: + case SymbolKind::Operator: + return QIcon::fromTheme("func"); + break; + case SymbolKind::Property: + return QIcon::fromTheme("snippet"); + case SymbolKind::Enum: + return QIcon::fromTheme("enum"); + break; + case SymbolKind::Field: + case SymbolKind::Variable: + case SymbolKind::Constant: + case SymbolKind::String: + case SymbolKind::Number: + case SymbolKind::Boolean: + case SymbolKind::Array: + case SymbolKind::TypeParameter: + return QIcon::fromTheme("var"); + case SymbolKind::Key: + case SymbolKind::Null: + return QIcon::fromTheme("keyword"); + case SymbolKind::EnumMember: + return QIcon::fromTheme("enumerator"); + default: + break; + } + + return QIcon(); +} + +QString SymbolManager::displayNameFromDocumentSymbol(SymbolKind kind, const QString &name, const QString &detail) +{ + switch (kind) { + case SymbolKind::Constructor: + return name + detail; + case SymbolKind::Method: + case SymbolKind::Function: { + const int lastParenIndex = detail.lastIndexOf(')'); + if (lastParenIndex == -1) + return name; + int leftParensNeeded = 1; + int i = -1; + for (i = lastParenIndex - 1; i >= 0; --i) { + switch (detail.at(i).toLatin1()) { + case ')': + ++leftParensNeeded; + break; + case '(': + --leftParensNeeded; + break; + default: + break; + } + if (leftParensNeeded == 0) + break; + } + if (leftParensNeeded > 0) + return name; + return name + detail.mid(i) + " -> " + detail.left(i); + } + case SymbolKind::Variable: + case SymbolKind::Field: + case SymbolKind::Constant: + if (detail.isEmpty()) + return name; + return name + " -> " + detail; + default: + return name; + } +} diff --git a/src/plugins/codeeditor/symbol/symbolmanager.h b/src/plugins/codeeditor/symbol/symbolmanager.h new file mode 100644 index 000000000..8b3441b6d --- /dev/null +++ b/src/plugins/codeeditor/symbol/symbolmanager.h @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SYMBOLMANAGER_H +#define SYMBOLMANAGER_H + +#include + +#include "common/lsp/protocol/new/languagefeatures.h" + +class SymbolManagerPrivate; +class SymbolManager +{ +public: + enum SymbolKind { + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26 + }; + + static SymbolManager *instance(); + + void setDocumentSymbols(const QString &file, const QList &docSymbols); + QList documentSymbols(const QString &file) const; + void setSymbolInformations(const QString &file, const QList &symbolInfos); + QList symbolInformations(const QString &file) const; + QPair findSymbol(const QString &file, int line, int column); + + QIcon iconFromKind(SymbolKind kind); + QString displayNameFromDocumentSymbol(SymbolKind kind, const QString &name, const QString &detail); + +private: + SymbolManager(); + ~SymbolManager(); + + SymbolManagerPrivate *const d; + friend class SymbolManagerPrivate; +}; + +#endif // SYMBOLMANAGER_H diff --git a/src/plugins/codeeditor/symbol/symbolview.cpp b/src/plugins/codeeditor/symbol/symbolview.cpp new file mode 100644 index 000000000..2c987190f --- /dev/null +++ b/src/plugins/codeeditor/symbol/symbolview.cpp @@ -0,0 +1,194 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "symbolview.h" +#include "symbolmanager.h" + +#include "common/util/eventdefinitions.h" + +#include +#include +#include +#include +#include + +DWIDGET_USE_NAMESPACE + +enum SymbolItemRole { + FilePathRole = Qt::UserRole + 10, + LineRole, + ColumnRole +}; + +class SymbolViewPrivate : public QObject +{ +public: + explicit SymbolViewPrivate(SymbolView *qq); + + void initUI(); + void initConnection(); + + QStandardItem *createSymbolItem(const QString &path, const newlsp::DocumentSymbol &symbol); + void handleItemClicked(const QModelIndex &index); + +public: + SymbolView *q; + + QTreeView *view { nullptr }; + QFileSystemModel pathModel; + QStandardItemModel symbolModel; +}; + +SymbolViewPrivate::SymbolViewPrivate(SymbolView *qq) + : q(qq) +{ +} + +void SymbolViewPrivate::initUI() +{ + q->setFixedWidth(400); + q->setWindowFlags(Qt::Popup); + + QVBoxLayout *layout = new QVBoxLayout(q); + layout->setContentsMargins(0, 0, 0, 0); + + view = new QTreeView(q); + view->setHeaderHidden(true); + view->setFrameShape(QFrame::NoFrame); + view->setEditTriggers(QTreeView::NoEditTriggers); + view->setIconSize({ 16, 16 }); + view->installEventFilter(q); + view->viewport()->installEventFilter(q); + + layout->addWidget(view); +} + +void SymbolViewPrivate::initConnection() +{ + connect(view, &QTreeView::clicked, this, &SymbolViewPrivate::handleItemClicked); +} + +QStandardItem *SymbolViewPrivate::createSymbolItem(const QString &path, const newlsp::DocumentSymbol &symbol) +{ + QStandardItem *item = new QStandardItem(); + const auto &name = SymbolManager::instance()->displayNameFromDocumentSymbol(static_cast(symbol.kind), + symbol.name, + symbol.detail.value_or(QString())); + item->setText(name); + item->setToolTip(name); + item->setIcon(SymbolManager::instance()->iconFromKind(static_cast(symbol.kind))); + item->setData(path, FilePathRole); + item->setData(symbol.range.start.line, LineRole); + item->setData(symbol.range.start.character, ColumnRole); + + auto children = symbol.children.value_or(QList()); + for (const auto &child : children) { + auto childItem = createSymbolItem(path, child); + item->appendRow(childItem); + } + + return item; +} + +void SymbolViewPrivate::handleItemClicked(const QModelIndex &index) +{ + auto path = index.data(QFileSystemModel::FilePathRole).toString(); + if (path.isEmpty()) { + path = index.data(FilePathRole).toString(); + if (path.isEmpty()) + return; + + int line = index.data(LineRole).toInt(); + int col = index.data(ColumnRole).toInt(); + editor.gotoPosition(path, line + 1, col); + } else { + if (QFileInfo(path).isDir()) { + view->setExpanded(index, !view->isExpanded(index)); + return; + } + + editor.openFile(QString(), path); + } + + q->hide(); +} + +SymbolView::SymbolView(QWidget *parent) + : DFrame(parent), + d(new SymbolViewPrivate(this)) +{ + d->initUI(); + d->initConnection(); +} + +void SymbolView::setRootPath(const QString &path) +{ + d->view->setModel(&d->pathModel); + d->pathModel.setRootPath(path); + auto index = d->pathModel.index(path); + d->view->setRootIndex(index); + d->view->sortByColumn(0, Qt::SortOrder::AscendingOrder); + + d->view->setColumnHidden(1, true); + d->view->setColumnHidden(2, true); + d->view->setColumnHidden(3, true); +} + +bool SymbolView::setSymbolPath(const QString &path) +{ + d->view->setModel(&d->symbolModel); + d->symbolModel.clear(); + const auto &docSymbolList = SymbolManager::instance()->documentSymbols(path); + if (docSymbolList.isEmpty()) { + const auto &symbolInfoList = SymbolManager::instance()->symbolInformations(path); + if (symbolInfoList.isEmpty()) + return false; + + for (const auto &info : symbolInfoList) { + QStandardItem *item = new QStandardItem(info.name); + item->setToolTip(info.name); + item->setIcon(SymbolManager::instance()->iconFromKind(static_cast(info.kind))); + item->setData(path, FilePathRole); + item->setData(info.location.range.start.line, LineRole); + item->setData(info.location.range.start.character, ColumnRole); + d->symbolModel.appendRow(item); + } + } else { + for (const auto &symbol : docSymbolList) { + QStandardItem *item = d->createSymbolItem(path, symbol); + d->symbolModel.appendRow(item); + } + } + + return true; +} + +void SymbolView::show(const QPoint &pos) +{ + show(); + move(pos); + d->view->setFocus(); +} + +void SymbolView::hideEvent(QHideEvent *event) +{ + Q_EMIT hidden(); + DFrame::hideEvent(event); +} + +bool SymbolView::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == d->view && event->type() == QEvent::KeyPress) { + auto keyEvent = dynamic_cast(event); + if (keyEvent && (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return)) { + d->handleItemClicked(d->view->currentIndex()); + return true; + } + } + + if (watched == d->view->viewport() && event->type() == QEvent::MouseButtonDblClick) + return true; + + return DFrame::eventFilter(watched, event); +} diff --git a/src/plugins/codeeditor/symbol/symbolview.h b/src/plugins/codeeditor/symbol/symbolview.h new file mode 100644 index 000000000..6219d0432 --- /dev/null +++ b/src/plugins/codeeditor/symbol/symbolview.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SYMBOLVIEW_H +#define SYMBOLVIEW_H + +#include + +class SymbolViewPrivate; +class SymbolView : public DTK_WIDGET_NAMESPACE::DFrame +{ + Q_OBJECT +public: + explicit SymbolView(QWidget *parent = nullptr); + + void setRootPath(const QString &path); + bool setSymbolPath(const QString &path); + + using DTK_WIDGET_NAMESPACE::DFrame::show; + void show(const QPoint &pos); + +Q_SIGNALS: + void hidden(); + +protected: + void hideEvent(QHideEvent *event) override; + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + SymbolViewPrivate *const d; +}; + +#endif // SYMBOLVIEW_H diff --git a/src/plugins/codeeditor/transceiver/codeeditorreceiver.cpp b/src/plugins/codeeditor/transceiver/codeeditorreceiver.cpp index be501f611..84e809123 100644 --- a/src/plugins/codeeditor/transceiver/codeeditorreceiver.cpp +++ b/src/plugins/codeeditor/transceiver/codeeditorreceiver.cpp @@ -4,8 +4,6 @@ #include "codeeditorreceiver.h" -#include "common/common.h" -#include "services/project/projectservice.h" #include "services/window/windowelement.h" #include "mainframe/texteditkeeper.h" @@ -15,9 +13,11 @@ CodeEditorReceiver::CodeEditorReceiver(QObject *parent) { using namespace std::placeholders; eventHandleMap.insert(editor.openFile.name, std::bind(&CodeEditorReceiver::processOpenFileEvent, this, _1)); + eventHandleMap.insert(editor.closeFile.name, std::bind(&CodeEditorReceiver::processCloseFileEvent, this, _1)); eventHandleMap.insert(editor.back.name, std::bind(&CodeEditorReceiver::processBackEvent, this, _1)); eventHandleMap.insert(editor.forward.name, std::bind(&CodeEditorReceiver::processForwardEvent, this, _1)); eventHandleMap.insert(editor.gotoLine.name, std::bind(&CodeEditorReceiver::processGotoLineEvent, this, _1)); + eventHandleMap.insert(editor.gotoPosition.name, std::bind(&CodeEditorReceiver::processGotoPositionEvent, this, _1)); eventHandleMap.insert(editor.addAnnotation.name, std::bind(&CodeEditorReceiver::processAddAnnotationEvent, this, _1)); eventHandleMap.insert(editor.removeAnnotation.name, std::bind(&CodeEditorReceiver::processRemoveAnnotationEvent, this, _1)); eventHandleMap.insert(editor.clearAllAnnotation.name, std::bind(&CodeEditorReceiver::processClearAllAnnotationEvent, this, _1)); @@ -40,7 +40,7 @@ dpf::EventHandler::Type CodeEditorReceiver::type() QStringList CodeEditorReceiver::topics() { - return { editor.topic, actionanalyse.topic, project.topic }; + return { editor.topic }; } void CodeEditorReceiver::eventProcess(const dpf::Event &event) @@ -60,6 +60,12 @@ void CodeEditorReceiver::processOpenFileEvent(const dpf::Event &event) Q_EMIT EditorCallProxy::instance()->reqOpenFile(workspace, fileName); } +void CodeEditorReceiver::processCloseFileEvent(const dpf::Event &event) +{ + QString fileName = event.property("fileName").toString(); + Q_EMIT EditorCallProxy::instance()->reqCloseFile(fileName); +} + void CodeEditorReceiver::processBackEvent(const dpf::Event &event) { Q_UNUSED(event) @@ -81,6 +87,14 @@ void CodeEditorReceiver::processGotoLineEvent(const dpf::Event &event) Q_EMIT EditorCallProxy::instance()->reqGotoLine(filePath, line); } +void CodeEditorReceiver::processGotoPositionEvent(const dpf::Event &event) +{ + QString filePath = event.property("fileName").toString(); + int line = event.property("line").toInt() - 1; + int col = event.property("column").toInt(); + Q_EMIT EditorCallProxy::instance()->reqGotoPosition(filePath, line, col); +} + void CodeEditorReceiver::processSetLineBackgroundColorEvent(const dpf::Event &event) { QString filePath = event.property("fileName").toString(); diff --git a/src/plugins/codeeditor/transceiver/codeeditorreceiver.h b/src/plugins/codeeditor/transceiver/codeeditorreceiver.h index ce4ff0ff7..f36e22a53 100644 --- a/src/plugins/codeeditor/transceiver/codeeditorreceiver.h +++ b/src/plugins/codeeditor/transceiver/codeeditorreceiver.h @@ -20,9 +20,11 @@ class CodeEditorReceiver : public dpf::EventHandler, dpf::AutoEventHandlerRegist private: void processOpenFileEvent(const dpf::Event &event); + void processCloseFileEvent(const dpf::Event &event); void processBackEvent(const dpf::Event &event); void processForwardEvent(const dpf::Event &event); void processGotoLineEvent(const dpf::Event &event); + void processGotoPositionEvent(const dpf::Event &event); void processSetLineBackgroundColorEvent(const dpf::Event &event); void processResetLineBackgroundEvent(const dpf::Event &event); void processClearLineBackgroundEvent(const dpf::Event &event); @@ -56,6 +58,7 @@ class EditorCallProxy : public QObject signals: void reqOpenFile(const QString &workspace, const QString &fileName); + void reqCloseFile(const QString &fileName); void reqBack(); void reqForward(); void reqGotoLine(const QString &fileName, int line); diff --git a/src/plugins/codegeex/codegeex.cpp b/src/plugins/codegeex/codegeex.cpp index 227fd2f7c..f9c47ff8b 100644 --- a/src/plugins/codegeex/codegeex.cpp +++ b/src/plugins/codegeex/codegeex.cpp @@ -32,17 +32,18 @@ bool CodeGeex::start() if (windowService->addNavigationItem) { QAction *action = new QAction(MWNA_CODEGEEX, this); action->setIcon(QIcon::fromTheme("codegeex-navigation")); - - windowService->addNavigationItem(new AbstractAction(action), Priority::medium); + auto actionImpl = new AbstractAction(action); + windowService->addNavigationItem(actionImpl, Priority::medium); auto codeGeex = new CodeGeeXWidget; auto codeGeexImpl = new AbstractWidget(codeGeex); windowService->registerWidget(MWNA_CODEGEEX, codeGeexImpl); + windowService->setDockHeaderName(MWNA_CODEGEEX, "codeGeex"); + windowService->bindWidgetToNavigation(MWNA_CODEGEEX, actionImpl); connect(action, &QAction::triggered, this, [=]() { windowService->raiseMode(CM_EDIT); windowService->showWidgetAtPosition(MWNA_CODEGEEX, Position::Left, true); - windowService->setDockHeaderName(MWNA_CODEGEEX, "codeGeex"); }, Qt::DirectConnection); } diff --git a/src/plugins/codegeex/codegeex.json b/src/plugins/codegeex/codegeex.json index 802384635..03f71c477 100644 --- a/src/plugins/codegeex/codegeex.json +++ b/src/plugins/codegeex/codegeex.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2023 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "AI", "Description" : "The AI plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=656d40c5bd766615b0b02e64&id=6597c7ad63a85b76997a070d", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=656d40c5bd766615b0b02e64&id=6597c7ad63a85b76997a070d", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/codegeex/copilot.cpp b/src/plugins/codegeex/copilot.cpp index 119f81f92..5e22ec670 100644 --- a/src/plugins/codegeex/copilot.cpp +++ b/src/plugins/codegeex/copilot.cpp @@ -192,7 +192,7 @@ void Copilot::translate() void Copilot::fixBug() { QString url = QString(kUrlSSEChat) + "?stream=true"; - copilotApi.postCommand(url, selectedText(), locale, commandFixBug); + copilotApi.postCommand(url, assembleCodeByCurrentFile(selectedText()), locale, commandFixBug); switchToCodegeexPage(); } @@ -200,7 +200,7 @@ void Copilot::fixBug() void Copilot::explain() { QString url = QString(kUrlSSEChat) + "?stream=true"; - copilotApi.postCommand(url, selectedText(), locale, commandExplain); + copilotApi.postCommand(url, assembleCodeByCurrentFile(selectedText()), locale, commandExplain); switchToCodegeexPage(); } @@ -208,7 +208,7 @@ void Copilot::explain() void Copilot::review() { QString url = QString(kUrlSSEChat) + "?stream=true"; - copilotApi.postCommand(url, selectedText(), locale, commandReview); + copilotApi.postCommand(url, assembleCodeByCurrentFile(selectedText()), locale, commandReview); switchToCodegeexPage(); } @@ -216,7 +216,7 @@ void Copilot::review() void Copilot::tests() { QString url = QString(kUrlSSEChat) + "?stream=true"; - copilotApi.postCommand(url, selectedText(), locale, commandTests); + copilotApi.postCommand(url, assembleCodeByCurrentFile(selectedText()), locale, commandTests); switchToCodegeexPage(); } @@ -256,3 +256,13 @@ void Copilot::switchToCodegeexPage() if (windowService->switchWidgetNavigation) windowService->switchWidgetNavigation(MWNA_CODEGEEX); } + +QString Copilot::assembleCodeByCurrentFile(const QString &code) +{ + auto filePath = editorService->currentFile(); + auto fileType = support_file::Language::id(filePath); + + QString result; + result = "```" + fileType + "\n" + code + "```"; + return result; +} diff --git a/src/plugins/codegeex/copilot.h b/src/plugins/codegeex/copilot.h index d99262ffb..6d0ffbd3c 100644 --- a/src/plugins/codegeex/copilot.h +++ b/src/plugins/codegeex/copilot.h @@ -56,6 +56,7 @@ public slots: QString locale { "zh" }; void switchToCodegeexPage(); bool responseValid(const QString &response); + QString assembleCodeByCurrentFile(const QString &code); CodeGeeX::CopilotApi copilotApi; dpfservice::EditorService *editorService = nullptr; diff --git a/src/plugins/codegeex/widgets/codeeditcomponent.h b/src/plugins/codegeex/widgets/codeeditcomponent.h index 34deaf130..4d884b1cf 100644 --- a/src/plugins/codegeex/widgets/codeeditcomponent.h +++ b/src/plugins/codegeex/widgets/codeeditcomponent.h @@ -6,9 +6,14 @@ #define CODEEDITCOMPONENT_H #include #include + #include #include +// Use this when in low version. +//#include +//#include + #include #include #include diff --git a/src/plugins/codegeex/widgets/codegeexwidget.cpp b/src/plugins/codegeex/widgets/codegeexwidget.cpp index 8b452e6f8..7c40881b4 100644 --- a/src/plugins/codegeex/widgets/codegeexwidget.cpp +++ b/src/plugins/codegeex/widgets/codegeexwidget.cpp @@ -205,7 +205,7 @@ void CodeGeeXWidget::initAskWidget() stackWidget = new QStackedWidget(this); stackWidget->setContentsMargins(0, 0, 0, 0); stackWidget->setFrameShape(QFrame::NoFrame); - stackWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + stackWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored); auto mainLayout = qobject_cast(layout()); mainLayout->setMargin(0); diff --git a/src/plugins/codegeex/widgets/translationpagewidget.cpp b/src/plugins/codegeex/widgets/translationpagewidget.cpp index 46a3545ec..3e6873e68 100644 --- a/src/plugins/codegeex/widgets/translationpagewidget.cpp +++ b/src/plugins/codegeex/widgets/translationpagewidget.cpp @@ -67,7 +67,6 @@ void TranslationPageWidget::initUI() inputEdit->showButtons(CodeEditComponent::None); inputEdit->setTitle(tr("Input Code")); inputEdit->setPlaceholderText(tr("Please input the code to be translated")); - inputEdit->setFixedHeight(280); layout->addWidget(inputEdit); QHBoxLayout *midLayout = new QHBoxLayout; diff --git a/src/plugins/codeporting/codeporting.json b/src/plugins/codeporting/codeporting.json index 2c16de3d7..f73552013 100644 --- a/src/plugins/codeporting/codeporting.json +++ b/src/plugins/codeporting/codeporting.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Utils", "Description" : "Help porting project code to other platform.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=656d40c5bd766615b0b02e64&id=656d415bbd766615b0b02e78", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=656d40c5bd766615b0b02e64&id=656d415bbd766615b0b02e78", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/collaborators/collaborators.cpp b/src/plugins/collaborators/collaborators.cpp index 9287d57a9..ae13b4fc1 100644 --- a/src/plugins/collaborators/collaborators.cpp +++ b/src/plugins/collaborators/collaborators.cpp @@ -24,28 +24,33 @@ bool Collaborators::start() if (windowService->addNavigationItem) { QAction *actionGit = new QAction(MWNA_GIT, this); actionGit->setIcon(QIcon::fromTheme("git-navigation")); + +#ifdef ENABLE_SVN // TODO(Any): svn should not contained in this plugin. QAction *actionSvn = new QAction(MWNA_SVN, this); actionSvn->setIcon(QIcon::fromTheme("svn-navigation")); - - windowService->addNavigationItem(new AbstractAction(actionGit), Priority::medium); windowService->addNavigationItem(new AbstractAction(actionSvn), Priority::medium); + AbstractWidget *svnMainWidgetImpl = new AbstractWidget(CVSkeeper::instance()->svnMainWidget()); + windowService->registerWidget(MWNA_SVN, svnMainWidgetImpl); + connect(actionSvn, &QAction::triggered, this, [=](){ + windowService->replaceWidget(MWNA_SVN, + Position::FullWindow); + windowService->hideStatusBar(); + }, Qt::DirectConnection); +#endif + windowService->addNavigationItem(new AbstractAction(actionGit), Priority::medium); + AbstractWidget *gitMainWidgetImpl = new AbstractWidget(CVSkeeper::instance()->gitMainWidget()); - AbstractWidget *svnMainWidgetImpl = new AbstractWidget(CVSkeeper::instance()->svnMainWidget()); + windowService->registerWidget(MWNA_GIT, gitMainWidgetImpl); - windowService->registerWidget(MWNA_SVN, svnMainWidgetImpl); + connect(actionGit, &QAction::triggered, this, [=](){ windowService->replaceWidget(MWNA_GIT, Position::FullWindow); windowService->hideStatusBar(); }, Qt::DirectConnection); - connect(actionSvn, &QAction::triggered, this, [=](){ - windowService->replaceWidget(MWNA_SVN, - Position::FullWindow); - windowService->hideStatusBar(); - }, Qt::DirectConnection); } } return true; diff --git a/src/plugins/collaborators/collaborators.json b/src/plugins/collaborators/collaborators.json index f8eb3413e..9801589fc 100644 --- a/src/plugins/collaborators/collaborators.json +++ b/src/plugins/collaborators/collaborators.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Utils", "Description" : "With git and svn verison control tool", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=656d40c5bd766615b0b02e64&id=656d4164bd766615b0b02e7a", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=656d40c5bd766615b0b02e64&id=656d4164bd766615b0b02e7a", "Depends" : [ {"Name" : "core"} ] diff --git a/src/plugins/collaborators/mainframe/svn/svnclientwidget.cpp b/src/plugins/collaborators/mainframe/svn/svnclientwidget.cpp index d362a368d..65d84d108 100644 --- a/src/plugins/collaborators/mainframe/svn/svnclientwidget.cpp +++ b/src/plugins/collaborators/mainframe/svn/svnclientwidget.cpp @@ -24,8 +24,7 @@ DWIDGET_USE_NAMESPACE SvnClientWidget::SvnClientWidget(QWidget *parent, Qt::WindowFlags flags) - : QMainWindow (parent, flags) - , mRepos(new QPinnableTabWidget()) + : QMainWindow(parent, flags), mRepos(new QPinnableTabWidget()) { mRepos = new QPinnableTabWidget(); const auto homeMenu = new DPushButton(); @@ -40,12 +39,12 @@ SvnClientWidget::SvnClientWidget(QWidget *parent, Qt::WindowFlags flags) const auto clone = menu->addAction(QAction::tr("Checkout repository")); ActionManager::getInstance()->registerAction(clone, "SVN.Checkout.Repository", - tr("Checkout repository"), QKeySequence(Qt::Modifier::CTRL | Qt::Modifier::SHIFT | Qt::Key::Key_C)); + tr("Checkout repository"), QKeySequence(Qt::Modifier::CTRL | Qt::Modifier::ALT | Qt::Key::Key_C)); connect(clone, &QAction::triggered, this, &SvnClientWidget::showCheckoutDialog); const auto open = menu->addAction(QAction::tr("Open repository")); ActionManager::getInstance()->registerAction(open, "SVN.Open.Repository", - tr("Open repository"), QKeySequence(Qt::Modifier::CTRL | Qt::Modifier::SHIFT | Qt::Key::Key_R)); + tr("Open repository"), QKeySequence(Qt::Modifier::CTRL | Qt::Modifier::ALT | Qt::Key::Key_R)); connect(open, &QAction::triggered, this, &SvnClientWidget::showOpenLocalRepos); mRepos->setObjectName("GitQlientTab"); @@ -89,13 +88,13 @@ void SvnClientWidget::showOpenLocalRepos() { QUrl url = QFileDialog::getExistingDirectoryUrl(nullptr, tr("select local reops")); if (!url.isEmpty()) { - addRepoTab(url.toLocalFile()); + addRepoTab(url.toLocalFile()); } } void SvnClientWidget::doCheckoutRepos(const QString &remote, const QString &local, const QString &user, const QString &passwd) { - auto dialog = qobject_cast(sender()); + auto dialog = qobject_cast(sender()); if (svnProgram().isEmpty()) { return; } @@ -103,11 +102,11 @@ void SvnClientWidget::doCheckoutRepos(const QString &remote, const QString &loca QEventLoop eventLoop; connect(&process, static_cast(&QProcess::finished), [&]() { - eventLoop.exit(); - }); + eventLoop.exit(); + }); process.setProgram(svnProgram()); - process.setArguments({"checkout", remote, local, "--username", user, "--password", passwd}); + process.setArguments({ "checkout", remote, local, "--username", user, "--password", passwd }); process.start(); process.waitForStarted(); StatusWidget *status = new StatusWidget(StatusWidget::Simple, dialog); @@ -123,13 +122,13 @@ void SvnClientWidget::doCheckoutRepos(const QString &remote, const QString &loca return; } - auto okCb = [=](bool checked){ + auto okCb = [=](bool checked) { Q_UNUSED(checked) addRepoTab(local, user, passwd); }; CommonDialog::okCancel(QString("checkout repos successful, now to open with user %0?").arg(user), - "Message", QMessageBox::Icon::Question, okCb); + "Message", QMessageBox::Icon::Question, okCb); } bool SvnClientWidget::isSvnDir(const QString &repoPath) diff --git a/src/plugins/commandproxy/commandproxy.json b/src/plugins/commandproxy/commandproxy.json index faabc2173..272956152 100644 --- a/src/plugins/commandproxy/commandproxy.json +++ b/src/plugins/commandproxy/commandproxy.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Core Plugins", "Description" : "The command line proxy plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document2?dirid=656d40a9bd766615b0b02e5e", + "UrlLink" : "https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e", "Depends" : [ {"Name" : "core"} ] diff --git a/src/plugins/console/CMakeLists.txt b/src/plugins/console/CMakeLists.txt index a80a91835..d2d926348 100644 --- a/src/plugins/console/CMakeLists.txt +++ b/src/plugins/console/CMakeLists.txt @@ -8,12 +8,14 @@ set(CMAKE_INCLUDE_CURRENT_DIR true) set(CXX_CPP consolewidget.cpp + consolemanager.cpp console.cpp console.json ) set(CXX_H consolewidget.h + consolemanager.h console.h ) diff --git a/src/plugins/console/console.cpp b/src/plugins/console/console.cpp index ed2abd532..298e02108 100644 --- a/src/plugins/console/console.cpp +++ b/src/plugins/console/console.cpp @@ -3,11 +3,11 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "console.h" +#include "consolemanager.h" #include "base/abstractwidget.h" #include "services/window/windowservice.h" #include "services/terminal/terminalservice.h" #include "common/util/eventdefinitions.h" -#include "consolewidget.h" using namespace dpfservice; void Console::initialize() @@ -31,16 +31,18 @@ bool Console::start() qInfo() << __FUNCTION__; auto &ctx = dpfInstance.serviceContext(); + auto consoleManager = new ConsoleManager; WindowService *windowService = ctx.service(WindowService::name()); if (windowService) { - windowService->addContextWidget(QString(tr("&Console")), new AbstractWidget(ConsoleWidget::instance()), true); + windowService->addContextWidget(TERMINAL_TAB_TEXT, new AbstractWidget(consoleManager), true); } // bind service. auto terminalService = ctx.service(TerminalService::name()); if (terminalService) { using namespace std::placeholders; - terminalService->executeCommand = std::bind(&ConsoleWidget::sendText, ConsoleWidget::instance(), _1); + terminalService->sendCommand = std::bind(&ConsoleManager::sendCommand, consoleManager, _1); + terminalService->executeCommand = std::bind(&ConsoleManager::executeCommand, consoleManager, _1, _2, _3, _4, _5); } return true; } diff --git a/src/plugins/console/console.json b/src/plugins/console/console.json index b27e648fd..582e98cb0 100644 --- a/src/plugins/console/console.json +++ b/src/plugins/console/console.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Utils", "Description" : "The terminal plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=656d40c5bd766615b0b02e64&id=664d8482685f572b03c2ac9d", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=656d40c5bd766615b0b02e64&id=664d8482685f572b03c2ac9d", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/console/consolemanager.cpp b/src/plugins/console/consolemanager.cpp new file mode 100644 index 000000000..9cbb00f3f --- /dev/null +++ b/src/plugins/console/consolemanager.cpp @@ -0,0 +1,226 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "consolemanager.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +DWIDGET_USE_NAMESPACE + +const int IdRole = Qt::UserRole + 1; + +class ConsoleManagerPrivate : public QObject +{ +public: + explicit ConsoleManagerPrivate(ConsoleManager *qq); + + void initUI(); + void initConnection(); + + void createDefaultConsole(); + void appendConsole(); + void removeConsole(); + void switchConsole(const QModelIndex &index, const QModelIndex &previous); + void updateButtonState(); + +public: + ConsoleManager *q; + + QMap consoleMap; + DStackedWidget *consoleStackedWidget { nullptr }; + QListView *consoleListView { nullptr }; + QStandardItemModel *model { nullptr }; + DToolButton *addConsoleBtn { nullptr }; + DToolButton *removeConsoleBtn { nullptr }; +}; + +ConsoleManagerPrivate::ConsoleManagerPrivate(ConsoleManager *qq) + : q(qq) +{ +} + +void ConsoleManagerPrivate::initUI() +{ + auto mainLayout = new QHBoxLayout(q); + mainLayout->setContentsMargins(0, 0, 0, 0); + + consoleStackedWidget = new DStackedWidget(q); + consoleStackedWidget->setMinimumWidth(500); + consoleStackedWidget->setContentsMargins(0, 0, 0, 0); + + DFrame *listViewFrame = new DFrame(q); + listViewFrame->setLineWidth(0); + DStyle::setFrameRadius(listViewFrame, 0); + + consoleListView = new QListView(listViewFrame); + consoleListView->setLineWidth(0); + model = new QStandardItemModel(consoleListView); + consoleListView->setModel(model); + + addConsoleBtn = new DToolButton(listViewFrame); + removeConsoleBtn = new DToolButton(listViewFrame); + + addConsoleBtn->setIcon(QIcon::fromTheme("binarytools_add")); + removeConsoleBtn->setIcon(QIcon::fromTheme("binarytools_reduce")); + removeConsoleBtn->setEnabled(false); + + QHBoxLayout *btnLayout = new QHBoxLayout(); + btnLayout->addWidget(addConsoleBtn); + btnLayout->addWidget(removeConsoleBtn); + btnLayout->setContentsMargins(0, 0, 0, 0); + btnLayout->setAlignment(Qt::AlignLeft); + btnLayout->setSpacing(5); + + QVBoxLayout *listViewLayout = new QVBoxLayout(listViewFrame); + listViewLayout->addLayout(btnLayout); + listViewLayout->addWidget(consoleListView); + listViewLayout->setContentsMargins(0, 0, 0, 0); + + QSplitter *splitter = new QSplitter(q); + splitter->addWidget(consoleStackedWidget); + splitter->addWidget(listViewFrame); + splitter->setStretchFactor(0, 1); + splitter->setStretchFactor(1, 0); + splitter->setHandleWidth(2); + + mainLayout->addWidget(splitter); +} + +void ConsoleManagerPrivate::initConnection() +{ + connect(consoleListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ConsoleManagerPrivate::switchConsole); + connect(addConsoleBtn, &DToolButton::clicked, this, &ConsoleManagerPrivate::appendConsole); + connect(removeConsoleBtn, &DToolButton::clicked, this, &ConsoleManagerPrivate::removeConsole); + connect(model, &QStandardItemModel::rowsRemoved, this, &ConsoleManagerPrivate::updateButtonState); + connect(model, &QStandardItemModel::rowsInserted, this, &ConsoleManagerPrivate::updateButtonState); +} + +void ConsoleManagerPrivate::createDefaultConsole() +{ + q->createConsole(ConsoleManager::tr("Terminal")); +} + +void ConsoleManagerPrivate::appendConsole() +{ + q->createConsole(ConsoleManager::tr("New Terminal")); +} + +void ConsoleManagerPrivate::removeConsole() +{ + auto index = consoleListView->currentIndex(); + if (!index.isValid()) + return; + + auto id = index.data(IdRole).toString(); + if (auto console = q->findConsole(id)) { + consoleStackedWidget->removeWidget(console); + console->deleteLater(); + } + + model->removeRow(index.row()); + consoleMap.remove(id); +} + +void ConsoleManagerPrivate::switchConsole(const QModelIndex &index, const QModelIndex &previous) +{ + auto id = index.data(IdRole).toString(); + if (auto console = q->findConsole(id)) { + console->setFocus(); + consoleStackedWidget->setCurrentWidget(console); + } +} + +void ConsoleManagerPrivate::updateButtonState() +{ + removeConsoleBtn->setEnabled(model->rowCount() > 1); +} + +ConsoleManager::ConsoleManager(QWidget *parent) + : QWidget(parent), + d(new ConsoleManagerPrivate(this)) +{ + d->initUI(); + d->initConnection(); +} + +ConsoleManager::~ConsoleManager() +{ + delete d; +} + +QTermWidget *ConsoleManager::currentConsole() +{ + //todo: when switch to other project`s path, open a new console + auto index = d->consoleListView->currentIndex(); + if (!index.isValid()) + return nullptr; + + auto id = index.data(IdRole).toString(); + return findConsole(id); +} + +QTermWidget *ConsoleManager::findConsole(const QString &id) +{ + return d->consoleMap.value(id, nullptr); +} + +QTermWidget *ConsoleManager::createConsole(const QString &name, bool startNow) +{ + auto id = QUuid::createUuid().toString(); + ConsoleWidget *console = new ConsoleWidget(this, startNow); + d->consoleMap.insert(id, console); + d->consoleStackedWidget->addWidget(console); + + QStandardItem *item = new QStandardItem(name); + item->setData(id, IdRole); + d->model->appendRow(item); + d->consoleListView->setCurrentIndex(d->model->index(d->model->rowCount() - 1, 0)); + + return console; +} + +void ConsoleManager::sendCommand(const QString &text) +{ + if (!currentConsole()) + d->createDefaultConsole(); + + currentConsole()->sendText(text); +} + +void ConsoleManager::executeCommand(const QString &name, const QString &program, const QStringList &args, const QString &workingDir, const QStringList &env) +{ + auto startNow = env.isEmpty(); // console start after set environment + auto console = createConsole(name, startNow); + if (!env.isEmpty()) { + console->setEnvironment(env); + console->startShellProgram(); + } + + auto dir = workingDir; + if (!dir.isEmpty() && QFile::exists(dir)) { + console->changeDir(dir); + console->sendText("clear\n"); + } + + QString cmd = program + ' '; + cmd += args.join(' ') + '\n'; + console->sendText(cmd); +} + +void ConsoleManager::showEvent(QShowEvent *event) +{ + Q_UNUSED(event); + if (!currentConsole()) + d->createDefaultConsole(); +} diff --git a/src/plugins/console/consolemanager.h b/src/plugins/console/consolemanager.h new file mode 100644 index 000000000..d649f6396 --- /dev/null +++ b/src/plugins/console/consolemanager.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef CONSOLEMANAGER_H +#define CONSOLEMANAGER_H + +#include "consolewidget.h" + +class ConsoleManagerPrivate; +class ConsoleManager : public QWidget +{ + Q_OBJECT +public: + ConsoleManager(QWidget *parent = nullptr); + ~ConsoleManager(); + + QTermWidget *currentConsole(); + QTermWidget *findConsole(const QString &id); + QTermWidget *createConsole(const QString &name, bool startNow = true); + void sendCommand(const QString &text); + void executeCommand(const QString &name, const QString &program, const QStringList &args, const QString &workingDir, const QStringList &env); + +private: + void showEvent(QShowEvent *event) override; + + ConsoleManagerPrivate *d; +}; + +#endif // CONSOLEMANAGER_H diff --git a/src/plugins/console/consolewidget.cpp b/src/plugins/console/consolewidget.cpp index 09a3eaf55..c5839c199 100644 --- a/src/plugins/console/consolewidget.cpp +++ b/src/plugins/console/consolewidget.cpp @@ -3,14 +3,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "consolewidget.h" +#include "services/project/projectservice.h" #include #include DWIDGET_USE_NAMESPACE - -static ConsoleWidget *ins{nullptr}; +using namespace dpfservice; class ConsoleWidgetPrivate { @@ -18,21 +18,16 @@ class ConsoleWidgetPrivate DMenu *menu = nullptr; QAction *consoleCopy = nullptr; QAction *consolePaste = nullptr; -}; + QAction *enterCurrentPath = nullptr; -ConsoleWidget *ConsoleWidget::instance() -{ - if (!ins) - ins = new ConsoleWidget; - return ins; -} + ProjectService *prjService = nullptr; +}; -ConsoleWidget::ConsoleWidget(QWidget *parent) - : QTermWidget(parent), +ConsoleWidget::ConsoleWidget(QWidget *parent, bool startNow) + : QTermWidget(startNow, parent), d(new ConsoleWidgetPrivate()) { setMargin(0); - setTerminalOpacity(0); setForegroundRole(QPalette::ColorRole::Window); setAutoFillBackground(true); setTerminalOpacity(1); @@ -45,13 +40,17 @@ ConsoleWidget::ConsoleWidget(QWidget *parent) setScrollBarPosition(QTermWidget::ScrollBarRight); setTerminalSizeHint(false); setAutoClose(false); - changeDir(QDir::homePath()); + + d->prjService = dpfGetService(ProjectService); + changeDir(d->prjService->getActiveProjectInfo().workspaceFolder()); sendText("clear\n"); d->consoleCopy = new QAction(tr("copy"), this); d->consolePaste = new QAction(tr("paste"), this); + d->enterCurrentPath = new QAction(tr("Enter current project root path"), this); QObject::connect(d->consoleCopy, &QAction::triggered, this, &QTermWidget::copyClipboard); QObject::connect(d->consolePaste, &QAction::triggered, this, &QTermWidget::pasteClipboard); + QObject::connect(d->enterCurrentPath, &QAction::triggered, this, &ConsoleWidget::enterCurrentProjectPath); QObject::connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, this, &ConsoleWidget::updateColorScheme); } @@ -68,12 +67,19 @@ void ConsoleWidget::contextMenuEvent(QContextMenuEvent *event) d->menu->setParent(this); d->menu->addAction(d->consoleCopy); d->menu->addAction(d->consolePaste); + d->menu->addAction(d->enterCurrentPath); } if (selectedText().isEmpty()) { d->consoleCopy->setEnabled(false); } else { d->consoleCopy->setEnabled(true); } + + if (d->prjService->getActiveProjectInfo().isEmpty()) + d->enterCurrentPath->setEnabled(false); + else + d->enterCurrentPath->setEnabled(true); + d->menu->exec(event->globalPos()); } @@ -85,3 +91,9 @@ void ConsoleWidget::updateColorScheme(DGuiApplicationHelper::ColorType themetype else if (availableColorSchemes().contains("BlackOnWhite")) this->setColorScheme("BlackOnWhite"); } + +void ConsoleWidget::enterCurrentProjectPath() +{ + auto path = d->prjService->getActiveProjectInfo().workspaceFolder(); + changeDir(path); +} diff --git a/src/plugins/console/consolewidget.h b/src/plugins/console/consolewidget.h index f01d67e2a..270bd6b42 100644 --- a/src/plugins/console/consolewidget.h +++ b/src/plugins/console/consolewidget.h @@ -18,8 +18,7 @@ class ConsoleWidget : public QTermWidget { Q_OBJECT public: - static ConsoleWidget *instance(); - explicit ConsoleWidget(QWidget *parent = nullptr); + explicit ConsoleWidget(QWidget *parent = nullptr, bool startNow = true); virtual ~ConsoleWidget(); protected: @@ -27,6 +26,8 @@ class ConsoleWidget : public QTermWidget void updateColorScheme(DGuiApplicationHelper::ColorType themetype); private: ConsoleWidgetPrivate *const d; + + void enterCurrentProjectPath(); }; #endif // CONSOLEWIDGET_H diff --git a/src/plugins/core/builtin/texts/context_widget_16px.svg b/src/plugins/core/builtin/texts/context_widget_16px.svg new file mode 100644 index 000000000..ec168539c --- /dev/null +++ b/src/plugins/core/builtin/texts/context_widget_16px.svg @@ -0,0 +1,7 @@ + + + ICON / view / bottom_show + + + + \ No newline at end of file diff --git a/src/plugins/core/builtin/texts/default_dock_16px.svg b/src/plugins/core/builtin/texts/default_dock_16px.svg new file mode 100644 index 000000000..744832bc3 --- /dev/null +++ b/src/plugins/core/builtin/texts/default_dock_16px.svg @@ -0,0 +1,7 @@ + + + ICON / sidebar /default + + + + \ No newline at end of file diff --git a/src/plugins/core/builtin/texts/docks_manager_16px.svg b/src/plugins/core/builtin/texts/docks_manager_16px.svg new file mode 100644 index 000000000..cd51e7580 --- /dev/null +++ b/src/plugins/core/builtin/texts/docks_manager_16px.svg @@ -0,0 +1,7 @@ + + + ICON / sidebar /more + + + + \ No newline at end of file diff --git a/src/plugins/core/builtin/texts/options_setting_black_32px.svg b/src/plugins/core/builtin/texts/options_setting_black_32px.svg new file mode 100644 index 000000000..7f226d4cd --- /dev/null +++ b/src/plugins/core/builtin/texts/options_setting_black_32px.svg @@ -0,0 +1,7 @@ + + + ICON / sidebar /setting + + + + \ No newline at end of file diff --git a/src/plugins/core/core.json b/src/plugins/core/core.json index b707fc8ab..6d76fb9f4 100644 --- a/src/plugins/core/core.json +++ b/src/plugins/core/core.json @@ -3,7 +3,7 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], diff --git a/src/plugins/core/core.qrc b/src/plugins/core/core.qrc index 4058286cc..4c2d2321f 100644 --- a/src/plugins/core/core.qrc +++ b/src/plugins/core/core.qrc @@ -13,11 +13,15 @@ builtin/texts/hide_16px.svg builtin/texts/notification_16px.svg builtin/texts/options_setting_22px.svg + builtin/texts/options_setting_black_32px.svg builtin/texts/plugins-navigation_22px.svg builtin/texts/recent-navigation_22px.svg builtin/texts/collapse_all_16px.svg builtin/texts/expand_all_16px.svg builtin/texts/hide_dock_16px.svg + builtin/texts/docks_manager_16px.svg + builtin/texts/context_widget_16px.svg + builtin/texts/default_dock_16px.svg builtin/light/icons/notification_info_20px.svg builtin/light/icons/notification_warning_20px.svg builtin/light/icons/notification_error_20px.svg diff --git a/src/plugins/core/depend/aptinstaller.cpp b/src/plugins/core/depend/aptinstaller.cpp new file mode 100644 index 000000000..a34092bb4 --- /dev/null +++ b/src/plugins/core/depend/aptinstaller.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "aptinstaller.h" + +#include "common/util/eventdefinitions.h" +#include "services/terminal/terminalservice.h" +#include "services/window/windowelement.h" + +#include + +using namespace dpfservice; + +APTInstaller::APTInstaller(QObject *parent) + : AbstractInstaller(parent) +{ +} + +QString APTInstaller::description() +{ + return tr("Install packages by apt"); +} + +bool APTInstaller::checkInstalled(const QString &package) +{ + QProcess process; + QString command = "dpkg-query -W -f='${Status}' " + package; + + process.start(command); + process.waitForFinished(); + + QString output = process.readAllStandardOutput(); + return output.contains("install ok installed"); +} + +void APTInstaller::install(const InstallInfo &info) +{ + if (!termSrv) + termSrv = dpfGetService(TerminalService); + + QStringList args; + args << "install" + << info.packageList; + + uiController.switchContext(TERMINAL_TAB_TEXT); + termSrv->executeCommand(info.plugin.isEmpty() ? "APTInstall" : info.plugin, "sudo apt", args, "", QStringList()); +} diff --git a/src/plugins/core/depend/aptinstaller.h b/src/plugins/core/depend/aptinstaller.h new file mode 100644 index 000000000..faca2dd6c --- /dev/null +++ b/src/plugins/core/depend/aptinstaller.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef APTINSTALLER_H +#define APTINSTALLER_H + +#include "base/abstractinstaller.h" + +namespace dpfservice { +class TerminalService; +} + +class APTInstaller : public AbstractInstaller +{ + Q_OBJECT +public: + explicit APTInstaller(QObject *parent = nullptr); + + QString description() override; + bool checkInstalled(const QString &package) override; + void install(const InstallInfo &info) override; + +private: + dpfservice::TerminalService *termSrv { nullptr }; +}; + +#endif // APTINSTALLER_H diff --git a/src/plugins/core/depend/dependencemanager.cpp b/src/plugins/core/depend/dependencemanager.cpp new file mode 100644 index 000000000..2fa402e0a --- /dev/null +++ b/src/plugins/core/depend/dependencemanager.cpp @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dependencemanager.h" +#include "notify/notificationmanager.h" + +#include +#include +#include +#include +#include + +#include + +const char CancelActionID[] { "cancel" }; +const char InstallActionID[] { "install_default" }; + +class DependenceManagerPrivate : public QObject +{ +public: + void init(); + void notify(); + void installHander(const QString &actId, const InstallInfo &info); + void checkInstalled(const QString &plugin, const QString &name, const QStringList &packageList); + +public: + QMutex mutex; + QMap installerMap; + QList infoList; + QTimer notifyTimer; + bool notifiable { false }; +}; + +void DependenceManagerPrivate::init() +{ + notifyTimer.setSingleShot(true); + notifyTimer.setInterval(500); + + connect(¬ifyTimer, &QTimer::timeout, this, &DependenceManagerPrivate::notify); +} + +void DependenceManagerPrivate::notify() +{ + using namespace std::placeholders; + if (!notifiable) + notifyTimer.start(); + + QStringList actionList { CancelActionID, DependenceManager::tr("Cancel"), + InstallActionID, DependenceManager::tr("Install") }; + + QMutexLocker lk(&mutex); + while (!infoList.isEmpty()) { + const auto &info = infoList.takeFirst(); + auto cb = std::bind(&DependenceManagerPrivate::installHander, this, _1, info); + QString msg = DependenceManager::tr("Request to install some dependency packages. " + "Do you want to install them?"); + NotificationManager::instance()->notify(0, info.plugin, msg, actionList, cb); + } +} + +void DependenceManagerPrivate::installHander(const QString &actId, const InstallInfo &info) +{ + if (actId != InstallActionID) + return; + + if (!installerMap.contains(info.installer)) + return; + + installerMap[info.installer]->install(info); +} + +void DependenceManagerPrivate::checkInstalled(const QString &plugin, const QString &name, const QStringList &packageList) +{ + QStringList list; + for (const auto &package : packageList) { + if (!installerMap[name]->checkInstalled(package)) + list << package; + } + + if (list.isEmpty()) + return; + + InstallInfo info; + info.installer = name; + info.plugin = plugin; + info.packageList = list; + + QMutexLocker lk(&mutex); + infoList << info; + lk.unlock(); + + QMetaObject::invokeMethod(¬ifyTimer, qOverload<>(&QTimer::start)); +} + +DependenceManager::DependenceManager(QObject *parent) + : QObject(parent), + d(new DependenceManagerPrivate) +{ + d->init(); +} + +DependenceManager::~DependenceManager() +{ + delete d; +} + +void DependenceManager::setNotifiable(bool notifiable) +{ + d->notifiable = notifiable; +} + +void DependenceManager::registerInstaller(const QString &name, AbstractInstaller *installer) +{ + if (d->installerMap.contains(name)) { + QString msg("The installer \"%1\" has been registered."); + qWarning() << msg.arg(name); + return; + } + + d->installerMap.insert(name, installer); +} + +bool DependenceManager::installPackageList(const QString &plugin, const QString &name, const QStringList &packageList, QString *error) +{ + if (!d->installerMap.contains(name)) { + QString msg("The installer \"%1\" has not been registered."); + if (error) + *error = msg.arg(name); + qWarning() << msg.arg(name); + return false; + } + + QtConcurrent::run(d, &DependenceManagerPrivate::checkInstalled, plugin, name, packageList); + return true; +} + +QMap DependenceManager::allInstaller() const +{ + return d->installerMap; +} diff --git a/src/plugins/core/depend/dependencemanager.h b/src/plugins/core/depend/dependencemanager.h new file mode 100644 index 000000000..42f3d3e15 --- /dev/null +++ b/src/plugins/core/depend/dependencemanager.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef DEPENDENCEMANAGER_H +#define DEPENDENCEMANAGER_H + +#include "base/abstractinstaller.h" + +#include + +class DependenceManagerPrivate; +class DependenceManager : public QObject +{ + Q_OBJECT +public: + explicit DependenceManager(QObject *parent = nullptr); + ~DependenceManager(); + + void setNotifiable(bool notifiable); + + void registerInstaller(const QString &name, AbstractInstaller *installer); + bool installPackageList(const QString &plugin, const QString &name, const QStringList &packageList, QString *error = nullptr); + QMap allInstaller() const; + +private: + DependenceManagerPrivate *const d; +}; + +#endif // DEPENDENCEMANAGER_H diff --git a/src/plugins/core/depend/pipinstaller.cpp b/src/plugins/core/depend/pipinstaller.cpp new file mode 100644 index 000000000..5f70ca836 --- /dev/null +++ b/src/plugins/core/depend/pipinstaller.cpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "pipinstaller.h" + +#include "common/util/eventdefinitions.h" +#include "services/terminal/terminalservice.h" +#include "services/window/windowelement.h" + +#include + +using namespace dpfservice; + +PIPInstaller::PIPInstaller(QObject *parent) + : AbstractInstaller(parent) +{ +} + +QString PIPInstaller::description() +{ + return tr("Install packages by pip"); +} + +bool PIPInstaller::checkInstalled(const QString &package) +{ + QProcess process; + QString cmd("pip3 show %1"); + process.start(cmd.arg(package)); + process.waitForFinished(); + + QString output = process.readAllStandardOutput(); + return !output.isEmpty(); +} + +void PIPInstaller::install(const InstallInfo &info) +{ + if (!termSrv) + termSrv = dpfGetService(TerminalService); + + QStringList args; + args << "install" + << info.packageList; + + uiController.switchContext(TERMINAL_TAB_TEXT); + termSrv->executeCommand(info.plugin.isEmpty() ? "PIPInstaller" : info.plugin, "pip3", args, "", QStringList()); +} diff --git a/src/plugins/core/depend/pipinstaller.h b/src/plugins/core/depend/pipinstaller.h new file mode 100644 index 000000000..e11b25f29 --- /dev/null +++ b/src/plugins/core/depend/pipinstaller.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef PIPINSTALLER_H +#define PIPINSTALLER_H + +#include "base/abstractinstaller.h" + +namespace dpfservice { +class TerminalService; +} + +class PIPInstaller : public AbstractInstaller +{ + Q_OBJECT +public: + explicit PIPInstaller(QObject *parent = nullptr); + + QString description() override; + bool checkInstalled(const QString &package) override; + void install(const InstallInfo &info) override; + +private: + dpfservice::TerminalService *termSrv { nullptr }; +}; + +#endif // PIPINSTALLER_H diff --git a/src/plugins/core/gui/dockheader.cpp b/src/plugins/core/gui/dockheader.cpp index 24f1de6dd..ef018070d 100644 --- a/src/plugins/core/gui/dockheader.cpp +++ b/src/plugins/core/gui/dockheader.cpp @@ -5,18 +5,23 @@ #include "dockheader.h" #include +#include +#include #include #include #include +#include DWIDGET_USE_NAMESPACE -class DockHeaderPrivate{ +class DockHeaderPrivate +{ friend class DockHeader; QHBoxLayout *mainLayout { nullptr }; QLabel *headerName { nullptr }; + DPushButton *select { nullptr }; }; DockHeader::DockHeader(QWidget *parent) @@ -24,13 +29,28 @@ DockHeader::DockHeader(QWidget *parent) { setAutoFillBackground(true); setBackgroundRole(DPalette::Base); - d->headerName = new QLabel(this); + d->headerName->setContentsMargins(0, 0, 0, 0); + + d->select = new DPushButton(this); + d->select->setFlat(true); + d->select->setIcon(QIcon::fromTheme("go-down")); + d->select->setFixedSize(16, 16); + d->select->setIconSize(QSize(12, 12)); + d->select->hide(); d->mainLayout = new QHBoxLayout(this); - d->mainLayout->setContentsMargins(10, 0, 10, 0); + d->mainLayout->setContentsMargins(0, 0, 0, 5); + d->mainLayout->setSpacing(0); d->mainLayout->setAlignment(Qt::AlignRight); - d->mainLayout->addWidget(d->headerName, Qt::AlignLeft); + + auto nameLayout = new QHBoxLayout; + nameLayout->setSpacing(4); + nameLayout->addWidget(d->headerName); + nameLayout->addWidget(d->select); + + d->mainLayout->addLayout(nameLayout, 1); + d->mainLayout->setAlignment(nameLayout, Qt::AlignLeft); } DockHeader::~DockHeader() @@ -39,13 +59,38 @@ DockHeader::~DockHeader() delete d; } -void DockHeader::addToolButton(DToolButton *btn) +void DockHeader::addWidget(QWidget *widget) { - btn->setFixedSize(20, 20); - d->mainLayout->insertWidget(1, btn); + widget->setFixedSize(QSize(26, 26)); + DStyle::setFrameRadius(widget, 6); + d->mainLayout->insertWidget(1, widget); } void DockHeader::setHeaderName(const QString &headerName) { d->headerName->setText(headerName); + QFont font = d->headerName->font(); + font.setWeight(QFont::Bold); + d->headerName->setFont(font); +} + +void DockHeader::setHeaderNames(const QList &headers) +{ + setHeaderName(headers.first()->text()); + DMenu *menu = new DMenu(this); + menu->addActions(headers); + d->select->show(); + connect(d->select, &DPushButton::clicked, this, [=]() { + auto action = menu->exec(mapToGlobal(geometry().bottomLeft())); + if (action) + setHeaderName(action->text()); + }, Qt::UniqueConnection); +} + +void DockHeader::mousePressEvent(QMouseEvent *event) +{ + if (d->headerName->geometry().contains(event->pos()) && d->select->isVisible()) + d->select->click(); + + DWidget::mousePressEvent(event); } diff --git a/src/plugins/core/gui/dockheader.h b/src/plugins/core/gui/dockheader.h index 061411325..3aec9d3ca 100644 --- a/src/plugins/core/gui/dockheader.h +++ b/src/plugins/core/gui/dockheader.h @@ -17,7 +17,11 @@ class DockHeader : public DTK_WIDGET_NAMESPACE::DWidget ~DockHeader(); void setHeaderName(const QString &headerName); - void addToolButton(DTK_WIDGET_NAMESPACE::DToolButton *btn); + void setHeaderNames(const QList &headers); + void addWidget(QWidget *widget); + +protected: + void mousePressEvent(QMouseEvent *event); private: DockHeaderPrivate *d; diff --git a/src/plugins/core/gui/loadingwidget.cpp b/src/plugins/core/gui/loadingwidget.cpp index 4e94bb752..8fc67bb60 100644 --- a/src/plugins/core/gui/loadingwidget.cpp +++ b/src/plugins/core/gui/loadingwidget.cpp @@ -18,7 +18,7 @@ loadingWidget::loadingWidget(QWidget *parent) setLogo(); loadingText = new DLabel(this); - loadingText->setText(tr("loading···")); + loadingText->setText(tr("loading...")); loadingText->setAlignment(Qt::AlignCenter); vlayout->addWidget(backgroundLogo); diff --git a/src/plugins/core/gui/navigationbar.cpp b/src/plugins/core/gui/navigationbar.cpp index 540581585..334be61ab 100644 --- a/src/plugins/core/gui/navigationbar.cpp +++ b/src/plugins/core/gui/navigationbar.cpp @@ -75,6 +75,21 @@ void NavigationBar::addNavItem(QAction *action, itemPositioin pos, quint8 priori auto btn = createToolBtn(action, false); bottomBtnsByPriority[priority].append(btn); } + updateUi(); +} + +void NavigationBar::addNavButton(DToolButton *btn, itemPositioin pos, quint8 priority) +{ + if (!btn) + return; + + btn->setFixedSize(QSize(36, 36)); + btn->setIconSize(QSize(22, 22)); + + if (pos == top) + topBtnsByPriority[priority].append(btn); + else + bottomBtnsByPriority[priority].append(btn); updateUi(); } @@ -103,6 +118,7 @@ DToolButton *NavigationBar::createToolBtn(QAction *action, bool isNavigationItem connect(action, &QAction::triggered, this, [=](){ setNavActionChecked(action->text(), true); }); + return navBtn; } diff --git a/src/plugins/core/gui/plugindetailsview.cpp b/src/plugins/core/gui/plugindetailsview.cpp index 13aee90f6..5821b98e7 100644 --- a/src/plugins/core/gui/plugindetailsview.cpp +++ b/src/plugins/core/gui/plugindetailsview.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,8 @@ #include #endif #include +#include +#include DWIDGET_USE_NAMESPACE using namespace dpfservice; @@ -29,33 +32,35 @@ using namespace dpfservice; class AutoZoomWebEngineView : public QWebEngineView { public: explicit AutoZoomWebEngineView(QWidget *parent = nullptr) - : QWebEngineView(parent) { - page()->settings()->setAttribute(QWebEngineSettings::ShowScrollBars, false); - connect(page(), &QWebEnginePage::loadFinished, [this](bool ok) { - if (ok) { - page()->runJavaScript("document.body.scrollWidth", [this](const QVariant &widthResult) { - pageWidth = widthResult.toInt(); - }); - } - } - ); - } + : QWebEngineView(parent) + { + page()->settings()->setAttribute(QWebEngineSettings::ShowScrollBars, false); + } protected: void resizeEvent(QResizeEvent *event) override { QWebEngineView::resizeEvent(event); - QSize newSize = event->size(); - qreal zoomFactor = calculateZoomFactor(newSize); + int pageWidth = static_cast(QGuiApplication::primaryScreen()->size().width() * webPageWidthScale); + + qreal zoomFactor = calculateZoomFactor(event->size(), pageWidth); setZoomFactor(zoomFactor); } - qreal calculateZoomFactor(const QSize &size) { + qreal calculateZoomFactor(const QSize &size, int pageWidth) + { + if (pageWidth == 0) + return 1; + qreal zoomFactor = static_cast(size.width()) / pageWidth; + if (zoomFactor > 1) { + zoomFactor = 1; + } return zoomFactor; } + private: - int pageWidth = 0; + qreal webPageWidthScale = 0.8; }; #endif @@ -149,6 +154,8 @@ void DetailsView::setupUi() mainLayout->setMargin(0); DFrame *detailFrame = new DFrame(this); auto detailLayout = new QHBoxLayout(detailFrame); + DStyle::setFrameRadius(detailFrame, 0); + detailFrame->setLineWidth(0); auto midLayout = new QVBoxLayout(); midLayout->setSpacing(0); @@ -162,15 +169,28 @@ void DetailsView::setupUi() loadBtn->setFixedSize(86, 36); loadBtn->setToolTip(tr("reLaunch when changed!")); loadBtn->setChecked(true); - connect(loadBtn, &DSuggestButton::clicked, this, &DetailsView::changeLoadBtnState); - operationLayout->addWidget(loadBtn, 0, Qt::AlignLeft); + operationLayout->addWidget(loadBtn); auto *cfgBtn = new DPushButton(this); - cfgBtn->setIcon(QIcon::fromTheme("options_setting")); - cfgBtn->setFlat(true); + cfgBtn->setIcon(QIcon::fromTheme("options_setting_black")); + cfgBtn->setFixedSize(36, 36); + cfgBtn->setToolTip(tr("Settings")); connect(cfgBtn, &DPushButton::clicked, this, &DetailsView::showCfgWidget); - operationLayout->addWidget(cfgBtn, 1, Qt::AlignLeft); - + operationLayout->addWidget(cfgBtn); + operationLayout->setSpacing(10); + + DLabel *tipLabel = new DLabel(this); + tipLabel->setText(tr("Relaunch required!")); + tipLabel->setForegroundRole(DPalette::TextWarning); + operationLayout->addWidget(tipLabel); + operationLayout->setAlignment(Qt::AlignLeft); + tipLabel->hide(); + + connect(loadBtn, &DSuggestButton::clicked, this, [=](){ + changeLoadBtnState(); + tipLabel->show(); + }); + logoLabel = new QLabel(this); auto logo = QIcon::fromTheme("default_plugin"); logoLabel->setPixmap(logo.pixmap(QSize(96, 96))); @@ -218,9 +238,37 @@ void DetailsView::initMetaInfoLayout() font.setPointSize(20); name->setFont(font); - version = new DLabel(this); - category = new DLabel(this); + versionFrame = new DFrame(this); + versionFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + versionFrame->setBackgroundRole(DPalette::FrameBorder); + versionFrame->setLineWidth(0); + DStyle::setFrameRadius(versionFrame, 4); + QVBoxLayout *versionLayout = new QVBoxLayout(versionFrame); + version = new DLabel(versionFrame); + version->setContentsMargins(7, 0, 7, 0); + versionLayout->addWidget(version); + versionLayout->setAlignment(Qt::AlignCenter); + versionLayout->setSpacing(0); + versionLayout->setContentsMargins(0, 0, 0, 0); + + categoryFrame = new DFrame(this); + categoryFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + DPalette palette = categoryFrame->palette(); + QColor color = palette.color(QPalette::Highlight); + color.setAlpha(20); + palette.setColor(QPalette::Base, color); + categoryFrame->setPalette(palette); + categoryFrame->setLineWidth(0); + DStyle::setFrameRadius(categoryFrame, 4); + QVBoxLayout *categoryLayout = new QVBoxLayout(categoryFrame); + category = new DLabel(categoryFrame); category->setForegroundRole(DPalette::LightLively); + category->setContentsMargins(7, 0, 7, 0); + categoryLayout->addWidget(category); + categoryLayout->setAlignment(Qt::AlignCenter); + categoryLayout->setSpacing(0); + categoryLayout->setContentsMargins(0, 0, 0, 0); + description = new DLabel(this); vendor = new DLabel(this); dependency = new DLabel(this); @@ -229,8 +277,8 @@ void DetailsView::initMetaInfoLayout() hbox->setAlignment(Qt::AlignLeft); hbox->setSpacing(10); hbox->addWidget(name); - hbox->addWidget(version); - hbox->addWidget(category); + hbox->addWidget(versionFrame); + hbox->addWidget(categoryFrame); metaInfoLayout->addLayout(hbox); metaInfoLayout->addWidget(vendor); diff --git a/src/plugins/core/gui/plugindetailsview.h b/src/plugins/core/gui/plugindetailsview.h index 7a745b5ab..923ccb088 100644 --- a/src/plugins/core/gui/plugindetailsview.h +++ b/src/plugins/core/gui/plugindetailsview.h @@ -8,6 +8,7 @@ #include #include +#include #include QT_BEGIN_NAMESPACE @@ -56,6 +57,8 @@ private slots: DTK_WIDGET_NAMESPACE::DLabel *description {nullptr}; DTK_WIDGET_NAMESPACE::DLabel *vendor {nullptr}; DTK_WIDGET_NAMESPACE::DLabel *dependency {nullptr}; + DTK_WIDGET_NAMESPACE::DFrame *versionFrame {nullptr}; + DTK_WIDGET_NAMESPACE::DFrame *categoryFrame {nullptr}; DTK_WIDGET_NAMESPACE::DPushButton *loadBtn {nullptr}; dpf::PluginMetaObjectPointer pluginMetaInfo; diff --git a/src/plugins/core/gui/pluginitemdelegate.cpp b/src/plugins/core/gui/pluginitemdelegate.cpp index d100a836a..52863348b 100644 --- a/src/plugins/core/gui/pluginitemdelegate.cpp +++ b/src/plugins/core/gui/pluginitemdelegate.cpp @@ -12,7 +12,7 @@ #include #include -inline constexpr int kRectRadius = { 5 }; // do not show rounded corners. +inline constexpr int kRectRadius = { 8 }; // do not show rounded corners. inline constexpr int kIconWidth = { 30 }; inline constexpr int kIconHeight = { 30 }; inline constexpr int kIconLeftMargin = { 10 }; @@ -191,7 +191,10 @@ void PluginItemDelegate::paintItemColumn(QPainter *painter, painter->save(); // display description. - painter->setOpacity(0.7); + if (isSelected) + painter->setPen(Qt::white); + else + painter->setOpacity(0.7); QString description = index.data(PluginListView::Description).toString(); QFontMetrics fm(option.font); description = fm.elidedText(description, Qt::ElideRight, diff --git a/src/plugins/core/gui/pluginstorewidget.cpp b/src/plugins/core/gui/pluginstorewidget.cpp index fd999f56e..b2455a49e 100644 --- a/src/plugins/core/gui/pluginstorewidget.cpp +++ b/src/plugins/core/gui/pluginstorewidget.cpp @@ -30,6 +30,8 @@ void PluginStoreWidget::slotSearchChanged(const QString &searchText) void PluginStoreWidget::initializeUi() { + setLineWidth(0); + DStyle::setFrameRadius(this, 0); inputEdit = new DSearchEdit(this); inputEdit->setPlaceHolder(tr("Search Extension")); connect(inputEdit, &DSearchEdit::textChanged, @@ -38,5 +40,8 @@ void PluginStoreWidget::initializeUi() QVBoxLayout *vLayout = new QVBoxLayout(this); setLayout(vLayout); vLayout->addWidget(inputEdit); + vLayout->addSpacing(5); vLayout->addWidget(pluginListView); + vLayout->setMargin(0); + vLayout->setContentsMargins(10, 0, 10, 0); } diff --git a/src/plugins/core/gui/workspacewidget.cpp b/src/plugins/core/gui/workspacewidget.cpp index 70bda560c..83b797254 100644 --- a/src/plugins/core/gui/workspacewidget.cpp +++ b/src/plugins/core/gui/workspacewidget.cpp @@ -25,28 +25,13 @@ void WorkspaceWidget::initUi() setMinimumWidth(kMinimumWidth); - editWorkspaceWidget = new DFrame(this); - editWorkspaceWidget->setLineWidth(0); - DStyle::setFrameRadius(editWorkspaceWidget, 0); stackEditWorkspaceWidget = new DStackedWidget(this); - workspaceTabBar = new DFrame(this); - workspaceTabBar->setLineWidth(0); - DStyle::setFrameRadius(workspaceTabBar, 0); - - QHBoxLayout *workspaceTabLayout = new QHBoxLayout(workspaceTabBar); - workspaceTabLayout->setContentsMargins(0, 0, 0, 0); - QVBoxLayout *workspaceVLayout = new QVBoxLayout(); - workspaceVLayout->setContentsMargins(0, 0, 0, 0); - workspaceVLayout->setSpacing(0); - workspaceVLayout->addWidget(stackEditWorkspaceWidget); - workspaceVLayout->addWidget(workspaceTabBar); - editWorkspaceWidget->setLayout(workspaceVLayout); - - mainLayout->addWidget(editWorkspaceWidget); + mainLayout->addWidget(stackEditWorkspaceWidget); } void WorkspaceWidget::addWorkspaceWidget(const QString &title, AbstractWidget *treeWidget, const QString &iconName) { + Q_UNUSED(iconName); auto qTreeWidget = static_cast(treeWidget->qWidget()); qTreeWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); @@ -54,22 +39,6 @@ void WorkspaceWidget::addWorkspaceWidget(const QString &title, AbstractWidget *t editWorkspaceWidgets.insert(title, qTreeWidget); stackEditWorkspaceWidget->addWidget(qTreeWidget); - DToolButton *tabBtn = new DToolButton; - tabBtn->setCheckable(true); - tabBtn->setChecked(false); - tabBtn->setToolTip(title); - tabBtn->setIcon(QIcon::fromTheme(iconName)); - tabBtn->setMinimumSize(QSize(24, 24)); - tabBtn->setIconSize(QSize(16, 16)); - tabBtn->setFocusPolicy(Qt::NoFocus); - - QHBoxLayout *btnLayout = static_cast(workspaceTabBar->layout()); - btnLayout->addWidget(tabBtn); - - connect(tabBtn, &DPushButton::clicked, qTreeWidget, [=]{ - switchWidgetWorkspace(title); - }); - workspaceTabButtons.insert(title, tabBtn); switchWidgetWorkspace(title); } @@ -91,20 +60,29 @@ QList WorkspaceWidget::getToolBtnByTitle(const QString &title) return toolBtnOfWidget.values(title); } -bool WorkspaceWidget::switchWidgetWorkspace(const QString &title) +void WorkspaceWidget::switchWidgetWorkspace(const QString &title) { + auto lastTitle = currentTitle(); auto widget = editWorkspaceWidgets[title]; stackEditWorkspaceWidget->setCurrentWidget(widget); - for (auto it = workspaceTabButtons.begin(); it != workspaceTabButtons.end(); ++it) { - it.value()->setChecked(false); - if (it.key() == title) - it.value()->setChecked(true); - } emit expandStateChange(widget->property("canExpand").toBool()); - emit workSpaceWidgeSwitched(title); - return false; + if (!addedToController || (lastTitle == title)) + return; + + //update toolbtn`s state at header + for (auto btn : getToolBtnByTitle(lastTitle)) { + toolBtnState[btn] = btn->isVisible(); + btn->setVisible(false); + } + + for (auto btn : getToolBtnByTitle(title)) { + if (toolBtnState.contains(btn)) + btn->setVisible(toolBtnState[btn]); + } + + emit workSpaceWidgeSwitched(title); } bool WorkspaceWidget::getCurrentExpandState() @@ -112,11 +90,13 @@ bool WorkspaceWidget::getCurrentExpandState() return stackEditWorkspaceWidget->currentWidget()->property("canExpand").toBool(); } -QString WorkspaceWidget::getCurrentTitle() const +QStringList WorkspaceWidget::allWidgetTitles() const { - for (auto it = workspaceTabButtons.begin(); it != workspaceTabButtons.end(); ++it) { - if (it.value()->isChecked() == true) - return it.key(); - } - return ""; + return editWorkspaceWidgets.keys(); +} + +QString WorkspaceWidget::currentTitle() const +{ + auto currentWidget = stackEditWorkspaceWidget->currentWidget(); + return editWorkspaceWidgets.key(currentWidget); } diff --git a/src/plugins/core/gui/workspacewidget.h b/src/plugins/core/gui/workspacewidget.h index 82643d6f3..e1c8cacd0 100644 --- a/src/plugins/core/gui/workspacewidget.h +++ b/src/plugins/core/gui/workspacewidget.h @@ -25,9 +25,12 @@ class WorkspaceWidget : public DWidget void registerToolBtnToWidget(DToolButton *btn, const QString &title); QList getAllToolBtn(); QList getToolBtnByTitle(const QString &title); - bool switchWidgetWorkspace(const QString &title); + void switchWidgetWorkspace(const QString &title); bool getCurrentExpandState(); - QString getCurrentTitle() const; + QStringList allWidgetTitles() const; + QString currentTitle() const; + + bool addedToController { false }; signals: void expandStateChange(bool canExpand); @@ -39,13 +42,12 @@ class WorkspaceWidget : public DWidget QVBoxLayout *mainLayout { nullptr }; QMap workspaceWidgets; - DFrame *editWorkspaceWidget { nullptr }; QMap editWorkspaceWidgets; QMap workspaceTabButtons; QMultiMap toolBtnOfWidget; + QMap toolBtnState; DStackedWidget *stackEditWorkspaceWidget { nullptr }; - DFrame *workspaceTabBar { nullptr }; }; #endif // WORKSPACEWIDGET_H diff --git a/src/plugins/core/locator/locatormanager.cpp b/src/plugins/core/locator/locatormanager.cpp index 5a8ffaa79..1ff8fdb47 100644 --- a/src/plugins/core/locator/locatormanager.cpp +++ b/src/plugins/core/locator/locatormanager.cpp @@ -286,6 +286,7 @@ void LocatorManager::initShortCut() shortCut->setKey(Qt::Modifier::CTRL | Qt::Key::Key_K); connect(shortCut, &QShortcut::activated, inputEdit, [=]() { inputEdit->setFocus(); + inputEdit->lineEdit()->selectAll(); }); QAction *action = new QAction(this); diff --git a/src/plugins/core/locator/popupwidget.cpp b/src/plugins/core/locator/popupwidget.cpp index 5ee654b9e..4b0a5dae5 100644 --- a/src/plugins/core/locator/popupwidget.cpp +++ b/src/plugins/core/locator/popupwidget.cpp @@ -65,7 +65,10 @@ int locatorModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; - return ColumnCount; + for (auto item : items) + if (!item.extraInfo.isEmpty()) + return ColumnCount; + return 1; } PopupWidget::PopupWidget(QWidget *parent) @@ -80,10 +83,13 @@ PopupWidget::PopupWidget(QWidget *parent) layout->setSpacing(0); layout->addWidget(tree); + tree->setMinimumWidth(500); tree->setHeaderHidden(true); tree->setRootIsDecorated(false); tree->setUniformRowHeights(true); + tree->header()->setSectionResizeMode(QHeaderView::Stretch); tree->header()->setStretchLastSection(true); + tree->setIconSize(QSize(16, 16)); connect(tree, &QTreeView::activated, this, [this](const QModelIndex &index) { emit this->selectIndex(index); diff --git a/src/plugins/core/modules/dependencemodule.cpp b/src/plugins/core/modules/dependencemodule.cpp new file mode 100644 index 000000000..9a01df638 --- /dev/null +++ b/src/plugins/core/modules/dependencemodule.cpp @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dependencemodule.h" +#include "depend/aptinstaller.h" +#include "depend/pipinstaller.h" + +#include "services/window/windowservice.h" + +using namespace dpfservice; + +void DependenceModule::initialize(Controller *_uiController) +{ + Q_UNUSED(_uiController) + + manager = new DependenceManager(this); + connect(&dpf::Listener::instance(), &dpf::Listener::pluginsStarted, this, [=] { + manager->setNotifiable(true); + QTimer::singleShot(500, this, &DependenceModule::installPluginDepends); + }); + + initDefaultInstaller(); + initInterfaces(); +} + +void DependenceModule::initDefaultInstaller() +{ + manager->registerInstaller("apt", new APTInstaller(this)); + manager->registerInstaller("pip", new PIPInstaller(this)); +} + +void DependenceModule::initInterfaces() +{ + auto &ctx = dpfInstance.serviceContext(); + WindowService *windowService = ctx.service(WindowService::name()); + Q_ASSERT(windowService); + + using namespace std::placeholders; + windowService->registerInstaller = std::bind(&DependenceManager::registerInstaller, manager, _1, _2); + windowService->installPackages = std::bind(&DependenceManager::installPackageList, manager, _1, _2, _3, _4); +} + +void DependenceModule::installPluginDepends() +{ + auto instance = dpf::LifeCycle::getPluginManagerInstance(); + auto collections = instance->pluginCollections(); + for (const auto &categories : collections.values()) { + for (const auto &meta : categories) { + if (!meta) + continue; + + const auto &depends = meta->installDepends(); + for (const auto &depend : depends) { + manager->installPackageList(meta->name(), depend.installer(), depend.packages()); + } + } + } +} diff --git a/src/plugins/core/modules/dependencemodule.h b/src/plugins/core/modules/dependencemodule.h new file mode 100644 index 000000000..c96c12848 --- /dev/null +++ b/src/plugins/core/modules/dependencemodule.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef DEPENDENCEMODULE_H +#define DEPENDENCEMODULE_H + +#include "abstractmodule.h" +#include "depend/dependencemanager.h" + +class DependenceModule : public AbstractModule +{ + Q_OBJECT +public: + virtual void initialize(Controller *_uiController) override; + +private: + void initDefaultInstaller(); + void initInterfaces(); + void installPluginDepends(); + + DependenceManager *manager { nullptr }; +}; + +#endif // DEPENDENCEMODULE_H diff --git a/src/plugins/core/modules/notificationmodule.cpp b/src/plugins/core/modules/notificationmodule.cpp index 65db9c5be..559f48d88 100644 --- a/src/plugins/core/modules/notificationmodule.cpp +++ b/src/plugins/core/modules/notificationmodule.cpp @@ -18,5 +18,9 @@ void NotificationModule::initialize(Controller *_uiController) Q_ASSERT(windowService); using namespace std::placeholders; - windowService->notify = std::bind(&NotificationManager::notify, NotificationManager::instance(), _1, _2, _3, _4); + auto notify = qOverload(&NotificationManager::notify); + windowService->notify = std::bind(notify, NotificationManager::instance(), _1, _2, _3, _4); + + auto notifyWithCb = qOverload(&NotificationManager::notify); + windowService->notifyWithCallback = std::bind(notifyWithCb, NotificationManager::instance(), _1, _2, _3, _4, _5); } diff --git a/src/plugins/core/modules/pluginmanagermodule.cpp b/src/plugins/core/modules/pluginmanagermodule.cpp index 2607510fc..82094c541 100644 --- a/src/plugins/core/modules/pluginmanagermodule.cpp +++ b/src/plugins/core/modules/pluginmanagermodule.cpp @@ -48,9 +48,13 @@ void PluginManagerModule::initialize(Controller *_uiController) uiController->registerWidget("pluginDetail", detailViewImpl); uiController->registerWidget(MWMTA_PLUGINS, storeWidgetImpl); + uiController->bindWidgetToNavigation(MWMTA_PLUGINS, actionOptionsImpl); QObject::connect(pluginManagerAction, &QAction::triggered, this, [this]() { uiController->showWidgetAtPosition("pluginDetail", Position::Central); uiController->showWidgetAtPosition(MWMTA_PLUGINS, Position::Left); + auto windowService = dpfGetService(dpfservice::WindowService); + if (windowService) + windowService->setDockHeaderName(MWMTA_PLUGINS, tr("Extensions")); }); } diff --git a/src/plugins/core/notify/gui/itemdelegate.cpp b/src/plugins/core/notify/gui/itemdelegate.cpp index 2d5cfb32c..757e5861f 100644 --- a/src/plugins/core/notify/gui/itemdelegate.cpp +++ b/src/plugins/core/notify/gui/itemdelegate.cpp @@ -40,7 +40,7 @@ QWidget *ItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem NotificationItemWidget *item = new NotificationItemWidget(parent, notify); layout->addWidget(item); - connect(item, &NotificationItemWidget::actionInvoked, view, &NotificationListView::actionInvoked); + connect(item, &NotificationItemWidget::actionInvoked, view, std::bind(&NotificationListView::actionInvoked, view, notify, std::placeholders::_1)); connect(item, &NotificationItemWidget::processed, view, &NotificationListView::processed); return frame; diff --git a/src/plugins/core/notify/gui/notificationcenterwidget.cpp b/src/plugins/core/notify/gui/notificationcenterwidget.cpp index a10b9bae2..f1685d471 100644 --- a/src/plugins/core/notify/gui/notificationcenterwidget.cpp +++ b/src/plugins/core/notify/gui/notificationcenterwidget.cpp @@ -140,9 +140,12 @@ void NotificationCenterWidget::handleClear() d->updateView(); } -void NotificationCenterWidget::handleActionInvoked(const QString &actId) +void NotificationCenterWidget::handleActionInvoked(EntityPtr ptr, const QString &actId) { - notifyManager.actionInvoked(actId); + if (ptr->callback()) + ptr->callback()(actId); + else + notifyManager.actionInvoked(actId); } void NotificationCenterWidget::handleProcessed(EntityPtr ptr) diff --git a/src/plugins/core/notify/gui/notificationcenterwidget.h b/src/plugins/core/notify/gui/notificationcenterwidget.h index f46c79c71..8c3fcbc2d 100644 --- a/src/plugins/core/notify/gui/notificationcenterwidget.h +++ b/src/plugins/core/notify/gui/notificationcenterwidget.h @@ -22,7 +22,7 @@ class NotificationCenterWidget : public DTK_WIDGET_NAMESPACE::DFloatingWidget public Q_SLOTS: void handleClear(); - void handleActionInvoked(const QString &actId); + void handleActionInvoked(EntityPtr ptr, const QString &actId); void handleProcessed(EntityPtr ptr); private: diff --git a/src/plugins/core/notify/gui/notificationlistview.h b/src/plugins/core/notify/gui/notificationlistview.h index 3c9b25c8c..61dc886c5 100644 --- a/src/plugins/core/notify/gui/notificationlistview.h +++ b/src/plugins/core/notify/gui/notificationlistview.h @@ -18,7 +18,7 @@ class NotificationListView : public DTK_WIDGET_NAMESPACE::DListView Q_SIGNALS: void processed(EntityPtr ptr); - void actionInvoked(const QString &actId); + void actionInvoked(EntityPtr ptr, const QString &actId); }; #endif // NOTIFICATIONLISTVIEW_H diff --git a/src/plugins/core/notify/notificationentity.cpp b/src/plugins/core/notify/notificationentity.cpp index 29b9f639b..9d69f99d4 100644 --- a/src/plugins/core/notify/notificationentity.cpp +++ b/src/plugins/core/notify/notificationentity.cpp @@ -11,10 +11,11 @@ class NotificationEntityPrivate QString message; QStringList actions; NotificationEntity::NotificationType type = NotificationEntity::Information; + NotifyCallback callback; }; NotificationEntity::NotificationEntity(NotificationType type, const QString &name, const QString &msg, - const QStringList &actions, QObject *parent) + const QStringList &actions, NotifyCallback cb, QObject *parent) : QObject(parent), d(new NotificationEntityPrivate) { @@ -22,6 +23,7 @@ NotificationEntity::NotificationEntity(NotificationType type, const QString &nam d->name = name; d->message = msg; d->actions = actions; + d->callback = cb; } NotificationEntity::~NotificationEntity() @@ -68,3 +70,13 @@ void NotificationEntity::setType(NotificationType type) { d->type = type; } + +NotifyCallback NotificationEntity::callback() const +{ + return d->callback; +} + +void NotificationEntity::setCallback(NotifyCallback cb) +{ + d->callback = cb; +} diff --git a/src/plugins/core/notify/notificationentity.h b/src/plugins/core/notify/notificationentity.h index 0fbd3e981..0c35ab4c1 100644 --- a/src/plugins/core/notify/notificationentity.h +++ b/src/plugins/core/notify/notificationentity.h @@ -9,6 +9,9 @@ #include #include +#include + +using NotifyCallback = std::function; class NotificationEntityPrivate; class NotificationEntity : public QObject @@ -23,7 +26,7 @@ class NotificationEntity : public QObject explicit NotificationEntity(NotificationType type, const QString &name = QString(), const QString &msg = QString(), const QStringList &actions = QStringList(), - QObject *parent = nullptr); + NotifyCallback cb = nullptr, QObject *parent = nullptr); ~NotificationEntity(); QString name() const; @@ -38,6 +41,9 @@ class NotificationEntity : public QObject NotificationType type() const; void setType(NotificationType type); + NotifyCallback callback() const; + void setCallback(NotifyCallback cb); + private: NotificationEntityPrivate *const d; }; diff --git a/src/plugins/core/notify/notificationmanager.cpp b/src/plugins/core/notify/notificationmanager.cpp index ed387994f..ab91403aa 100644 --- a/src/plugins/core/notify/notificationmanager.cpp +++ b/src/plugins/core/notify/notificationmanager.cpp @@ -115,9 +115,14 @@ NotificationManager *NotificationManager::instance() } void NotificationManager::notify(uint type, const QString &name, const QString &msg, const QStringList &actions) +{ + notify(type, name, msg, actions, nullptr); +} + +void NotificationManager::notify(uint type, const QString &name, const QString &msg, const QStringList &actions, NotifyCallback cb) { EntityPtr notification = std::make_shared(static_cast(type), - name, msg, actions); + name, msg, actions, cb); if (d->ncWidget && d->ncWidget->isVisible()) { d->allEntities.push_front(notification); @@ -198,12 +203,16 @@ void NotificationManager::bubbleDismissed(Bubble *bubble) void NotificationManager::bubbleActionInvoked(Bubble *bubble, const QString &actId) { + auto entity = bubble->entity(); if (d->displayedBubbleList.contains(bubble)) { d->displayedBubbleList.removeOne(bubble); bubble->close(); } - notifyManager.actionInvoked(actId); + if (entity->callback()) + entity->callback()(actId); + else + notifyManager.actionInvoked(actId); } bool NotificationManager::eventFilter(QObject *watched, QEvent *event) diff --git a/src/plugins/core/notify/notificationmanager.h b/src/plugins/core/notify/notificationmanager.h index 9a80cb2bd..e266d0f05 100644 --- a/src/plugins/core/notify/notificationmanager.h +++ b/src/plugins/core/notify/notificationmanager.h @@ -20,6 +20,7 @@ class NotificationManager : public QObject public Q_SLOTS: void notify(uint type, const QString &name, const QString &msg, const QStringList &actions); + void notify(uint type, const QString &name, const QString &msg, const QStringList &actions, NotifyCallback cb); void show(); void hide(); void toggle(); diff --git a/src/plugins/core/uicontroller/controller.cpp b/src/plugins/core/uicontroller/controller.cpp index 2613c508e..fab083475 100644 --- a/src/plugins/core/uicontroller/controller.cpp +++ b/src/plugins/core/uicontroller/controller.cpp @@ -16,6 +16,7 @@ #include "modules/documentfindmodule.h" #include "modules/contextmodule.h" #include "modules/notificationmodule.h" +#include "modules/dependencemodule.h" #include "locator/locatormanager.h" #include "find/placeholdermanager.h" #include "common/util/utils.h" @@ -37,16 +38,13 @@ static Controller *ins { nullptr }; -//WN = window name -inline const QString WN_CONTEXTWIDGET = "contextWidget"; inline const QString WN_LOADINGWIDGET = "loadingWidget"; -inline const QString WN_WORKSPACE = "workspaceWidget"; // MW = MainWindow inline constexpr int MW_WIDTH { 1280 }; inline constexpr int MW_HEIGHT { 860 }; -inline constexpr int MW_MIN_WIDTH { 1280 }; +inline constexpr int MW_MIN_WIDTH { 1080 }; inline constexpr int MW_MIN_HEIGHT { 600 }; using namespace dpfservice; @@ -55,10 +53,42 @@ DWIDGET_USE_NAMESPACE struct WidgetInfo { QString name; - DWidget *widget; - Position pos; - bool replace; - bool isVisible; + DWidget *widget { nullptr }; + QDockWidget *dockWidget { nullptr }; + QString headerName; + QList headerList; + QList headerWidget; // set button to header after create dock + QIcon icon; + + Position defaultPos; // set position after create dock + bool replace { false }; // hide current position`s dock before show + bool defaultVisible { true }; + bool created { false }; // has already create dock + bool hiddenByManual { false }; + + bool operator==(const WidgetInfo &info) + { + if (name == info.name && widget == info.widget) + return true; + return false; + }; +}; + +class DocksManagerButton : public DToolButton +{ +public: + explicit DocksManagerButton(QWidget *parent, Controller *con) + : DToolButton(parent), controller(con) { setMouseTracking(true); } + +protected: + void mouseMoveEvent(QMouseEvent *event) override + { + if (controller) + controller->showCurrentDocksManager(); + } + +private: + Controller *controller { nullptr }; }; class ControllerPrivate @@ -66,18 +96,18 @@ class ControllerPrivate MainWindow *mainWindow { nullptr }; loadingWidget *loadingwidget { nullptr }; WorkspaceWidget *workspace { nullptr }; - bool showWorkspace { nullptr }; - - QMap widgetWaitForAdd; - QMap addedWidget; + DWidget *navigationToolBar { nullptr }; NavigationBar *navigationBar { nullptr }; QMap navigationActions; + QMap widgetBindToNavigation; + DocksManagerButton *docksManager { nullptr }; DWidget *leftTopToolBar { nullptr }; DSearchEdit *locatorBar { nullptr }; DWidget *rightTopToolBar { nullptr }; QMap topToolBtn; + QMap dockButtons; QMap contextWidgets; QMap tabButtons; @@ -85,21 +115,20 @@ class ControllerPrivate DStackedWidget *stackContextWidget { nullptr }; DFrame *contextTabBar { nullptr }; QHBoxLayout *contextButtonLayout { nullptr }; - bool contextWidgetAdded { false }; WindowStatusBar *statusBar { nullptr }; DMenu *menu { nullptr }; - QStringList hiddenWidgetList; - QStringList validModeList { CM_EDIT, CM_DEBUG, CM_RECENT }; QMap modePluginMap { { CM_EDIT, MWNA_EDIT }, { CM_RECENT, MWNA_RECENT }, { CM_DEBUG, MWNA_DEBUG } }; - QString mode { "" }; //mode: CM_EDIT/CM_DEBUG/CM_RECENT - QMap> modeInfo; + QString mode { "" }; // mode: CM_EDIT/CM_DEBUG/CM_RECENT + QMap modeInfo; QString currentNavigation { "" }; QMap modules; + QMap allWidgets; + QList currentDocks; friend class Controller; }; @@ -128,16 +157,18 @@ Controller::Controller(QObject *parent) { initMainWindow(); initNavigationBar(); - initContextWidget(); initStatusBar(); + initContextWidget(); initWorkspaceWidget(); initTopToolBar(); + initDocksManager(); registerService(); registerModule("pluginManagerModule", new PluginManagerModule()); registerModule("docFindModule", new DocumentFindModule()); registerModule("contextModule", new ContextModule()); registerModule("notifyModule", new NotificationModule()); + registerModule("dependenceModule", new DependenceModule()); initModules(); } @@ -169,13 +200,16 @@ void Controller::registerService() windowService->showWidgetAtPosition = std::bind(&Controller::showWidgetAtPosition, this, _1, _2, _3); } if (!windowService->setDockHeaderName) { - windowService->setDockHeaderName = std::bind(&MainWindow::setDockHeadername, d->mainWindow, _1, _2); + windowService->setDockHeaderName = std::bind(&Controller::setDockHeaderName, this, _1, _2); + } + if (!windowService->setDockHeaderList) { + windowService->setDockHeaderList = std::bind(&Controller::setDockHeaderList, this, _1, _2); } if (!windowService->deleteDockHeader) { windowService->deleteDockHeader = std::bind(&MainWindow::deleteDockHeader, d->mainWindow, _1); } if (!windowService->addToolBtnToDockHeader) { - windowService->addToolBtnToDockHeader = std::bind(&MainWindow::addToolBtnToDockHeader, d->mainWindow, _1, _2); + windowService->addToolBtnToDockHeader = std::bind(&MainWindow::addWidgetToDockHeader, d->mainWindow, _1, _2); } if (!windowService->setDockWidgetFeatures) { windowService->setDockWidgetFeatures = std::bind(&MainWindow::setDockWidgetFeatures, d->mainWindow, _1, _2); @@ -192,6 +226,9 @@ void Controller::registerService() if (!windowService->switchWidgetNavigation) { windowService->switchWidgetNavigation = std::bind(&Controller::switchWidgetNavigation, this, _1); } + if (!windowService->bindWidgetToNavigation) { + windowService->bindWidgetToNavigation = std::bind(&Controller::bindWidgetToNavigation, this, _1, _2); + } if (!windowService->getAllNavigationItemName) { windowService->getAllNavigationItemName = std::bind(&NavigationBar::getAllNavigationItemName, d->navigationBar); } @@ -240,6 +277,9 @@ void Controller::registerService() if (!windowService->showTopToolBar) { windowService->showTopToolBar = std::bind(&Controller::showTopToolBar, this); } + if (!windowService->setTopToolItemVisible) { + windowService->setTopToolItemVisible = std::bind(&Controller::setTopToolItemVisible, this, _1, _2); + } if (!windowService->removeTopToolItem) { windowService->removeTopToolItem = std::bind(&Controller::removeTopToolItem, this, _1); } @@ -261,12 +301,21 @@ void Controller::registerService() if (!windowService->registerToolBtnToWorkspaceWidget) { windowService->registerToolBtnToWorkspaceWidget = std::bind(&WorkspaceWidget::registerToolBtnToWidget, d->workspace, _1, _2); } + if (!windowService->registerWidgetToDockHeader) { + windowService->registerWidgetToDockHeader = std::bind(&Controller::registerWidgetToDockHeader, this, _1, _2); + } if (!windowService->createFindPlaceHolder) { windowService->createFindPlaceHolder = std::bind(&PlaceHolderManager::createPlaceHolder, PlaceHolderManager::instance(), _1, _2); } + if (!windowService->getCentralWidgetName) { + windowService->getCentralWidgetName = std::bind(&MainWindow::getCentralWidgetName, d->mainWindow); + } if (!windowService->getCurrentDockName) { windowService->getCurrentDockName = std::bind(&MainWindow::getCurrentDockName, d->mainWindow, _1); } + if (!windowService->resizeDocks) { + windowService->resizeDocks = std::bind(&Controller::resizeDocks, this, _1, _2, _3); + } } Controller::~Controller() @@ -281,6 +330,26 @@ Controller::~Controller() delete d; } +void Controller::createDockWidget(WidgetInfo &info) +{ + auto dock = d->mainWindow->addWidget(info.name, info.widget, info.defaultPos); + info.dockWidget = dock; + info.created = true; + + if (!info.headerName.isEmpty()) + d->mainWindow->setDockHeaderName(info.name, info.headerName); + else if (!info.headerList.isEmpty()) + d->mainWindow->setDockHeaderList(info.name, info.headerList); + + if (!info.headerWidget.isEmpty()) { + for (auto btn : info.headerWidget) + d->mainWindow->addWidgetToDockHeader(info.name, btn); + } + + if (info.icon.isNull()) + info.icon = QIcon::fromTheme("default_dock"); +} + void Controller::raiseMode(const QString &mode) { if (!d->validModeList.contains(mode)) { @@ -288,29 +357,37 @@ void Controller::raiseMode(const QString &mode) return; } - auto widgetInfoList = d->modeInfo[mode]; - foreach (auto widgetInfo, widgetInfoList) { + auto widgetList = d->modeInfo[mode]; + foreach (auto widgetName, widgetList) { + auto &widgetInfo = d->allWidgets[widgetName]; if (widgetInfo.replace) - d->mainWindow->hideWidget(widgetInfo.pos); - d->mainWindow->showWidget(widgetInfo.name); - //widget in mainWindow is Dock(widget), show dock and hide widget. - if (!widgetInfo.isVisible) - widgetInfo.widget->hide(); + d->mainWindow->hideWidget(widgetInfo.defaultPos); + if (!widgetInfo.hiddenByManual) + d->mainWindow->showWidget(widgetInfo.name); + // widget in mainWindow is Dock(widget), show dock and hide widget + widgetInfo.widget->setVisible(widgetInfo.defaultVisible); + if (widgetInfo.dockWidget) + d->currentDocks.append(widgetInfo.name); } + checkDocksManager(); + if (mode == CM_RECENT) { d->mode = mode; + uiController.modeRaised(CM_RECENT); return; } + showStatusBar(); + if (mode == CM_EDIT) showWorkspace(); showTopToolBar(); showContextWidget(); - showStatusBar(); d->mode = mode; + uiController.modeRaised(mode); } void Controller::replaceWidget(const QString &name, Position pos) @@ -320,17 +397,20 @@ void Controller::replaceWidget(const QString &name, Position pos) void Controller::insertWidget(const QString &name, Position pos, Qt::Orientation orientation) { - if (d->widgetWaitForAdd.contains(name)) { - d->mainWindow->addWidget(name, d->widgetWaitForAdd[name], pos, orientation); - } else if (d->addedWidget.contains(name)) { - d->mainWindow->showWidget(name); - } else { + if (d->allWidgets.contains(name)) { qWarning() << "no widget named:" << name; return; } - d->addedWidget.insert(name, d->widgetWaitForAdd[name]); - d->widgetWaitForAdd.remove(name); + auto &info = d->allWidgets[name]; + if (!info.created) { + auto dock = d->mainWindow->addWidget(name, info.widget, pos, orientation); + info.dockWidget = dock; + info.created = true; + info.replace = false; + } else { + d->mainWindow->showWidget(name); + } } void Controller::hideWidget(const QString &name) @@ -345,34 +425,52 @@ void Controller::registerWidgetToMode(const QString &name, AbstractWidget *abstr return; } + if (d->allWidgets.contains(name)) { + auto &info = d->allWidgets[name]; + if (!d->modeInfo[mode].contains(name)) { + if (info.defaultPos != pos) + qWarning() << "widget named: " << name << "has registed to another position"; + d->modeInfo[mode].append(name); + } else { + qWarning() << "Widget named: " << name << "has alreay registed"; + } + return; + } + DWidget *qWidget = static_cast(abstractWidget->qWidget()); if (!qWidget->parent()) qWidget->setParent(d->mainWindow); WidgetInfo widgetInfo; widgetInfo.name = name; - widgetInfo.pos = pos; + widgetInfo.defaultPos = pos; widgetInfo.replace = replace; widgetInfo.widget = qWidget; - widgetInfo.isVisible = isVisible; + widgetInfo.defaultVisible = isVisible; + widgetInfo.icon = abstractWidget->getDisplayIcon(); - d->addedWidget.insert(name, qWidget); - d->mainWindow->addWidget(name, qWidget, pos); + createDockWidget(widgetInfo); d->mainWindow->hideWidget(name); - d->modeInfo[mode].append(widgetInfo); + d->allWidgets.insert(name, widgetInfo); + d->modeInfo[mode].append(name); } void Controller::registerWidget(const QString &name, AbstractWidget *abstractWidget) { - if (d->widgetWaitForAdd.contains(name) || d->addedWidget.contains(name)) + if (d->allWidgets.contains(name)) return; auto widget = static_cast(abstractWidget->qWidget()); if (!widget->parent()) widget->setParent(d->mainWindow); - d->widgetWaitForAdd.insert(name, widget); + WidgetInfo widgetInfo; + widgetInfo.name = name; + widgetInfo.widget = widget; + widgetInfo.icon = abstractWidget->getDisplayIcon(); + + d->allWidgets.insert(name, widgetInfo); } void Controller::showWidgetAtPosition(const QString &name, Position pos, bool replace) @@ -380,16 +478,75 @@ void Controller::showWidgetAtPosition(const QString &name, Position pos, bool re if (replace) d->mainWindow->hideWidget(pos); - if (d->widgetWaitForAdd.contains(name)) { - d->mainWindow->addWidget(name, d->widgetWaitForAdd[name], pos); - d->addedWidget.insert(name, d->widgetWaitForAdd[name]); - d->widgetWaitForAdd.remove(name); - } else if (d->addedWidget.contains(name)) { + if (!d->allWidgets.contains(name)) { + qWarning() << "no widget named: " << name; + return; + } + + auto &info = d->allWidgets[name]; + if (!info.created) { + info.defaultPos = pos; + info.replace = replace; + createDockWidget(info); + } else if (!info.hiddenByManual || d->widgetBindToNavigation.contains(name)) { d->mainWindow->showWidget(name); - } else { - qWarning() << "no widget named:" << name; + info.hiddenByManual = false; + } + + if (pos != Position::Central && pos != Position::FullWindow) { + if (replace) { + for (auto name : d->currentDocks) { + if (d->mainWindow->positionOfDock(name) == pos) + d->currentDocks.removeOne(name); + } + } + d->currentDocks.append(name); + } + + checkDocksManager(); +} + +void Controller::resizeDocks(const QList &docks, const QList &sizes, Qt::Orientation orientation) +{ + QList dockWidgets; + for (auto dockName : docks) { + if (d->allWidgets.contains(dockName) && d->allWidgets[dockName].created) + dockWidgets.append(d->allWidgets[dockName].dockWidget); + else + qWarning() << "Dock named: " << dockName << "has not created!"; + } + + d->mainWindow->resizeDocks(dockWidgets, sizes, orientation); + QApplication::processEvents(); // process layout update +} + +void Controller::setDockHeaderName(const QString &dockName, const QString &headerName) +{ + if (!d->allWidgets.contains(dockName)) { + qWarning() << "No widget named: " << dockName; + return; + } + + auto &info = d->allWidgets[dockName]; + info.headerName = headerName; + + if (info.created) + d->mainWindow->setDockHeaderName(dockName, headerName); +} + +void Controller::setDockHeaderList(const QString &dockName, const QList &actions) +{ + if (!d->allWidgets.contains(dockName)) { + qWarning() << "No widget named: " << dockName; return; } + + auto &info = d->allWidgets[dockName]; + info.headerName = ""; + info.headerList = actions; + + if (info.created) + d->mainWindow->setDockHeaderList(dockName, actions); } void Controller::addNavigationItem(AbstractAction *action, quint8 priority) @@ -415,22 +572,64 @@ void Controller::addNavigationItemToBottom(AbstractAction *action, quint8 priori void Controller::switchWidgetNavigation(const QString &navName) { d->navigationBar->setNavActionChecked(navName, true); - if (d->currentNavigation == navName) + if (d->currentNavigation == navName) { + auto action = d->navigationActions[navName]; + if (d->widgetBindToNavigation.values().contains(action)) { + auto dockName = d->widgetBindToNavigation.key(action); + auto &info = d->allWidgets[dockName]; + if (info.hiddenByManual) + d->mainWindow->showWidget(dockName); + info.hiddenByManual = false; + } return; + } d->currentNavigation = navName; d->mainWindow->hideAllWidget(); d->mainWindow->hideTopTollBar(); hideStatusBar(); + d->currentDocks.clear(); + for (auto btn : d->dockButtons.values()) + btn->hide(); + if (d->modePluginMap.values().contains(navName)) raiseMode(d->modePluginMap.key(navName)); d->navigationActions[navName]->trigger(); - //send event + // send event uiController.switchToWidget(navName); } +void Controller::bindWidgetToNavigation(const QString &dockName, AbstractAction *abstractAction) +{ + auto action = abstractAction->qAction(); + if (!action || d->widgetBindToNavigation.values().contains(action)) { + qWarning() << action->text() << " Navigation has already bind to a widget or action invalid"; + return; + } + + if (!d->allWidgets.contains(dockName)) { + qWarning() << "no widget named: " << dockName; + return; + } + + d->widgetBindToNavigation.insert(dockName, action); +} + +void Controller::registerWidgetToDockHeader(const QString &dockName, QWidget *widget) +{ + if (!d->allWidgets.contains(dockName)) { + qWarning() << "No widget named: " << dockName; + return; + } + + auto &info = d->allWidgets[dockName]; + if (info.created) + d->mainWindow->addWidgetToDockHeader(dockName, widget); + info.headerWidget.append(widget); +} + void Controller::addContextWidget(const QString &title, AbstractWidget *contextWidget, bool isVisible) { DWidget *qWidget = static_cast(contextWidget->qWidget()); @@ -462,14 +661,14 @@ void Controller::addContextWidget(const QString &title, AbstractWidget *contextW void Controller::showContextWidget() { - if (!d->contextWidgetAdded) { - d->mainWindow->addWidget(WN_CONTEXTWIDGET, d->contextWidget, Position::Bottom); - d->addedWidget.insert(WN_CONTEXTWIDGET, d->contextWidget); + auto contextInfo = d->allWidgets[WN_CONTEXTWIDGET]; + if (!contextInfo.created) { + createDockWidget(d->allWidgets[WN_CONTEXTWIDGET]); d->mainWindow->deleteDockHeader(WN_CONTEXTWIDGET); - d->contextWidgetAdded = true; - } else { + } else if (!contextInfo.hiddenByManual) { d->mainWindow->showWidget(WN_CONTEXTWIDGET); } + d->currentDocks.append(WN_CONTEXTWIDGET); } bool Controller::hasContextWidget(const QString &title) @@ -484,8 +683,16 @@ void Controller::hideContextWidget() void Controller::switchContextWidget(const QString &title) { - qInfo() << __FUNCTION__; + if (!d->contextWidgets.contains(title)) { + qWarning() << "No ContextWidget named: " << title; + return; + } + d->stackContextWidget->setCurrentWidget(d->contextWidgets[title]); + if (d->stackContextWidget->isHidden()) { + d->contextWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + d->stackContextWidget->show(); + } if (d->tabButtons.contains(title)) d->tabButtons[title]->show(); @@ -509,7 +716,7 @@ void Controller::addChildMenu(AbstractMenu *abstractMenu) } } - //make the `helper` menu at the last + // make the `helper` menu at the last for (QAction *action : d->menu->actions()) { if (action->text() == MWM_TOOLS) { d->menu->insertMenu(action, inputMenu); @@ -517,7 +724,7 @@ void Controller::addChildMenu(AbstractMenu *abstractMenu) } } - //add menu to last + // add menu to last d->menu->addMenu(inputMenu); } @@ -641,8 +848,8 @@ void Controller::addWidgetToTopTool(AbstractWidget *abstractWidget, bool addSepa } else { hlayout = qobject_cast(d->leftTopToolBar->layout()); } - - //sort + + // sort auto index = 0; widget->setProperty("toptool_priority", priority); for (; index < hlayout->count(); index++) { @@ -654,7 +861,7 @@ void Controller::addWidgetToTopTool(AbstractWidget *abstractWidget, bool addSepa } if (addSeparator) { - DWidget* separator = new DWidget(d->mainWindow); + DWidget *separator = new DWidget(d->mainWindow); DVerticalLine *line = new DVerticalLine(d->mainWindow); auto separatorLayout = new QHBoxLayout(separator); separator->setProperty("toptool_priority", priority - 1); @@ -718,8 +925,8 @@ void Controller::loading() this, [=]() { d->mainWindow->removeWidget(WN_LOADINGWIDGET); - d->navigationBar->show(); - d->mainWindow->setToolbar(Qt::ToolBarArea::LeftToolBarArea, d->navigationBar); + d->navigationToolBar->show(); + d->mainWindow->setToolbar(Qt::ToolBarArea::LeftToolBarArea, d->navigationToolBar); }); } @@ -729,15 +936,27 @@ void Controller::initMainWindow() if (!d->mainWindow) { d->mainWindow = new MainWindow; d->mainWindow->setMinimumSize(MW_MIN_WIDTH, MW_MIN_HEIGHT); - d->mainWindow->resize(MW_WIDTH, MW_HEIGHT); - initMenu(); - if (CommandParser::instance().getModel() != CommandParser::CommandLine) { + QString initFile = CustomPaths::user(CustomPaths::Configures) + "/mainwindow.ini"; + QFile file(initFile); + if (file.open(QFile::ReadOnly)) { + QByteArray ba; + QDataStream inFile(&file); + inFile >> ba; + d->mainWindow->restoreGeometry(ba); + d->mainWindow->show(); + } else { + d->mainWindow->resize(MW_WIDTH, MW_HEIGHT); d->mainWindow->showMaximized(); - loading(); } + if (CommandParser::instance().getModel() == CommandParser::CommandLine) { + d->mainWindow->hide(); + } + + loading(); + auto desktop = QApplication::desktop(); int currentScreenIndex = desktop->screenNumber(d->mainWindow); QList screenList = QGuiApplication::screens(); @@ -748,6 +967,15 @@ void Controller::initMainWindow() int screenHeight = screenRect.height(); d->mainWindow->move((screenWidth - d->mainWindow->width()) / 2, (screenHeight - d->mainWindow->height()) / 2); } + + connect(d->mainWindow, &MainWindow::dockHidden, this, [=](const QString &dockName) { + if (d->allWidgets.contains(dockName)) { + auto &info = d->allWidgets[dockName]; + info.hiddenByManual = true; + if (d->widgetBindToNavigation.contains(dockName)) + d->navigationBar->setNavActionChecked(d->widgetBindToNavigation[dockName]->text(), false); + } + }); } } @@ -756,8 +984,13 @@ void Controller::initNavigationBar() qInfo() << __FUNCTION__; if (d->navigationBar) return; + d->navigationToolBar = new DWidget(d->mainWindow); + auto vLayout = new QVBoxLayout(d->navigationToolBar); d->navigationBar = new NavigationBar(d->mainWindow); - d->navigationBar->hide(); + d->navigationToolBar->hide(); + + vLayout->addWidget(d->navigationBar); + vLayout->setContentsMargins(0, 0, 2, 0); } void Controller::initMenu() @@ -803,7 +1036,7 @@ void Controller::createHelpActions() QDesktopServices::openUrl(QUrl("https://github.com/linuxdeepin/deepin-unioncode/issues")); }); QAction::connect(actionHelpDoc, &QAction::triggered, this, [=]() { - QDesktopServices::openUrl(QUrl("https://ecology.chinauos.com/adaptidentification/doc_new/#document2?dirid=656d40a9bd766615b0b02e5e")); + QDesktopServices::openUrl(QUrl("https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e")); }); } @@ -861,7 +1094,7 @@ void Controller::initContextWidget() hideBtn->setFixedSize(35, 35); hideBtn->setIcon(QIcon::fromTheme("hide_dock")); hideBtn->setToolTip(tr("Hide ContextWidget")); - connect(hideBtn, &DToolButton::clicked, d->contextWidget, [=](){ + connect(hideBtn, &DToolButton::clicked, d->contextWidget, [=]() { if (d->stackContextWidget->isVisible()) { d->stackContextWidget->hide(); d->contextWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); @@ -885,8 +1118,20 @@ void Controller::initContextWidget() contextVLayout->addWidget(d->stackContextWidget); d->contextWidget->setLayout(contextVLayout); - //add contextWidget after add centralWidget or it`s height is incorrect - //d->mainWindow->addWidget(WN_CONTEXTWIDGET, d->contextWidget, Position::Bottom); + // add contextWidget after add centralWidget or it`s height is incorrect + WidgetInfo info; + info.name = WN_CONTEXTWIDGET; + info.widget = d->contextWidget; + info.defaultPos = Position::Bottom; + info.icon = QIcon::fromTheme("context_widget"); + + if (d->statusBar) { + auto btn = createDockButton(info); + btn->setChecked(true); + d->statusBar->insertPermanentWidget(0, btn); + } + + d->allWidgets.insert(WN_CONTEXTWIDGET, info); } void Controller::initStatusBar() @@ -902,7 +1147,15 @@ void Controller::initWorkspaceWidget() { if (d->workspace) return; + d->workspace = new WorkspaceWidget(d->mainWindow); + + WidgetInfo info; + info.name = WN_WORKSPACE; + info.widget = d->workspace; + info.defaultPos = Position::Left; + info.replace = true; + d->allWidgets.insert(WN_WORKSPACE, info); } void Controller::initTopToolBar() @@ -912,7 +1165,7 @@ void Controller::initTopToolBar() hlayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); hlayout->setSpacing(0); hlayout->setContentsMargins(0, 0, 0, 0); - + d->locatorBar = LocatorManager::instance()->getInputEdit(); d->rightTopToolBar = new DWidget(d->mainWindow); @@ -935,6 +1188,21 @@ void Controller::initModules() } } +void Controller::initDocksManager() +{ + d->docksManager = new DocksManagerButton(d->navigationToolBar, this); + d->docksManager ->setIcon(QIcon::fromTheme("docks_manager")); + d->docksManager ->setFocusPolicy(Qt::NoFocus); + d->docksManager ->setToolTip(tr("Show docks in this view")); + d->docksManager->hide(); + d->navigationBar->addNavButton(d->docksManager , NavigationBar::bottom, Priority::low); + + connect(d->navigationBar, &NavigationBar::leave, this, [=]() { + for (auto btn : d->dockButtons.values()) + btn->hide(); + }); +} + void Controller::addMenuShortCut(QAction *action, QKeySequence keySequence) { QKeySequence key = keySequence; @@ -988,44 +1256,51 @@ void Controller::registerActionShortCut(AbstractAction *action) void Controller::showWorkspace() { - if (d->showWorkspace != true) { - d->mainWindow->addWidget(WN_WORKSPACE, d->workspace, Position::Left); + auto &workSpaceInfo = d->allWidgets[WN_WORKSPACE]; + if (!workSpaceInfo.created) { + createDockWidget(d->allWidgets[WN_WORKSPACE]); + d->mainWindow->showWidget(WN_WORKSPACE); d->mainWindow->resizeDock(WN_WORKSPACE, QSize(300, 300)); - d->addedWidget.insert(WN_WORKSPACE, d->workspace); - d->showWorkspace = true; - for (auto btn : d->workspace->getAllToolBtn()) - d->mainWindow->addToolBtnToDockHeader(WN_WORKSPACE, btn); + d->mainWindow->addWidgetToDockHeader(WN_WORKSPACE, btn); DToolButton *expandAll = new DToolButton(d->workspace); expandAll->setToolTip(tr("Expand All")); expandAll->setIcon(QIcon::fromTheme("expand_all")); - d->mainWindow->addToolBtnToDockHeader(WN_WORKSPACE, expandAll); - connect(expandAll, &DToolButton::clicked, this, [](){workspace.expandAll();}); + d->mainWindow->addWidgetToDockHeader(WN_WORKSPACE, expandAll); + connect(expandAll, &DToolButton::clicked, this, []() { workspace.expandAll(); }); DToolButton *foldAll = new DToolButton(d->workspace); foldAll->setToolTip(tr("Fold All")); foldAll->setIcon(QIcon::fromTheme("collapse_all")); - d->mainWindow->addToolBtnToDockHeader(WN_WORKSPACE, foldAll); - connect(foldAll, &DToolButton::clicked, this, [](){workspace.foldAll();}); + d->mainWindow->addWidgetToDockHeader(WN_WORKSPACE, foldAll); + connect(foldAll, &DToolButton::clicked, this, []() { workspace.foldAll(); }); expandAll->setVisible(d->workspace->getCurrentExpandState()); foldAll->setVisible(d->workspace->getCurrentExpandState()); - d->mainWindow->setDockHeadername(WN_WORKSPACE, d->workspace->getCurrentTitle()); - connect(d->workspace, &WorkspaceWidget::expandStateChange, this, [=](bool canExpand){ + auto titles = d->workspace->allWidgetTitles(); + QList headers; + for (auto title : titles) { + QAction *action = new QAction(title, d->workspace); + connect(action, &QAction::triggered, this, [=]() { d->workspace->switchWidgetWorkspace(title); }); + headers.append(action); + } + d->mainWindow->setDockHeaderList(WN_WORKSPACE, headers); + d->mainWindow->setDockHeaderName(WN_WORKSPACE, d->workspace->currentTitle()); + + connect(d->workspace, &WorkspaceWidget::expandStateChange, this, [=](bool canExpand) { expandAll->setVisible(canExpand); foldAll->setVisible(canExpand); }); - connect(d->workspace, &WorkspaceWidget::workSpaceWidgeSwitched, this, [=](const QString &title){ - d->mainWindow->setDockHeadername(WN_WORKSPACE, title); - for (auto btn : d->workspace->getAllToolBtn()) - btn->setVisible(d->workspace->getToolBtnByTitle(title).contains(btn) ? true : false); - }); - } - d->mainWindow->showWidget(WN_WORKSPACE); + d->workspace->addedToController = true; + bindWidgetToNavigation(WN_WORKSPACE, new AbstractAction(d->navigationActions[MWNA_EDIT])); + } else { + d->mainWindow->showWidget(WN_WORKSPACE); + } + d->currentDocks.append(WN_WORKSPACE); } DToolButton *Controller::createIconButton(QAction *action) @@ -1045,3 +1320,69 @@ void Controller::removeTopToolItem(AbstractAction *action) delete iconBtn; d->topToolBtn.remove(action->qAction()); } + +bool Controller::checkDocksManager() +{ + for (auto dock : d->currentDocks) { + auto &dockInfo = d->allWidgets[dock]; + if (!dockInfo.dockWidget || d->widgetBindToNavigation.contains(dock) || dockInfo.name == WN_CONTEXTWIDGET) + continue; + + if (!d->dockButtons.contains(dock)) { + auto btn = createDockButton(dockInfo); + btn->hide(); + d->navigationBar->addNavButton(btn, NavigationBar::bottom, Priority::high); + d->dockButtons.insert(dockInfo.name, btn); + } + + d->docksManager->show(); + return true; + } + + d->docksManager->hide(); + return false; +} + +void Controller::showCurrentDocksManager() +{ + for (auto dock : d->currentDocks) { + auto &dockInfo = d->allWidgets[dock]; + if (!d->dockButtons.contains(dockInfo.name)) + continue; + + auto btn = d->dockButtons[dockInfo.name]; + btn->show(); + btn->setChecked(dockInfo.dockWidget->isVisible()); + } +} + +DToolButton *Controller::createDockButton(const WidgetInfo &info) +{ + DToolButton *btn = new DToolButton(d->navigationToolBar); + btn->setIcon(info.icon); + btn->setToolTip(info.name); + btn->setCheckable(true); + connect(btn, &DToolButton::clicked, this, [=]() { + auto &dockInfo = d->allWidgets[info.name]; + if (dockInfo.dockWidget->isVisible()) { + d->mainWindow->hideWidget(dockInfo.name); + btn->setChecked(false); + dockInfo.hiddenByManual = true; + } else { + d->mainWindow->showWidget(dockInfo.name); + btn->setChecked(true); + dockInfo.hiddenByManual = false; + } + }, + Qt::UniqueConnection); + return btn; +} + +void Controller::setTopToolItemVisible(AbstractAction *action, bool visible) +{ + if (!action || !action->qAction()) + return; + + auto iconBtn = d->topToolBtn.value(action->qAction()); + iconBtn->setVisible(visible); +} diff --git a/src/plugins/core/uicontroller/mainwindow.cpp b/src/plugins/core/uicontroller/mainwindow.cpp index 552fdf54c..1152af9d0 100644 --- a/src/plugins/core/uicontroller/mainwindow.cpp +++ b/src/plugins/core/uicontroller/mainwindow.cpp @@ -3,11 +3,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "mainwindow.h" +#include "common/util/eventdefinitions.h" +#include "common/util/custompaths.h" #include #include #include +#include #include using dpfservice::Position; @@ -62,7 +65,7 @@ MainWindow::MainWindow(QWidget *parent) setAttribute(Qt::WA_DeleteOnClose); addTopToolBar(); - setContextMenuPolicy(Qt::NoContextMenu); //donot show left toolbar`s contextmenu + setContextMenuPolicy(Qt::NoContextMenu); //donot show left toolbar`s contextmenu //setStyleSheet("QMainWindow::separator { width: 2px; margin: 0px; padding: 0px; }"); setCorner(Qt::Corner::BottomLeftCorner, Qt::DockWidgetArea::LeftDockWidgetArea); @@ -71,6 +74,13 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { + QString initFile = CustomPaths::user(CustomPaths::Configures) + "/mainwindow.ini"; + QFile file(initFile); + if (file.open(QFile::WriteOnly)) { + QDataStream outFile(&file); + outFile << saveGeometry(); + } + delete d; } @@ -83,6 +93,7 @@ DDockWidget *MainWindow::createDockWidget(DWidget *widget) delete dock->titleBarWidget(); auto header = new DockHeader(this); + header->setContentsMargins(10, 8, 8, 8); dock->setTitleBarWidget(header); dock->setWidget(widget); @@ -90,7 +101,7 @@ DDockWidget *MainWindow::createDockWidget(DWidget *widget) return dock; } -void MainWindow::setDockHeadername(const QString &dockName, const QString &headerName) +void MainWindow::setDockHeaderName(const QString &dockName, const QString &headerName) { if (!d->dockList.contains(dockName)) return; @@ -102,6 +113,18 @@ void MainWindow::setDockHeadername(const QString &dockName, const QString &heade titleBar->setHeaderName(headerName); } +void MainWindow::setDockHeaderList(const QString &dockName, const QList &actions) +{ + if (!d->dockList.contains(dockName)) + return; + + auto dock = d->dockList[dockName]; + auto titleBar = qobject_cast(dock->titleBarWidget()); + + if (titleBar) + titleBar->setHeaderNames(actions); +} + void MainWindow::deleteDockHeader(const QString &name) { if (!d->dockList.contains(name)) @@ -116,7 +139,7 @@ void MainWindow::deleteDockHeader(const QString &name) dock->setTitleBarWidget(new QWidget()); } -void MainWindow::addToolBtnToDockHeader(const QString &dockName, DToolButton *btn) +void MainWindow::addWidgetToDockHeader(const QString &dockName, QWidget *widget) { if (!d->dockList.contains(dockName)) return; @@ -125,14 +148,14 @@ void MainWindow::addToolBtnToDockHeader(const QString &dockName, DToolButton *bt auto titleBar = qobject_cast(dock->titleBarWidget()); if (titleBar) - titleBar->addToolButton(btn); + titleBar->addWidget(widget); } -void MainWindow::addWidget(const QString &name, QWidget *widget, Position pos) +QDockWidget *MainWindow::addWidget(const QString &name, QWidget *widget, Position pos) { if (d->dockList.contains(name)) { qWarning() << "dockWidget-" << name << "is already exist"; - return; + return nullptr; } if (pos == Position::FullWindow) @@ -142,10 +165,11 @@ void MainWindow::addWidget(const QString &name, QWidget *widget, Position pos) if (!d->centralWidgetName.isEmpty() && centralWidget()) hideWidget(d->centralWidgetName); + widget->show(); setCentralWidget(widget); d->centralWidgetName = name; d->centralWidgets.insert(name, widget); - return; + return nullptr; } auto area = positionTodockArea(pos); @@ -159,18 +183,19 @@ void MainWindow::addWidget(const QString &name, QWidget *widget, Position pos) //add close btn to dock`s header initDockHeader(dock, pos); + return dock; } -void MainWindow::addWidget(const QString &name, QWidget *widget, dpfservice::Position pos, Qt::Orientation orientation) +QDockWidget *MainWindow::addWidget(const QString &name, QWidget *widget, dpfservice::Position pos, Qt::Orientation orientation) { if (d->dockList.contains(name)) { qWarning() << "dockWidget-" << name << "is already exist"; - return; + return nullptr; } if (pos == Position::Central || pos == Position::FullWindow) { addWidget(name, widget, pos); - return; + return nullptr; } auto area = positionTodockArea(pos); @@ -184,27 +209,24 @@ void MainWindow::addWidget(const QString &name, QWidget *widget, dpfservice::Pos //add close btn to dock`s header initDockHeader(dock, pos); + return dock; } void MainWindow::initDockHeader(DDockWidget *dock, dpfservice::Position pos) { - if (pos != Position::Left) - return; - auto closeBtn = new DToolButton(dock); - closeBtn->setCheckable(true); closeBtn->setIcon(QIcon::fromTheme("hide_dock")); closeBtn->setToolTip(tr("Hide Dock Widget")); + closeBtn->setCheckable(false); - addToolBtnToDockHeader(d->dockList.key(dock), closeBtn); + addWidgetToDockHeader(d->dockList.key(dock), closeBtn); - connect(closeBtn, &DToolButton::clicked, dock, [=](){ + connect(closeBtn, &DToolButton::clicked, dock, [=]() { + auto dockName = d->dockList.key(dock); if (dock->isVisible()) { dock->hide(); - } else { - dock->show(); - resizeDock(d->dockList.key(dock), QSize(300, 300)); } + emit dockHidden(dockName); }); } @@ -221,13 +243,11 @@ void MainWindow::resizeDock(QDockWidget *dock) } //(100, 30) means dockWidget havn`t init - if(size == QSize(100, 30)) + if (size == QSize(100, 30)) size = QSize(300, 300); - if (area == Qt::LeftDockWidgetArea || area == Qt::RightDockWidgetArea) - resizeDocks({ dock }, { size.width() }, Qt::Horizontal); - else - resizeDocks({ dock }, { size.height() }, Qt::Vertical); + resizeDocks({ dock }, { size.width() }, Qt::Horizontal); + resizeDocks({ dock }, { size.height() }, Qt::Vertical); } void MainWindow::resizeDock(const QString &dockName, QSize size) @@ -236,11 +256,9 @@ void MainWindow::resizeDock(const QString &dockName, QSize size) return; auto dock = d->dockList[dockName]; - auto area = dockWidgetArea(dock); - if (area == Qt::LeftDockWidgetArea || area == Qt::RightDockWidgetArea) - resizeDocks({ dock }, { size.width() }, Qt::Horizontal); - else - resizeDocks({ dock }, { size.height() }, Qt::Vertical); + + resizeDocks({ dock }, { size.width() }, Qt::Horizontal); + resizeDocks({ dock }, { size.height() }, Qt::Vertical); } void MainWindow::replaceWidget(const QString &name, QWidget *widget, Position pos) @@ -275,23 +293,29 @@ void MainWindow::removeWidget(Position pos) } } -QString MainWindow::getCurrentDockName(dpfservice::Position pos) +QString MainWindow::getCentralWidgetName() +{ + return d->centralWidgetName; +} + +QStringList MainWindow::getCurrentDockName(dpfservice::Position pos) { if (pos == Position::Central || pos == Position::FullWindow) - return d->centralWidgetName; + return { d->centralWidgetName }; + QStringList ret {}; auto area = positionTodockArea(pos); for (auto dock : d->dockList.values()) { if (dockWidgetArea(dock) == area && dock->isVisible() == true) - return d->dockList.key(dock); + ret << d->dockList.key(dock); } - return QString(); + return ret; } -void MainWindow::setDockWidgetFeatures(const QString &name,QDockWidget::DockWidgetFeatures feature) +void MainWindow::setDockWidgetFeatures(const QString &name, QDockWidget::DockWidgetFeatures feature) { - if(!d->dockList.contains(name)) { + if (!d->dockList.contains(name)) { qWarning() << name << " is not a dockWidget."; return; } @@ -311,7 +335,9 @@ void MainWindow::removeWidget(const QString &name) } removeDockWidget(d->dockList[name]); + auto dock = d->dockList[name]; d->dockList.remove(name); + delete dock; } void MainWindow::hideWidget(const QString &name) @@ -339,16 +365,15 @@ void MainWindow::hideWidget(Position pos) } if (pos == Position::Central) { - if(centralWidget()) + if (centralWidget()) hideWidget(d->centralWidgetName); return; } auto area = positionTodockArea(pos); foreach (auto name, d->dockList.keys()) { - if (dockWidgetArea(d->dockList[name]) == area) { + if (dockWidgetArea(d->dockList[name]) == area) d->dockList[name]->hide(); - } } } @@ -368,7 +393,7 @@ void MainWindow::showWidget(const QString &name) //Prevent the dock widget being stretched when switching central widget QList restoreDock; foreach (auto dock, d->dockList) { - if(dock->isVisible()) { + if (dock->isVisible()) { dock->setVisible(false); restoreDock.append(dock); } @@ -396,25 +421,6 @@ void MainWindow::showWidget(const QString &name) d->dockList[name]->setVisible(true); } -void MainWindow::showWidget(Position pos) -{ - if (pos == Position::FullWindow) { - hideAllWidget(); - } - - if ((pos == Position::Central || pos == Position::FullWindow) && centralWidget()) { - showWidget(d->centralWidgetName); - return; - } - - auto area = positionTodockArea(pos); - foreach (auto name, d->dockList.keys()) { - if (dockWidgetArea(d->dockList[name]) == area) { - d->dockList[name]->show(); - } - } -} - void MainWindow::showAllWidget() { foreach (auto dock, d->dockList.values()) @@ -472,15 +478,13 @@ void MainWindow::addTopToolBar() hl->addLayout(d->middleHlayout, 1); hl->addLayout(d->rightHlayout, 1); - QHBoxLayout *titleBarLayout = static_cast(titlebar()->layout()); titleBarLayout->insertWidget(1, d->topToolbar); } - void MainWindow::setLeftTopToolWidget(DWidget *widget) { - if(!d->leftHlayout) + if (!d->leftHlayout) return; d->leftHlayout->addWidget(widget); @@ -488,7 +492,7 @@ void MainWindow::setLeftTopToolWidget(DWidget *widget) void MainWindow::setMiddleTopToolWidget(DWidget *widget) { - if(!d->middleHlayout) + if (!d->middleHlayout) return; d->middleHlayout->addWidget(widget); @@ -496,7 +500,7 @@ void MainWindow::setMiddleTopToolWidget(DWidget *widget) void MainWindow::setRightTopToolWidget(DWidget *widget) { - if(!d->rightHlayout) + if (!d->rightHlayout) return; d->rightHlayout->addWidget(widget); @@ -515,3 +519,25 @@ void MainWindow::hideTopTollBar() titlebar()->setTitle(QString("Deepin Union Code")); } + +Position MainWindow::positionOfDock(const QString &dockName) +{ + if (!d->dockList.contains(dockName)) { + qWarning() << "no dockWidget named: " << dockName; + return Position::FullWindow; + } + + auto area = dockWidgetArea(d->dockList[dockName]); + switch (area) { + case Qt::LeftDockWidgetArea: + return Position::Left; + case Qt::RightDockWidgetArea: + return Position::Right; + case Qt::TopDockWidgetArea: + return Position::Top; + case Qt::BottomDockWidgetArea: + return Position::Bottom; + default: + return Position::Left; + } +} diff --git a/src/plugins/core/uicontroller/mainwindow.h b/src/plugins/core/uicontroller/mainwindow.h index 80e81896f..a9501a6da 100644 --- a/src/plugins/core/uicontroller/mainwindow.h +++ b/src/plugins/core/uicontroller/mainwindow.h @@ -26,8 +26,8 @@ class MainWindow : public DMainWindow MainWindow(QWidget *parent = nullptr); ~MainWindow(); //dockWidget - void addWidget(const QString &name, QWidget *widget, Position pos = Position::FullWindow); - void addWidget(const QString &name, QWidget *widget, Position pos, Qt::Orientation orientation); + QDockWidget *addWidget(const QString &name, QWidget *widget, Position pos = Position::FullWindow); + QDockWidget *addWidget(const QString &name, QWidget *widget, Position pos, Qt::Orientation orientation); void replaceWidget(const QString &name, QWidget *widget, Position pos = Position::FullWindow); @@ -36,20 +36,20 @@ class MainWindow : public DMainWindow void hideAllWidget(); void showWidget(const QString &name); - void showWidget(Position pos); void showAllWidget(); void removeWidget(const QString &name); void removeWidget(Position pos); - QString getCurrentDockName(Position pos); + QString getCentralWidgetName(); + QStringList getCurrentDockName(Position pos); - void setDockHeadername(const QString &dockName, const QString &headerName); + void setDockHeaderName(const QString &dockName, const QString &headerName); + void setDockHeaderList(const QString &dockName, const QList &actions); void deleteDockHeader(const QString &name); - void addToolBtnToDockHeader(const QString &dockName, DToolButton *btn); + void addWidgetToDockHeader(const QString &dockName, QWidget *widget); void setDockWidgetFeatures(const QString &name, QDockWidget::DockWidgetFeatures feature); - //size(width,heigt) width used for widget in Left and Right. - //heigth used for widget in top and bottom + void resizeDock(const QString &dockName, QSize size); //only work for dockWidget,can`t work for centralWidget void splitWidgetOrientation(const QString &first, const QString &second, Qt::Orientation orientation); @@ -65,6 +65,10 @@ class MainWindow : public DMainWindow void setRightTopToolWidget(DWidget *widget); static Qt::DockWidgetArea positionTodockArea(Position pos); + Position positionOfDock(const QString &dockName); + +signals: + void dockHidden(const QString &dockName); private: MainWindowPrivate *d; diff --git a/src/plugins/cxx/cmake/cmakedebug.cpp b/src/plugins/cxx/cmake/cmakedebug.cpp index b57a81e2a..30d2f0cb5 100644 --- a/src/plugins/cxx/cmake/cmakedebug.cpp +++ b/src/plugins/cxx/cmake/cmakedebug.cpp @@ -5,11 +5,13 @@ #include "cmakedebug.h" #include "services/option/optionmanager.h" +#include "services/project/projectservice.h" #include #include #include +using namespace dpfservice; class CMakeDebugPrivate { friend class CMakeDebug; @@ -52,6 +54,7 @@ bool CMakeDebug::requestDAPPort(const QString &uuid, const QString &kit, << kit << targetPath << arguments; + bool ret = QDBusConnection::sessionBus().send(msg); if (!ret) { retMsg = tr("Request cxx dap port failed, please retry."); @@ -68,12 +71,21 @@ bool CMakeDebug::isLaunchNotAttach() dap::LaunchRequest CMakeDebug::launchDAP(const QString &targetPath, const QStringList &argments) { + auto prjService = dpfGetService(ProjectService); + dap::array env; + if (prjService) { + ProjectInfo prjInfo = prjService->getActiveProjectInfo(); + for (QString envItem : prjInfo.runEnvironment()) + env.push_back(envItem.toStdString()); + } + dap::LaunchRequest request; request.name = "(gdb) Launch"; request.type = "cppdbg"; request.request = "launch"; request.program = targetPath.toStdString(); request.stopAtEntry = false; + request.environment = env; dap::array arrayArg; foreach (QString arg, argments) { arrayArg.push_back(arg.toStdString()); diff --git a/src/plugins/cxx/cmake/cmakegenerator.cpp b/src/plugins/cxx/cmake/cmakegenerator.cpp index 67fff8349..46215e8bd 100644 --- a/src/plugins/cxx/cmake/cmakegenerator.cpp +++ b/src/plugins/cxx/cmake/cmakegenerator.cpp @@ -99,10 +99,12 @@ RunCommandInfo CMakeGenerator::getRunArguments(const ProjectInfo &projectInfo, c { Q_UNUSED(currentFile) - RunCommandInfo runCommaindInfo; - runCommaindInfo.program = projectInfo.runProgram(); - runCommaindInfo.arguments = projectInfo.runCustomArgs(); - runCommaindInfo.workingDir = projectInfo.runWorkspaceDir(); - runCommaindInfo.envs = projectInfo.runEnvironment(); - return runCommaindInfo; + RunCommandInfo runCommandInfo; + runCommandInfo.program = projectInfo.runProgram(); + runCommandInfo.arguments = projectInfo.runCustomArgs(); + runCommandInfo.workingDir = projectInfo.runWorkspaceDir(); + runCommandInfo.envs = projectInfo.runEnvironment(); + runCommandInfo.runInTerminal = projectInfo.runInTerminal(); + + return runCommandInfo; } diff --git a/src/plugins/cxx/cmake/project/cmake.qrc b/src/plugins/cxx/cmake/project/cmake.qrc index 521352c9e..8771b3492 100644 --- a/src/plugins/cxx/cmake/project/cmake.qrc +++ b/src/plugins/cxx/cmake/project/cmake.qrc @@ -1,5 +1,6 @@ icons/project_executable_20px.svg + icons/library_20px.svg diff --git a/src/plugins/cxx/cmake/project/cmakeasynparse.cpp b/src/plugins/cxx/cmake/project/cmakeasynparse.cpp index 8c4fa552c..49e5a217c 100644 --- a/src/plugins/cxx/cmake/project/cmakeasynparse.cpp +++ b/src/plugins/cxx/cmake/project/cmakeasynparse.cpp @@ -103,7 +103,7 @@ QString getTargetRootPath(const CMakeBuildTarget &target, const dpfservice::Proj auto buildDirectory = prjInfo.buildFolder(); if(workingDirectory.startsWith(buildDirectory)) { workingDirectory.remove(buildDirectory); - return topPath + QDir::separator() + workingDirectory; + return topPath + workingDirectory; } //get target root path by srcFiles path @@ -169,8 +169,11 @@ QStandardItem *CmakeAsynParse::parseProject(QStandardItem *rootItem, const dpfse auto cbpParser = TargetsManager::instance()->cbpParser(); // add cmakefile to tree first. auto cmakeList = cbpParser->getCmakeFileList(); + QSet cmakeFiles {}; for (auto &cmakeFile : cmakeList) { QString cmakeFilePath = cmakeFile.get()->getfilePath(); + if (cmakeFilePath.endsWith("CMakeLists.txt")) + cmakeFiles.insert(cmakeFilePath); QFileInfo cmakeFileInfo(cmakeFilePath); if (cmakeFileInfo.fileName().toLower() == kProjectFile.toLower()) { auto cmakeParentItem = rootItem; @@ -197,7 +200,7 @@ QStandardItem *CmakeAsynParse::parseProject(QStandardItem *rootItem, const dpfse } } - QSet allFiles {}; + QSet commonFiles {}; const QList &targets = cbpParser->getBuildTargets(); for (auto target : targets) { if (target.type == kUtility) { @@ -220,8 +223,10 @@ QStandardItem *CmakeAsynParse::parseProject(QStandardItem *rootItem, const dpfse prefix = CDT_TARGETS_TYPE::get()->Lib; } QMetaObject::invokeMethod(this, [=](){ - // executable should different with library. - targetItem->setIcon(QIcon::fromTheme("project_executable")); + if (target.type == kExecutable) + targetItem->setIcon(QIcon::fromTheme("project_executable")); + else if (target.type == kStaticLibrary || target.type == kDynamicLibrary) + targetItem->setIcon(QIcon::fromTheme("library")); }); QString title = prefix + target.title; targetItem->setText(title); @@ -254,11 +259,13 @@ QStandardItem *CmakeAsynParse::parseProject(QStandardItem *rootItem, const dpfse srcItem->setText(srcFileInfo.fileName()); srcItem->setToolTip(srcFileInfo.filePath()); srcItem->setIcon(CustomIcons::icon(srcFileInfo)); + if (srcFileInfo.isDir()) + emit directoryCreated(srcFileInfo.filePath()); if (parentItem) parentItem->appendRow(srcItem); - allFiles.insert(src); + commonFiles.insert(src); } } @@ -269,8 +276,13 @@ QStandardItem *CmakeAsynParse::parseProject(QStandardItem *rootItem, const dpfse tempInfo.setRunProgram(activeExecTarget.output); tempInfo.setRunWorkspaceDir(activeExecTarget.workingDir); } - tempInfo.setSourceFiles(allFiles); - tempInfo.setExePrograms(TargetsManager::instance()->getExeTargetNamesList()); + + tempInfo.setSourceFiles(commonFiles + cmakeFiles); + auto exePrograms = TargetsManager::instance()->getExeTargetNamesList(); + qSort(exePrograms.begin(), exePrograms.end(), [](const QString &s1, const QString &s2){ + return s1.toLower() < s2.toLower(); + }); + tempInfo.setExePrograms(exePrograms); tempInfo.setCurrentProgram(TargetsManager::instance()->getActivedTargetByTargetType(TargetType::kActiveExecTarget).name); ProjectInfo::set(rootItem, tempInfo); emit parseProjectEnd({ rootItem, true }); @@ -338,6 +350,7 @@ QStandardItem *CmakeAsynParse::createParentItem(QStandardItem *rootItem, const Q item->setIcon(::cmakeFolderIcon()); // append to parent. QStandardItem *parentItem = findParentItem(rootItem, relative); + emit directoryCreated(basePath+relative); parentItem->appendRow(item); sortParentItem(parentItem); } diff --git a/src/plugins/cxx/cmake/project/cmakeasynparse.h b/src/plugins/cxx/cmake/project/cmakeasynparse.h index f54a99483..e152380e5 100644 --- a/src/plugins/cxx/cmake/project/cmakeasynparse.h +++ b/src/plugins/cxx/cmake/project/cmakeasynparse.h @@ -62,6 +62,7 @@ class CmakeAsynParse : public QObject signals: void parseProjectEnd(const ParseInfo &info); void parseActionsEnd(const ParseInfo> &info); + void directoryCreated(const QString &path); public slots: QStandardItem *parseProject(QStandardItem *rootItem, const dpfservice::ProjectInfo &info); diff --git a/src/plugins/cxx/cmake/project/cmakeprojectgenerator.cpp b/src/plugins/cxx/cmake/project/cmakeprojectgenerator.cpp index 168f4d37f..0faff6fc9 100644 --- a/src/plugins/cxx/cmake/project/cmakeprojectgenerator.cpp +++ b/src/plugins/cxx/cmake/project/cmakeprojectgenerator.cpp @@ -24,6 +24,7 @@ #include #include #include +#include using namespace config; using namespace dpfservice; @@ -39,6 +40,12 @@ class CmakeProjectGeneratorPrivate QHash asynItemThreadPolls; QList reloadCmakeFileItems; dpfservice::ProjectInfo configureProjectInfo; + QHash projectWatchers; + QList projectsWaitingUpdate; + + QMap cmakeItems; + bool reConfigure = false; + QSet toExpand; }; CmakeProjectGenerator::CmakeProjectGenerator() @@ -62,6 +69,18 @@ CmakeProjectGenerator::CmakeProjectGenerator() actionProperties(prjInfo, this->rootItem); }); + QObject::connect(ProjectCmakeProxy::instance(), + &ProjectCmakeProxy::nodeExpanded, + this, [this](const QString &filePath){ + d->toExpand.insert(filePath); + }); + + QObject::connect(ProjectCmakeProxy::instance(), + &ProjectCmakeProxy::nodeCollapsed, + this, [this](const QString &filePath){ + d->toExpand.remove(filePath); + }); + connect(TargetsManager::instance(), &TargetsManager::initialized, this, &CmakeProjectGenerator::targetInitialized); // main thread init watcher class @@ -86,8 +105,15 @@ CmakeProjectGenerator::CmakeProjectGenerator() inputAction->setShortCutInfo("Build.RunCMake", runCMake->text()); dpfGetService(WindowService)->addAction(dpfservice::MWM_BUILD, inputAction); - QObject::connect(runCMake, &QAction::triggered, this,[this](){ - this->runCMake(this->rootItem, {}); + QObject::connect(runCMake, &QAction::triggered, this, [this](){ + auto prjService = dpfGetService(ProjectService); + auto activePrjInfo = prjService->getActiveProjectInfo(); + for (auto item : d->cmakeItems.values()) { + if (item.isSame(activePrjInfo)) { + this->runCMake(d->cmakeItems.key(item), {}); + break; + } + } }); QObject::connect(config::ConfigUtil::instance(), &config::ConfigUtil::configureDone, @@ -102,8 +128,45 @@ CmakeProjectGenerator::CmakeProjectGenerator() dpfGetService(WindowService)->addAction(dpfservice::MWM_BUILD, abstractClearCMake); QObject::connect(clearCMake, &QAction::triggered, this, [this](){ + auto prjService = dpfGetService(ProjectService); + auto activePrjInfo = prjService->getActiveProjectInfo(); + for (auto item : d->cmakeItems.values()) { + if (item.isSame(activePrjInfo)) + this->rootItem = d->cmakeItems.key(item); + } + this->clearCMake(this->rootItem); }); + + QObject::connect(config::ConfigUtil::instance(), &config::ConfigUtil::configureDone, + [this](const dpfservice::ProjectInfo &info) { + configure(info); + }); + + // add clear cmake menu item + QAction *clearCMake = new QAction(tr("Clear CMake")); + auto abstractClearCMake = new AbstractAction(clearCMake, this); + abstractClearCMake->setShortCutInfo("Build.ClearCMake", clearCMake->text()); + dpfGetService(WindowService)->addAction(dpfservice::MWM_BUILD, abstractClearCMake); + + QObject::connect(clearCMake, &QAction::triggered, this, [this](){ + this->clearCMake(this->rootItem); + }); +} + +void CmakeProjectGenerator::clearCMake(QStandardItem *root) +{ + auto path = dpfservice::ProjectInfo::get(root).buildFolder(); + auto cmakeFiles = path + "/CMakeFiles"; + auto cmakeCaches = path + "/CMakeCache.txt"; + QFile(cmakeCaches).remove(); + + QDir dir(cmakeFiles); + if (dir.exists()) { + dir.removeRecursively(); + } else { + qWarning() << "CMakeFiles directory does not exist."; + } } void CmakeProjectGenerator::clearCMake(QStandardItem *root) @@ -185,7 +248,18 @@ bool CmakeProjectGenerator::configure(const dpfservice::ProjectInfo &projInfo) if (isSuccess) { ProjectCmakeProxy::instance()->setBuildCommandUuid(commandInfo.uuid); // display root item before everything is done. - rootItem = ProjectGenerator::createRootItem(projInfo); + // check if run cmake to a existed project by project root path + auto newRoot = ProjectGenerator::createRootItem(projInfo); + auto isOpend = ProjectGenerator::isOpenedProject(projInfo.kitName(), projInfo.language(), projInfo.workspaceFolder()); + if (!rootItem || (rootItem->data(Qt::DisplayRole) != newRoot->data(Qt::DisplayRole)) || !isOpend) + d->reConfigure = false; + else { + d->reConfigure = true; + rootItem->setData(ParsingState::Wait, Parsing_State_Role); + } + + d->cmakeItems.insert(newRoot, projInfo); + rootItem = newRoot; setRootItemToView(rootItem); dpfservice::ProjectGenerator::configure(projInfo); @@ -206,6 +280,10 @@ QStandardItem *CmakeProjectGenerator::createRootItem(const dpfservice::ProjectIn d->asynItemThreadPolls[rootItem] = new QThreadPool; auto parse = new CmakeAsynParse; + auto fileWatcher = new QFileSystemWatcher(this); + d->projectWatchers.insert(rootItem, fileWatcher); + auto thisProject = rootItem; + d->projectsWaitingUpdate.append(thisProject); // asyn free parse, that .project file parse QObject::connect(parse, &CmakeAsynParse::parseProjectEnd, @@ -214,6 +292,16 @@ QStandardItem *CmakeProjectGenerator::createRootItem(const dpfservice::ProjectIn // active after everything done. project.activeProject(info.kitName(), info.language(), info.workspaceFolder()); delete parse; + + d->projectsWaitingUpdate.removeOne(thisProject); + + if (!d->reConfigure) + return; + + QMetaObject::invokeMethod(this, [this]() { + auto prjService = dpfGetService(ProjectService); + prjService->expandItemByFile(d->toExpand.toList()); + }); }); // asyn execute logic, that .project file parse @@ -221,6 +309,20 @@ QStandardItem *CmakeProjectGenerator::createRootItem(const dpfservice::ProjectIn parse, &CmakeAsynParse::parseProject, rootItem, info); + connect(parse, &CmakeAsynParse::directoryCreated, this, [=](const QString &path){ + if (!fileWatcher->directories().contains(path)) + fileWatcher->addPath(path); + }); + + connect(fileWatcher, &QFileSystemWatcher::directoryChanged, this, [=](const QString &path){ + if (d->projectsWaitingUpdate.contains(thisProject)) + return; + + auto windowService = dpfGetService(WindowService); + windowService->notify(0, "CMakeProject", tr("Files in project %1 have changed, needs to run cmake to update").arg(thisProject->text()), {}); + d->projectsWaitingUpdate.append(thisProject); + }); + return rootItem; } @@ -237,6 +339,15 @@ void CmakeProjectGenerator::removeRootItem(QStandardItem *root) d->asynItemThreadPolls.remove(root); } + if (d->reloadCmakeFileItems.contains(root)) + d->reloadCmakeFileItems.removeOne(root); + + if (rootItem == root) + rootItem = nullptr; + d->cmakeItems.remove(root); + + removeWatcher(root); + recursionRemoveItem(root); } @@ -378,18 +489,12 @@ void CmakeProjectGenerator::setRootItemToView(QStandardItem *root) if (!projectService) return; - WindowService *windowService = ctx.service(WindowService::name()); - if (!windowService) - return; - if (root) { - // setting item to view - if (projectService->addRootItem) + // check if run cmake to a existed project by project root path + if (!d->reConfigure) { projectService->addRootItem(root); - - // expand view from tree two level - if (projectService->expandedDepth) projectService->expandedDepth(root, 2); + } uiController.doSwitch(MWNA_EDIT); uiController.switchWorkspace(MWCWT_PROJECTS); @@ -419,8 +524,10 @@ void CmakeProjectGenerator::doBuildCmdExecuteEnd(const BuildCommandInfo &info, i } mutex.unlock(); - if (reloadItem) { + if (reloadItem && d->reConfigure) { + projectService->addRootItem(rootItem); d->reloadCmakeFileItems.removeOne(reloadItem); //clean cache + if (status == 0) { projectService->removeRootItem(reloadItem); createRootItem(d->configureProjectInfo); @@ -442,12 +549,17 @@ void CmakeProjectGenerator::runCMake(QStandardItem *root, const QPairreloadCmakeFileItems.contains(root)) return; + if (rootItem != root) + rootItem = root; + // get current project info auto proInfo = dpfservice::ProjectInfo::get(root); // cache the reload item d->reloadCmakeFileItems.append(root); + removeWatcher(root); + // reconfigure project info configure(proInfo); } @@ -455,6 +567,7 @@ void CmakeProjectGenerator::runCMake(QStandardItem *root, const QPairgetConfigureParamPointer(); @@ -558,3 +671,13 @@ void CmakeProjectGenerator::createBuildMenu(QMenu *menu) addBuildMenu("Build.ClearCMake"); menu->addSeparator(); } + +void CmakeProjectGenerator::removeWatcher(QStandardItem *root) +{ + auto watcher = d->projectWatchers[root]; + if (watcher) + delete watcher; + d->projectWatchers.remove(root); + if (d->projectsWaitingUpdate.contains(root)) + d->projectsWaitingUpdate.removeOne(root); +} diff --git a/src/plugins/cxx/cmake/project/cmakeprojectgenerator.h b/src/plugins/cxx/cmake/project/cmakeprojectgenerator.h index 25157e47b..4adaddf54 100644 --- a/src/plugins/cxx/cmake/project/cmakeprojectgenerator.h +++ b/src/plugins/cxx/cmake/project/cmakeprojectgenerator.h @@ -44,6 +44,7 @@ private slots: void createTargetsRunConfigure(const QString &workDirectory, config::RunConfigure &runConfigure); void createBuildMenu(QMenu *menu); void clearCMake(QStandardItem *root); + void removeWatcher(QStandardItem *root); QMutex mutex; QStandardItem *rootItem = nullptr; diff --git a/src/plugins/cxx/cmake/project/icons/library_20px.svg b/src/plugins/cxx/cmake/project/icons/library_20px.svg new file mode 100644 index 000000000..b09ed01c2 --- /dev/null +++ b/src/plugins/cxx/cmake/project/icons/library_20px.svg @@ -0,0 +1,65 @@ + + + ICON / list/library + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/cxx/cmake/project/properties/bulidCfgWidget/buildpropertypage.cpp b/src/plugins/cxx/cmake/project/properties/bulidCfgWidget/buildpropertypage.cpp index 220bc199b..3a4b0e8d7 100644 --- a/src/plugins/cxx/cmake/project/properties/bulidCfgWidget/buildpropertypage.cpp +++ b/src/plugins/cxx/cmake/project/properties/bulidCfgWidget/buildpropertypage.cpp @@ -12,7 +12,7 @@ #include "cmakeCfgWidget/cmakepropertypage.h" #include -#include +#include #include #include #include @@ -41,15 +41,21 @@ DetailPropertyWidget::DetailPropertyWidget(QWidget *parent) : ConfigureWidget(parent) , d(new DetailPropertyWidgetPrivate()) { - setBackgroundRole(QPalette::Window); - setFrameShape(QFrame::Shape::NoFrame); - - d->buildStepsPane = new StepsPane(this); - d->cleanStepsPane = new StepsPane(this); - d->envWidget = new EnvironmentWidget(this); - d->cmakeWidget = new CMakePropertyPage(this); - - DStackedWidget *stackWidget = new DStackedWidget(this); + setLineWidth(0); + DFrame *mainFrame = new DFrame(this); + mainFrame->setBackgroundRole(QPalette::Window); + mainFrame->setLineWidth(0); + + d->buildStepsPane = new StepsPane(mainFrame); + d->buildStepsPane->setBackgroundRole(QPalette::Window); + d->cleanStepsPane = new StepsPane(mainFrame); + d->cleanStepsPane->setBackgroundRole(QPalette::Window); + d->envWidget = new EnvironmentWidget(mainFrame); + d->envWidget->setBackgroundRole(QPalette::Window); + d->cmakeWidget = new CMakePropertyPage(mainFrame); + d->cmakeWidget->setBackgroundRole(QPalette::Window); + + DStackedWidget *stackWidget = new DStackedWidget(mainFrame); stackWidget->insertWidget(0, d->buildStepsPane); stackWidget->insertWidget(1, d->cleanStepsPane); stackWidget->insertWidget(2, d->envWidget); @@ -59,19 +65,19 @@ DetailPropertyWidget::DetailPropertyWidget(QWidget *parent) if (buildPage) connect(d->cmakeWidget, &CMakePropertyPage::cacheFileUpdated, buildPage, &BuildPropertyPage::cacheFileUpdated); - DButtonBoxButton *btnBuild = new DButtonBoxButton(QObject::tr("Build Steps"), this); + DButtonBoxButton *btnBuild = new DButtonBoxButton(QObject::tr("Build Steps"), mainFrame); btnBuild->setCheckable(true); btnBuild->setChecked(true); - DButtonBoxButton *btnClean = new DButtonBoxButton(QObject::tr("Clean Steps"), this); - DButtonBoxButton *btnEnv = new DButtonBoxButton(QObject::tr("Runtime Env"), this); - DButtonBoxButton *btnCMake = new DButtonBoxButton(QObject::tr("CMake config"), this); + DButtonBoxButton *btnClean = new DButtonBoxButton(QObject::tr("Clean Steps"), mainFrame); + DButtonBoxButton *btnEnv = new DButtonBoxButton(QObject::tr("Build Environment"), mainFrame); + DButtonBoxButton *btnCMake = new DButtonBoxButton(QObject::tr("CMake config"), mainFrame); - DButtonBox *btnbox = new DButtonBox(this); + DButtonBox *btnbox = new DButtonBox(mainFrame); QList list { btnBuild, btnClean, btnEnv, btnCMake}; btnbox->setButtonList(list, true); - auto frame = new DWidget(this); - auto layout = new QVBoxLayout(this); + auto frame = new DWidget(mainFrame); + auto layout = new QVBoxLayout(); layout->setAlignment(Qt::AlignHCenter); layout->addWidget(btnbox); frame->setLayout(layout); @@ -86,9 +92,15 @@ DetailPropertyWidget::DetailPropertyWidget(QWidget *parent) else if (button == btnCMake) stackWidget->setCurrentIndex(3); }); - - addWidget(frame); - addWidget(stackWidget); + QVBoxLayout *contentayout = new QVBoxLayout(); + contentayout->addWidget(frame); + contentayout->addWidget(stackWidget); + contentayout->setContentsMargins(0, 0, 0, 0); + mainFrame->setLayout(contentayout); + + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->addWidget(mainFrame); + mainLayout->setMargin(0); } DetailPropertyWidget::~DetailPropertyWidget() @@ -178,6 +190,7 @@ void BuildPropertyPage::setupOverviewUI() QHBoxLayout *configureLayout = new QHBoxLayout(); d->configureComboBox = new DComboBox(this); + d->configureComboBox->setFixedWidth(220); configureLayout->addWidget(d->configureComboBox); configureLayout->setSpacing(10); configureLayout->addStretch(); @@ -206,8 +219,10 @@ void BuildPropertyPage::setupOverviewUI() QHBoxLayout *hLayout = new QHBoxLayout(); d->outputDirEdit = new DLineEdit(this); d->outputDirEdit->lineEdit()->setReadOnly(true); - auto button = new QPushButton(this); - button->setText(tr("Browse")); + auto button = new DSuggestButton(this); + button->setIcon(DStyle::standardIcon(style(), DStyle::SP_SelectElement)); + button->setIconSize(QSize(24, 24)); + button->setFixedSize(36, 36); connect(button, &QPushButton::clicked, [this](){ QString outputDirectory = QFileDialog::getExistingDirectory(this, "Output directory", d->outputDirEdit->text()); if (!outputDirectory.isEmpty()) { @@ -228,10 +243,11 @@ void BuildPropertyPage::setupOverviewUI() overviewLayout->setSpacing(0); overviewLayout->setMargin(0); - overviewLayout->setSpacing(5); + overviewLayout->setSpacing(10); auto formlayout = new QFormLayout(this); - formlayout->setContentsMargins(0, 0, 0, 0); + formlayout->setSpacing(15); + formlayout->setContentsMargins(0, 0, 0, 10); formlayout->addRow(QLabel::tr("Build configuration:"), configureLayout); formlayout->addRow(tr("Output direcotry:"), hLayout); diff --git a/src/plugins/cxx/cmake/project/properties/bulidCfgWidget/stepspane.cpp b/src/plugins/cxx/cmake/project/properties/bulidCfgWidget/stepspane.cpp index 6534b3eef..c3d693c45 100644 --- a/src/plugins/cxx/cmake/project/properties/bulidCfgWidget/stepspane.cpp +++ b/src/plugins/cxx/cmake/project/properties/bulidCfgWidget/stepspane.cpp @@ -279,8 +279,12 @@ void StepsPane::setValues(const config::StepItem &item) d->toolArguments->setText(item.buildArguments.join(" ")); QMap data; - foreach (auto targetName, item.allTargetNames) { - data.insert(targetName, targetName == item.activeTargetName ? true : false); + if (item.type == config::StepType::Build) { + foreach (auto targetName, item.allTargetNames) { + data.insert(targetName, targetName == item.activeTargetName ? true : false); + } + } else if (item.type == config::StepType::Clean) { + data.insert("clean", true); } d->model->setData(data); diff --git a/src/plugins/cxx/cmake/project/properties/configutil.cpp b/src/plugins/cxx/cmake/project/properties/configutil.cpp index 2012b58e9..22ecc986d 100644 --- a/src/plugins/cxx/cmake/project/properties/configutil.cpp +++ b/src/plugins/cxx/cmake/project/properties/configutil.cpp @@ -212,7 +212,7 @@ bool ConfigUtil::updateProjectInfo(dpfservice::ProjectInfo &info, const ProjectC } else if (iterStep->type == StepType::Clean) { QString cleanTarget = iterStep->activeTargetName; if (cleanTarget.isEmpty()) { - cleanTarget = "all"; + cleanTarget = "clean"; } TargetsManager::instance()->updateActivedCleanTarget(cleanTarget); arguments << cleanTarget; @@ -240,6 +240,7 @@ bool ConfigUtil::updateProjectInfo(dpfservice::ProjectInfo &info, const ProjectC info.setRunCustomArgs(arguments); info.setRunWorkspaceDir(iterRun->workDirectory); info.setCurrentProgram(iterRun->targetName); + info.setRunInTerminal(iterRun->runInTermal); TargetsManager::instance()->updateActiveExceTarget(iterRun->targetName); break; diff --git a/src/plugins/cxx/cmake/project/properties/configutil.h b/src/plugins/cxx/cmake/project/properties/configutil.h index 902f232bf..9d4f54034 100644 --- a/src/plugins/cxx/cmake/project/properties/configutil.h +++ b/src/plugins/cxx/cmake/project/properties/configutil.h @@ -58,18 +58,6 @@ struct EnvironmentItem { bool enable = true; QMap environments; - void setQDebugLevel(bool enable) - { - enableQDebugLevel = enable; - QString loggingValue = enableQDebugLevel ? "*.debug=true" : "*.debug=false"; - environments.insert("QT_LOGGING_RULES", loggingValue); - } - - bool isQDebugLevelEnable() const - { - return enableQDebugLevel; - } - EnvironmentItem() { initEnvironments(); } @@ -91,7 +79,6 @@ struct EnvironmentItem { foreach (auto key, env.keys()) { environments.insert(key, env.value(key)); } - setQDebugLevel(enableQDebugLevel); } friend QDataStream &operator<<(QDataStream &stream, const EnvironmentItem &data) @@ -122,6 +109,7 @@ struct TargetRunConfigure { QString arguments; QString workDirectory; EnvironmentItem env; + bool runInTermal { false }; friend QDataStream &operator<<(QDataStream &stream, const TargetRunConfigure &data) { @@ -130,6 +118,7 @@ struct TargetRunConfigure { stream << data.arguments; stream << data.workDirectory; stream << data.env; + stream << data.runInTermal; return stream; } @@ -141,6 +130,7 @@ struct TargetRunConfigure { stream >> data.arguments; stream >> data.workDirectory; stream >> data.env; + stream >> data.runInTermal; return stream; } diff --git a/src/plugins/cxx/cmake/project/properties/environmentwidget.cpp b/src/plugins/cxx/cmake/project/properties/environmentwidget.cpp index 21ba05a1c..983073ae8 100644 --- a/src/plugins/cxx/cmake/project/properties/environmentwidget.cpp +++ b/src/plugins/cxx/cmake/project/properties/environmentwidget.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include @@ -108,11 +110,30 @@ QVariant EnvironmentModel::headerData(int section, Qt::Orientation orientation, return {}; } -void EnvironmentModel::append(const QString &key, const QString &value) +QModelIndex EnvironmentModel::append(const QString &key, const QString &value) { - beginInsertRows({}, d->envs.keys().count(), d->envs.keys().count()); + auto list = d->envs.keys(); + list.append(key); + qSort(list); + int pos = list.indexOf(key); + + beginInsertRows(QModelIndex(), pos, pos); d->envs.insert(key, value); endInsertRows(); + emit dataChanged(index(pos, 0), index(pos, 1)); + return index(pos, 0); +} + +void EnvironmentModel::remove(QModelIndex &index) +{ + if (d->envs.keys().isEmpty() || index.row() < 0 || index.row() >= d->envs.count()) + return; + int row = index.row(); + beginRemoveRows(QModelIndex(), row, row); + QString key = d->envs.keys()[index.row()]; + d->envs.remove(key); + endRemoveRows(); + emit dataChanged(index, index); } void EnvironmentModel::update(const QMap &data) @@ -121,6 +142,7 @@ void EnvironmentModel::update(const QMap &data) d->envs.clear(); d->envs = data; endResetModel(); + emit dataChanged(index(0, 0), index(d->envs.count() - 1, 1)); } const QMap EnvironmentModel::getEnvironment() const @@ -135,14 +157,16 @@ class EnvironmentWidgetPrivate QVBoxLayout *vLayout{nullptr}; DTableView *tableView{nullptr}; DCheckBox *enableEnvCB{nullptr}; - DCheckBox *enableQDebugLevelCB{nullptr}; + DIconButton *appendButton = nullptr; + DIconButton *deleteButton = nullptr; + DIconButton *resetButton = nullptr; EnvironmentModel *model{nullptr}; config::EnvironmentItem *envShadow{nullptr}; }; -EnvironmentWidget::EnvironmentWidget(QWidget *parent) +EnvironmentWidget::EnvironmentWidget(QWidget *parent, EnvType type) : DFrame(parent) , d(new EnvironmentWidgetPrivate) { @@ -168,6 +192,7 @@ EnvironmentWidget::EnvironmentWidget(QWidget *parent) if (!d->model) d->model = new EnvironmentModel(); + connect(d->model, &EnvironmentModel::dataChanged, this, &EnvironmentWidget::envUpdated); d->tableView->setModel(d->model); @@ -183,21 +208,45 @@ EnvironmentWidget::EnvironmentWidget(QWidget *parent) d->enableEnvCB->setText(tr("Enable All Environment")); d->enableEnvCB->setChecked(true); - // add enable qdebug level check box. - if (!d->enableQDebugLevelCB) - d->enableQDebugLevelCB = new DCheckBox(this); - - connect(d->enableQDebugLevelCB, &DCheckBox::stateChanged, this, &EnvironmentWidget::onEnableQDebugLevel); - - d->enableQDebugLevelCB->setText(tr("Enable Qt Debug Level")); - d->enableQDebugLevelCB->setChecked(false); + //append + d->appendButton = new DIconButton(this); + d->appendButton->setIcon(QIcon::fromTheme("binarytools_add")); + d->appendButton->setIconSize({16, 16}); + d->appendButton->setFlat(true); + d->appendButton->setToolTip(tr("append")); + + //Delete + d->deleteButton = new DIconButton(this); + d->deleteButton->setIcon(QIcon::fromTheme("binarytools_reduce")); + d->deleteButton->setIconSize({16, 16}); + d->deleteButton->setFlat(true); + d->deleteButton->setToolTip(tr("reduce")); + + //Reset + d->resetButton = new DIconButton(this); + d->resetButton->setIcon(QIcon::fromTheme("binarytools_reset")); + d->resetButton->setIconSize({16, 16}); + d->resetButton->setFlat(true); + d->resetButton->setToolTip(tr("reset")); + + QHBoxLayout *btnLayout = new QHBoxLayout(); + btnLayout->addWidget(d->appendButton); + btnLayout->addWidget(d->deleteButton); + btnLayout->addWidget(d->resetButton); + btnLayout->addStretch(1); + btnLayout->setSpacing(5); + btnLayout->setContentsMargins(5, 0, 0, 0); + + connect(d->appendButton, &DPushButton::clicked, this, &EnvironmentWidget::appendRow); + connect(d->deleteButton, &DPushButton::clicked, this, &EnvironmentWidget::deleteRow); + connect(d->resetButton, &DPushButton::clicked, this, &EnvironmentWidget::initModel); // instert to layout. d->vLayout->setSpacing(0); d->vLayout->setMargin(0); d->vLayout->addWidget(d->tableView); + d->vLayout->addLayout(btnLayout); d->vLayout->addWidget(d->enableEnvCB); - d->vLayout->addWidget(d->enableQDebugLevelCB); } EnvironmentWidget::~EnvironmentWidget() @@ -206,6 +255,29 @@ EnvironmentWidget::~EnvironmentWidget() delete d; } +void EnvironmentWidget::appendRow() +{ + auto index = d->model->append("", ""); + d->tableView->setCurrentIndex(index); +} + +void EnvironmentWidget::deleteRow() +{ + QModelIndex index = d->tableView->currentIndex(); + d->model->remove(index); +} + +void EnvironmentWidget::initModel() +{ + QMap envs; + QStringList keys = QProcessEnvironment::systemEnvironment().keys(); + for (auto key : keys) { + QString value = QProcessEnvironment::systemEnvironment().value(key); + envs.insert(key, value); + } + d->model->update(envs); +} + void EnvironmentWidget::getValues(config::EnvironmentItem &env) { env.enable = d->enableEnvCB->isChecked(); @@ -216,7 +288,6 @@ void EnvironmentWidget::getValues(config::EnvironmentItem &env) void EnvironmentWidget::setValues(const config::EnvironmentItem &env) { d->enableEnvCB->setChecked(env.enable); - d->enableQDebugLevelCB->setChecked(env.isQDebugLevelEnable()); d->model->update(env.environments); } @@ -224,19 +295,5 @@ void EnvironmentWidget::updateEnvList(config::EnvironmentItem *env) { d->envShadow = env; d->enableEnvCB->setChecked(env->enable); - d->enableQDebugLevelCB->setChecked(env->isQDebugLevelEnable()); d->model->update(env->environments); } - -void EnvironmentWidget::onEnableQDebugLevel() -{ - if (!d->envShadow) - return; - - bool isQDebugLevelEnabled = d->enableQDebugLevelCB->isChecked(); - d->envShadow->setQDebugLevel(isQDebugLevelEnabled); - - d->model->update(d->envShadow->environments); -} - - diff --git a/src/plugins/cxx/cmake/project/properties/environmentwidget.h b/src/plugins/cxx/cmake/project/properties/environmentwidget.h index 2f5769290..7e76c17dd 100644 --- a/src/plugins/cxx/cmake/project/properties/environmentwidget.h +++ b/src/plugins/cxx/cmake/project/properties/environmentwidget.h @@ -10,6 +10,11 @@ #include +enum EnvType { + BuildCfg, + RunCfg +}; + class EnvironmentModelPrivate; class EnvironmentModel : public QAbstractTableModel { @@ -32,7 +37,8 @@ class EnvironmentModel : public QAbstractTableModel QVariant headerData(int section, Qt::Orientation orientation, int role) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; - void append(const QString &key, const QString &value); + QModelIndex append(const QString &key, const QString &value); + void remove(QModelIndex &index); void update(const QMap &data); const QMap getEnvironment() const; @@ -46,15 +52,19 @@ class EnvironmentWidget : public DTK_WIDGET_NAMESPACE::DFrame Q_OBJECT public: - explicit EnvironmentWidget(QWidget *parent = nullptr); + explicit EnvironmentWidget(QWidget *parent = nullptr, EnvType type = EnvType::BuildCfg); virtual ~EnvironmentWidget(); + void appendRow(); + void deleteRow(); + void initModel(); + void getValues(config::EnvironmentItem &env); void setValues(const config::EnvironmentItem &env); void updateEnvList(config::EnvironmentItem *env); -private slots: - void onEnableQDebugLevel(); +signals: + void envUpdated(); private: EnvironmentWidgetPrivate *const d; diff --git a/src/plugins/cxx/cmake/project/properties/runCfgWidget/runconfigpane.cpp b/src/plugins/cxx/cmake/project/properties/runCfgWidget/runconfigpane.cpp index 11757ce71..ad5cb24eb 100644 --- a/src/plugins/cxx/cmake/project/properties/runCfgWidget/runconfigpane.cpp +++ b/src/plugins/cxx/cmake/project/properties/runCfgWidget/runconfigpane.cpp @@ -8,11 +8,12 @@ #include "services/project/projectservice.h" #include +#include +#include +#include #include #include -#include -#include #include #include #include @@ -26,7 +27,8 @@ class RunConfigPanePrivate DLineEdit *cmdArgsLineEdit{nullptr}; DLineEdit *workingDirLineEdit{nullptr}; - DLineEdit *excutableLabel{nullptr}; + DLineEdit *executableEdit{nullptr}; + DCheckBox *runInTerminal {nullptr}; QFormLayout *formLayout{nullptr}; EnvironmentWidget *environmentWidget{nullptr}; @@ -56,10 +58,13 @@ void RunConfigPane::setupUi() d->formLayout = new QFormLayout(mainFrame); // excutable label ui. - d->excutableLabel = new DLineEdit(mainFrame); - d->excutableLabel->setText(tr("Here is the executable path")); - d->excutableLabel->lineEdit()->setReadOnly(true); - d->formLayout->addRow(tr("Executable path:"), d->excutableLabel); + d->executableEdit = new DLineEdit(mainFrame); + d->executableEdit->setPlaceholderText(tr("Here is the executable path")); + connect(d->executableEdit, &DLineEdit::editingFinished, this, [this](){ + if (d->targetRunParam) + d->targetRunParam->targetPath = d->executableEdit->text(); + }); + d->formLayout->addRow(tr("Executable path:"), d->executableEdit); d->formLayout->setSpacing(10); // command line ui. @@ -72,8 +77,10 @@ void RunConfigPane::setupUi() // working directory ui. QHBoxLayout *browLayout = new QHBoxLayout(mainFrame); - auto browseBtn = new QPushButton(mainFrame); - browseBtn->setText(tr("Browse")); + DSuggestButton *btnBrowser = new DSuggestButton(mainFrame); + btnBrowser->setIcon(DStyle::standardIcon(style(), DStyle::SP_SelectElement)); + btnBrowser->setIconSize(QSize(24, 24)); + btnBrowser->setFixedSize(36, 36); d->workingDirLineEdit = new DLineEdit(mainFrame); d->workingDirLineEdit->lineEdit()->setReadOnly(true); connect(d->workingDirLineEdit, &DLineEdit::textChanged, [this](){ @@ -81,20 +88,30 @@ void RunConfigPane::setupUi() d->targetRunParam->workDirectory = d->workingDirLineEdit->text().trimmed(); }); browLayout->addWidget(d->workingDirLineEdit); - browLayout->addWidget(browseBtn); + browLayout->addWidget(btnBrowser); d->formLayout->addRow(tr("Working directory:"), browLayout); - connect(browseBtn, &QPushButton::clicked, [this](){ + connect(btnBrowser, &QPushButton::clicked, [this](){ QString outputDirectory = QFileDialog::getExistingDirectory(this, tr("Working directory"), d->workingDirLineEdit->text()); if (!outputDirectory.isEmpty()) { d->workingDirLineEdit->setText(outputDirectory.toUtf8()); } }); + d->runInTerminal = new DCheckBox(this); + d->formLayout->addRow(tr("Run in terminal:"), d->runInTerminal); + connect(d->runInTerminal, &DCheckBox::stateChanged, this, [this](int state){ + Q_UNUSED(state); + d->targetRunParam->runInTermal = d->runInTerminal->isChecked(); + }); + mainFrame->setLayout(d->formLayout); - d->environmentWidget = new EnvironmentWidget(this); + d->environmentWidget = new EnvironmentWidget(this, EnvType::RunCfg); vLayout->addWidget(mainFrame); vLayout->addWidget(d->environmentWidget); + connect(d->environmentWidget, &EnvironmentWidget::envUpdated, this, [=](){ + d->environmentWidget->getValues(d->targetRunParam->env); + }); vLayout->setMargin(0); } @@ -105,7 +122,8 @@ void RunConfigPane::updateUi() d->cmdArgsLineEdit->setText(d->targetRunParam->arguments); d->workingDirLineEdit->setText(d->targetRunParam->workDirectory); d->currentTargetName = d->targetRunParam->targetName; - d->excutableLabel->setText(d->targetRunParam->targetPath); + d->executableEdit->setText(d->targetRunParam->targetPath); + d->runInTerminal->setChecked(d->targetRunParam->runInTermal); } void RunConfigPane::setTargetRunParam(config::TargetRunConfigure *targetRunParam) diff --git a/src/plugins/cxx/cmake/project/properties/runCfgWidget/runpropertypage.cpp b/src/plugins/cxx/cmake/project/properties/runCfgWidget/runpropertypage.cpp index 9e5ca6b61..b1754f4c5 100644 --- a/src/plugins/cxx/cmake/project/properties/runCfgWidget/runpropertypage.cpp +++ b/src/plugins/cxx/cmake/project/properties/runCfgWidget/runpropertypage.cpp @@ -8,6 +8,7 @@ #include "runconfigpane.h" #include "configutil.h" #include "targetsmanager.h" +#include "services/project/projectservice.h" #include #include @@ -20,6 +21,7 @@ #include using namespace config; +using namespace dpfservice; class RunPropertyWidgetPrivate { @@ -53,7 +55,7 @@ void RunPropertyPage::setupUi() ConfigureWidget *runCfgWidget = new ConfigureWidget(this); runCfgWidget->setFrameShape(QFrame::Shape::NoFrame); - DLabel *runCfgLabel = new DLabel(tr("Run configuration:")); + DLabel *runCfgLabel = new DLabel(tr("Run configuration:"), this); d->exeComboBox = new DComboBox(); d->exeComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); d->exeComboBox->setFixedWidth(220); @@ -105,8 +107,17 @@ void RunPropertyPage::saveConfig() for (; iter != param->buildTypeConfigures.end(); ++iter) { if (param->defaultType == iter->type) { iter->runConfigure.targetsRunConfigure = d->targetsRunConfigure; - //do not update execute program when save config - //iter->runConfigure.defaultTargetName = d->exeComboBox->currentText(); + iter->runConfigure.defaultTargetName = d->projectInfo.currentProgram(); + } + if (param->tempSelType == iter->type) { + d->projectInfo = dpfGetService(ProjectService)->getActiveProjectInfo(); + auto activeTarget = TargetsManager::instance()->getActivedTargetByTargetType(kActiveExecTarget); + for (auto it : iter->runConfigure.targetsRunConfigure) { + if (it.targetName == activeTarget.name) { + d->projectInfo.setRunEnvironment(it.env.toList()); + break; + } + } } } diff --git a/src/plugins/cxx/cmake/project/transceiver/projectcmakereceiver.cpp b/src/plugins/cxx/cmake/project/transceiver/projectcmakereceiver.cpp index e94c5c59a..d0d73201a 100644 --- a/src/plugins/cxx/cmake/project/transceiver/projectcmakereceiver.cpp +++ b/src/plugins/cxx/cmake/project/transceiver/projectcmakereceiver.cpp @@ -35,8 +35,8 @@ void ProjectCmakeReceiver::eventProcess(const dpf::Event &event) builderEvent(event); } - if (event.data() == project.activedProject.name) { - QVariant proInfoVar = event.property(project.activedProject.pKeys[0]); + if (event.data() == project.activatedProject.name) { + QVariant proInfoVar = event.property(project.activatedProject.pKeys[0]); dpfservice::ProjectInfo projectInfo = qvariant_cast(proInfoVar); TargetsManager::instance()->readTargets(projectInfo.buildFolder(), projectInfo.workspaceFolder()); } @@ -54,6 +54,8 @@ void ProjectCmakeReceiver::eventProcess(const dpf::Event &event) if (event.data() == project.projectUpdated.name) { QVariant proInfoVar = event.property("projectInfo"); dpfservice::ProjectInfo projectInfo = qvariant_cast(proInfoVar); + if (projectInfo.kitName() != CmakeProjectGenerator::toolKitName()) + return; auto *param = config::ConfigUtil::instance()->getConfigureParamPointer(); auto iter = param->buildTypeConfigures.begin(); @@ -71,6 +73,18 @@ void ProjectCmakeReceiver::eventProcess(const dpf::Event &event) emit ProjectCmakeProxy::instance()->openProjectPropertys(projectInfo); } + + if (event.data() == project.projectNodeExpanded.name) { + auto index = event.property("modelIndex").value(); + auto filePath = index.data(Qt::ToolTipRole).toString(); + emit ProjectCmakeProxy::instance()->nodeExpanded(filePath); + } + + if (event.data() == project.projectNodeCollapsed.name) { + auto index = event.property("modelIndex").value(); + auto filePath = index.data(Qt::ToolTipRole).toString(); + emit ProjectCmakeProxy::instance()->nodeCollapsed(filePath); + } } void ProjectCmakeReceiver::builderEvent(const dpf::Event &event) diff --git a/src/plugins/cxx/cmake/project/transceiver/projectcmakereceiver.h b/src/plugins/cxx/cmake/project/transceiver/projectcmakereceiver.h index afedf76ba..a30229c4c 100644 --- a/src/plugins/cxx/cmake/project/transceiver/projectcmakereceiver.h +++ b/src/plugins/cxx/cmake/project/transceiver/projectcmakereceiver.h @@ -43,6 +43,8 @@ class ProjectCmakeProxy : public QObject void buildExecuteEnd(const BuildCommandInfo &commandInfo, int status = 0); void fileDeleted(const QString &filePath); void openProjectPropertys(const dpfservice::ProjectInfo &prjInfo); + void nodeExpanded(const QString &filePath); + void nodeCollapsed(const QString &filePath); private: QString buildCommandUuid; diff --git a/src/plugins/cxx/cxxplugin.json b/src/plugins/cxx/cxxplugin.json index 315a98078..e58ea2bf0 100644 --- a/src/plugins/cxx/cxxplugin.json +++ b/src/plugins/cxx/cxxplugin.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Languages", "Description" : "The cxx plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=664d969d685f572b03c2aca9&id=664d96d7685f572b03c2acab", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=664d969d685f572b03c2aca9&id=664d96d7685f572b03c2acab", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/cxx/lexer/scilexercpp.cpp b/src/plugins/cxx/lexer/scilexercpp.cpp index 4ad16f346..6f5685e01 100644 --- a/src/plugins/cxx/lexer/scilexercpp.cpp +++ b/src/plugins/cxx/lexer/scilexercpp.cpp @@ -323,7 +323,7 @@ QColor SciLexerCPP::defaultColor(int style) const case InactivePreProcessorComment: case InactiveTaskMarker: case InactiveUserLiteral: - return isDarkTheme ? QColor("#969696") : QColor("#c0c0c0"); + return isDarkTheme ? QColor("#969696") : QColor("#8a8a8a"); case UserLiteral: return isDarkTheme ? QColor("#d6cf9a") : QColor("#c06000"); @@ -386,16 +386,16 @@ QColor SciLexerCPP::defaultPaper(int style) const const char *SciLexerCPP::keywords(int set) const { if (set == 1) - return "and and_eq asm auto bitand bitor bool break case " - "catch char class compl const const_cast continue " - "default delete do double dynamic_cast else enum " - "explicit export extern false float for friend goto if " - "inline int long mutable namespace new not not_eq " - "operator or or_eq private protected public register " - "reinterpret_cast return short signed sizeof static " - "static_cast struct switch template this throw true " - "try typedef typeid typename union unsigned using " - "virtual void volatile wchar_t while xor xor_eq"; + return "and and_eq asm auto bitand bitor bool break case catch " + "char class compl const const_cast continue default " + "delete do double dynamic_cast else enum explicit export " + "extern false farcall float for friend goto if inline int " + "long mutable namespace new not not_eq nullptr null out " + "operator or or_eq override private protected public println register " + "reinterpret_cast return short signed sizeof static system " + "static_cast struct switch template this throw true try typedef " + "typeid typename union unsigned using virtual void volatile " + "wchar_t while xor xor_eq"; if (set == 3) return "a addindex addtogroup anchor arg attention author b " diff --git a/src/plugins/cxx/ninja/ninjagenerator.cpp b/src/plugins/cxx/ninja/ninjagenerator.cpp index 6e7f6e97e..effa2f904 100644 --- a/src/plugins/cxx/ninja/ninjagenerator.cpp +++ b/src/plugins/cxx/ninja/ninjagenerator.cpp @@ -103,6 +103,7 @@ RunCommandInfo NinjaGenerator::getRunArguments(const ProjectInfo &projectInfo, c runCommandInfo.program = targetPath; runCommandInfo.arguments = projectInfo.runCustomArgs(); runCommandInfo.workingDir = workspace; + runCommandInfo.runInTerminal = false; // todo : config "run in terminal" return runCommandInfo; } diff --git a/src/plugins/cxx/ninja/project/ninjaasynparse.cpp b/src/plugins/cxx/ninja/project/ninjaasynparse.cpp index 230d23d82..00971b563 100644 --- a/src/plugins/cxx/ninja/project/ninjaasynparse.cpp +++ b/src/plugins/cxx/ninja/project/ninjaasynparse.cpp @@ -13,9 +13,9 @@ class NinjaAsynParsePrivate { - friend class NinjaAsynParse; + friend class NinjaAsynParse; QDomDocument xmlDoc; - QThread *thread {nullptr}; + QThread *thread { nullptr }; QString rootPath; QList rows {}; }; @@ -82,7 +82,7 @@ void NinjaAsynParse::createRows(const QString &path) d->rootPath = rootPath; QFileSystemWatcher::addPath(d->rootPath); - {// 避免变量冲突 迭代文件夹 + { // 避免变量冲突 迭代文件夹 QDir dir; dir.setPath(rootPath); dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs); @@ -92,19 +92,16 @@ void NinjaAsynParse::createRows(const QString &path) QString childPath = dirItera.next().remove(0, rootPath.size()); QFileSystemWatcher::addPath(dirItera.filePath()); QStandardItem *item = findItem(childPath); - QMetaObject::invokeMethod(this, [&](){ - QIcon icon = CustomIcons::icon(dirItera.fileInfo()); - auto newItem = new QStandardItem(icon, dirItera.fileName()); - newItem->setToolTip(dirItera.filePath()); - if (!item) { - d->rows.append(newItem); - } else { - item->appendRow(newItem); - } - }); + auto newItem = new QStandardItem(dirItera.fileName()); + newItem->setToolTip(dirItera.filePath()); + if (!item) { + d->rows.append(newItem); + } else { + item->appendRow(newItem); + } } } - {// 避免变量冲突 迭代文件 + { // 避免变量冲突 迭代文件 QDir dir; dir.setPath(rootPath); dir.setFilter(QDir::NoDotAndDotDot | QDir::Files); @@ -113,17 +110,13 @@ void NinjaAsynParse::createRows(const QString &path) while (fileItera.hasNext()) { QString childPath = fileItera.next().remove(0, rootPath.size()); QStandardItem *item = findItem(childPath); - // run in main thread. - QMetaObject::invokeMethod(this, [&](){ - QIcon icon = CustomIcons::icon(fileItera.fileInfo()); - auto newItem = new QStandardItem(icon, fileItera.fileName()); - newItem->setToolTip(fileItera.filePath()); - if (!item) { - d->rows.append(newItem); - } else { - item->appendRow(newItem); - } - }); + auto newItem = new QStandardItem(fileItera.fileName()); + newItem->setToolTip(fileItera.filePath()); + if (!item) { + d->rows.append(newItem); + } else { + item->appendRow(newItem); + } } } } @@ -138,7 +131,7 @@ QList NinjaAsynParse::rows(const QStandardItem *item) const } QStandardItem *NinjaAsynParse::findItem(const QString &path, - QStandardItem *parent) const + QStandardItem *parent) const { QString pathTemp = path; if (pathTemp.endsWith(QDir::separator())) { @@ -157,7 +150,7 @@ QStandardItem *NinjaAsynParse::findItem(const QString &path, QStringList splitPaths = pathTemp.split(QDir::separator()); QString name = splitPaths.takeFirst(); - QList currRows{}; + QList currRows {}; if (parent) { currRows = rows(parent); } else { diff --git a/src/plugins/cxx/ninja/project/ninjaprojectgenerator.cpp b/src/plugins/cxx/ninja/project/ninjaprojectgenerator.cpp index 8c53142a2..482abdb1b 100644 --- a/src/plugins/cxx/ninja/project/ninjaprojectgenerator.cpp +++ b/src/plugins/cxx/ninja/project/ninjaprojectgenerator.cpp @@ -130,6 +130,10 @@ void NinjaProjectGenerator::doProjectChildsModified(const QList while (rootItem->hasChildren()) { rootItem->takeRow(0); } + for (auto &item : items) { + item->setIcon(CustomIcons::icon(item->toolTip())); + } + rootItem->appendRows(items); } rootItem->setData(ParsingState::Done, Parsing_State_Role); diff --git a/src/plugins/debugger/dap/dapdebugger.cpp b/src/plugins/debugger/dap/dapdebugger.cpp index 8b172d54e..8cba1c7ea 100644 --- a/src/plugins/debugger/dap/dapdebugger.cpp +++ b/src/plugins/debugger/dap/dapdebugger.cpp @@ -75,6 +75,7 @@ class DebuggerPrivate DEBUG::DebugSession *currentSession { nullptr }; dap::integer threadId = 0; + QList threads; StackFrameData currentValidFrame; /** @@ -115,6 +116,8 @@ class DebuggerPrivate QMultiMap bps; bool isRemote = false; RemoteInfo remoteInfo; + + bool startAttaching = false; }; DebuggerPrivate::~DebuggerPrivate() @@ -196,13 +199,13 @@ void DAPDebugger::startDebug() d->isRemote = false; if (d->currentSession == d->remoteSession) d->currentSession = d->localSession; - - updateRunState(kPreparing); + auto &ctx = dpfInstance.serviceContext(); LanguageService *service = ctx.service(LanguageService::name()); if (service) { auto generator = service->create(d->activeProjectKitName); if (generator) { + updateRunState(kPreparing); if (generator->isNeedBuild()) { d->currentBuildUuid = requestBuild(); } else { @@ -239,6 +242,45 @@ void DAPDebugger::startDebugRemote(const RemoteInfo &info) updateRunState(kPreparing); } +void DAPDebugger::attachDebug(const QString &processId) +{ + if (d->runState != kNoRun) { + qWarning() << "can`t attaching to debugee when debuging other application"; + DDialog dialog; + dialog.setMessage(tr("can`t attaching to debugee when debuging other application")); + dialog.setIcon(QIcon::fromTheme("dialog-warning")); + dialog.addButton(tr("Cancel")); + dialog.exec(); + return; + } + + d->isRemote = false; + d->startAttaching = true; + d->currentSession = d->localSession; + + // only support gdb for now + updateRunState(kStart); + QString debuggerTool = OptionManager::getInstance()->getCxxDebuggerToolPath(); + if (!debuggerTool.contains("gdb")) { + auto msg = tr("The gdb is required, please install it in console with \"sudo apt install gdb\", " + "and then restart the tool, reselect the CMake Debugger in Options Dialog..."); + printOutput(msg, OutputPane::OutputFormat::ErrorMessage); + } + + //todo : change signal to other debuger + QDBusMessage msg = QDBusMessage::createSignal("/path", + "com.deepin.unioncode.interface", + "getDebugPort"); + d->requestDAPPortPpid = QString(getpid()); + msg << d->requestDAPPortPpid + << "gdb" + << processId + << QStringList(); + bool ret = QDBusConnection::sessionBus().send(msg); + if (!ret) + printOutput(tr("Request cxx dap port failed, please retry."), OutputPane::OutputFormat::ErrorMessage); +} + void DAPDebugger::detachDebug() { } @@ -271,13 +313,15 @@ void DAPDebugger::abortDebug() if (generator) { if (generator->isStopDAPManually()) { stopDAP(); - } else { - d->currentSession->terminate(); } } } AppOutputPane::instance()->setProcessFinished("debugPane"); } + + d->currentSession->terminate(); + if (d->startAttaching) + d->startAttaching = false; } void DAPDebugger::restartDebug() @@ -509,7 +553,16 @@ void DAPDebugger::registerDapHandlers() || event.reason == "function-finished" || event.reason == "end-stepping-range" || (event.reason == "signal-received" && d->pausing) - || event.reason == "goto") { + || event.reason == "goto" + || (event.reason == "unknown" && d->startAttaching)) { + //when attaching to running program . it won`t receive initialized event and event`s reason is "unknwon" + //so initial breakpoints in here + if (d->startAttaching) { + d->currentSession->getRawSession()->setReadyForBreakpoints(true); + debugService->sendAllBreakpoints(d->currentSession); + + d->startAttaching = false; + } if (event.threadId) { d->threadId = event.threadId.value(0); int curThreadID = static_cast(d->threadId); @@ -571,11 +624,19 @@ void DAPDebugger::registerDapHandlers() Q_UNUSED(event) qInfo() << "\n--> recv : " << "ThreadEvent"; + + if (event.reason == "started") + d->threads.append(event.threadId); + + if (event.reason == "exited") { + d->threads.removeOne(event.threadId); + if (d->threads.isEmpty()) + updateRunState(kNoRun); + } }); // The event indicates that the target has produced some output. dapSession->registerHandler([&](const OutputEvent &event) { - Q_UNUSED(event) qInfo() << "\n--> recv : " << "OutputEvent\n" << "content : " << event.output.c_str(); @@ -705,17 +766,15 @@ void DAPDebugger::handleEvents(const dpf::Event &event) } } else if (event.data() == debugger.prepareDebugProgress.name) { printOutput(event.property(debugger.prepareDebugProgress.pKeys[0]).toString()); - } else if (event.data() == project.activedProject.name) { - getActiveProjectInfo() = qvariant_cast(event.property(project.activedProject.pKeys[0])); - d->activeProjectKitName = getActiveProjectInfo().kitName(); - updateRunState(kNoRun); - } else if (event.data() == project.createdProject.name) { - getActiveProjectInfo() = qvariant_cast(event.property(project.createdProject.pKeys[0])); - d->activeProjectKitName = getActiveProjectInfo().kitName(); - updateRunState(kNoRun); + } else if (event.data() == project.activatedProject.name) { + d->projectInfo = qvariant_cast(event.property("projectInfo")); + d->activeProjectKitName = d->projectInfo.kitName(); } else if (event.data() == project.deletedProject.name) { - d->activeProjectKitName.clear(); - updateRunState(kNoRun); + auto prjInfo = event.property("projectInfo").value(); + if (d->projectInfo.isSame(prjInfo)) { + d->activeProjectKitName.clear(); + updateRunState(kNoRun); + } } else if (event.data() == editor.switchedFile.name) { QString filePath = event.property(editor.switchedFile.pKeys[0]).toString(); if (d->currentOpenedFileName != filePath) { @@ -1143,6 +1202,9 @@ void DAPDebugger::updateWatchingVariables() void DAPDebugger::exitDebug() { + //abort debugger + abortDebug(); + // Change UI. editor.removeDebugLine(); d->variablesPane->hide(); @@ -1162,6 +1224,7 @@ void DAPDebugger::updateRunState(DAPDebugger::RunState state) switch (state) { case kNoRun: exitDebug(); + AppOutputPane::instance()->setProcessFinished("debugPane"); break; case kRunning: case kCustomRunning: @@ -1339,8 +1402,20 @@ void DAPDebugger::launchSession(int port, const QMap ¶m, port, iniRequet); + if (!bSuccess) { + qCritical() << "startDebug failed!"; + return; + } + // Launch debuggee. - if (bSuccess) { + if (d->startAttaching) { + dap::object obj; + obj["processId"] = param.value("targetPath").toString().toStdString(); + dap::AttachRequest request; + request.name = kitName.toStdString(); //kitName: gdb + request.connect = obj; + bSuccess &= d->currentSession->attach(request); + } else { auto &ctx = dpfInstance.serviceContext(); LanguageService *service = ctx.service(LanguageService::name()); if (service) { @@ -1354,10 +1429,10 @@ void DAPDebugger::launchSession(int port, const QMap ¶m, bSuccess &= d->currentSession->attach(request); } } - } else + } else { bSuccess &= false; + } } - if (!bSuccess) { qCritical() << "startDebug failed!"; } else { diff --git a/src/plugins/debugger/dap/dapdebugger.h b/src/plugins/debugger/dap/dapdebugger.h index 125e4a4eb..1f03fb497 100644 --- a/src/plugins/debugger/dap/dapdebugger.h +++ b/src/plugins/debugger/dap/dapdebugger.h @@ -53,6 +53,7 @@ class DAPDebugger : public AbstractDebugger void startDebug() override; void startDebugRemote(const RemoteInfo &info) override; + void attachDebug(const QString &processId) override; void detachDebug() override; void interruptDebug() override; diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index 5af40af5d..4e1359d70 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -49,10 +49,14 @@ bool DebuggerPlugin::start() if (windowService->addNavigationItem) { QAction *action = new QAction(MWNA_DEBUG, this); action->setIcon(QIcon::fromTheme("debug-navigation")); - windowService->addNavigationItem(new AbstractAction(action), Priority::high); + auto *actionImpl = new AbstractAction(action); + windowService->addNavigationItem(actionImpl, Priority::high); windowService->registerWidgetToMode(mainWindow, new AbstractWidget(debugManager->getDebugMainPane()), CM_DEBUG, Position::Left, true, true); + windowService->bindWidgetToNavigation(mainWindow, actionImpl); windowService->setDockHeaderName(mainWindow, tr("debug")); - windowService->registerWidget(localsPane, new AbstractWidget(debugManager->getLocalsPane())); + auto localsPaneImpl = new AbstractWidget(debugManager->getLocalsPane()); + localsPaneImpl->setDisplayIcon(QIcon::fromTheme("variable_watchers")); + windowService->registerWidget(localsPane, localsPaneImpl); connect(action, &QAction::triggered, this, [=]() { if (debugManager->getRunState() != AbstractDebugger::kNoRun) windowService->showWidgetAtPosition(localsPane, Position::Right, true); diff --git a/src/plugins/debugger/debugmanager.cpp b/src/plugins/debugger/debugmanager.cpp index 022453967..1d893a56f 100644 --- a/src/plugins/debugger/debugmanager.cpp +++ b/src/plugins/debugger/debugmanager.cpp @@ -7,6 +7,7 @@ #include "debuggersignals.h" #include "debuggerglobals.h" #include "interface/menumanager.h" +#include "interface/attachinfodialog.h" #include "services/debugger/debuggerservice.h" #include "services/language/languageservice.h" @@ -140,6 +141,16 @@ void DebugManager::remoteDebug(RemoteInfo info) AsynInvokeWithParam(currentDebugger->startDebugRemote, info); } +void DebugManager::attachDebug() +{ + AttachInfoDialog dialog; + connect(&dialog, &AttachInfoDialog::attachToProcessId, this, [=](const QString &processId){ + AsynInvokeWithParam(currentDebugger->attachDebug, processId); + }); + + dialog.exec(); +} + void DebugManager::detachDebug() { AsynInvoke(currentDebugger->detachDebug()); @@ -200,8 +211,8 @@ void DebugManager::handleEvents(const dpf::Event &event) QString data = event.data().toString(); if (event.data() == debugger.prepareDebugProgress.name) { // TODO(logan) - } else if (event.data() == project.activedProject.name) { - auto projectInfo = qvariant_cast(event.property(project.activedProject.pKeys[0])); + } else if (event.data() == project.activatedProject.name) { + auto projectInfo = qvariant_cast(event.property(project.activatedProject.pKeys[0])); activeProjectKitName = projectInfo.kitName(); } else if (event.data() == project.createdProject.name) { auto projectInfo = qvariant_cast(event.property(project.createdProject.pKeys[0])); diff --git a/src/plugins/debugger/debugmanager.h b/src/plugins/debugger/debugmanager.h index 807374ef1..2528c872e 100644 --- a/src/plugins/debugger/debugmanager.h +++ b/src/plugins/debugger/debugmanager.h @@ -51,6 +51,7 @@ public slots: */ void remoteDebug(RemoteInfo info); void run(); + void attachDebug(); void detachDebug(); void interruptDebug(); diff --git a/src/plugins/debugger/icons/executable_16px.svg b/src/plugins/debugger/icons/executable_16px.svg new file mode 100644 index 000000000..32862faaf --- /dev/null +++ b/src/plugins/debugger/icons/executable_16px.svg @@ -0,0 +1,45 @@ + + + ICON / list/可执行目标 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/debugger/interface/attachinfodialog.cpp b/src/plugins/debugger/interface/attachinfodialog.cpp new file mode 100644 index 000000000..cd3dfe76c --- /dev/null +++ b/src/plugins/debugger/interface/attachinfodialog.cpp @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "attachinfodialog.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +DWIDGET_USE_NAMESPACE + +AttachInfoDialog::AttachInfoDialog(QWidget *parent) + : DDialog(parent) +{ + initUi(); + initButton(); +} + +void AttachInfoDialog::initUi() +{ + setFixedSize(500, 500); + DComboBox *cbBox = new DComboBox(this); + cbBox->addItem("gdb"); + DLineEdit *edit = new DLineEdit(this); + + auto widget = new DWidget(this); + auto fLayout = new QFormLayout(widget); + fLayout->addRow(new QLabel(tr("debugger:"), this), cbBox); + fLayout->addRow(new QLabel(tr("filter:"), this), edit); + + view = new DTableView(this); + view->setShowGrid(false); + view->setAlternatingRowColors(true); + model = new QStandardItemModel(this); + model->setHorizontalHeaderLabels({tr("Process Id"), tr("Process Path")}); + proxy = new QSortFilterProxyModel(this); + proxy->setSourceModel(model); + proxy->setFilterKeyColumn(1); + proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); + view->setModel(proxy); + + view->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + view->verticalHeader()->hide(); + view->setEditTriggers(QAbstractItemView::NoEditTriggers); + view->setSelectionBehavior(QAbstractItemView::SelectRows); + setIcon(QIcon::fromTheme("ide")); + insertContent(0, widget); + insertContent(1, view); + + connect(edit, &DLineEdit::textChanged, this, [=](const QString &text){ + proxy->setFilterRegExp(QString(".*%1.*").arg(text)); + }); + + updateProcess(); +} + +void AttachInfoDialog::initButton() +{ + auto buttonBox = new DWidget(this); + auto buttonLayout = new QHBoxLayout(buttonBox); + + DPushButton *btnUpdate = new DPushButton(tr("Update"), this); + DPushButton *btnCancel = new DPushButton(tr("Cancel"), this); + DPushButton *btnAttaching = new DPushButton(tr("Attaching"), this); + btnAttaching->setEnabled(false); + + buttonLayout->addWidget(btnUpdate); + buttonLayout->addWidget(btnCancel); + buttonLayout->addWidget(btnAttaching); + + connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, [=](){ + if (view->selectionModel()->hasSelection()) + btnAttaching->setEnabled(true); + else + btnAttaching->setEnabled(false); + }); + connect(btnUpdate, &DPushButton::clicked, this, [=](){ + updateProcess(); + }); + connect(btnCancel, &DPushButton::clicked, this, [=](){ + reject(); + }); + connect(btnAttaching, &DPushButton::clicked, this, [=](){ + auto selectedItem = view->selectionModel()->selectedRows(0).at(0); + emit attachToProcessId(selectedItem.data().toString()); + accept(); + }); + + addContent(buttonBox); +} + +void AttachInfoDialog::updateProcess() +{ + model->removeRows(1, model->rowCount() - 1); + + QStringList procList = QDir("/proc").entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &pid : procList) { + QString exePath = QString("/proc/%1/exe").arg(pid); + if (QFile::exists(exePath)) { + QFileInfo fileInfo(exePath); + QString path = fileInfo.symLinkTarget(); + if (path.isEmpty()) { + path = exePath; + } + + model->appendRow({new QStandardItem(pid), new QStandardItem(path)}); + } + } +} diff --git a/src/plugins/debugger/interface/attachinfodialog.h b/src/plugins/debugger/interface/attachinfodialog.h new file mode 100644 index 000000000..48f2abd15 --- /dev/null +++ b/src/plugins/debugger/interface/attachinfodialog.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include +#include + +class AttachInfoDialog : public Dtk::Widget::DDialog +{ + Q_OBJECT +public: + AttachInfoDialog(QWidget *parent = nullptr); + ~AttachInfoDialog(){} + +signals: + void attachToProcessId(const QString &pid); + +private: + void initUi(); + void initButton(); + void updateProcess(); + + Dtk::Widget::DTableView *view = nullptr; + QStandardItemModel *model = nullptr; + QSortFilterProxyModel *proxy = nullptr; +}; diff --git a/src/plugins/debugger/interface/breakpointview.cpp b/src/plugins/debugger/interface/breakpointview.cpp index 206fe4952..0ebffdd6d 100644 --- a/src/plugins/debugger/interface/breakpointview.cpp +++ b/src/plugins/debugger/interface/breakpointview.cpp @@ -6,6 +6,7 @@ #include "common/common.h" #include "breakpointmodel.h" #include "base/baseitemdelegate.h" +#include "services/editor/editorservice.h" #include #include @@ -29,6 +30,7 @@ BreakpointView::BreakpointView(QWidget *parent) setFrameStyle(QFrame::NoFrame); setAlternatingRowColors(true); setItemDelegate(new BaseItemDelegate(this)); + editService = dpfGetService(dpfservice::EditorService); connect(this, &QAbstractItemView::clicked, this, [=](const QModelIndex &index) { @@ -44,7 +46,7 @@ BreakpointView::~BreakpointView() void BreakpointView::contextMenuEvent(QContextMenuEvent *event) { DTreeView::contextMenuEvent(event); - BreakpointModel* bpModel = static_cast(model()); + BreakpointModel *bpModel = static_cast(model()); if (bpModel->breakpointSize() == 0) return; @@ -64,7 +66,7 @@ void BreakpointView::contextMenuEvent(QContextMenuEvent *event) bool allEnabled = true; bool allDisabled = true; QModelIndexList allRows; - for(int index = 0; index < bpModel->breakpointSize(); index++) { + for (int index = 0; index < bpModel->breakpointSize(); index++) { allRows.append(bpModel->index(index, 0)); auto bp = bpModel->BreakpointAt(index); if (bp.enabled) @@ -75,25 +77,25 @@ void BreakpointView::contextMenuEvent(QContextMenuEvent *event) QMenu menu(this); if (!allSelectEnabled) - menu.addAction(tr("Enable selected breakpoints"), this, [=](){enableBreakpoints(rows);}); + menu.addAction(tr("Enable selected breakpoints"), this, [=]() { enableBreakpoints(rows); }); if (!allSelectDisabled) - menu.addAction(tr("Disable selected breakpoints"), this, [=](){disableBreakpoints(rows);}); + menu.addAction(tr("Disable selected breakpoints"), this, [=]() { disableBreakpoints(rows); }); menu.addSeparator(); if (!rows.isEmpty()) - menu.addAction(tr("Remove selected breakpoints"), this, [=]() {removeBreakpoints(rows);}); + menu.addAction(tr("Remove selected breakpoints"), this, [=]() { removeBreakpoints(rows); }); - menu.addAction(tr("Remove all breakpoints"), this, [=]() {removeBreakpoints(allRows);}); + menu.addAction(tr("Remove all breakpoints"), this, [=]() { removeBreakpoints(allRows); }); menu.addSeparator(); if (!allEnabled) - menu.addAction(tr("Enable all breakpoints"), this, [=](){enableBreakpoints(allRows);}); + menu.addAction(tr("Enable all breakpoints"), this, [=]() { enableBreakpoints(allRows); }); if (!allDisabled) - menu.addAction(tr("Disable all breakpoints"), this, [=](){disableBreakpoints(allRows);}); + menu.addAction(tr("Disable all breakpoints"), this, [=]() { disableBreakpoints(allRows); }); // select one item if (rows.size() == 1) { - menu.addAction(tr("Edit Condition"), this, [=](){editBreakpointCondition(rows.at(0));}); + menu.addAction(tr("Edit Condition"), this, [=]() { editBreakpointCondition(rows.at(0)); }); } menu.exec(event->globalPos()); @@ -101,34 +103,46 @@ void BreakpointView::contextMenuEvent(QContextMenuEvent *event) void BreakpointView::enableBreakpoints(const QModelIndexList &rows) { - BreakpointModel* bpModel = static_cast(model()); + BreakpointModel *bpModel = static_cast(model()); + QStringList openedFiles = editService->openedFiles(); for (auto row : rows) { auto bp = bpModel->BreakpointAt(row.row()); - editor.setBreakpointEnabled(bp.filePath, bp.lineNumber, true); + if (openedFiles.contains(bp.filePath)) + editor.setBreakpointEnabled(bp.filePath, bp.lineNumber, true); + else + editor.breakpointStatusChanged(bp.filePath, bp.lineNumber, true); } } void BreakpointView::disableBreakpoints(const QModelIndexList &rows) { - BreakpointModel* bpModel = static_cast(model()); + BreakpointModel *bpModel = static_cast(model()); + QStringList openedFiles = editService->openedFiles(); for (auto row : rows) { auto bp = bpModel->BreakpointAt(row.row()); - editor.setBreakpointEnabled(bp.filePath, bp.lineNumber, false); + if (openedFiles.contains(bp.filePath)) + editor.setBreakpointEnabled(bp.filePath, bp.lineNumber, false); + else + editor.breakpointStatusChanged(bp.filePath, bp.lineNumber, false); } } void BreakpointView::removeBreakpoints(const QModelIndexList &rows) { - BreakpointModel* bpModel = static_cast(model()); + BreakpointModel *bpModel = static_cast(model()); + QStringList openedFiles = editService->openedFiles(); for (auto row : rows) { auto bp = bpModel->BreakpointAt(row.row()); - editor.removeBreakpoint(bp.filePath, bp.lineNumber); + if (openedFiles.contains(bp.filePath)) + editor.removeBreakpoint(bp.filePath, bp.lineNumber); + else + editor.breakpointRemoved(bp.filePath, bp.lineNumber); } } void BreakpointView::editBreakpointCondition(const QModelIndex &index) { - BreakpointModel* bpModel = static_cast(model()); + BreakpointModel *bpModel = static_cast(model()); auto bp = bpModel->BreakpointAt(index.row()); editor.setBreakpointCondition(bp.filePath, bp.lineNumber); diff --git a/src/plugins/debugger/interface/breakpointview.h b/src/plugins/debugger/interface/breakpointview.h index 9499a568f..0dba34e5d 100644 --- a/src/plugins/debugger/interface/breakpointview.h +++ b/src/plugins/debugger/interface/breakpointview.h @@ -9,6 +9,10 @@ #include +namespace dpfservice { +class EditorService; +} + class BreakpointView : public DTK_WIDGET_NAMESPACE::DTreeView { public: @@ -25,7 +29,8 @@ class BreakpointView : public DTK_WIDGET_NAMESPACE::DTreeView void removeBreakpoints(const QModelIndexList &rows); void editBreakpointCondition(const QModelIndex &idnex); - QHeaderView *headerView {nullptr}; + QHeaderView *headerView { nullptr }; + dpfservice::EditorService *editService { nullptr }; }; -#endif // BREAKPOINTVIEW_H +#endif // BREAKPOINTVIEW_H diff --git a/src/plugins/debugger/interface/menumanager.cpp b/src/plugins/debugger/interface/menumanager.cpp index 425db7d5e..8e0479a81 100644 --- a/src/plugins/debugger/interface/menumanager.cpp +++ b/src/plugins/debugger/interface/menumanager.cpp @@ -15,7 +15,8 @@ #include using namespace dpfservice; -MenuManager::MenuManager(QObject *parent) : QObject(parent) +MenuManager::MenuManager(QObject *parent) + : QObject(parent) { } @@ -25,7 +26,7 @@ void MenuManager::initialize(WindowService *windowService) return; auto initAction = [&](QAction *action, const QString &id, const QString &description, - QKeySequence key, const QString &iconName) -> AbstractAction*{ + QKeySequence key, const QString &iconName) -> AbstractAction * { action->setIcon(QIcon::fromTheme(iconName)); auto actionImpl = new AbstractAction(action, this); actionImpl->setShortCutInfo(id, description, key); @@ -39,7 +40,7 @@ void MenuManager::initialize(WindowService *windowService) "debugger_start"); windowService->addAction(MWM_DEBUG, actionImpl); windowService->addTopToolItem(actionImpl, false, Priority::medium); -#if 0 // not used yet. +#if 0 // not used yet. detachDebugger.reset(new QAction("Detach Debugger")); connect(detachDebugger.get(), &QAction::triggered, debugManager, &DebugManager::detachDebug); actionImpl = new AbstractAction(detachDebugger.get()); @@ -80,7 +81,7 @@ void MenuManager::initialize(WindowService *windowService) restartDebugging->setEnabled(false); connect(restartDebugging.get(), &QAction::triggered, debugManager, &DebugManager::restartDebug); actionImpl = initAction(restartDebugging.get(), "Debug.Restart.Debugging", - MWMDA_RESTART_DEBUGGING, QKeySequence(Qt::Modifier::CTRL | Qt::Key::Key_B), + MWMDA_RESTART_DEBUGGING, QKeySequence(Qt::Modifier::SHIFT | Qt::Key::Key_B), "restart_debug"); windowService->addAction(MWM_DEBUG, actionImpl); appOutPutPane->registerItemToToolBar(debugToolBarName, actionImpl->qAction(), false); @@ -111,9 +112,9 @@ void MenuManager::initialize(WindowService *windowService) "debugger_stepout"); windowService->addAction(MWM_DEBUG, actionImpl); appOutPutPane->registerItemToToolBar(debugToolBarName, actionImpl->qAction(), false); - + remoteDebug.reset(new QAction(MWMDA_REMOTE_DEBUG)); - connect(remoteDebug.get(), &QAction::triggered, debugManager, [=](){ + connect(remoteDebug.get(), &QAction::triggered, debugManager, [=]() { auto remoteDlg = new RemoteDebugDlg(); remoteDlg->setAttribute(Qt::WA_DeleteOnClose); remoteDlg->exec(); @@ -122,6 +123,13 @@ void MenuManager::initialize(WindowService *windowService) MWMDA_REMOTE_DEBUG, QKeySequence(), "debugger_remotedebug"); windowService->addAction(MWM_DEBUG, actionImpl); + + attachDebugging.reset(new QAction(MWMDA_ATTACH_DEBUG)); + connect(attachDebugging.get(), &QAction::triggered, debugManager, &DebugManager::attachDebug); + actionImpl = initAction(attachDebugging.get(), "Debug.Attach.Debugging", + MWMDA_ATTACH_DEBUG, QKeySequence(), + "debugger_start"); + windowService->addAction(MWM_DEBUG, actionImpl); } void MenuManager::handleRunStateChanged(AbstractDebugger::RunState state) @@ -131,7 +139,7 @@ void MenuManager::handleRunStateChanged(AbstractDebugger::RunState state) case AbstractDebugger::kPreparing: case AbstractDebugger::kStart: startDebugging->setEnabled(true); -#if 0 // not used yet. +#if 0 // not used yet. detachDebugger->setEnabled(true); #endif interrupt->setEnabled(false); @@ -141,11 +149,12 @@ void MenuManager::handleRunStateChanged(AbstractDebugger::RunState state) stepOver->setEnabled(false); stepIn->setEnabled(false); stepOut->setEnabled(false); + attachDebugging->setEnabled(true); break; case AbstractDebugger::kRunning: startDebugging->setEnabled(false); -#if 0 // not used yet. +#if 0 // not used yet. detachDebugger->setEnabled(false); #endif interrupt->setEnabled(true); @@ -155,10 +164,11 @@ void MenuManager::handleRunStateChanged(AbstractDebugger::RunState state) stepOver->setEnabled(false); stepIn->setEnabled(false); stepOut->setEnabled(false); + attachDebugging->setEnabled(false); break; case AbstractDebugger::kStopped: startDebugging->setEnabled(false); -#if 0 // not used yet. +#if 0 // not used yet. detachDebugger->setEnabled(false); #endif interrupt->setEnabled(false); @@ -168,6 +178,7 @@ void MenuManager::handleRunStateChanged(AbstractDebugger::RunState state) stepOver->setEnabled(true); stepIn->setEnabled(true); stepOut->setEnabled(true); + attachDebugging->setEnabled(false); break; case AbstractDebugger::kCustomRunning: startDebugging->setEnabled(false); @@ -178,6 +189,7 @@ void MenuManager::handleRunStateChanged(AbstractDebugger::RunState state) stepOver->setEnabled(false); stepIn->setEnabled(false); stepOut->setEnabled(false); + attachDebugging->setEnabled(false); break; default: diff --git a/src/plugins/debugger/interface/menumanager.h b/src/plugins/debugger/interface/menumanager.h index 34f908294..56143171d 100644 --- a/src/plugins/debugger/interface/menumanager.h +++ b/src/plugins/debugger/interface/menumanager.h @@ -31,6 +31,7 @@ public slots: private: QSharedPointer startDebugging; + QSharedPointer attachDebugging; QSharedPointer detachDebugger; QSharedPointer interrupt; QSharedPointer continueDebugging; diff --git a/src/plugins/debugger/resource.qrc b/src/plugins/debugger/resource.qrc index 24a9c5f99..2617bfb33 100644 --- a/src/plugins/debugger/resource.qrc +++ b/src/plugins/debugger/resource.qrc @@ -1,5 +1,6 @@ + icons/executable_16px.svg texts/debug-navigation_22px.svg texts/debugger_continue_16px.svg texts/debugger_interrupt_16px.svg @@ -7,6 +8,7 @@ texts/debugger_stepout_16px.svg texts/debugger_stepover_16px.svg texts/restart_debug_16px.svg + texts/variable_watchers_16px.svg light/icons/breakpoint_14px.svg light/icons/disabled_breakpoint_14px.svg light/icons/debugger_start_16px.svg diff --git a/src/plugins/debugger/runner/runner.cpp b/src/plugins/debugger/runner/runner.cpp index 794fb0ad3..3e52c1e48 100644 --- a/src/plugins/debugger/runner/runner.cpp +++ b/src/plugins/debugger/runner/runner.cpp @@ -14,6 +14,7 @@ #include "services/builder/builderservice.h" #include "services/window/windowservice.h" #include "services/editor/editorservice.h" +#include "services/terminal/terminalservice.h" #include #include @@ -131,13 +132,13 @@ void Runner::handleEvents(const dpf::Event &event) if (d->currentOpenedFilePath == filePath) { d->currentOpenedFilePath.clear(); } - } else if (event.data() == project.activedProject.name) { + } else if (event.data() == project.activatedProject.name) { QVariant proInfoVar = event.property("projectInfo"); dpfservice::ProjectInfo projectInfo = qvariant_cast(proInfoVar); auto programs = projectInfo.exePrograms(); d->runProgram->clear(); for (auto program : programs) - d->runProgram->addItem(QIcon::fromTheme("run"), program); + d->runProgram->addItem(QIcon::fromTheme("executable"), program); d->runProgram->setCurrentText(projectInfo.currentProgram()); } } @@ -149,7 +150,6 @@ void Runner::running() return; } - uiController.switchContext(tr("&Application Output")); LanguageService *service = dpfGetService(LanguageService); if (service) { @@ -157,9 +157,18 @@ void Runner::running() if (generator) { dpfservice::ProjectInfo activeProjInfo = dpfGetService(ProjectService)->getActiveProjectInfo(); RunCommandInfo args = generator->getRunArguments(activeProjInfo, d->currentOpenedFilePath); - QtConcurrent::run([=](){ - execCommand(args); - }); + if (!args.runInTerminal) { + uiController.switchContext(tr("&Application Output")); + + QtConcurrent::run([=]() { + execCommand(args); + }); + } else { + uiController.switchContext(TERMINAL_TAB_TEXT); + + auto terminalService = dpfGetService(TerminalService); + terminalService->executeCommand(args.program, args.program, args.arguments, args.workingDir, args.envs); + } } } } diff --git a/src/plugins/debugger/texts/variable_watchers_16px.svg b/src/plugins/debugger/texts/variable_watchers_16px.svg new file mode 100644 index 000000000..591b44e9a --- /dev/null +++ b/src/plugins/debugger/texts/variable_watchers_16px.svg @@ -0,0 +1,7 @@ + + + ICON / sidebar /variable_table + + + + \ No newline at end of file diff --git a/src/plugins/filebrowser/filebrowser.json b/src/plugins/filebrowser/filebrowser.json index 613db8fe8..dfdb75110 100644 --- a/src/plugins/filebrowser/filebrowser.json +++ b/src/plugins/filebrowser/filebrowser.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Core Plugins", "Description" : "The file browser plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document2?dirid=656d40a9bd766615b0b02e5e", + "UrlLink" : "https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/filebrowser/mainframe/filetreeview.cpp b/src/plugins/filebrowser/mainframe/filetreeview.cpp index d25505bbc..656fdb5d3 100644 --- a/src/plugins/filebrowser/mainframe/filetreeview.cpp +++ b/src/plugins/filebrowser/mainframe/filetreeview.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include DWIDGET_USE_NAMESPACE @@ -27,9 +28,37 @@ const QString DELETE_MESSAGE_TEXT { DTreeView::tr("The delete operation will be const QString DELETE_WINDOW_TEXT { DTreeView::tr("Delete Warning") }; +class FileSortFilterProxyModel : public QSortFilterProxyModel +{ +public: + explicit FileSortFilterProxyModel(QObject *parent = nullptr); + +protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +}; + +FileSortFilterProxyModel::FileSortFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} + +bool FileSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + QFileSystemModel *fsModel = qobject_cast(sourceModel()); + // Folder always comes before file + if (fsModel && fsModel->fileInfo(left).isDir() && !fsModel->fileInfo(right).isDir()) { + return true; + } else if (fsModel && !fsModel->fileInfo(left).isDir() && fsModel->fileInfo(right).isDir()) { + return false; + } + + return QSortFilterProxyModel::lessThan(left, right); +} + class TreeViewPrivate { friend class FileTreeView; + FileSortFilterProxyModel *sortModel { nullptr }; QFileSystemModel *model { nullptr }; DMenu *menu { nullptr }; QStack moveToTrashStack; @@ -42,7 +71,14 @@ FileTreeView::FileTreeView(QWidget *parent) setLineWidth(0); d->model = new QFileSystemModel(this); d->menu = new DMenu(this); - setModel(d->model); + + d->sortModel = new FileSortFilterProxyModel(this); + d->sortModel->setSourceModel(d->model); + d->sortModel->sort(0); + d->sortModel->setDynamicSortFilter(true); + d->sortModel->setSortCaseSensitivity(Qt::CaseInsensitive); + + setModel(d->sortModel); setItemDelegate(new BaseItemDelegate(this)); header()->setSectionResizeMode(DHeaderView::ResizeMode::ResizeToContents); setAlternatingRowColors(true); @@ -61,9 +97,8 @@ void FileTreeView::setProjectInfo(const dpfservice::ProjectInfo &proInfo) { d->proInfo = proInfo; d->model->setRootPath(proInfo.workspaceFolder()); - auto index = d->model->index(proInfo.workspaceFolder()); - DTreeView::expand(index); - DTreeView::setRootIndex(index); + auto index = d->sortModel->mapFromSource(d->model->index(d->proInfo.workspaceFolder())); + setRootIndex(index); emit rootPathChanged(proInfo.workspaceFolder()); } @@ -72,7 +107,7 @@ void FileTreeView::selOpen() QModelIndexList indexs = selectedIndexes(); QSet countPaths; for (auto index : indexs) { - countPaths << d->model->filePath(index); + countPaths << d->model->filePath(d->sortModel->mapToSource(index)); } for (auto path : countPaths) { @@ -86,7 +121,7 @@ void FileTreeView::selMoveToTrash() QModelIndexList indexs = selectedIndexes(); QSet countPaths; for (auto index : indexs) { - countPaths << d->model->filePath(index); + countPaths << d->model->filePath(d->sortModel->mapToSource(index)); } QStringList errFilePaths; @@ -115,7 +150,7 @@ void FileTreeView::selRemove() QModelIndexList indexs = selectedIndexes(); QStringList countPaths; for (auto index : indexs) { - countPaths << d->model->filePath(index); + countPaths << d->model->filePath(d->sortModel->mapToSource(index)); } // Remove duplicates countPaths = countPaths.toSet().toList(); @@ -126,12 +161,13 @@ void FileTreeView::selRemove() dialog.setMessage(message); dialog.setWindowTitle(tr("Delete Warining")); dialog.setIcon(QIcon::fromTheme("dialog-warning")); - dialog.insertButton(0, tr("Ok")); - dialog.insertButton(1, tr("Cancel")); - int code = dialog.exec(); + dialog.insertButton(0, tr("Cancel")); + dialog.insertButton(1, tr("OK")); + dialog.exec(); - if (code == 1) + if (dialog.result() != QDialog::Accepted) { return; + } bool hasError = false; QStringList errFilePaths; @@ -155,7 +191,7 @@ void FileTreeView::selRename() if (indexs.isEmpty()) return; - QString filePath = d->model->filePath(indexs[0]); + QString filePath = d->model->filePath(d->sortModel->mapToSource(indexs.first())); QFileInfo fileInfo(filePath); auto dialog = new DDialog(this); @@ -170,25 +206,25 @@ void FileTreeView::selRename() dialog->addContent(inputEdit); dialog->addButton(tr("Ok"), true, DDialog::ButtonRecommend); - QObject::connect(dialog, &DDialog::buttonClicked, dialog, [=](){ + QObject::connect(dialog, &DDialog::buttonClicked, dialog, [=]() { QString newFileName = inputEdit->text(); - QString newPath = fileInfo.absoluteDir().filePath(newFileName); - if (fileInfo.isFile()) { - QFile file(filePath); - if (file.rename(newPath)) { - qDebug() << "File renamed successfully."; - } else { - qDebug() << "Failed to rename file."; - } - } else if (fileInfo.isDir()) { - QDir dir(filePath); - if (dir.rename(filePath, newPath)) { - qDebug() << "Directory renamed successfully."; - } else { - qDebug() << "Failed to rename directory."; - } - } - dialog->accept(); + QString newPath = fileInfo.absoluteDir().filePath(newFileName); + if (fileInfo.isFile()) { + QFile file(filePath); + if (file.rename(newPath)) { + qDebug() << "File renamed successfully."; + } else { + qDebug() << "Failed to rename file."; + } + } else if (fileInfo.isDir()) { + QDir dir(filePath); + if (dir.rename(filePath, newPath)) { + qDebug() << "Directory renamed successfully."; + } else { + qDebug() << "Failed to rename directory."; + } + } + dialog->accept(); }); dialog->exec(); @@ -234,7 +270,7 @@ void FileTreeView::createNewOperation(const QString &newName, NewType type) if (indexs.isEmpty()) return; - QString Path = d->model->filePath(indexs[0]); + QString Path = d->model->filePath(d->sortModel->mapToSource(indexs.first())); QFileInfo upDirInfo(Path); if (upDirInfo.isDir()) { @@ -265,7 +301,7 @@ void FileTreeView::recoverFromTrash() void FileTreeView::doDoubleClicked(const QModelIndex &index) { - QString filePath = d->model->filePath(index); + QString filePath = d->model->filePath(d->sortModel->mapToSource(index)); if (QFileInfo(filePath).isFile()) editor.openFile(QString(), filePath); } @@ -288,7 +324,7 @@ DMenu *FileTreeView::createContextMenu(const QModelIndexList &indexs) DMenu *menu = new DMenu(); - QString filePath = d->model->filePath(indexs[0]); + QString filePath = d->model->filePath(d->sortModel->mapToSource(indexs.first())); QFileInfo info(filePath); QAction *openAction = new QAction(tr("Open")); diff --git a/src/plugins/filebrowser/mainframe/filetreeview.h b/src/plugins/filebrowser/mainframe/filetreeview.h index 25cada9a8..7845dff50 100644 --- a/src/plugins/filebrowser/mainframe/filetreeview.h +++ b/src/plugins/filebrowser/mainframe/filetreeview.h @@ -24,8 +24,9 @@ class FileTreeView : public DTK_WIDGET_NAMESPACE::DTreeView public: explicit FileTreeView(QWidget *parent = nullptr); virtual ~FileTreeView(); - void setProjectInfo(const dpfservice::ProjectInfo &proInfo); + public slots: + void setProjectInfo(const dpfservice::ProjectInfo &proInfo); void selOpen(); void selMoveToTrash(); void selRemove(); diff --git a/src/plugins/filebrowser/transceiver/filebrowserreceiver.cpp b/src/plugins/filebrowser/transceiver/filebrowserreceiver.cpp index 3d83fb0d4..451c4abaf 100644 --- a/src/plugins/filebrowser/transceiver/filebrowserreceiver.cpp +++ b/src/plugins/filebrowser/transceiver/filebrowserreceiver.cpp @@ -10,10 +10,8 @@ #include "common/common.h" FileBrowserReceiver::FileBrowserReceiver(QObject *parent) - : dpf::EventHandler (parent) - , dpf::AutoEventHandlerRegister () + : dpf::EventHandler(parent), dpf::AutoEventHandlerRegister() { - } dpf::EventHandler::Type FileBrowserReceiver::type() @@ -23,20 +21,31 @@ dpf::EventHandler::Type FileBrowserReceiver::type() QStringList FileBrowserReceiver::topics() { - return {project.topic}; //绑定menu 事件 + return { project.topic }; //绑定menu 事件 } void FileBrowserReceiver::eventProcess(const dpf::Event &event) { - if (event.data() == project.activedProject.name) { - QVariant proInfoVar = event.property(project.activedProject.pKeys[0]); + if (event.data() == project.activatedProject.name) { + QVariant proInfoVar = event.property(project.activatedProject.pKeys[0]); dpfservice::ProjectInfo proInfo = qvariant_cast(proInfoVar); - TreeViewKeeper::instance()->treeView()->setProjectInfo(proInfo); + + QMetaObject::invokeMethod(TreeViewKeeper::instance()->treeView(), + "setProjectInfo", + Qt::QueuedConnection, + Q_ARG(dpfservice::ProjectInfo, proInfo)); } else if (event.data() == project.deletedProject.name) { - TreeViewKeeper::instance()->treeView()->setProjectInfo({}); + QMetaObject::invokeMethod(TreeViewKeeper::instance()->treeView(), + "setProjectInfo", + Qt::QueuedConnection, + Q_ARG(dpfservice::ProjectInfo, {})); } else if (event.data() == project.createdProject.name) { - QVariant proInfoVar = event.property(project.activedProject.pKeys[0]); + QVariant proInfoVar = event.property(project.activatedProject.pKeys[0]); dpfservice::ProjectInfo proInfo = qvariant_cast(proInfoVar); - TreeViewKeeper::instance()->treeView()->setProjectInfo(proInfo); + + QMetaObject::invokeMethod(TreeViewKeeper::instance()->treeView(), + "setProjectInfo", + Qt::QueuedConnection, + Q_ARG(dpfservice::ProjectInfo, proInfo)); } } diff --git a/src/plugins/filebrowser/transceiver/filebrowserreceiver.h b/src/plugins/filebrowser/transceiver/filebrowserreceiver.h index 81f128661..8133447f4 100644 --- a/src/plugins/filebrowser/transceiver/filebrowserreceiver.h +++ b/src/plugins/filebrowser/transceiver/filebrowserreceiver.h @@ -14,6 +14,8 @@ class FileBrowserReceiver: public dpf::EventHandler, dpf::AutoEventHandlerRegist explicit FileBrowserReceiver(QObject * parent = nullptr); static Type type(); static QStringList topics(); + +private: void eventProcess(const dpf::Event &event) override; }; diff --git a/src/plugins/find/CMakeLists.txt b/src/plugins/find/CMakeLists.txt index 34a7e7b4e..5f832d8e2 100644 --- a/src/plugins/find/CMakeLists.txt +++ b/src/plugins/find/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(${PROJECT_NAME} ${PROJECT_SOURCES} ${QT_THEME} find.qrc - ) +) target_link_libraries(${PROJECT_NAME} framework diff --git a/src/plugins/find/constants.cpp b/src/plugins/find/constants.cpp new file mode 100644 index 000000000..5ae655922 --- /dev/null +++ b/src/plugins/find/constants.cpp @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "constants.h" + +QString Utils::expandRegExpReplacement(const QString &replaceText, const QStringList &capturedTexts) +{ + // handles \1 \\ \& \t \n $1 $$ $& + QString result; + const int numCaptures = capturedTexts.size() - 1; + const int replaceLength = replaceText.length(); + for (int i = 0; i < replaceLength; ++i) { + QChar c = replaceText.at(i); + if (c == QLatin1Char('\\') && i < replaceLength - 1) { + c = replaceText.at(++i); + if (c == QLatin1Char('\\')) { + result += QLatin1Char('\\'); + } else if (c == QLatin1Char('&')) { + result += QLatin1Char('&'); + } else if (c == QLatin1Char('t')) { + result += QLatin1Char('\t'); + } else if (c == QLatin1Char('n')) { + result += QLatin1Char('\n'); + } else if (c.isDigit()) { + int index = c.unicode() - '1'; + if (index < numCaptures) { + result += capturedTexts.at(index + 1); + } // else add nothing + } else { + result += QLatin1Char('\\'); + result += c; + } + } else if (c == '$' && i < replaceLength - 1) { + c = replaceText.at(++i); + if (c == '$') { + result += '$'; + } else if (c == '&') { + result += capturedTexts.at(0); + } else if (c.isDigit()) { + int index = c.unicode() - '1'; + if (index < numCaptures) { + result += capturedTexts.at(index + 1); + } // else add nothing + } else { + result += '$'; + result += c; + } + } else { + result += c; + } + } + return result; +} diff --git a/src/plugins/find/constants.h b/src/plugins/find/constants.h index 1fb3d3d64..f196d748b 100644 --- a/src/plugins/find/constants.h +++ b/src/plugins/find/constants.h @@ -8,33 +8,77 @@ #include #include -struct SearchParams -{ - QStringList filePathList; - QString searchText; - bool sensitiveFlag; - bool wholeWordsFlag; - QStringList patternsList; - QStringList exPatternsList; - QMap projectInfoMap; +enum ResultRole { + LineRole = Qt::UserRole + 1, + ColumnRole, + KeywordRole, + MatchedLengthRole, + FilePathRole, + ReplaceTextRole }; -struct ReplaceParams -{ - QStringList filePathList; - QString searchText; - QString replaceText; +enum SearchScope { + AllProjects = 0, + CurrentProject, + CurrentFile +}; + +enum SearchFlag { + SearchNoFlag = 0, + SearchCaseSensitively = 1, + SearchWholeWords = 1 << 1, + SearchRegularExpression = 1 << 2 +}; +Q_DECLARE_FLAGS(SearchFlags, SearchFlag) + +enum MessageType { + Information, + Warning }; struct FindItem { QString filePathName; - int lineNumber; + int line = -1; + int column = -1; + QString keyword; + int matchedLength; + QStringList capturedTexts; QString context; + + inline bool operator==(const FindItem &other) + { + return filePathName == other.filePathName && line == other.line + && column == other.column; + } }; using FindItemList = QList; -using ProjectInfo = QMap; + +struct SearchParams +{ + QString keyword; + QStringList projectFileList; + QStringList editFileList; + QStringList includeList; + QStringList excludeList; + SearchFlags flags = SearchNoFlag; + SearchScope scope = AllProjects; +}; + +struct ReplaceParams +{ + QStringList editFileList; + QMap resultMap; + QString replaceText; + SearchFlags flags = SearchNoFlag; +}; + +class Utils +{ +public: + static QString expandRegExpReplacement(const QString &replaceText, const QStringList &capturedTexts); +}; Q_DECLARE_METATYPE(SearchParams) Q_DECLARE_METATYPE(ReplaceParams) diff --git a/src/plugins/find/find.qrc b/src/plugins/find/find.qrc index 4aaeb3f53..0fc94d194 100644 --- a/src/plugins/find/find.qrc +++ b/src/plugins/find/find.qrc @@ -1,7 +1,16 @@ - - texts/search-find_16px.svg - actions/find_matchComplete_16px.svg - icons/find_noResults_96px.png + + + icons/deepin/builtin/icons/find_noResults_96px.png + icons/deepin/builtin/texts/match_case_16px.svg + icons/deepin/builtin/texts/refresh_16px.svg + icons/deepin/builtin/texts/delete_16px.svg + icons/deepin/builtin/texts/option_16px.svg + icons/deepin/builtin/texts/regex_16px.svg + icons/deepin/builtin/texts/replace_16px.svg + icons/deepin/builtin/texts/replace_all_16px.svg + icons/deepin/builtin/texts/search_22px.svg + icons/deepin/builtin/texts/stop_search_16px.svg + icons/deepin/builtin/texts/whole_word_16px.svg diff --git a/src/plugins/find/findplugin.cpp b/src/plugins/find/findplugin.cpp index 94bccae9b..225edbf4b 100644 --- a/src/plugins/find/findplugin.cpp +++ b/src/plugins/find/findplugin.cpp @@ -5,8 +5,9 @@ #include "findplugin.h" #include "services/window/windowservice.h" -#include "findtoolwindow.h" -#include "searchresultwindow.h" +#include "services/editor/editorservice.h" +#include "gui/advancedsearchwidget.h" + #include "common/common.h" #include "base/abstractmenu.h" #include "base/abstractwidget.h" @@ -32,30 +33,32 @@ bool FindPlugin::start() abort(); } - DMenu *editMenu = new DMenu(QMenu::tr("&Edit")); - AbstractMenu *menuImpl = new AbstractMenu(editMenu); + registerShortcut(); + registerToSidebar(); + + return true; +} +void FindPlugin::switchToSearch() +{ + windowService->raiseMode(CM_EDIT); + windowService->showWidgetAtPosition(MWNA_ADVANCEDSEARCH, Position::Left, true); +} + +void FindPlugin::registerShortcut() +{ QAction *advancedFindAction = new QAction(this); auto advancedFindActionImpl = new AbstractAction(advancedFindAction); advancedFindActionImpl->setShortCutInfo("Edit.Advanced.Find", tr("Advanced Find"), QKeySequence(Qt::Modifier::CTRL | Qt::Modifier::SHIFT | Qt::Key_F)); - connect(advancedFindAction, &QAction::triggered, [=] { - uiController.switchContext(tr("Advanced &Search")); + connect(advancedFindAction, &QAction::triggered, qApp, [=] { + auto editSrv = dpfGetService(EditorService); + const auto &selectedText = editSrv->getSelectedText(); + if (!selectedText.isEmpty()) + advSearchWidget->setSearchText(selectedText); + windowService->switchWidgetNavigation(MWNA_ADVANCEDSEARCH); }); - - menuImpl->addAction(advancedFindActionImpl); - - windowService->addChildMenu(menuImpl); - - AbstractWidget *widgetImpl = new AbstractWidget(new FindToolWindow()); - windowService->addContextWidget(tr("Advanced &Search"), widgetImpl, true); - - return true; -} - -void FindPlugin::sendSwitchSearchResult() -{ - uiController.switchContext(tr("Advanced &Search")); + windowService->addAction("&Edit", advancedFindActionImpl); } dpf::Plugin::ShutdownFlag FindPlugin::stop() @@ -63,3 +66,19 @@ dpf::Plugin::ShutdownFlag FindPlugin::stop() qInfo() << __FUNCTION__; return Sync; } + +void FindPlugin::registerToSidebar() +{ + QAction *action = new QAction(MWNA_ADVANCEDSEARCH, this); + action->setIcon(QIcon::fromTheme("search")); + auto actionImpl = new AbstractAction(action); + windowService->addNavigationItem(actionImpl, Priority::highest); + + advSearchWidget = new AdvancedSearchWidget; + windowService->registerWidget(MWNA_ADVANCEDSEARCH, new AbstractWidget(advSearchWidget)); + windowService->setDockHeaderName(MWNA_ADVANCEDSEARCH, tr("ADVANCED SEARCH")); + windowService->bindWidgetToNavigation(MWNA_ADVANCEDSEARCH, actionImpl); + advSearchWidget->initOperator(); + + connect(action, &QAction::triggered, this, &FindPlugin::switchToSearch, Qt::DirectConnection); +} diff --git a/src/plugins/find/findplugin.h b/src/plugins/find/findplugin.h index 42d369657..4e7d00197 100644 --- a/src/plugins/find/findplugin.h +++ b/src/plugins/find/findplugin.h @@ -8,9 +8,9 @@ #include namespace dpfservice { - class WindowService; +class WindowService; } - +class AdvancedSearchWidget; class FindPlugin : public dpf::Plugin { Q_OBJECT @@ -20,13 +20,13 @@ class FindPlugin : public dpf::Plugin virtual bool start() override; virtual dpf::Plugin::ShutdownFlag stop() override; -signals: - void asyncStopFinished(); - void onFindActionTriggered(); - private: - void sendSwitchSearchResult(); + void switchToSearch(); + void registerShortcut(); + void registerToSidebar(); + + AdvancedSearchWidget *advSearchWidget = nullptr; dpfservice::WindowService *windowService = nullptr; }; -#endif // FINDPLUGIN_H +#endif // FINDPLUGIN_H diff --git a/src/plugins/find/findplugin.json b/src/plugins/find/findplugin.json index 5c942535b..42933aec0 100644 --- a/src/plugins/find/findplugin.json +++ b/src/plugins/find/findplugin.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Core Plugins", "Description" : "The find plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document2?dirid=656d40a9bd766615b0b02e5e", + "UrlLink" : "https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e", "Depends" : [ {"Name" : "codeeditor"}, {"Name" : "debugger"} diff --git a/src/plugins/find/findtoolwindow.cpp b/src/plugins/find/findtoolwindow.cpp deleted file mode 100644 index 06ceb9ad5..000000000 --- a/src/plugins/find/findtoolwindow.cpp +++ /dev/null @@ -1,431 +0,0 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "findtoolwindow.h" -#include "searchresultwindow.h" -#include "transceiver/findreceiver.h" -#include "util/searcreplacehworker.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -DWIDGET_USE_NAMESPACE -class FindToolWindowPrivate -{ - FindToolWindowPrivate() {} - ~FindToolWindowPrivate(); - - DStackedWidget *stackedWidget { nullptr }; - SearchResultWindow *searchResultWindow { nullptr }; - QSet allProjectsPathList { nullptr }; - QString currentProjectPath; - QString currentFilePath; - QMap projectInfoMap; - QSharedPointer searchReplaceWorker { nullptr }; - QThread thread; - - DComboBox *scopeComboBox { nullptr }; - DLineEdit *searchLineEdit { nullptr }; - DCheckBox *senseCheckBox { nullptr }; - DCheckBox *wholeWordsCheckBox { nullptr }; - DCheckBox *regularCheckBox { nullptr }; - DLineEdit *patternLineEdit { nullptr }; - DLineEdit *expatternLineEdit { nullptr }; - DSuggestButton *senseCheckBtnOn { nullptr }; - DPushButton *senseCheckBtnOff { nullptr }; - DSuggestButton *wholeWordsCheckBtnOn { nullptr }; - DPushButton *wholeWordsCheckBtnOff { nullptr }; - - bool senseCheckBtnFlag = false; - bool wholeWordsCheckBtnFlag = false; - friend class FindToolWindow; -}; - -FindToolWindowPrivate::~FindToolWindowPrivate() -{ - searchReplaceWorker->stop(); - thread.quit(); - thread.wait(); -} - -FindToolWindow::FindToolWindow(QWidget *parent) - : QWidget(parent), - d(new FindToolWindowPrivate()) -{ - setupUi(); - initWorker(); - connect(FindEventTransmit::instance(), QOverload::of(&FindEventTransmit::sendProjectPath), - [=](const QString &projectPath, const QString &language) { - d->currentProjectPath = projectPath; - d->projectInfoMap.insert(projectPath, language); - d->allProjectsPathList.insert(projectPath); - }); - - connect(FindEventTransmit::instance(), QOverload::of(&FindEventTransmit::sendRemovedProject), - [=](const QString &projectPath) { - d->currentProjectPath = ""; - d->allProjectsPathList.remove(projectPath); - d->projectInfoMap.remove(projectPath); - }); - - connect(FindEventTransmit::instance(), QOverload::of(&FindEventTransmit::sendCurrentEditFile), - [=](const QString &filePath, bool actived) { - if (actived) { - d->currentFilePath = filePath; - } else - d->currentFilePath = ""; - }); -} - -FindToolWindow::~FindToolWindow() -{ - delete d; -} - -void FindToolWindow::setupUi() -{ - DFrame *mainPaneFrame = new DFrame(this); - DStyle::setFrameRadius(mainPaneFrame, 0); - d->stackedWidget = new DStackedWidget(); - QHBoxLayout *hLayout = new QHBoxLayout(); - hLayout->setContentsMargins(0, 0, 0, 0); - - DScrollArea *scrollArea = new DScrollArea(mainPaneFrame); - scrollArea->setWidgetResizable(true); - scrollArea->setWidget(d->stackedWidget); - scrollArea->setLineWidth(0); - - mainPaneFrame->setLineWidth(0); - hLayout->addWidget(mainPaneFrame); - QHBoxLayout *scrollLayout = new QHBoxLayout(); - scrollLayout->addWidget(scrollArea); - mainPaneFrame->setLayout(scrollLayout); - - QWidget *searchParamWidget = new QWidget(); - QWidget *searchResultWidget = new QWidget(); - - addSearchParamWidget(searchParamWidget); - addSearchResultWidget(searchResultWidget); - - d->stackedWidget->addWidget(searchParamWidget); - d->stackedWidget->addWidget(searchResultWidget); - d->stackedWidget->setCurrentIndex(0); - - setLayout(hLayout); -} - -void FindToolWindow::initWorker() -{ - d->searchReplaceWorker.reset(new SearchReplaceWorker); - connect(d->searchReplaceWorker.data(), &SearchReplaceWorker::matched, this, &FindToolWindow::handleSearchMatched, Qt::QueuedConnection); - connect(d->searchReplaceWorker.data(), &SearchReplaceWorker::searchFinished, this, &FindToolWindow::handleSearchFinished, Qt::QueuedConnection); - connect(d->searchReplaceWorker.data(), &SearchReplaceWorker::replaceFinished, this, &FindToolWindow::handleReplaceFinished, Qt::QueuedConnection); - - d->searchReplaceWorker->moveToThread(&d->thread); - d->thread.start(); -} - -void FindToolWindow::addSearchParamWidget(QWidget *parentWidget) -{ - QFormLayout *formLayout = new QFormLayout(); - parentWidget->setLayout(formLayout); - - DLabel *scopeLabel = new DLabel(QLabel::tr("Scope:")); - d->scopeComboBox = new DComboBox(parentWidget); - d->scopeComboBox->addItem(tr("All Projects")); - d->scopeComboBox->addItem(tr("Current Project")); - d->scopeComboBox->addItem(tr("Current File")); - d->scopeComboBox->setFixedWidth(369); - - DLabel *searchLabel = new DLabel(QLabel::tr("Search for:")); - QHBoxLayout *hlayout = new QHBoxLayout(); - d->searchLineEdit = new DLineEdit(parentWidget); - d->searchLineEdit->setFixedWidth(277); - d->searchLineEdit->setPlaceholderText(tr("thread")); - - d->senseCheckBtnOn = new DSuggestButton(parentWidget); - d->senseCheckBtnOn->setText("Aa"); - d->senseCheckBtnOn->setFixedSize(36, 36); - d->senseCheckBtnOn->hide(); - - d->senseCheckBtnOff = new DPushButton(parentWidget); - d->senseCheckBtnOff->setText("aa"); - d->senseCheckBtnOff->setFixedSize(36, 36); - - d->wholeWordsCheckBtnOn = new DSuggestButton(parentWidget); - d->wholeWordsCheckBtnOn->setIcon(QIcon::fromTheme("find_matchComplete")); - d->wholeWordsCheckBtnOn->setFixedSize(36, 36); - d->wholeWordsCheckBtnOn->hide(); - - d->wholeWordsCheckBtnOff = new DPushButton(parentWidget); - d->wholeWordsCheckBtnOff->setIcon(QIcon::fromTheme("find_matchComplete")); - d->wholeWordsCheckBtnOff->setFixedSize(36, 36); - - hlayout->addWidget(d->searchLineEdit); - hlayout->addWidget(d->senseCheckBtnOn); - hlayout->addWidget(d->senseCheckBtnOff); - hlayout->addWidget(d->wholeWordsCheckBtnOn); - hlayout->addWidget(d->wholeWordsCheckBtnOff); - - DLabel *patternLabel = new DLabel(QLabel::tr("File pattern:"), parentWidget); - d->patternLineEdit = new DLineEdit(parentWidget); - d->patternLineEdit->setPlaceholderText(tr("e.g.*.ts,src/**/include")); - d->patternLineEdit->setFixedWidth(369); - - DLabel *expatternLabel = new DLabel(QLabel::tr("Exclusion pattern:"), parentWidget); - d->expatternLineEdit = new DLineEdit(parentWidget); - d->expatternLineEdit->setPlaceholderText(tr("e.g.*.ts,src/**/include")); - d->expatternLineEdit->setFixedWidth(369); - - QHBoxLayout *btnLayout = new QHBoxLayout(); - DPushButton *searchBtn = new DPushButton(QPushButton::tr("Search"), parentWidget); - searchBtn->setMinimumWidth(120); - searchBtn->setMaximumWidth(120); - searchBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - DPushButton *replaceBtn = new DPushButton(QPushButton::tr("Search && Replace"), parentWidget); - replaceBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - replaceBtn->setMinimumWidth(120); - replaceBtn->setMaximumWidth(120); - - btnLayout->addWidget(searchBtn); - btnLayout->addWidget(replaceBtn); - btnLayout->setContentsMargins(213, 0, 0, 0); - - connect(searchBtn, &QAbstractButton::clicked, this, &FindToolWindow::search); - connect(replaceBtn, &QAbstractButton::clicked, this, &FindToolWindow::replace); - - connect(d->senseCheckBtnOn, &QPushButton::clicked, this, &FindToolWindow::onSenseCheckBtnClicked); - connect(d->wholeWordsCheckBtnOn, &QPushButton::clicked, this, &FindToolWindow::onwholeWordsCheckBtnClicked); - connect(d->senseCheckBtnOff, &QPushButton::clicked, this, &FindToolWindow::onSenseCheckBtnClicked); - connect(d->wholeWordsCheckBtnOff, &QPushButton::clicked, this, &FindToolWindow::onwholeWordsCheckBtnClicked); - - formLayout->setContentsMargins(26, 10, 0, 0); - formLayout->setSpacing(10); - formLayout->addRow(scopeLabel, d->scopeComboBox); - formLayout->addRow(searchLabel, hlayout); - formLayout->addRow(patternLabel, d->patternLineEdit); - formLayout->addRow(expatternLabel, d->expatternLineEdit); - formLayout->addRow(btnLayout); - formLayout->setAlignment(btnLayout, Qt::AlignLeft); -} - -void FindToolWindow::onSenseCheckBtnClicked() -{ - d->senseCheckBtnFlag = !d->senseCheckBtnFlag; - if (d->senseCheckBtnFlag) { - d->senseCheckBtnOff->hide(); - d->senseCheckBtnOn->show(); - } else { - d->senseCheckBtnOn->hide(); - d->senseCheckBtnOff->show(); - } -} - -void FindToolWindow::onwholeWordsCheckBtnClicked() -{ - d->wholeWordsCheckBtnFlag = !d->wholeWordsCheckBtnFlag; - if (d->wholeWordsCheckBtnFlag) { - d->wholeWordsCheckBtnOff->hide(); - d->wholeWordsCheckBtnOn->show(); - } else { - d->wholeWordsCheckBtnOn->hide(); - d->wholeWordsCheckBtnOff->show(); - } -} - -void FindToolWindow::addSearchResultWidget(QWidget *parentWidget) -{ - QVBoxLayout *vLayout = new QVBoxLayout(); - parentWidget->setLayout(vLayout); - - d->searchResultWindow = new SearchResultWindow(); - connect(d->searchResultWindow, &SearchResultWindow::reqBack, this, &FindToolWindow::switchSearchParamWidget); - connect(d->searchResultWindow, &SearchResultWindow::reqReplace, this, &FindToolWindow::handleReplace); - vLayout->addWidget(d->searchResultWindow); -} - -void FindToolWindow::search() -{ - searchText(); - d->searchResultWindow->setRepalceWidgtVisible(false); -} - -void FindToolWindow::createMessageDialog(const QString &message) -{ - DDialog *messageDialog = new DDialog(this); - messageDialog->setIcon(QIcon::fromTheme("dialog-warning")); - messageDialog->setMessage(message); - messageDialog->insertButton(0, tr("Ok")); - messageDialog->setAttribute(Qt::WA_DeleteOnClose); - connect(messageDialog, &DDialog::buttonClicked, [=](int index) { - if (index == 0) { - messageDialog->reject(); - } - }); - - messageDialog->exec(); -} - -bool FindToolWindow::checkSelectedScopeValid() -{ - int index = d->scopeComboBox->currentIndex(); - switch (index) { - case 0: { - if (d->allProjectsPathList.isEmpty()) { - createMessageDialog(tr("All projects path is empty, please import!")); - return false; - } - break; - } - case 1: { - if (d->currentProjectPath.isEmpty()) { - createMessageDialog(tr("Current project path is empty, please import!")); - return false; - } - break; - } - case 2: { - if (d->currentFilePath.isEmpty()) { - createMessageDialog(tr("Current project path is empty, please import!")); - return false; - } - break; - } - default: { - createMessageDialog(tr("Scope is not selected, please select!")); - return false; - } - } - - return true; -} - -bool FindToolWindow::getSearchParams(SearchParams *searchParams) -{ - if (!checkSelectedScopeValid()) - return false; - QString text = d->searchLineEdit->text(); - if (text.isEmpty()) { - d->searchLineEdit->showAlertMessage(tr("Search for text is empty, please input!")); - return false; - } - - QStringList searchPathList; - int index = d->scopeComboBox->currentIndex(); - switch (index) { - case 0: - searchPathList = d->allProjectsPathList.values(); - break; - case 1: - searchPathList = QStringList { d->currentProjectPath }; - break; - case 2: - searchPathList = QStringList { d->currentFilePath }; - break; - default: - break; - } - - searchParams->filePathList = searchPathList; - searchParams->searchText = text; - searchParams->sensitiveFlag = d->senseCheckBtnFlag; - searchParams->wholeWordsFlag = d->wholeWordsCheckBtnFlag; - searchParams->patternsList = d->patternLineEdit->text().trimmed().split(",", QString::SkipEmptyParts); - searchParams->exPatternsList = d->expatternLineEdit->text().trimmed().split(",", QString::SkipEmptyParts); - searchParams->exPatternsList << "*.so" << "*.o"; - searchParams->projectInfoMap = d->projectInfoMap; - - return true; -} - -void FindToolWindow::searchText() -{ - SearchParams params; - if (!getSearchParams(¶ms)) { - return; - } - - d->searchResultWindow->showMsg(true, tr("Searching, please wait...")); - d->searchResultWindow->clear(); - metaObject()->invokeMethod(d->searchReplaceWorker.data(), - "addSearchTask", - Qt::QueuedConnection, - Q_ARG(SearchParams, params)); - d->stackedWidget->setCurrentIndex(1); -} - -void FindToolWindow::replace() -{ - if (!checkSelectedScopeValid()) - return; - searchText(); - d->searchResultWindow->setRepalceWidgtVisible(true); -} - -void FindToolWindow::switchSearchParamWidget() -{ - d->searchReplaceWorker->stop(); - d->stackedWidget->setCurrentIndex(0); -} - -void FindToolWindow::handleSearchMatched() -{ - const auto &results = d->searchReplaceWorker->getResults(); - if (results.isEmpty()) - return; - - d->searchResultWindow->appendResults(results, d->projectInfoMap); -} - -void FindToolWindow::handleSearchFinished() -{ - d->searchResultWindow->searchFinished(); -} - -void FindToolWindow::handleReplace(const QString &text) -{ - ReplaceParams params; - params.replaceText = text; - params.searchText = d->searchLineEdit->text(); - int index = d->scopeComboBox->currentIndex(); - switch (index) { - case 0: - params.filePathList = d->allProjectsPathList.values(); - break; - case 1: - params.filePathList = QStringList { d->currentProjectPath }; - break; - case 2: - params.filePathList = QStringList { d->currentFilePath }; - break; - default: - break; - } - metaObject()->invokeMethod(d->searchReplaceWorker.data(), - "addReplaceTask", - Qt::QueuedConnection, - Q_ARG(ReplaceParams, params)); -} - -void FindToolWindow::handleReplaceFinished(int result) -{ - d->searchResultWindow->replaceFinished(result == 0); -} diff --git a/src/plugins/find/findtoolwindow.h b/src/plugins/find/findtoolwindow.h deleted file mode 100644 index 90bc2db53..000000000 --- a/src/plugins/find/findtoolwindow.h +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef FINDTOOLWINDOW_H -#define FINDTOOLWINDOW_H - -#include "searchresultwindow.h" - -#include - -class FindToolWindowPrivate; -class FindToolWindow : public QWidget -{ - Q_OBJECT -public: - explicit FindToolWindow(QWidget *parent = nullptr); - ~FindToolWindow() override; - -signals: - -private: - void setupUi(); - void initWorker(); - void search(); - void searchText(); - void replace(); - void addSearchParamWidget(QWidget *parentWidget); - void addSearchResultWidget(QWidget *parentWidget); - void switchSearchParamWidget(); - bool checkSelectedScopeValid(); - bool getSearchParams(SearchParams *searchParams); - void createMessageDialog(const QString &message); - - FindToolWindowPrivate *const d; - -private slots: - void onSenseCheckBtnClicked(); - void onwholeWordsCheckBtnClicked(); - - void handleSearchMatched(); - void handleSearchFinished(); - void handleReplace(const QString &text); - void handleReplaceFinished(int result); -}; - -#endif // FINDTOOLWINDOW_H diff --git a/src/plugins/find/gui/advancedsearchwidget.cpp b/src/plugins/find/gui/advancedsearchwidget.cpp new file mode 100644 index 000000000..ea0e15a8f --- /dev/null +++ b/src/plugins/find/gui/advancedsearchwidget.cpp @@ -0,0 +1,506 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "advancedsearchwidget.h" +#include "searchresultwidget.h" +#include "maincontroller/maincontroller.h" + +#include "services/window/windowservice.h" +#include "services/project/projectservice.h" +#include "services/editor/editorservice.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace dpfservice; +DWIDGET_USE_NAMESPACE + +class AdvancedSearchWidgetPrivate : public QObject +{ +public: + explicit AdvancedSearchWidgetPrivate(AdvancedSearchWidget *qq); + + void initUI(); + void initConnection(); + void initOperator(); + + DToolButton *registerOperator(const QIcon &icon, const QString &description, std::function handler); + QWidget *createSearchParamWidget(); + QWidget *createOptionWidget(); + DToolButton *createOptionButton(const QIcon &icon, const QString &description); + + SearchParams searchParams(); + ReplaceParams replaceParams(const QMap &resultMap); + bool checkSearchParamsVaild(const SearchParams ¶ms); + int showMessage(const QString &msg); + void toggleSearchState(bool searching); + void updateReplaceInfo(); + + void refresh(); + void search(); + void replace(const QMap &resultMap); + void stop(); + void clear(); + + void handleSearchMatched(); + void handleSearchFinished(); + void handleReplaceAll(); + void handleReplaceFinished(); + +public: + AdvancedSearchWidget *q; + + // header + DToolButton *refreshBtn { nullptr }; + DToolButton *stopSearchBtn { nullptr }; + DToolButton *expandBtn { nullptr }; + DToolButton *collapseBtn { nullptr }; + DToolButton *clearBtn { nullptr }; + DSpinner *searchSpinner { nullptr }; + + // top + DLineEdit *searchEdit { nullptr }; + DLineEdit *replaceEdit { nullptr }; + DPushButton *replaceAllBtn { nullptr }; + DComboBox *searchScopeCB { nullptr }; + DLineEdit *includeEdit { nullptr }; + DLineEdit *excludeEdit { nullptr }; + QWidget *optionWidget { nullptr }; + + DToolButton *caseBtn { nullptr }; + DToolButton *wholeWordBtn { nullptr }; + DToolButton *regexBtn { nullptr }; + DToolButton *optionBtn { nullptr }; + + // result + SearchResultWidget *resultWidget { nullptr }; + + // util + WindowService *winSrv { nullptr }; + MainController *controller { nullptr }; + QTimer startTimer; +}; + +AdvancedSearchWidgetPrivate::AdvancedSearchWidgetPrivate(AdvancedSearchWidget *qq) + : q(qq), + controller(new MainController(q)) +{ + startTimer.setSingleShot(true); + startTimer.setInterval(500); +} + +void AdvancedSearchWidgetPrivate::initUI() +{ + q->setAutoFillBackground(true); + q->setBackgroundRole(QPalette::Base); + + QVBoxLayout *mainLayout = new QVBoxLayout(q); + mainLayout->setContentsMargins(10, 5, 10, 0); + + auto widget = createSearchParamWidget(); + resultWidget = new SearchResultWidget(q); + + mainLayout->addWidget(widget); + mainLayout->addSpacing(20); + mainLayout->addWidget(resultWidget, 1); +} + +void AdvancedSearchWidgetPrivate::initConnection() +{ + connect(&startTimer, &QTimer::timeout, this, &AdvancedSearchWidgetPrivate::search); + connect(searchEdit, &DLineEdit::textChanged, this, &AdvancedSearchWidgetPrivate::refresh); + connect(includeEdit, &DLineEdit::textChanged, this, &AdvancedSearchWidgetPrivate::refresh); + connect(excludeEdit, &DLineEdit::textChanged, this, &AdvancedSearchWidgetPrivate::refresh); + connect(searchScopeCB, &DComboBox::currentTextChanged, this, &AdvancedSearchWidgetPrivate::refresh); + connect(refreshBtn, &DToolButton::clicked, this, &AdvancedSearchWidgetPrivate::refresh); + connect(caseBtn, &DToolButton::clicked, this, &AdvancedSearchWidgetPrivate::refresh); + connect(wholeWordBtn, &DToolButton::clicked, this, &AdvancedSearchWidgetPrivate::refresh); + connect(optionBtn, &DToolButton::clicked, this, [this] { optionWidget->setVisible(optionBtn->isChecked()); }); + connect(replaceAllBtn, &DToolButton::clicked, this, &AdvancedSearchWidgetPrivate::handleReplaceAll); + connect(replaceEdit, &DLineEdit::textEdited, this, &AdvancedSearchWidgetPrivate::updateReplaceInfo); + connect(regexBtn, &DToolButton::clicked, this, [this] { + refresh(); + updateReplaceInfo(); + }); + + connect(stopSearchBtn, &DToolButton::clicked, this, &AdvancedSearchWidgetPrivate::stop); + connect(controller, &MainController::matched, this, &AdvancedSearchWidgetPrivate::handleSearchMatched); + connect(controller, &MainController::searchFinished, this, &AdvancedSearchWidgetPrivate::handleSearchFinished); + connect(controller, &MainController::replaceFinished, this, &AdvancedSearchWidgetPrivate::handleReplaceFinished); + connect(resultWidget, &SearchResultWidget::requestReplace, this, &AdvancedSearchWidgetPrivate::replace); + connect(resultWidget, &SearchResultWidget::resultCountChanged, this, + [this] { + // Not updated in search + if (!searchSpinner->isPlaying()) + replaceAllBtn->setEnabled(!resultWidget->isEmpty()); + }); +} + +void AdvancedSearchWidgetPrivate::initOperator() +{ + collapseBtn = registerOperator(QIcon::fromTheme("collapse_all"), + AdvancedSearchWidget::tr("Collapse All"), + std::bind(&SearchResultWidget::collapseAll, resultWidget)); + expandBtn = registerOperator(QIcon::fromTheme("expand_all"), + AdvancedSearchWidget::tr("Expand All"), + std::bind(&SearchResultWidget::expandAll, resultWidget)); + clearBtn = registerOperator(QIcon::fromTheme("delete"), + AdvancedSearchWidget::tr("Clear Search Results"), + std::bind(&AdvancedSearchWidgetPrivate::clear, this)); + refreshBtn = registerOperator(QIcon::fromTheme("refresh"), + AdvancedSearchWidget::tr("Refresh"), + std::bind(&AdvancedSearchWidgetPrivate::refresh, this)); + stopSearchBtn = registerOperator(QIcon::fromTheme("stop_search"), + AdvancedSearchWidget::tr("Stop Search"), + std::bind(&AdvancedSearchWidgetPrivate::stop, this)); + stopSearchBtn->hide(); + + searchSpinner = new DSpinner(q); + winSrv->registerWidgetToDockHeader(MWNA_ADVANCEDSEARCH, searchSpinner); + searchSpinner->hide(); +} + +DToolButton *AdvancedSearchWidgetPrivate::registerOperator(const QIcon &icon, const QString &description, std::function handler) +{ + if (!winSrv) + winSrv = dpfGetService(WindowService); + + DToolButton *btn = new DToolButton(q); + btn->setIcon(icon); + btn->setIconSize({ 16, 16 }); + btn->setToolTip(description); + connect(btn, &DToolButton::clicked, this, handler); + + winSrv->registerWidgetToDockHeader(MWNA_ADVANCEDSEARCH, btn); + return btn; +} + +QWidget *AdvancedSearchWidgetPrivate::createSearchParamWidget() +{ + QWidget *widget = new QWidget(q); + QVBoxLayout *layout = new QVBoxLayout(widget); + layout->setContentsMargins(0, 0, 0, 0); + + searchEdit = new DLineEdit(q); + searchEdit->setPlaceholderText(AdvancedSearchWidget::tr("Search")); + caseBtn = createOptionButton(QIcon::fromTheme("match_case"), AdvancedSearchWidget::tr("Match Case")); + wholeWordBtn = createOptionButton(QIcon::fromTheme("whole_word"), AdvancedSearchWidget::tr("Match Whole Word")); + regexBtn = createOptionButton(QIcon::fromTheme("regex"), AdvancedSearchWidget::tr("Use Regular Expression")); + + replaceEdit = new DLineEdit(q); + replaceEdit->setPlaceholderText(AdvancedSearchWidget::tr("Replace")); + replaceAllBtn = new DPushButton(AdvancedSearchWidget::tr("Replace All"), q); + + QGridLayout *gridLayout = new QGridLayout; + gridLayout->setColumnStretch(0, 1); + gridLayout->setContentsMargins(0, 0, 0, 0); + gridLayout->addWidget(searchEdit, 0, 0); + gridLayout->addWidget(caseBtn, 0, 1); + gridLayout->addWidget(wholeWordBtn, 0, 2); + gridLayout->addWidget(regexBtn, 0, 3); + gridLayout->addWidget(replaceEdit, 1, 0); + gridLayout->addWidget(replaceAllBtn, 1, 1, 1, 3); + + searchScopeCB = new DComboBox(q); + searchScopeCB->addItem(AdvancedSearchWidget::tr("All Projects"), AllProjects); + searchScopeCB->addItem(AdvancedSearchWidget::tr("Current Project"), CurrentProject); + searchScopeCB->addItem(AdvancedSearchWidget::tr("Current File"), CurrentFile); + optionBtn = createOptionButton(QIcon::fromTheme("option"), AdvancedSearchWidget::tr("Toggle Search Details")); + + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->setContentsMargins(0, 0, 0, 0); + hLayout->addWidget(searchScopeCB); + hLayout->addWidget(optionBtn); + + optionWidget = createOptionWidget(); + optionWidget->setVisible(false); + + layout->addLayout(gridLayout); + layout->addWidget(new DLabel(AdvancedSearchWidget::tr("Scope:"))); + layout->addLayout(hLayout); + layout->addWidget(optionWidget); + + return widget; +} + +QWidget *AdvancedSearchWidgetPrivate::createOptionWidget() +{ + QWidget *widget = new QWidget(q); + QVBoxLayout *layout = new QVBoxLayout(widget); + layout->setContentsMargins(0, 0, 0, 0); + + includeEdit = new DLineEdit(q); + includeEdit->setPlaceholderText(AdvancedSearchWidget::tr("e.g.*.ts,src/**/include")); + + excludeEdit = new DLineEdit(q); + excludeEdit->setPlaceholderText(AdvancedSearchWidget::tr("e.g.*.ts,src/**/include")); + + layout->addWidget(new DLabel(AdvancedSearchWidget::tr("Files To Include:"))); + layout->addWidget(includeEdit); + layout->addWidget(new DLabel(AdvancedSearchWidget::tr("Files To Exclude:"))); + layout->addWidget(excludeEdit); + + return widget; +} + +DToolButton *AdvancedSearchWidgetPrivate::createOptionButton(const QIcon &icon, const QString &description) +{ + DToolButton *btn = new DToolButton(q); + btn->setCheckable(true); + btn->setIcon(icon); + btn->setToolTip(description); + + return btn; +} + +SearchParams AdvancedSearchWidgetPrivate::searchParams() +{ + SearchParams params; + params.keyword = searchEdit->text(); + + auto projectSrv = dpfGetService(ProjectService); + auto editSrv = dpfGetService(EditorService); + int scope = searchScopeCB->currentData().toInt(); + switch (scope) { + case AllProjects: { + params.editFileList = editSrv->openedFiles(); + + const auto &infoList = projectSrv->getAllProjectInfo(); + for (const auto &info : infoList) { + params.projectFileList.append(info.sourceFiles().toList()); + } + } break; + case CurrentProject: { + params.editFileList = editSrv->openedFiles(); + + const auto &info = projectSrv->getActiveProjectInfo(); + params.projectFileList.append(info.sourceFiles().toList()); + } break; + case CurrentFile: { + auto curFile = editSrv->currentFile(); + if (!curFile.isEmpty()) + params.editFileList << curFile; + } break; + default: + break; + } + + params.scope = static_cast(scope); + params.flags |= caseBtn->isChecked() ? SearchCaseSensitively : SearchNoFlag; + params.flags |= wholeWordBtn->isChecked() ? SearchWholeWords : SearchNoFlag; + params.flags |= regexBtn->isChecked() ? SearchRegularExpression : SearchNoFlag; + params.includeList = includeEdit->text().trimmed().split(",", QString::SkipEmptyParts); + params.excludeList = excludeEdit->text().trimmed().split(",", QString::SkipEmptyParts); + + return params; +} + +ReplaceParams AdvancedSearchWidgetPrivate::replaceParams(const QMap &resultMap) +{ + auto editSrv = dpfGetService(EditorService); + + ReplaceParams params; + params.editFileList = editSrv->openedFiles(); + params.resultMap = resultMap; + params.replaceText = replaceEdit->text(); + params.flags |= caseBtn->isChecked() ? SearchCaseSensitively : SearchNoFlag; + params.flags |= wholeWordBtn->isChecked() ? SearchWholeWords : SearchNoFlag; + params.flags |= regexBtn->isChecked() ? SearchRegularExpression : SearchNoFlag; + + return params; +} + +bool AdvancedSearchWidgetPrivate::checkSearchParamsVaild(const SearchParams ¶ms) +{ + if (params.keyword.isEmpty()) + return false; + + switch (params.scope) { + case AllProjects: + if (params.projectFileList.isEmpty()) { + resultWidget->showMessage(AdvancedSearchWidget::tr("All projects path is empty, please import!"), Warning); + return false; + } + break; + case CurrentProject: + if (params.projectFileList.isEmpty()) { + resultWidget->showMessage(AdvancedSearchWidget::tr("Current projects path is empty, please import!"), Warning); + return false; + } + break; + case CurrentFile: + if (params.editFileList.isEmpty()) { + resultWidget->showMessage(AdvancedSearchWidget::tr("No files are currently open, please open!"), Warning); + return false; + } + break; + default: + break; + } + + return true; +} + +int AdvancedSearchWidgetPrivate::showMessage(const QString &msg) +{ + DDialog dlg; + dlg.setMessage(msg); + dlg.setWindowTitle(AdvancedSearchWidget::tr("Advance Search")); + dlg.setIcon(QIcon::fromTheme("dialog-warning")); + dlg.setWordWrapMessage(true); + dlg.addButton(AdvancedSearchWidget::tr("Cancel", "button")); + dlg.addButton(AdvancedSearchWidget::tr("Continue", "button"), true, DDialog::ButtonWarning); + return dlg.exec(); +} + +void AdvancedSearchWidgetPrivate::toggleSearchState(bool searching) +{ + searchSpinner->setVisible(searching); + refreshBtn->setVisible(!searching); + stopSearchBtn->setVisible(searching); + replaceAllBtn->setEnabled(!searching && !resultWidget->isEmpty()); + if (searching) { + searchSpinner->start(); + } else { + searchSpinner->stop(); + } +} + +void AdvancedSearchWidgetPrivate::updateReplaceInfo() +{ + resultWidget->setReplaceText(replaceEdit->text(), regexBtn->isChecked()); +} + +void AdvancedSearchWidgetPrivate::refresh() +{ + stop(); + resultWidget->clear(); + startTimer.start(); +} + +void AdvancedSearchWidgetPrivate::search() +{ + const auto ¶ms = searchParams(); + if (!checkSearchParamsVaild(params)) + return; + + toggleSearchState(true); + controller->search(params); +} + +void AdvancedSearchWidgetPrivate::replace(const QMap &resultMap) +{ + if (searchSpinner->isPlaying()) + return; + + searchSpinner->setVisible(true); + searchSpinner->start(); + const auto ¶ms = replaceParams(resultMap); + controller->replace(params); +} + +void AdvancedSearchWidgetPrivate::stop() +{ + toggleSearchState(false); + controller->stop(); +} + +void AdvancedSearchWidgetPrivate::clear() +{ + searchEdit->clear(); + replaceEdit->clear(); + resultWidget->clear(); +} + +void AdvancedSearchWidgetPrivate::handleSearchMatched() +{ + const auto &result = controller->takeAll(); + resultWidget->appendResults(result); +} + +void AdvancedSearchWidgetPrivate::handleSearchFinished() +{ + toggleSearchState(false); + if (resultWidget->isEmpty()) + resultWidget->showMessage(AdvancedSearchWidget::tr("No results found.")); +} + +void AdvancedSearchWidgetPrivate::handleReplaceAll() +{ + const auto &resultMap = resultWidget->allResult(); + int totalResults = 0; + for (const auto &result : resultMap) + totalResults += result.count(); + + QString replaceText = replaceEdit->text(); + const int totalFiles = resultMap.count(); + QString msg; + if (totalFiles == 1 && totalResults == 1) { + msg = replaceText.isEmpty() + ? AdvancedSearchWidget::tr("Replace 1 occurence across 1 file?") + : AdvancedSearchWidget::tr("Replace 1 occurence across 1 file with %1?").arg(replaceText); + } else if (totalFiles == 1) { + msg = replaceText.isEmpty() + ? AdvancedSearchWidget::tr("Replace %1 occurences across 1 file?").arg(totalResults) + : AdvancedSearchWidget::tr("Replace %1 occurences across 1 file with %2?").arg(totalResults).arg(replaceText); + } else { + msg = replaceText.isEmpty() + ? AdvancedSearchWidget::tr("Replace %1 occurences across %2 files?").arg(totalResults).arg(totalFiles) + : AdvancedSearchWidget::tr("Replace %1 occurences across %2 files with %3?").arg(totalResults).arg(totalFiles).arg(replaceText); + } + // 0: cancel 1:contionue + int code = showMessage(msg); + if (1 == code) + replace(resultMap); +} + +void AdvancedSearchWidgetPrivate::handleReplaceFinished() +{ + searchSpinner->setVisible(false); + searchSpinner->stop(); +} + +AdvancedSearchWidget::AdvancedSearchWidget(QWidget *parent) + : QWidget(parent), + d(new AdvancedSearchWidgetPrivate(this)) +{ + d->initUI(); + d->initConnection(); +} + +AdvancedSearchWidget::~AdvancedSearchWidget() +{ + delete d; +} + +void AdvancedSearchWidget::initOperator() +{ + d->initOperator(); +} + +QString AdvancedSearchWidget::searchText() const +{ + return d->searchEdit->text(); +} + +void AdvancedSearchWidget::setSearchText(const QString &text) +{ + d->searchEdit->setText(text); +} + +void AdvancedSearchWidget::showEvent(QShowEvent *e) +{ + d->searchSpinner->setFixedSize(16, 16); + d->searchEdit->setFocus(); + d->searchEdit->lineEdit()->selectAll(); + QWidget::showEvent(e); +} diff --git a/src/plugins/find/gui/advancedsearchwidget.h b/src/plugins/find/gui/advancedsearchwidget.h new file mode 100644 index 000000000..b608d872a --- /dev/null +++ b/src/plugins/find/gui/advancedsearchwidget.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ADVANCEDSEARCHWIDGET_H +#define ADVANCEDSEARCHWIDGET_H + +#include + +class AdvancedSearchWidgetPrivate; +class AdvancedSearchWidget : public QWidget +{ + Q_OBJECT +public: + explicit AdvancedSearchWidget(QWidget *parent = nullptr); + ~AdvancedSearchWidget(); + + void initOperator(); + QString searchText() const; + void setSearchText(const QString &text); + +protected: + void showEvent(QShowEvent *e) override; + +private: + AdvancedSearchWidgetPrivate *const d; +}; + +#endif // ADVANCEDSEARCHWIDGET_H diff --git a/src/plugins/find/gui/searchresultitemdelegate.cpp b/src/plugins/find/gui/searchresultitemdelegate.cpp new file mode 100644 index 000000000..819acc1f9 --- /dev/null +++ b/src/plugins/find/gui/searchresultitemdelegate.cpp @@ -0,0 +1,508 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "constants.h" +#include "searchresultitemdelegate.h" +#include "searchresultmodel.h" + +#include +#include +#ifdef DTKWIDGET_CLASS_DPaletteHelper +# include +#endif + +#include +#include +#include +#include + +DWIDGET_USE_NAMESPACE + +inline constexpr int Radius { 8 }; +inline constexpr int Padding = { 6 }; +inline constexpr int ItemMargin { 4 }; +inline constexpr int ExpandArrowWidth { 20 }; +inline constexpr int ExpandArrowHeight { 20 }; +inline constexpr int ArrowAndIconDistance { 8 }; +inline constexpr int SpacePadding { 8 }; +inline constexpr int LineNumberWidth { 40 }; +inline constexpr int OptionButtonSize { 20 }; +inline constexpr int CountNumberSize { 20 }; + +SearchResultItemDelegate::SearchResultItemDelegate(QAbstractItemView *parent) + : DStyledItemDelegate(parent) +{ +} + +void SearchResultItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + if (!index.isValid()) + return DStyledItemDelegate::paint(painter, option, index); + + QStyleOptionViewItem opt = option; + DStyledItemDelegate::initStyleOption(&opt, index); + painter->setRenderHint(QPainter::Antialiasing); + + drawBackground(painter, opt); + if (!index.parent().isValid()) { + const auto &iconRect = drawFileIcon(painter, opt, index); + drawNameItem(painter, opt, index, iconRect); + } else { + drawContextItem(painter, option, index); + } +} + +QSize SearchResultItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + return { 1000, 24 }; +} + +bool SearchResultItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) +{ + if (event->type() == QEvent::ToolTip) { + QString toolTip; + QRect replaceRect = replaceButtonRect(option.rect); + QRect closeRect = closeButtonRect(option.rect); + if (replaceRect.contains(event->pos())) { + toolTip = index.model()->hasChildren(index) ? tr("Replace All") : tr("Replace"); + } else if (closeRect.contains(event->pos())) { + toolTip = tr("Dismiss"); + } + + if (!toolTip.isEmpty()) { + QToolTip::showText(event->globalPos(), toolTip, view); + return true; + } else { + QToolTip::hideText(); + } + } + + return DStyledItemDelegate::helpEvent(event, view, option, index); +} + +bool SearchResultItemDelegate::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress && object == view()->viewport()) { + auto mouseEvent = dynamic_cast(event); + auto index = view()->indexAt(mouseEvent->pos()); + if (!index.isValid()) + return DStyledItemDelegate::eventFilter(object, event); + + auto itemRect = view()->visualRect(index); + auto arrowRect = this->arrowRect(iconRect(itemRect)); + if (arrowRect.contains(mouseEvent->pos())) { + if (view()->isExpanded(index)) { + view()->collapse(index); + } else { + view()->expand(index); + } + return true; + } + + auto replaceRect = replaceButtonRect(itemRect); + if (replaceRect.contains(mouseEvent->pos())) { + auto model = qobject_cast(view()->model()); + if (model) { + Q_EMIT model->requestReplace(index); + return true; + } + } + + auto closeRect = closeButtonRect(itemRect); + if (closeRect.contains(mouseEvent->pos())) { + auto model = qobject_cast(view()->model()); + if (model) { + model->remove(index); + return true; + } + } + } + + return DStyledItemDelegate::eventFilter(object, event); +} + +QTreeView *SearchResultItemDelegate::view() const +{ + return qobject_cast(parent()); +} + +void SearchResultItemDelegate::drawBackground(QPainter *painter, const QStyleOptionViewItem &option) const +{ + painter->save(); + if (option.state.testFlag(QStyle::State_Selected)) { + QColor bgColor = option.palette.color(QPalette::Normal, QPalette::Highlight); + painter->setBrush(bgColor); + painter->setPen(Qt::NoPen); + painter->drawRoundedRect(option.rect, Radius, Radius); + } else if (option.state.testFlag(QStyle::State_MouseOver)) { +#ifdef DTKWIDGET_CLASS_DPaletteHelper + DPalette palette = DPaletteHelper::instance()->palette(option.widget); +#else + DPalette palette = DGuiApplicationHelper::instance()->applicationPalette(); +#endif + painter->setBrush(palette.brush(DPalette::ItemBackground)); + painter->setPen(Qt::NoPen); + painter->drawRoundedRect(option.rect, Radius, Radius); + } + painter->restore(); +} + +QRect SearchResultItemDelegate::drawFileIcon(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + auto iconRect = this->iconRect(option.rect); + drawIcon(painter, option, option.icon, iconRect); + drawExpandArrow(painter, iconRect, option, index); + return iconRect; +} + +QRect SearchResultItemDelegate::drawExpandArrow(QPainter *painter, const QRect &iconRect, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + if (!view()) + return {}; + + QStyleOptionViewItem opt = option; + opt.rect = arrowRect(iconRect).marginsRemoved(QMargins(5, 5, 5, 5)); + + painter->save(); + bool isSelected = (option.state & QStyle::State_Selected) && option.showDecorationSelected; + if (isSelected) { + painter->setPen(option.palette.color(QPalette::Active, QPalette::HighlightedText)); + } else { + painter->setPen(option.palette.color(QPalette::Active, QPalette::Text)); + } + + auto style = option.widget->style(); + if (view()->isExpanded(index)) { + style->drawPrimitive(QStyle::PE_IndicatorArrowDown, &opt, painter, nullptr); + } else { + style->drawPrimitive(QStyle::PE_IndicatorArrowRight, &opt, painter, nullptr); + } + + painter->restore(); + return opt.rect; +} + +QRect SearchResultItemDelegate::drawResultCount(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + painter->setFont(opt.font); + auto resultCount = QString::number(index.model()->rowCount(index)); + auto countWidth = qMax(opt.fontMetrics.width(resultCount) + ItemMargin, CountNumberSize); + + QRect countRect = opt.rect; + countRect.setSize({ countWidth, CountNumberSize }); + countRect.moveLeft(opt.rect.right() - countWidth - ItemMargin); + countRect.moveTop(countRect.top() + ((opt.rect.bottom() - countRect.bottom()) / 2)); + + painter->save(); +#ifdef DTKWIDGET_CLASS_DPaletteHelper + DPalette palette = DPaletteHelper::instance()->palette(option.widget); +#else + DPalette palette = DGuiApplicationHelper::instance()->applicationPalette(); +#endif + painter->setBrush(palette.brush(DPalette::ObviousBackground)); + painter->setPen(Qt::NoPen); + painter->drawRoundedRect(countRect, Radius, Radius); + + bool isSelected = (option.state & QStyle::State_Selected) && option.showDecorationSelected; + if (isSelected) { + painter->setPen(option.palette.color(QPalette::Active, QPalette::HighlightedText)); + } else { + painter->setPen(option.palette.color(QPalette::Active, QPalette::Text)); + } + + QTextOption textOption; + textOption.setTextDirection(option.direction); + textOption.setAlignment(QStyle::visualAlignment(option.direction, Qt::AlignCenter)); + painter->drawText(countRect, resultCount, textOption); + painter->restore(); + return countRect; +} + +QRect SearchResultItemDelegate::drawOptionButton(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + + // draw close button + QRect iconRect = closeButtonRect(opt.rect); + QIcon closeIcon = DStyle::standardIcon(opt.widget->style(), DStyle::SP_CloseButton); + drawIcon(painter, opt, closeIcon, iconRect); + + // draw replace button + QIcon replaceIcon; + if (!index.parent().isValid()) + replaceIcon = QIcon::fromTheme("replace_all"); + else + replaceIcon = QIcon::fromTheme("replace"); + + iconRect = replaceButtonRect(opt.rect); + drawIcon(painter, opt, replaceIcon, iconRect); + + return iconRect; +} + +void SearchResultItemDelegate::drawNameItem(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index, const QRect &iconRect) const +{ + QStyleOptionViewItem opt = option; + painter->setFont(opt.font); + + // draw count + QRect countRect; + QRect optionRect; + if (!opt.state.testFlag(QStyle::State_Selected) && !opt.state.testFlag(QStyle::State_MouseOver)) + countRect = drawResultCount(painter, opt, index); + else + optionRect = drawOptionButton(painter, opt, index); + +#ifdef DTKWIDGET_CLASS_DPaletteHelper + DPalette palette = DPaletteHelper::instance()->palette(option.widget); +#else + DPalette palette = DGuiApplicationHelper::instance()->applicationPalette(); +#endif + QRect nameRect = opt.rect; + nameRect.setLeft(iconRect.right() + Padding); + if (countRect.isValid()) + nameRect.setRight(countRect.left() - Padding); + if (optionRect.isValid()) + nameRect.setRight(optionRect.left() - Padding); + QString fileName = index.data(Qt::DisplayRole).toString(); + const auto &nameFormat = createFormatRange(opt, 0, fileName.length(), + palette.color(QPalette::Normal, DPalette::BrightText), {}); + drawDisplay(painter, option, nameRect, fileName, { nameFormat }); + + QString filePath = index.data(FilePathRole).toString(); + const auto &pathFormat = createFormatRange(opt, 0, filePath.length(), + palette.color(QPalette::Normal, DPalette::TextTips), {}); + + QRect pathRect = nameRect; + pathRect.setLeft(nameRect.left() + opt.fontMetrics.width(fileName) + SpacePadding); + drawDisplay(painter, option, pathRect, filePath, { pathFormat }); +} + +void SearchResultItemDelegate::drawContextItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + painter->setFont(opt.font); + + // draw option button + QRect optionRect; + if (opt.state.testFlag(QStyle::State_Selected) || opt.state.testFlag(QStyle::State_MouseOver)) + optionRect = drawOptionButton(painter, option, index); + + const auto &lineNumber = QString::number(index.data(LineRole).toInt()); + const auto &matchedLength = index.data(MatchedLengthRole).toInt(); + const auto &column = index.data(ColumnRole).toInt(); + auto context = index.data(Qt::DisplayRole).toString(); + const auto &replaceText = index.data(ReplaceTextRole).toString(); + +#ifdef DTKWIDGET_CLASS_DPaletteHelper + DPalette palette = DPaletteHelper::instance()->palette(option.widget); +#else + DPalette palette = DGuiApplicationHelper::instance()->applicationPalette(); +#endif + + // draw line number + const auto &lineNumbFormat = createFormatRange(opt, 0, lineNumber.length(), + palette.color(QPalette::Normal, DPalette::PlaceholderText), {}); + QRect lineNumberRect = opt.rect; + lineNumberRect.setSize({ LineNumberWidth, lineNumberRect.height() }); + opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter; + drawDisplay(painter, opt, lineNumberRect, lineNumber, { lineNumbFormat }); + + // draw context + QList formats; + QRect textRect = opt.rect; + textRect.setLeft(LineNumberWidth + SpacePadding); + if (optionRect.isValid()) + textRect.setRight(optionRect.left() - Padding); + if (!replaceText.isEmpty()) { + int replaceTextOffset = column + matchedLength; + context.insert(replaceTextOffset, replaceText); + QColor matchedBackground; + QColor replaceBackground; + if (DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::LightType) { + matchedBackground.setNamedColor("#F9625C"); + matchedBackground.setAlpha(180); + replaceBackground.setNamedColor("#BCDD75"); + replaceBackground.setAlpha(180); + } else { + matchedBackground.setNamedColor("#DB5C5C"); + matchedBackground.setAlpha(180); + replaceBackground.setNamedColor("#57965C"); + replaceBackground.setAlpha(180); + } + formats << createFormatRange(opt, column, matchedLength, {}, matchedBackground); + formats << createFormatRange(opt, replaceTextOffset, replaceText.length(), {}, replaceBackground); + } else { + QColor background; + if (DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::LightType) { + background.setNamedColor("#F3B517"); + background.setAlpha(180); + } else { + background.setNamedColor("#F2C55C"); + background.setAlpha(220); + } + formats << createFormatRange(opt, column, matchedLength, {}, background); + } + drawDisplay(painter, option, textRect, context, formats); +} + +void SearchResultItemDelegate::drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, + const QString &text, const QList &format) const +{ + if (option.state & QStyle::State_Selected) { + painter->setPen(option.palette.color(QPalette::Normal, QPalette::HighlightedText)); + } else { + painter->setPen(option.palette.color(QPalette::Normal, QPalette::Text)); + } + + if (text.isEmpty()) + return; + + const QStyleOptionViewItem opt = option; + + const QWidget *widget = option.widget; + QStyle *style = widget ? widget->style() : QApplication::style(); + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, widget) + 1; + QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding + const bool wrapText = opt.features & QStyleOptionViewItem::WrapText; + QTextOption textOption; + textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap); + textOption.setTextDirection(option.direction); + textOption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment)); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(option.font); + textLayout.setText(text); + + QSizeF textLayoutSize = doTextLayout(&textLayout, textRect.width()); + + if (textRect.width() < textLayoutSize.width() + || textRect.height() < textLayoutSize.height()) { + QString elided; + int start = 0; + int end = text.indexOf(QChar::LineSeparator, start); + if (end == -1) { + elided += option.fontMetrics.elidedText(text, option.textElideMode, textRect.width()); + } else { + while (end != -1) { + elided += option.fontMetrics.elidedText(text.mid(start, end - start), + option.textElideMode, textRect.width()); + elided += QChar::LineSeparator; + start = end + 1; + end = text.indexOf(QChar::LineSeparator, start); + } + // let's add the last line (after the last QChar::LineSeparator) + elided += option.fontMetrics.elidedText(text.mid(start), + option.textElideMode, textRect.width()); + } + textLayout.setText(elided); + textLayoutSize = doTextLayout(&textLayout, textRect.width()); + } + + const QSize layoutSize(textRect.width(), int(textLayoutSize.height())); + const QRect layoutRect = QStyle::alignedRect(option.direction, option.displayAlignment, + layoutSize, textRect); + + textLayout.draw(painter, layoutRect.topLeft(), format.toVector(), layoutRect); +} + +QSizeF SearchResultItemDelegate::doTextLayout(QTextLayout *textLayout, int width) const +{ + qreal height = 0; + qreal widthUsed = 0; + textLayout->beginLayout(); + while (true) { + QTextLine line = textLayout->createLine(); + if (!line.isValid()) + break; + line.setLineWidth(width); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout->endLayout(); + return QSizeF(widthUsed, height); +} + +QRect SearchResultItemDelegate::iconRect(const QRect &itemRect) const +{ + QRect iconRect = itemRect; + QSize iconSize = view()->iconSize(); + iconRect.setSize(iconSize); + + iconRect.moveLeft(iconRect.left() + ItemMargin + ExpandArrowWidth - ArrowAndIconDistance); + iconRect.moveTop(iconRect.top() + ((itemRect.bottom() - iconRect.bottom()) / 2)); + + return iconRect; +} + +QRect SearchResultItemDelegate::arrowRect(const QRect &iconRect) const +{ + QRect arrowRect = iconRect; + + arrowRect.setSize(QSize(ExpandArrowWidth, ExpandArrowHeight)); + arrowRect.moveTop(iconRect.top() + (iconRect.bottom() - arrowRect.bottom()) / 2); + arrowRect.moveCenter(QPoint(iconRect.left() - ArrowAndIconDistance, arrowRect.center().y())); + + return arrowRect; +} + +QRect SearchResultItemDelegate::replaceButtonRect(const QRect &itemRect) const +{ + QRect replaceButtonRect = itemRect; + + replaceButtonRect.setSize(view()->iconSize()); + replaceButtonRect.moveLeft(itemRect.right() - 2 * view()->iconSize().width() - 2 * ItemMargin); + replaceButtonRect.moveTop(replaceButtonRect.top() + ((itemRect.bottom() - replaceButtonRect.bottom()) / 2)); + + return replaceButtonRect; +} + +QRect SearchResultItemDelegate::closeButtonRect(const QRect &itemRect) const +{ + QRect closeButtonRect = itemRect; + + closeButtonRect.setSize(view()->iconSize()); + closeButtonRect.moveLeft(itemRect.right() - view()->iconSize().width() - ItemMargin); + closeButtonRect.moveTop(closeButtonRect.top() + ((itemRect.bottom() - closeButtonRect.bottom()) / 2)); + + return closeButtonRect; +} + +QTextLayout::FormatRange SearchResultItemDelegate::createFormatRange(const QStyleOptionViewItem &option, int start, int length, + const QColor &foreground, const QColor &background) const +{ + QTextLayout::FormatRange range; + if (foreground.isValid() && !(option.state & QStyle::State_Selected)) + range.format.setForeground(foreground); + if (background.isValid()) + range.format.setBackground(background); + range.start = start; + range.length = length; + return range; +} + +void SearchResultItemDelegate::drawIcon(QPainter *painter, const QStyleOptionViewItem &option, const QIcon &icon, const QRect &rect) const +{ + QIcon::Mode iconMode = QIcon::Normal; + if (!(option.state.testFlag(QStyle::State_Enabled))) + iconMode = QIcon::Disabled; + if (option.state.testFlag(QStyle::State_Selected)) + iconMode = QIcon::Selected; + + auto px = icon.pixmap({ 18, 18 }, iconMode); + px.setDevicePixelRatio(qApp->devicePixelRatio()); + + qreal x = rect.x(); + qreal y = rect.y(); + qreal w = px.width() / px.devicePixelRatio(); + qreal h = px.height() / px.devicePixelRatio(); + y += (rect.size().height() - h) / 2.0; + x += (rect.size().width() - w) / 2.0; + + painter->drawPixmap(qRound(x), qRound(y), px); +} diff --git a/src/plugins/find/gui/searchresultitemdelegate.h b/src/plugins/find/gui/searchresultitemdelegate.h new file mode 100644 index 000000000..cd1e419d1 --- /dev/null +++ b/src/plugins/find/gui/searchresultitemdelegate.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SEARCHRESULTITEMDELEGATE_H +#define SEARCHRESULTITEMDELEGATE_H + +#include +#include + +class QTreeView; +class SearchResultItemDelegate : public DTK_WIDGET_NAMESPACE::DStyledItemDelegate +{ + Q_OBJECT +public: + explicit SearchResultItemDelegate(QAbstractItemView *parent = nullptr); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +protected: + bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override; + bool eventFilter(QObject *object, QEvent *event) override; + +private: + QTreeView *view() const; + void drawBackground(QPainter *painter, const QStyleOptionViewItem &option) const; + QRect drawFileIcon(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QRect drawExpandArrow(QPainter *painter, const QRect &iconRect, + const QStyleOptionViewItem &option, const QModelIndex &index) const; + QRect drawResultCount(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QRect drawOptionButton(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + void drawNameItem(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index, const QRect &iconRect) const; + void drawContextItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + void drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, + const QString &text, const QList &format) const; + QSizeF doTextLayout(QTextLayout *textLayout, int width) const; + QRect iconRect(const QRect &itemRect) const; + QRect arrowRect(const QRect &iconRect) const; + QRect replaceButtonRect(const QRect &itemRect) const; + QRect closeButtonRect(const QRect &itemRect) const; + QTextLayout::FormatRange createFormatRange(const QStyleOptionViewItem &option, int start, int length, + const QColor &foreground, const QColor &background) const; + void drawIcon(QPainter *painter, const QStyleOptionViewItem &option, const QIcon &icon, const QRect &rect) const; +}; + +#endif // SEARCHRESULTITEMDELEGATE_H diff --git a/src/plugins/find/gui/searchresultmodel.cpp b/src/plugins/find/gui/searchresultmodel.cpp new file mode 100644 index 000000000..1903f690f --- /dev/null +++ b/src/plugins/find/gui/searchresultmodel.cpp @@ -0,0 +1,262 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "searchresultmodel.h" + +#include + +#include + +DWIDGET_USE_NAMESPACE + +SearchResultModel::SearchResultModel(QObject *parent) + : QAbstractItemModel(parent) +{ +} + +int SearchResultModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QVariant SearchResultModel::data(const QModelIndex &index, int role) const +{ + if (auto item = findItem(index)) + return data(*item, role); + + QString group = findGroup(index); + if (!group.isEmpty()) + return data(group, role); + + return QVariant(); +} + +QModelIndex SearchResultModel::index(int row, int column, const QModelIndex &parent) const +{ + if (column == 0 && parent.isValid()) { + QString group = findGroup(parent); + if (!group.isEmpty()) { + auto items = resultData.value(group); + if (row < items.count()) { + auto &item = items.at(row); + return createIndex(row, 0, const_cast(&item)); + } + } + } else if (column == 0 && row < resultData.size()) { + return createIndex(row, 0); + } + + return QModelIndex(); +} + +QModelIndex SearchResultModel::parent(const QModelIndex &child) const +{ + if (auto tool = findItem(child)) { + int groupIndex = 0; + for (const auto &itemsInGroup : resultData) { + if (itemsInGroup.contains(*tool)) + return index(groupIndex, 0); + ++groupIndex; + } + } + return QModelIndex(); +} + +int SearchResultModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return resultData.size(); + + if (findItem(parent)) + return 0; + + QString group = findGroup(parent); + if (!group.isEmpty()) + return resultData.value(group).count(); + + return 0; +} + +Qt::ItemFlags SearchResultModel::flags(const QModelIndex &index) const +{ + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +void SearchResultModel::clear() +{ + beginResetModel(); + resultData.clear(); + endResetModel(); +} + +void SearchResultModel::setReplaceText(const QString &text, bool regex) +{ + replaceText = text; + enableRegex = regex; +} + +FindItem *SearchResultModel::findItem(const QModelIndex &index) const +{ + auto item = static_cast(index.internalPointer()); + return item; +} + +QString SearchResultModel::findGroup(const QModelIndex &index) const +{ + if (index.isValid() && !index.parent().isValid() && index.column() == 0 && index.row() >= 0) { + const QList &keys = resultData.keys(); + if (index.row() < keys.count()) + return keys.at(index.row()); + } + + return QString(); +} + +void SearchResultModel::appendResult(const FindItemList &list) +{ + QMap result; + for (const auto &item : list) { + if (!resultData.contains(item.filePathName)) { + if (result.contains(item.filePathName)) { + addItem(item.filePathName, result[item.filePathName]); + result.clear(); + } + addGroup(item.filePathName); + result[item.filePathName].append(item); + } else { + result[item.filePathName].append(item); + } + } + + for (auto iter = result.begin(); iter != result.end(); ++iter) + addItem(iter.key(), iter.value()); +} + +QMap SearchResultModel::allResult() const +{ + return resultData; +} + +QMap SearchResultModel::findResult(const QModelIndex &index) const +{ + QMap resultMap; + if (hasChildren(index)) { + const auto &group = findGroup(index); + resultMap.insert(group, resultData[group]); + } else { + auto item = findItem(index); + if (!item) + return {}; + + resultMap.insert(item->filePathName, { *item }); + } + + return resultMap; +} + +void SearchResultModel::remove(const QModelIndex &index) +{ + if (hasChildren(index)) { + const auto &group = findGroup(index); + removeGroup(group); + } else { + auto item = findItem(index); + if (!item) + return; + removeItem(item->filePathName, *item); + } +} + +void SearchResultModel::addGroup(const QString &group) +{ + QList groupList = resultData.keys(); + groupList.append(group); + std::stable_sort(std::begin(groupList), std::end(groupList)); + int pos = groupList.indexOf(group); + + beginInsertRows(QModelIndex(), pos, pos); + resultData.insert(group, FindItemList()); + endInsertRows(); +} + +void SearchResultModel::addItem(const QString &group, const FindItemList &itemList) +{ + int row = resultData.keys().indexOf(group); + auto parent = index(row, 0); + int pos = resultData[group].count(); + + beginInsertRows(parent, pos, pos + itemList.size()); + resultData[group].append(itemList); + endInsertRows(); +} + +void SearchResultModel::removeGroup(const QString &group) +{ + if (!resultData.contains(group)) + return; + + int pos = resultData.keys().indexOf(group); + beginRemoveRows(QModelIndex(), pos, pos); + resultData.remove(group); + endRemoveRows(); +} + +void SearchResultModel::removeItem(const QString &group, const FindItem &item) +{ + if (!resultData.contains(group) || !resultData[group].contains(item)) + return; + + if (resultData[group].count() == 1) + return removeGroup(group); + + int row = resultData.keys().indexOf(group); + auto parent = index(row, 0); + int pos = resultData[group].indexOf(item); + + beginRemoveRows(parent, pos, pos); + resultData[group].removeOne(item); + endRemoveRows(); +} + +QVariant SearchResultModel::data(const FindItem &item, int role) const +{ + switch (role) { + case Qt::ToolTipRole: + case Qt::DisplayRole: + return item.context; + case LineRole: + return item.line; + case ColumnRole: + return item.column; + case KeywordRole: + return item.keyword; + case MatchedLengthRole: + return item.matchedLength; + case ReplaceTextRole: + if (!item.capturedTexts.isEmpty()) + return Utils::expandRegExpReplacement(replaceText, item.capturedTexts); + return replaceText; + default: + break; + } + + return QVariant(); +} + +QVariant SearchResultModel::data(const QString &group, int role) const +{ + switch (role) { + case Qt::DisplayRole: + return QFileInfo(group).fileName(); + case FilePathRole: + case Qt::ToolTipRole: + return group; + case Qt::DecorationRole: + return DFileIconProvider::globalProvider()->icon(QFileInfo(group)); + default: + break; + } + + return QVariant(); +} diff --git a/src/plugins/find/gui/searchresultmodel.h b/src/plugins/find/gui/searchresultmodel.h new file mode 100644 index 000000000..f4820b2d9 --- /dev/null +++ b/src/plugins/find/gui/searchresultmodel.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SEARCHRESULTMODEL_H +#define SEARCHRESULTMODEL_H + +#include "constants.h" + +#include + +class SearchResultModel : public QAbstractItemModel +{ + Q_OBJECT +public: + explicit SearchResultModel(QObject *parent = nullptr); + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + + void clear(); + void setReplaceText(const QString &text, bool regex); + FindItem *findItem(const QModelIndex &index) const; + QString findGroup(const QModelIndex &index) const; + void appendResult(const FindItemList &list); + QMap allResult() const; + QMap findResult(const QModelIndex &index) const; + void remove(const QModelIndex &index); + +Q_SIGNALS: + void requestReplace(const QModelIndex &index); + +private: + void addGroup(const QString &group); + void addItem(const QString &group, const FindItemList &itemList); + void removeGroup(const QString &group); + void removeItem(const QString &group, const FindItem &item); + QVariant data(const FindItem &item, int role = Qt::DisplayRole) const; + QVariant data(const QString &group, int role = Qt::DisplayRole) const; + + QMap resultData; + QString replaceText; + bool enableRegex { false }; +}; + +#endif // SEARCHRESULTMODEL_H diff --git a/src/plugins/find/gui/searchresultwidget.cpp b/src/plugins/find/gui/searchresultwidget.cpp new file mode 100644 index 000000000..ff6cf975d --- /dev/null +++ b/src/plugins/find/gui/searchresultwidget.cpp @@ -0,0 +1,209 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "searchresultwidget.h" +#include "searchresultmodel.h" +#include "searchresultitemdelegate.h" + +#include "common/util/eventdefinitions.h" + +#include +#include + +#include +#include +#include +#include +#include + +DWIDGET_USE_NAMESPACE + +class SearchResultWidgetPrivate : public QObject +{ +public: + explicit SearchResultWidgetPrivate(SearchResultWidget *qq); + + void initUI(); + void initConnection(); + + void openFile(const QModelIndex &index); + void handleMenuRequested(const QPoint &pos); + void updateMessage(); + void readyReplace(const QModelIndex &index); + +public: + SearchResultWidget *q; + + DLabel *msgLabel { nullptr }; + DTreeView *resultView { nullptr }; + SearchResultModel resultModel; +}; + +SearchResultWidgetPrivate::SearchResultWidgetPrivate(SearchResultWidget *qq) + : q(qq) +{ +} + +void SearchResultWidgetPrivate::initUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(q); + mainLayout->setContentsMargins(0, 0, 0, 0); + + msgLabel = new DLabel(q); + msgLabel->setWordWrap(true); + + resultView = new DTreeView(q); + resultView->setModel(&resultModel); + resultView->setItemDelegate(new SearchResultItemDelegate(resultView)); + resultView->setContextMenuPolicy(Qt::CustomContextMenu); + resultView->setHeaderHidden(true); + resultView->setFrameShape(QFrame::NoFrame); + resultView->setIconSize({ 16, 16 }); + resultView->setIndentation(0); + + mainLayout->addWidget(msgLabel); + mainLayout->addWidget(resultView); +} + +void SearchResultWidgetPrivate::initConnection() +{ + connect(resultView, &DTreeView::doubleClicked, this, &SearchResultWidgetPrivate::openFile); + connect(resultView, &DTreeView::customContextMenuRequested, this, &SearchResultWidgetPrivate::handleMenuRequested); + connect(&resultModel, &SearchResultModel::rowsInserted, this, &SearchResultWidgetPrivate::updateMessage); + connect(&resultModel, &SearchResultModel::rowsRemoved, this, &SearchResultWidgetPrivate::updateMessage); + connect(&resultModel, &SearchResultModel::modelReset, this, &SearchResultWidgetPrivate::updateMessage); + connect(&resultModel, &SearchResultModel::requestReplace, this, &SearchResultWidgetPrivate::readyReplace); +} + +void SearchResultWidgetPrivate::openFile(const QModelIndex &index) +{ + auto item = resultModel.findItem(index); + if (!item) + return; + + editor.gotoPosition(item->filePathName, item->line, item->column); +} + +void SearchResultWidgetPrivate::handleMenuRequested(const QPoint &pos) +{ + QModelIndex index = resultView->indexAt(pos); + if (!index.isValid()) + return; + + QMenu menu; + if (resultModel.hasChildren(index)) { + menu.addAction(SearchResultWidget::tr("Replace All"), this, std::bind(&SearchResultWidgetPrivate::readyReplace, this, index)); + menu.addAction(SearchResultWidget::tr("Dismiss"), &resultModel, std::bind(&SearchResultModel::remove, &resultModel, index)); + menu.addSeparator(); + menu.addAction(SearchResultWidget::tr("Copy Path"), this, + [index] { + const auto &path = index.data(FilePathRole).toString(); + QGuiApplication::clipboard()->setText(path); + }); + } else { + menu.addAction(SearchResultWidget::tr("Replace"), this, std::bind(&SearchResultWidgetPrivate::readyReplace, this, index)); + menu.addAction(SearchResultWidget::tr("Dismiss"), &resultModel, std::bind(&SearchResultModel::remove, &resultModel, index)); + } + + menu.exec(QCursor::pos()); +} + +void SearchResultWidgetPrivate::updateMessage() +{ + Q_EMIT q->resultCountChanged(); + const auto &result = resultModel.allResult(); + if (result.isEmpty()) { + msgLabel->clear(); + return; + } + + int totalResult = 0; + for (const auto &list : result) { + totalResult += list.size(); + } + + QString msg; + if (result.count() == 1 && result.first().count() == 1) { + msg = SearchResultWidget::tr("1 result in 1 file"); + } else if (result.count() == 1) { + msg = SearchResultWidget::tr("%1 results in 1 file").arg(totalResult); + } else { + msg = SearchResultWidget::tr("%1 results in %2 files").arg(totalResult).arg(result.count()); + } + + msgLabel->setText(msg); +} + +void SearchResultWidgetPrivate::readyReplace(const QModelIndex &index) +{ + const auto &result = resultModel.findResult(index); + resultModel.remove(index); + Q_EMIT q->requestReplace(result); +} + +SearchResultWidget::SearchResultWidget(QWidget *parent) + : QWidget(parent), + d(new SearchResultWidgetPrivate(this)) +{ + d->initUI(); + d->initConnection(); +} + +SearchResultWidget::~SearchResultWidget() +{ + delete d; +} + +void SearchResultWidget::clear() +{ + d->resultModel.clear(); +} + +void SearchResultWidget::setReplaceText(const QString &text, bool regex) +{ + d->resultModel.setReplaceText(text, regex); + d->resultView->viewport()->update(); +} + +void SearchResultWidget::appendResults(const FindItemList &itemList) +{ + d->resultModel.appendResult(itemList); +} + +QMap SearchResultWidget::allResult() const +{ + return d->resultModel.allResult(); +} + +bool SearchResultWidget::isEmpty() const +{ + return d->resultModel.rowCount() == 0; +} + +void SearchResultWidget::expandAll() +{ + QApplication::setOverrideCursor(Qt::WaitCursor); + d->resultView->expandAll(); + QApplication::restoreOverrideCursor(); +} + +void SearchResultWidget::collapseAll() +{ + d->resultView->collapseAll(); +} + +void SearchResultWidget::showMessage(const QString &msg, MessageType type) +{ + switch (type) { + case Information: + d->msgLabel->setForegroundRole(DPalette::TextTips); + break; + case Warning: + d->msgLabel->setForegroundRole(DPalette::TextWarning); + default: + break; + } + + d->msgLabel->setText(msg); +} diff --git a/src/plugins/find/gui/searchresultwidget.h b/src/plugins/find/gui/searchresultwidget.h new file mode 100644 index 000000000..6ae2b66ad --- /dev/null +++ b/src/plugins/find/gui/searchresultwidget.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SEARCHRESULTWIDGET_H +#define SEARCHRESULTWIDGET_H + +#include "constants.h" + +#include + +class SearchResultWidgetPrivate; +class SearchResultWidget : public QWidget +{ + Q_OBJECT +public: + explicit SearchResultWidget(QWidget *parent = nullptr); + ~SearchResultWidget(); + + void clear(); + void setReplaceText(const QString &text, bool regex); + void appendResults(const FindItemList &itemList); + QMap allResult() const; + bool isEmpty() const; + + void expandAll(); + void collapseAll(); + void showMessage(const QString &msg, MessageType type = Information); + +Q_SIGNALS: + void requestReplace(const QMap &resultMap); + void resultCountChanged(); + +private: + SearchResultWidgetPrivate *const d; +}; + +#endif // SEARCHRESULTWIDGET_H diff --git a/src/plugins/find/icons/find_noResults_96px.png b/src/plugins/find/icons/deepin/builtin/icons/find_noResults_96px.png similarity index 100% rename from src/plugins/find/icons/find_noResults_96px.png rename to src/plugins/find/icons/deepin/builtin/icons/find_noResults_96px.png diff --git a/src/plugins/find/icons/deepin/builtin/texts/delete_16px.svg b/src/plugins/find/icons/deepin/builtin/texts/delete_16px.svg new file mode 100644 index 000000000..b685950fe --- /dev/null +++ b/src/plugins/find/icons/deepin/builtin/texts/delete_16px.svg @@ -0,0 +1,11 @@ + + + ICON / action /delete + + + + + + + + \ No newline at end of file diff --git a/src/plugins/find/icons/deepin/builtin/texts/match_case_16px.svg b/src/plugins/find/icons/deepin/builtin/texts/match_case_16px.svg new file mode 100644 index 000000000..53bf1172d --- /dev/null +++ b/src/plugins/find/icons/deepin/builtin/texts/match_case_16px.svg @@ -0,0 +1,7 @@ + + + ICON / search /match_case + + + + \ No newline at end of file diff --git a/src/plugins/find/icons/deepin/builtin/texts/option_16px.svg b/src/plugins/find/icons/deepin/builtin/texts/option_16px.svg new file mode 100644 index 000000000..e73027378 --- /dev/null +++ b/src/plugins/find/icons/deepin/builtin/texts/option_16px.svg @@ -0,0 +1,9 @@ + + + ICON / search /option + + + + + + \ No newline at end of file diff --git a/src/plugins/find/icons/deepin/builtin/texts/refresh_16px.svg b/src/plugins/find/icons/deepin/builtin/texts/refresh_16px.svg new file mode 100644 index 000000000..10610282c --- /dev/null +++ b/src/plugins/find/icons/deepin/builtin/texts/refresh_16px.svg @@ -0,0 +1,7 @@ + + + ICON / action/Refresh + + + + \ No newline at end of file diff --git a/src/plugins/find/icons/deepin/builtin/texts/regex_16px.svg b/src/plugins/find/icons/deepin/builtin/texts/regex_16px.svg new file mode 100644 index 000000000..6d20e8c96 --- /dev/null +++ b/src/plugins/find/icons/deepin/builtin/texts/regex_16px.svg @@ -0,0 +1,7 @@ + + + ICON / search /regex + + + + \ No newline at end of file diff --git a/src/plugins/find/icons/deepin/builtin/texts/replace_16px.svg b/src/plugins/find/icons/deepin/builtin/texts/replace_16px.svg new file mode 100644 index 000000000..d545b346a --- /dev/null +++ b/src/plugins/find/icons/deepin/builtin/texts/replace_16px.svg @@ -0,0 +1,7 @@ + + + ICON / search /replace + + + + \ No newline at end of file diff --git a/src/plugins/find/icons/deepin/builtin/texts/replace_all_16px.svg b/src/plugins/find/icons/deepin/builtin/texts/replace_all_16px.svg new file mode 100644 index 000000000..89da84ccf --- /dev/null +++ b/src/plugins/find/icons/deepin/builtin/texts/replace_all_16px.svg @@ -0,0 +1,7 @@ + + + ICON / search /replace_all + + + + \ No newline at end of file diff --git a/src/plugins/find/icons/deepin/builtin/texts/search_22px.svg b/src/plugins/find/icons/deepin/builtin/texts/search_22px.svg new file mode 100644 index 000000000..d7edd726e --- /dev/null +++ b/src/plugins/find/icons/deepin/builtin/texts/search_22px.svg @@ -0,0 +1,7 @@ + + + ICON / sidebar /search + + + + \ No newline at end of file diff --git a/src/plugins/find/icons/deepin/builtin/texts/stop_search_16px.svg b/src/plugins/find/icons/deepin/builtin/texts/stop_search_16px.svg new file mode 100644 index 000000000..11c7de9e3 --- /dev/null +++ b/src/plugins/find/icons/deepin/builtin/texts/stop_search_16px.svg @@ -0,0 +1,7 @@ + + + ICON / search /stop_search + + + + \ No newline at end of file diff --git a/src/plugins/find/actions/find_matchComplete_16px.svg b/src/plugins/find/icons/deepin/builtin/texts/whole_word_16px.svg similarity index 100% rename from src/plugins/find/actions/find_matchComplete_16px.svg rename to src/plugins/find/icons/deepin/builtin/texts/whole_word_16px.svg diff --git a/src/plugins/find/maincontroller/maincontroller.cpp b/src/plugins/find/maincontroller/maincontroller.cpp new file mode 100644 index 000000000..5a891cf46 --- /dev/null +++ b/src/plugins/find/maincontroller/maincontroller.cpp @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "maincontroller.h" +#include "task/taskcommander.h" + +#include +#include + +class MainControllerPrivate +{ +public: + ~MainControllerPrivate(); + +public: + TaskCommander *currentTask { nullptr }; +}; + +MainControllerPrivate::~MainControllerPrivate() +{ + if (currentTask) { + currentTask->stop(); + currentTask->deleteSelf(); + currentTask = nullptr; + } +} + +MainController::MainController(QObject *parent) + : QObject(parent), + d(new MainControllerPrivate) +{ +} + +MainController::~MainController() +{ + delete d; +} + +bool MainController::search(const SearchParams ¶ms) +{ + stop(); + if (params.keyword.isEmpty()) + return false; + + auto task = new TaskCommander(); + connect(task, &TaskCommander::matched, this, &MainController::matched, Qt::DirectConnection); + connect(task, &TaskCommander::finished, this, &MainController::searchFinished, Qt::DirectConnection); + + if (task->search(params)) { + d->currentTask = task; + return true; + } + + task->deleteSelf(); + return false; +} + +bool MainController::replace(const ReplaceParams ¶ms) +{ + stop(); + auto task = new TaskCommander(); + connect(task, &TaskCommander::finished, this, &MainController::replaceFinished, Qt::DirectConnection); + + if (task->replace(params)) { + d->currentTask = task; + return true; + } + + task->deleteSelf(); + return false; +} + +void MainController::stop() +{ + if (!d->currentTask) + return; + + disconnect(d->currentTask, nullptr, this, nullptr); + d->currentTask->stop(); + d->currentTask->deleteSelf(); + d->currentTask = nullptr; +} + +FindItemList MainController::takeAll() +{ + if (d->currentTask) + return d->currentTask->takeAll(); + + return {}; +} diff --git a/src/plugins/find/maincontroller/maincontroller.h b/src/plugins/find/maincontroller/maincontroller.h new file mode 100644 index 000000000..65af381d1 --- /dev/null +++ b/src/plugins/find/maincontroller/maincontroller.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef MAINCONTROLLER_H +#define MAINCONTROLLER_H + +#include "constants.h" + +class MainControllerPrivate; +class MainController : public QObject +{ + Q_OBJECT +public: + explicit MainController(QObject *parent = nullptr); + ~MainController(); + + bool search(const SearchParams ¶ms); + bool replace(const ReplaceParams ¶ms); + void stop(); + FindItemList takeAll(); + +Q_SIGNALS: + void matched(); + void searchFinished(); + void replaceFinished(); + +private: + MainControllerPrivate *const d; +}; + +#endif // MAINCONTROLLER_H diff --git a/src/plugins/find/maincontroller/task/taskcommander.cpp b/src/plugins/find/maincontroller/task/taskcommander.cpp new file mode 100644 index 000000000..d9e540a4d --- /dev/null +++ b/src/plugins/find/maincontroller/task/taskcommander.cpp @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "taskcommander.h" +#include "maincontroller/worker/searchreplaceworker.h" + +#include +#include +#include + +class TaskCommanderPrivate : public QObject +{ +public: + explicit TaskCommanderPrivate(TaskCommander *qq); + ~TaskCommanderPrivate(); + + void onFinished(); + void onMatched(); + +public: + TaskCommander *q; + + QThread workThread; + QSharedPointer srWorker { nullptr }; + + QReadWriteLock rwLock; + FindItemList resultList; + + volatile bool isWorking = false; + bool deleted = false; + bool finished = false; +}; + +TaskCommanderPrivate::TaskCommanderPrivate(TaskCommander *qq) + : q(qq), + srWorker(new SearchReplaceWorker) +{ + + connect(srWorker.data(), &SearchReplaceWorker::matched, this, &TaskCommanderPrivate::onMatched, Qt::DirectConnection); + connect(srWorker.data(), &SearchReplaceWorker::finished, this, &TaskCommanderPrivate::onFinished, Qt::QueuedConnection); + + srWorker->moveToThread(&workThread); + workThread.start(); +} + +TaskCommanderPrivate::~TaskCommanderPrivate() +{ + metaObject()->invokeMethod(srWorker.data(), &SearchReplaceWorker::stop); + workThread.quit(); + workThread.wait(); +} + +void TaskCommanderPrivate::onFinished() +{ + if (deleted) { + q->deleteLater(); + disconnect(q, nullptr, nullptr, nullptr); + } else if (!finished) { + finished = true; + emit q->finished(); + } +} + +void TaskCommanderPrivate::onMatched() +{ + if (!srWorker->hasItem()) + return; + + auto results = srWorker->takeAll(); + QWriteLocker lk(&rwLock); + bool isEmpty = resultList.isEmpty(); + + resultList += results; + if (isEmpty) + QMetaObject::invokeMethod(q, "matched", Qt::QueuedConnection); +} + +TaskCommander::TaskCommander(QObject *parent) + : QObject(parent), + d(new TaskCommanderPrivate(this)) +{ +} + +bool TaskCommander::search(const SearchParams ¶ms) +{ + if (d->isWorking) + return false; + + d->isWorking = true; + QMetaObject::invokeMethod(d->srWorker.data(), + "search", + Qt::QueuedConnection, + Q_ARG(SearchParams, params)); + + return true; +} + +bool TaskCommander::replace(const ReplaceParams ¶ms) +{ + if (d->isWorking) + return false; + + QMetaObject::invokeMethod(d->srWorker.data(), + "replace", + Qt::QueuedConnection, + Q_ARG(ReplaceParams, params)); + + d->isWorking = true; + return true; +} + +void TaskCommander::stop() +{ + metaObject()->invokeMethod(d->srWorker.data(), &SearchReplaceWorker::stop); + d->isWorking = false; +} + +void TaskCommander::deleteSelf() +{ + if (d->finished) + delete this; + else + d->deleted = true; +} + +FindItemList TaskCommander::takeAll() +{ + QReadLocker lk(&d->rwLock); + return std::move(d->resultList); +} diff --git a/src/plugins/find/maincontroller/task/taskcommander.h b/src/plugins/find/maincontroller/task/taskcommander.h new file mode 100644 index 000000000..3623a9750 --- /dev/null +++ b/src/plugins/find/maincontroller/task/taskcommander.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef TASKCOMMANDER_H +#define TASKCOMMANDER_H + +#include "constants.h" + +#include + +class TaskCommanderPrivate; +class TaskCommander : public QObject +{ + Q_OBJECT +public: + explicit TaskCommander(QObject *parent = nullptr); + + bool search(const SearchParams ¶ms); + bool replace(const ReplaceParams ¶ms); + void stop(); + void deleteSelf(); + FindItemList takeAll(); + +Q_SIGNALS: + void matched(); + void finished(); + +private: + TaskCommanderPrivate *const d; +}; + +#endif // TASKCOMMANDER_H diff --git a/src/plugins/find/maincontroller/worker/searchreplaceworker.cpp b/src/plugins/find/maincontroller/worker/searchreplaceworker.cpp new file mode 100644 index 000000000..1776e55c8 --- /dev/null +++ b/src/plugins/find/maincontroller/worker/searchreplaceworker.cpp @@ -0,0 +1,422 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "searchreplaceworker.h" +#include "searchreplaceworker_p.h" + +#include "common/util/qtcassert.h" + +#include +#include +#include +#include + +using namespace dpfservice; + +inline constexpr int MaxResultCount { 20000 }; + +SearchReplaceWorkerPrivate::SearchReplaceWorkerPrivate(SearchReplaceWorker *qq) + : q(qq) +{ +} + +void SearchReplaceWorkerPrivate::startAll() +{ + QTC_ASSERT(!jobList.isEmpty(), return ); + QTC_ASSERT(!process, return ); + + currentJob = 0; + startNextJob(); +} + +void SearchReplaceWorkerPrivate::startNextJob() +{ + QTC_ASSERT(currentJob < jobList.count(), return ); + + Job job = jobList.at(currentJob); + process.reset(new QProcess); + connect(process.get(), &QProcess::readyReadStandardOutput, q, + std::bind(&SearchReplaceWorker::handleReadSearchResult, q, job.keyword, job.flags)); + connect(process.get(), qOverload(&QProcess::finished), q, &SearchReplaceWorker::processDone); + + process->setProgram(job.program); + process->setArguments(job.arguments); + process->start(); + if (!job.channelData.isEmpty()) { + process->write(job.channelData.toUtf8()); + process->closeWriteChannel(); + } +} + +void SearchReplaceWorkerPrivate::createSearchJob(const SearchParams ¶ms) +{ + jobList.clear(); + switch (params.scope) { + case AllProjects: + case CurrentProject: { + auto tmpParams = params; + processWorkingFiles(tmpParams.projectFileList, tmpParams.editFileList); + for (const auto &file : tmpParams.editFileList) { + const auto &job = buildSearchJob({ file }, tmpParams.includeList, tmpParams.excludeList, + tmpParams.keyword, tmpParams.flags, true); + jobList << job; + } + + const auto &job = buildSearchJob(tmpParams.projectFileList, tmpParams.includeList, tmpParams.excludeList, + tmpParams.keyword, tmpParams.flags, false); + jobList << job; + } break; + case CurrentFile: { + const auto &job = buildSearchJob(params.editFileList, params.includeList, params.excludeList, + params.keyword, params.flags, true); + jobList << job; + } break; + default: + break; + } +} + +SearchReplaceWorkerPrivate::Job +SearchReplaceWorkerPrivate::buildSearchJob(const QStringList &fileList, + const QStringList &includeList, + const QStringList &excludeList, + const QString &keyword, SearchFlags flags, + bool isOpenedFile) +{ + if (fileList.isEmpty()) + return {}; + + Job job; + job.program = "grep"; + job.keyword = keyword; + job.flags = flags; + job.arguments << "-Hn"; + if (!flags.testFlag(SearchCaseSensitively)) + job.arguments << "-i"; + + if (flags.testFlag(SearchWholeWords)) { + job.arguments << "-w"; + } + + if (flags.testFlag(SearchRegularExpression)) { + job.arguments << "-P"; + } else { + job.arguments << "-F"; + } + + if (!includeList.isEmpty()) + job.arguments << "--include=" + includeList.join(" --include="); + if (!excludeList.isEmpty()) + job.arguments << "--exclude=" + excludeList.join(" --exclude="); + job.arguments << keyword; + + if (isOpenedFile) { + if (!editSrv) + editSrv = dpfGetService(EditorService); + job.channelData = editSrv->fileText(fileList.first()); + job.arguments << "--label=" + fileList.first(); + } else { + job.arguments << fileList; + } + + return job; +} + +void SearchReplaceWorkerPrivate::parseResultWithRegExp(const QString &fileName, const QString &keyword, + const QString &contents, int line, SearchFlags flags) +{ + const QString term = flags & SearchWholeWords + ? QString::fromLatin1("\\b%1\\b").arg(keyword) + : keyword; + const auto patternOptions = (flags & SearchCaseSensitively) + ? QRegularExpression::NoPatternOption + : QRegularExpression::CaseInsensitiveOption; + const QRegularExpression expression = QRegularExpression(term, patternOptions); + if (!expression.isValid()) + return; + + QRegularExpressionMatch match; + int lengthOfContents = contents.length(); + int pos = 0; + while ((match = expression.match(contents, pos)).hasMatch()) { + pos = match.capturedStart(); + FindItem findItem; + findItem.filePathName = fileName; + findItem.line = line; + findItem.keyword = keyword; + findItem.context = contents; + findItem.column = pos; + findItem.matchedLength = match.capturedLength(); + findItem.capturedTexts = match.capturedTexts(); + + { + QMutexLocker lk(&mutex); + searchResults.append(findItem); + } + ++resultCount; + if (match.capturedLength() == 0) + break; + pos += match.capturedLength(); + if (pos >= lengthOfContents) + break; + } +} + +void SearchReplaceWorkerPrivate::parseResultWithoutRegExp(const QString &fileName, const QString &keyword, + const QString &contents, int line, SearchFlags flags) +{ + const bool caseSensitive = (flags & SearchCaseSensitively); + const bool wholeWord = (flags & SearchWholeWords); + const QString keywordLower = keyword.toLower(); + const QString keywordUpper = keyword.toUpper(); + const int keywordMaxIndex = keyword.length() - 1; + const QChar *keywordData = keyword.constData(); + const QChar *keywordDataLower = keywordLower.constData(); + const QChar *keywordDataUpper = keywordUpper.constData(); + + const int contentsLength = contents.length(); + const QChar *contentsPtr = contents.constData(); + const QChar *contentsEnd = contentsPtr + contentsLength - 1; + for (const QChar *regionPtr = contentsPtr; regionPtr + keywordMaxIndex <= contentsEnd; ++regionPtr) { + const QChar *regionEnd = regionPtr + keywordMaxIndex; + if ((caseSensitive && *regionPtr == keywordData[0] + && *regionEnd == keywordData[keywordMaxIndex]) + || + // case insensitive + (!caseSensitive && (*regionPtr == keywordDataLower[0] || *regionPtr == keywordDataUpper[0]) + && (*regionEnd == keywordDataLower[keywordMaxIndex] + || *regionEnd == keywordDataUpper[keywordMaxIndex]))) { + bool equal = true; + + // whole word check + const QChar *beforeRegion = regionPtr - 1; + const QChar *afterRegion = regionEnd + 1; + if (wholeWord + && (((beforeRegion >= contentsPtr) + && (beforeRegion->isLetterOrNumber() + || ((*beforeRegion) == QLatin1Char('_')))) + || ((afterRegion <= contentsEnd) + && (afterRegion->isLetterOrNumber() + || ((*afterRegion) == QLatin1Char('_')))))) { + equal = false; + } else { + // check all chars + int regionIndex = 1; + for (const QChar *regionCursor = regionPtr + 1; + regionCursor < regionEnd; + ++regionCursor, ++regionIndex) { + if ( // case sensitive + (caseSensitive + && *regionCursor != keywordData[regionIndex]) + || + // case insensitive + (!caseSensitive + && *regionCursor != keywordDataLower[regionIndex] + && *regionCursor != keywordDataUpper[regionIndex])) { + equal = false; + break; + } + } + } + + if (equal) { + FindItem result; + result.filePathName = fileName; + result.line = line; + result.column = regionPtr - contentsPtr; + result.context = contents; + result.keyword = keyword; + result.matchedLength = keywordMaxIndex + 1; + + ++resultCount; + regionPtr += keywordMaxIndex; + + QMutexLocker lk(&mutex); + searchResults.append(result); + } + } + } +} + +void SearchReplaceWorkerPrivate::processWorkingFiles(QStringList &baseFiles, QStringList &openedFiles) +{ + for (int i = 0; i < openedFiles.size();) { + if (!baseFiles.contains(openedFiles.at(i))) { + openedFiles.removeAt(i); + } else { + baseFiles.removeOne(openedFiles.at(i)); + ++i; + } + } +} + +void SearchReplaceWorkerPrivate::replaceLocalFile(const QString &fileName, const QString &replacement, const FindItemList &itemList) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) + return; + + QTextStream in(&file); + QStringList lines; + while (!in.atEnd()) { + lines.append(in.readLine()); + } + file.seek(0); + + // When there are multiple results in a row, + // the offset may change after replacement + int offset = 0; + int lastReplaceLine = 0; + for (const auto &item : itemList) { + int realLine = item.line - 1; + if (lines.count() <= realLine) + continue; + + QString newText = item.capturedTexts.isEmpty() + ? replacement + : Utils::expandRegExpReplacement(replacement, item.capturedTexts); + int index = item.column; + if (lastReplaceLine == realLine) + index += offset; + else + offset = 0; + + lastReplaceLine = realLine; + offset += newText.length() - item.matchedLength; + lines[realLine].replace(index, item.matchedLength, newText); + } + + QTextStream out(&file); + out << lines.join('\n'); + file.resize(file.pos()); + file.close(); +} + +void SearchReplaceWorkerPrivate::replaceOpenedFile(const QString &fileName, const QString &replacement, const FindItemList &itemList) +{ + if (!editSrv) + editSrv = dpfGetService(EditorService); + + // When there are multiple results in a row, + // the offset may change after replacement + int offset = 0; + int lastReplaceLine = 0; + for (const auto &item : itemList) { + int realLine = item.line - 1; + QString newText = item.capturedTexts.isEmpty() + ? replacement + : Utils::expandRegExpReplacement(replacement, item.capturedTexts); + int index = item.column; + if (lastReplaceLine == realLine) + index += offset; + else + offset = 0; + + lastReplaceLine = realLine; + offset += newText.length() - item.matchedLength; + editSrv->replaceRange(fileName, realLine, index, item.matchedLength, newText); + } +} + +SearchReplaceWorker::SearchReplaceWorker(QObject *parent) + : QObject(parent), + d(new SearchReplaceWorkerPrivate(this)) +{ + qRegisterMetaType("FindItemList"); +} + +SearchReplaceWorker::~SearchReplaceWorker() +{ + stop(); + delete d; +} + +void SearchReplaceWorker::stop() +{ + d->isStop = true; + if (d->process) { + disconnect(d->process.get(), nullptr, this, nullptr); + d->process->kill(); + d->process.reset(); + } +} + +bool SearchReplaceWorker::hasItem() const +{ + QMutexLocker lk(&d->mutex); + return !d->searchResults.isEmpty(); +} + +FindItemList SearchReplaceWorker::takeAll() +{ + QMutexLocker lk(&d->mutex); + return std::move(d->searchResults); +} + +void SearchReplaceWorker::search(const SearchParams ¶ms) +{ + d->resultCount = 0; + d->createSearchJob(params); + if (d->jobList.isEmpty()) { + Q_EMIT finished(); + return; + } + + d->startAll(); +} + +void SearchReplaceWorker::replace(const ReplaceParams ¶ms) +{ + auto iter = params.resultMap.cbegin(); + for (; iter != params.resultMap.cend() && !d->isStop; ++iter) { + if (params.editFileList.contains(iter.key())) { + QMetaObject::invokeMethod(d, "replaceOpenedFile", + Qt::QueuedConnection, + Q_ARG(QString, iter.key()), + Q_ARG(QString, params.replaceText), + Q_ARG(FindItemList, iter.value())); + } else { + d->replaceLocalFile(iter.key(), params.replaceText, iter.value()); + } + } + + Q_EMIT finished(); +} + +void SearchReplaceWorker::handleReadSearchResult(const QString &keyword, SearchFlags flags) +{ + const auto resultLineRegex = QRegularExpression(R"((.+):([0-9]+):(.+))", QRegularExpression::NoPatternOption); + while (d->resultCount < MaxResultCount && d->process->canReadLine()) { + const auto &line = d->process->readLine(); + QRegularExpressionMatch regMatch; + if ((regMatch = resultLineRegex.match(line)).hasMatch()) { + auto name = regMatch.captured(1); + auto line = regMatch.captured(2).toInt(); + auto context = regMatch.captured(3); + + flags.testFlag(SearchRegularExpression) ? d->parseResultWithRegExp(name, keyword, context, line, flags) + : d->parseResultWithoutRegExp(name, keyword, context, line, flags); + } + } + + if (hasItem()) + Q_EMIT matched(); + + if (d->resultCount >= MaxResultCount) { + stop(); + Q_EMIT finished(); + } +} + +void SearchReplaceWorker::processDone() +{ + ++d->currentJob; + if (d->currentJob < d->jobList.count()) { + d->process.release()->deleteLater(); + d->startNextJob(); + return; + } + + Q_EMIT finished(); +} diff --git a/src/plugins/find/maincontroller/worker/searchreplaceworker.h b/src/plugins/find/maincontroller/worker/searchreplaceworker.h new file mode 100644 index 000000000..3ef277586 --- /dev/null +++ b/src/plugins/find/maincontroller/worker/searchreplaceworker.h @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SEARCHREPLACEWORKER_H +#define SEARCHREPLACEWORKER_H + +#include "constants.h" + +#include + +class SearchReplaceWorkerPrivate; +class SearchReplaceWorker : public QObject +{ + Q_OBJECT +public: + explicit SearchReplaceWorker(QObject *parent = nullptr); + ~SearchReplaceWorker(); + + bool hasItem() const; + FindItemList takeAll(); + +public Q_SLOTS: + void stop(); + void search(const SearchParams ¶ms); + void replace(const ReplaceParams ¶ms); + +Q_SIGNALS: + void matched(); + void finished(); + +private Q_SLOTS: + void handleReadSearchResult(const QString &keyword, SearchFlags flags); + void processDone(); + +private: + friend class SearchReplaceWorkerPrivate; + SearchReplaceWorkerPrivate *const d; +}; + +#endif // SEARCHREPLACEWORKER_H diff --git a/src/plugins/find/maincontroller/worker/searchreplaceworker_p.h b/src/plugins/find/maincontroller/worker/searchreplaceworker_p.h new file mode 100644 index 000000000..7b89b56a1 --- /dev/null +++ b/src/plugins/find/maincontroller/worker/searchreplaceworker_p.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SEARCHREPLACEWORKER_P_H +#define SEARCHREPLACEWORKER_P_H + +#include "searchreplaceworker.h" + +#include "services/editor/editorservice.h" + +class SearchReplaceWorkerPrivate : public QObject +{ + Q_OBJECT +public: + struct Job + { + QString program; + QStringList arguments; + QString channelData; + QString keyword; + SearchFlags flags; + }; + + explicit SearchReplaceWorkerPrivate(SearchReplaceWorker *qq); + + void startAll(); + void startNextJob(); + + void createSearchJob(const SearchParams ¶ms); + Job buildSearchJob(const QStringList &fileList, + const QStringList &includeList, + const QStringList &excludeList, + const QString &keyword, + SearchFlags flags, + bool isOpenedFile); + + void parseResultWithRegExp(const QString &fileName, const QString &keyword, const QString &contents, int line, SearchFlags flags); + void parseResultWithoutRegExp(const QString &fileName, const QString &keyword, const QString &contents, int line, SearchFlags flags); + + void processWorkingFiles(QStringList &baseFiles, QStringList &openedFiles); + void replaceLocalFile(const QString &fileName, const QString &replacement, const FindItemList &itemList); + Q_INVOKABLE void replaceOpenedFile(const QString &fileName, const QString &replacement, const FindItemList &itemList); + +public: + SearchReplaceWorker *q; + + dpfservice::EditorService *editSrv { nullptr }; + QMutex mutex; + FindItemList searchResults; + std::unique_ptr process { nullptr }; + + QAtomicInteger isStop { false }; + QList jobList; + int currentJob = 0; + int resultCount = 0; +}; + +#endif // SEARCHREPLACEWORKER_P_H diff --git a/src/plugins/find/searchresultwindow.cpp b/src/plugins/find/searchresultwindow.cpp deleted file mode 100644 index 204e5ab9b..000000000 --- a/src/plugins/find/searchresultwindow.cpp +++ /dev/null @@ -1,358 +0,0 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "searchresultwindow.h" -#include "common/common.h" -#include "qobjectdefs.h" -#include "base/baseitemdelegate.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -ItemProxy::ItemProxy(QObject *parent) - : QObject(parent) -{ -} - -void ItemProxy::setRuningState(bool isRuning) -{ - this->isRuning = isRuning; -} - -void ItemProxy::addTask(const FindItemList &itemList) -{ - if (!isRuning) - return; - - QHash>> findItemHash; - for (const FindItem &findItem : itemList) { - if (!isRuning) - return; - - QString key = findItem.filePathName; - auto value = qMakePair(findItem.lineNumber, findItem.context); - if (findItemHash.contains(key)) { - QList> valueList = findItemHash.value(key); - valueList.append(value); - findItemHash[key] = valueList; - } else { - findItemHash.insert(key, { value }); - } - } - - QList viewItemList; - auto iter = findItemHash.begin(); - for (; iter != findItemHash.end(); ++iter) { - if (!isRuning) { - qDeleteAll(viewItemList); - return; - } - - QList> contentList = iter.value(); - QStandardItem *parentItem = new QStandardItem(iter.key() + " (" + QString::number(contentList.count()) + ")"); - parentItem->setData(iter.key()); - parentItem->setEditable(false); - viewItemList << parentItem; - for (const auto &content : contentList) { - if (!isRuning) { - qDeleteAll(viewItemList); - return; - } - - QString title = QString::number(content.first) + " " + content.second; - QStandardItem *childItem = new QStandardItem(title); - childItem->setEditable(false); - int lineNumber = content.first; - childItem->setData(lineNumber); - parentItem->appendRow(childItem); - } - } - - Q_EMIT taskCompleted(viewItemList); -} - -class SearchResultTreeViewPrivate -{ - SearchResultTreeViewPrivate() {} - ~SearchResultTreeViewPrivate(); - - QMap projectInfoMap; - QThread thread; - QSharedPointer proxy; - friend class SearchResultTreeView; -}; - -SearchResultTreeViewPrivate::~SearchResultTreeViewPrivate() -{ - proxy->setRuningState(false); - thread.quit(); - thread.wait(); -} - -SearchResultTreeView::SearchResultTreeView(QWidget *parent) - : DTreeView(parent), d(new SearchResultTreeViewPrivate()) -{ - QAbstractItemModel *itemModel = new QStandardItemModel(this); - setModel(itemModel); - - QObject::connect(this, &DTreeView::doubleClicked, [=](const QModelIndex &index) { - if (!index.isValid()) - return; - if (!index.parent().isValid()) - return; - QModelIndex parentIndex = index.parent(); - QString filePath = parentIndex.data(Qt::UserRole + 1).toString().trimmed(); - int lineNumber = index.data(Qt::UserRole + 1).toInt(); - qInfo() << filePath << lineNumber; - - foreach (QString key, d->projectInfoMap.keys()) { - if (filePath.contains(key, Qt::CaseInsensitive)) { - editor.gotoLine(filePath, lineNumber); - break; - } - } - }); - - d->proxy.reset(new ItemProxy); - connect(d->proxy.data(), &ItemProxy::taskCompleted, this, &SearchResultTreeView::appendItems, Qt::QueuedConnection); - - d->proxy->moveToThread(&d->thread); - d->thread.start(); -} - -SearchResultTreeView::~SearchResultTreeView() -{ - delete d; -} - -void SearchResultTreeView::appendData(const FindItemList &itemList, const ProjectInfo &projectInfo) -{ - d->projectInfoMap = projectInfo; - d->proxy->setRuningState(true); - metaObject()->invokeMethod(d->proxy.data(), - "addTask", - Qt::QueuedConnection, - Q_ARG(FindItemList, itemList)); -} - -QIcon SearchResultTreeView::icon(const QString &data) -{ - QFileInfo info(data); - return iconProvider.icon(info); -} - -void SearchResultTreeView::appendItems(const QList &itemList) -{ - auto model = qobject_cast(SearchResultTreeView::model()); - if (!model) - return; - - for (auto item : itemList) { - item->setIcon(icon(item->data().toString())); - model->appendRow(item); - } -} - -void SearchResultTreeView::clearData() -{ - d->proxy->setRuningState(false); - auto model = qobject_cast(SearchResultTreeView::model()); - model->clear(); -} - -class SearchResultWindowPrivate -{ - SearchResultWindowPrivate() {} - SearchResultTreeView *treeView { nullptr }; - QWidget *replaceWidget { nullptr }; - DLineEdit *replaceEdit { nullptr }; - DLabel *resultLabel { nullptr }; - QLabel *iconLabel { nullptr }; - DDialog *replaceTextDialog { nullptr }; - DDialog *replaceWarningDialog { nullptr }; - DPushButton *replaceBtn { nullptr }; - bool replaceTextFlag = false; - SearchParams searchParams; - int resultCount { 0 }; - - friend class SearchResultWindow; -}; - -SearchResultWindow::SearchResultWindow(QWidget *parent) - : QWidget(parent), d(new SearchResultWindowPrivate()) -{ - setupUi(); - - qRegisterMetaType("FindItemList"); - qRegisterMetaType("ProjectInfo"); -} - -SearchResultWindow::~SearchResultWindow() -{ - delete d; -} - -void SearchResultWindow::clear() -{ - d->iconLabel->setVisible(true); - d->treeView->clearData(); -} - -void SearchResultWindow::setupUi() -{ - d->replaceWidget = new QWidget(this); - QHBoxLayout *replaceLayout = new QHBoxLayout(); - - d->replaceEdit = new DLineEdit(this); - d->replaceEdit->setFixedWidth(280); - d->replaceEdit->setPlaceholderText(tr("Replace")); - d->replaceBtn = new DPushButton(DPushButton::tr("Replace"), this); - d->replaceBtn->setFixedSize(120, 36); - d->replaceWidget->setLayout(replaceLayout); - - replaceLayout->addWidget(d->replaceEdit, 0, Qt::AlignLeft); - replaceLayout->addWidget(d->replaceBtn, 0, Qt::AlignLeft); - replaceLayout->setAlignment(Qt::AlignLeft); - - QHBoxLayout *hLayout = new QHBoxLayout(); - DIconButton *cleanBtn = new DIconButton(this); //Clean && Return - cleanBtn->setIcon(QIcon::fromTheme("go-previous")); - QSize iconSize(12, 12); - cleanBtn->setIconSize(iconSize); - - cleanBtn->setFixedSize(36, 36); - d->resultLabel = new DLabel(this); - - hLayout->addWidget(cleanBtn); - hLayout->addWidget(d->replaceWidget); - hLayout->addWidget(d->resultLabel); - hLayout->setAlignment(Qt::AlignLeft); - - d->treeView = new SearchResultTreeView(this); - d->treeView->setHeaderHidden(true); - d->treeView->setLineWidth(0); - d->treeView->setItemDelegate(new BaseItemDelegate(this)); - - QVBoxLayout *vLayout = new QVBoxLayout(); - // vLayout->setAlignment(Qt::AlignTop); - vLayout->setContentsMargins(0, 0, 0, 0); - vLayout->addLayout(hLayout); - vLayout->addWidget(d->treeView, 1); - - d->iconLabel = new QLabel(this); - QVBoxLayout *iconLayout = new QVBoxLayout(); - d->iconLabel->setPixmap(QIcon::fromTheme("find_noResults").pixmap(QSize(96, 96))); - iconLayout->addWidget(d->iconLabel, Qt::AlignCenter); - - iconLayout->setAlignment(Qt::AlignCenter); - vLayout->addLayout(iconLayout); - vLayout->setAlignment(iconLayout, Qt::AlignCenter); - - connect(cleanBtn, &DIconButton::clicked, this, &SearchResultWindow::clean); - connect(d->replaceBtn, &DPushButton::clicked, this, &SearchResultWindow::replace); - - setLayout(vLayout); - setRepalceWidgtVisible(false); -} - -void SearchResultWindow::setRepalceWidgtVisible(bool visible) -{ - d->replaceBtn->setEnabled(!visible); - d->replaceWidget->setVisible(visible); -} - -void SearchResultWindow::appendResults(const FindItemList &itemList, const ProjectInfo &projectInfo) -{ - d->treeView->setVisible(true); - d->iconLabel->setVisible(false); - d->treeView->appendData(itemList, projectInfo); - d->resultCount += itemList.count(); - QString msg = tr("%1 matches found.").arg(d->resultCount); - showMsg(true, msg); -} - -void SearchResultWindow::searchFinished() -{ - if (d->resultCount > 0) { - QString msg = tr("Search completed, %1 matches found.").arg(d->resultCount); - showMsg(true, msg); - d->replaceBtn->setEnabled(true); - return; - } - - d->treeView->setVisible(false); - d->iconLabel->setVisible(true); - showMsg(false, tr("No match found!")); -} - -void SearchResultWindow::replaceFinished(bool success) -{ - QString msg = success ? tr("Replacement successful!") : tr("Replace failed!"); - showMsg(success, msg); -} - -void SearchResultWindow::clean() -{ - d->resultCount = 0; - d->treeView->clearData(); - emit reqBack(); -} - -void SearchResultWindow::replace() -{ - showMsg(true, tr("Replacing, please wait...")); - QString replaceText = d->replaceEdit->text(); - if (replaceText.isEmpty()) { - d->replaceTextFlag = false; - d->replaceTextDialog = new DDialog(this); - d->replaceTextDialog->setIcon(QIcon::fromTheme("dialog-warning")); - d->replaceTextDialog->setMessage(tr("Repalce text is empty, will continue?")); - d->replaceTextDialog->insertButton(0, tr("No")); - d->replaceTextDialog->insertButton(1, tr("Yes"), true, DDialog::ButtonRecommend); - - connect(d->replaceTextDialog, &DDialog::buttonClicked, this, [=](int index) { - if (index == 0) { - d->replaceTextFlag = true; - d->replaceTextDialog->reject(); - } else if (index == 1) { - d->replaceTextDialog->accept(); - } - }); - d->replaceTextDialog->exec(); - } - - if (d->replaceTextFlag) - return; - - Q_EMIT reqReplace(replaceText); -} - -void SearchResultWindow::showMsg(bool succeed, QString msg) -{ - QPalette palette = d->resultLabel->palette(); - QColor textColor = this->palette().color(QPalette::WindowText); - - int red, green, blue, alpha; - textColor.getRgb(&red, &green, &blue, &alpha); - // 降低透明度 - alpha = static_cast(alpha * 0.5); // 0.5 表示减少50% - - QColor newColor = QColor::fromRgb(red, green, blue, alpha); - - palette.setColor(QPalette::WindowText, newColor); - d->resultLabel->setPalette(palette); - d->resultLabel->setText(msg); -} diff --git a/src/plugins/find/searchresultwindow.h b/src/plugins/find/searchresultwindow.h deleted file mode 100644 index e79e70c8d..000000000 --- a/src/plugins/find/searchresultwindow.h +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef SEARCHRESULTWINDOW_H -#define SEARCHRESULTWINDOW_H - -#include "constants.h" - -#include -#include -#include -#include - -DWIDGET_USE_NAMESPACE - -class ItemProxy : public QObject -{ - Q_OBJECT -public: - explicit ItemProxy(QObject *parent = nullptr); - - void setRuningState(bool isRuning); - -public Q_SLOTS: - void addTask(const FindItemList &itemList); - -Q_SIGNALS: - void taskCompleted(const QList &itemList); - -private: - QAtomicInteger isRuning { false }; -}; - -class SearchResultTreeViewPrivate; -class SearchResultTreeView : public DTreeView -{ - Q_OBJECT - DFileIconProvider iconProvider; - -public: - explicit SearchResultTreeView(QWidget *parent = nullptr); - ~SearchResultTreeView(); - - void appendData(const FindItemList &itemList, const ProjectInfo &projectInfo); - void clearData(); - virtual QIcon icon(const QString &data); - -private Q_SLOTS: - void appendItems(const QList &itemList); - -private: - SearchResultTreeViewPrivate *const d; -}; - -class SearchResultWindowPrivate; -class SearchResultWindow : public DWidget -{ - Q_OBJECT -public: - explicit SearchResultWindow(QWidget *parent = nullptr); - ~SearchResultWindow(); - - void clear(); - void appendResults(const FindItemList &itemList, const ProjectInfo &projectInfo); - void searchFinished(); - void replaceFinished(bool success); - void setRepalceWidgtVisible(bool hide); - void showMsg(bool succeed, QString msg); - -signals: - void reqBack(); - void reqReplace(const QString &text); - -private: - void setupUi(); - void clean(); - void replace(); - - SearchResultWindowPrivate *const d; -}; - -#endif // SEARCHRESULTWINDOW_H diff --git a/src/plugins/find/texts/search-find_16px.svg b/src/plugins/find/texts/search-find_16px.svg deleted file mode 100644 index 1752db147..000000000 --- a/src/plugins/find/texts/search-find_16px.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - ICON / toolbar / find - - - - \ No newline at end of file diff --git a/src/plugins/find/transceiver/findreceiver.cpp b/src/plugins/find/transceiver/findreceiver.cpp deleted file mode 100644 index 720dcf692..000000000 --- a/src/plugins/find/transceiver/findreceiver.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "findreceiver.h" -#include "common/common.h" -#include "services/project/projectservice.h" - -FindReceiver::FindReceiver(QObject *parent) - : dpf::EventHandler(parent) -{ - -} - -dpf::EventHandler::Type FindReceiver::type() -{ - return dpf::EventHandler::Type::Sync; -} - -QStringList FindReceiver::topics() -{ - return { project.topic, editor.topic }; -} - -void FindReceiver::eventProcess(const dpf::Event &event) -{ - if (event.data() == project.activedProject.name) { - dpfservice::ProjectInfo projectInfo = qvariant_cast( - event.property(project.activedProject.pKeys[0])); - QString workspace = projectInfo.workspaceFolder(); - QString language = projectInfo.language(); - emit FindEventTransmit::instance()->sendProjectPath(workspace, language); - } else if(event.data() == project.openProject.name) { - dpfservice::ProjectInfo projectInfo = qvariant_cast( - event.property(project.openProject.pKeys[0])); - QString workspace = projectInfo.workspaceFolder(); - QString language = projectInfo.language(); - emit FindEventTransmit::instance()->sendProjectPath(workspace, language); - } else if (event.data() == project.deletedProject.name){ - dpfservice::ProjectInfo projectInfo = qvariant_cast( - event.property(project.deletedProject.pKeys[0])); - QString workspace = projectInfo.workspaceFolder(); - emit FindEventTransmit::instance()->sendRemovedProject(workspace); - } else if (event.data() == editor.fileOpened.name) { - QString filePath = event.property(editor.fileOpened.pKeys[0]).toString(); - emit FindEventTransmit::instance()->sendCurrentEditFile(filePath, true); - } else if (event.data() == editor.fileClosed.name) { - QString filePath = event.property(editor.switchedFile.pKeys[0]).toString(); - emit FindEventTransmit::instance()->sendCurrentEditFile(filePath, false); - } else if (event.data() == editor.switchedFile.name) { - QString filePath = event.property(editor.switchedFile.pKeys[0]).toString(); - emit FindEventTransmit::instance()->sendCurrentEditFile(filePath, true); - } -} - -FindEventTransmit::FindEventTransmit(QObject *parent) - : QObject(parent) -{ -} - -FindEventTransmit::~FindEventTransmit() -{ - -} - -FindEventTransmit* FindEventTransmit::instance() -{ - static FindEventTransmit instance; - return &instance; -} diff --git a/src/plugins/find/transceiver/findreceiver.h b/src/plugins/find/transceiver/findreceiver.h deleted file mode 100644 index bda8f0f10..000000000 --- a/src/plugins/find/transceiver/findreceiver.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef FINDRECEIVER_H -#define FINDRECEIVER_H - -#include - -class FindReceiver : public dpf::EventHandler, dpf::AutoEventHandlerRegister -{ - Q_OBJECT -public: - explicit FindReceiver(QObject *parent = nullptr); - static Type type(); - static QStringList topics(); - virtual void eventProcess(const dpf::Event& event) override; -}; - -class FindEventTransmit : public QObject -{ - Q_OBJECT - Q_DISABLE_COPY(FindEventTransmit) -public: - static FindEventTransmit* instance(); - -signals: - void sendProjectPath(const QString &projectPath, const QString &language); - void sendRemovedProject(const QString &projectPath); - void sendCurrentEditFile(const QString &filePath, bool actived); - -private: - explicit FindEventTransmit(QObject *parent = nullptr); - virtual ~FindEventTransmit(); -}; - - -#endif // FINDRECEIVER_H diff --git a/src/plugins/git/client/gitclient.cpp b/src/plugins/git/client/gitclient.cpp index baca35853..8ecac9d69 100644 --- a/src/plugins/git/client/gitclient.cpp +++ b/src/plugins/git/client/gitclient.cpp @@ -44,7 +44,7 @@ class GitClientPrivate : public QObject QString GitClientPrivate::findRepository(const QString &filePath) { - if (filePath == "/") + if (filePath == "/" || filePath.isEmpty()) return {}; QFileInfo info(filePath); @@ -245,6 +245,11 @@ bool GitClient::setupInstantBlame(const QString &filePath) return checkRepositoryExist(filePath); } +void GitClient::clearInstantBlame() +{ + d->ibWidget->clear(); +} + bool GitClient::gitLog(const QString &filePath, bool isProject) { QString repository; @@ -275,16 +280,17 @@ bool GitClient::gitDiff(const QString &filePath, bool isProject) return true; } -void GitClient::show(const QString &source, const QString &commitId) +bool GitClient::show(const QString &source, const QString &commitId) { if (!d->canShow(commitId)) - return; + return false; QString repository; if (!checkRepositoryExist(source, &repository)) - return; + return false; d->show(repository, commitId); + return true; } QWidget *GitClient::instantBlameWidget() const diff --git a/src/plugins/git/client/gitclient.h b/src/plugins/git/client/gitclient.h index 47ed76748..8b98d2f0f 100644 --- a/src/plugins/git/client/gitclient.h +++ b/src/plugins/git/client/gitclient.h @@ -21,10 +21,11 @@ class GitClient : public QObject bool checkRepositoryExist(const QString &filePath, QString *repository = nullptr); bool setupInstantBlame(const QString &filePath); + void clearInstantBlame(); bool gitLog(const QString &filePath, bool isProject); bool blameFile(const QString &filePath); bool gitDiff(const QString &filePath, bool isProject); - void show(const QString &source, const QString &commitId); + bool show(const QString &source, const QString &commitId); QWidget *instantBlameWidget() const; QWidget *gitTabWidget() const; diff --git a/src/plugins/git/git.json b/src/plugins/git/git.json index 7cdb264a0..f0e7350e4 100644 --- a/src/plugins/git/git.json +++ b/src/plugins/git/git.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Version Management", "Description" : "The git plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=656d40c5bd766615b0b02e64&id=664d802b685f572b03c2ac96", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=656d40c5bd766615b0b02e64&id=664d802b685f572b03c2ac96", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/git/gui/instantblamewidget.cpp b/src/plugins/git/gui/instantblamewidget.cpp index 538d0a2c3..85c59d985 100644 --- a/src/plugins/git/gui/instantblamewidget.cpp +++ b/src/plugins/git/gui/instantblamewidget.cpp @@ -55,6 +55,12 @@ void InstantBlameWidget::setInfo(const QString &info) label->setText(format.arg(ret.author, ret.authorTime.toString("yyyy-MM-dd"))); } +void InstantBlameWidget::clear() +{ + label->clear(); + label->setToolTip(""); +} + void InstantBlameWidget::mousePressEvent(QMouseEvent *event) { const auto &info = label->toolTip(); @@ -65,8 +71,8 @@ void InstantBlameWidget::mousePressEvent(QMouseEvent *event) if (editSrv) { auto file = editSrv->currentFile(); auto ret = parserBlameOutput(info.split('\n')); - GitClient::instance()->show(file, ret.sha1); - editSrv->switchWidget(GitWindow); + if (GitClient::instance()->show(file, ret.sha1)) + editSrv->switchWidget(GitWindow); } QWidget::mousePressEvent(event); diff --git a/src/plugins/git/gui/instantblamewidget.h b/src/plugins/git/gui/instantblamewidget.h index b18519b7b..74dfa7fff 100644 --- a/src/plugins/git/gui/instantblamewidget.h +++ b/src/plugins/git/gui/instantblamewidget.h @@ -17,6 +17,7 @@ class InstantBlameWidget : public QWidget explicit InstantBlameWidget(QWidget *parent = nullptr); void setInfo(const QString &info); + void clear(); protected: void mousePressEvent(QMouseEvent *event) override; diff --git a/src/plugins/git/transceiver/gitreceiver.cpp b/src/plugins/git/transceiver/gitreceiver.cpp index 36ca660a3..caa975e87 100644 --- a/src/plugins/git/transceiver/gitreceiver.cpp +++ b/src/plugins/git/transceiver/gitreceiver.cpp @@ -17,7 +17,7 @@ GitReceiver::GitReceiver(QObject *parent) using namespace std::placeholders; eventHandleMap.insert(editor.switchedFile.name, std::bind(&GitReceiver::handleSwitchedFileEvent, this, _1)); eventHandleMap.insert(editor.contextMenu.name, std::bind(&GitReceiver::handleContextMenuEvent, this, _1)); - eventHandleMap.insert(project.activedProject.name, std::bind(&GitReceiver::handleProjectChangedEvent, this, _1)); + eventHandleMap.insert(project.activatedProject.name, std::bind(&GitReceiver::handleProjectChangedEvent, this, _1)); eventHandleMap.insert(project.deletedProject.name, std::bind(&GitReceiver::handleProjectChangedEvent, this, _1)); } @@ -67,6 +67,7 @@ void GitReceiver::handleSwitchedFileEvent(const dpf::Event &event) eventHandleMap.insert(editor.cursorPositionChanged.name, std::bind(&GitReceiver::handleCursorPositionChangedEvent, this, std::placeholders::_1)); } else if (eventHandleMap.contains(editor.cursorPositionChanged.name)) { eventHandleMap.remove(editor.cursorPositionChanged.name); + GitClient::instance()->clearInstantBlame(); } } diff --git a/src/plugins/git/utils/gitmenumanager.cpp b/src/plugins/git/utils/gitmenumanager.cpp index b1ffaebb1..f042ec002 100644 --- a/src/plugins/git/utils/gitmenumanager.cpp +++ b/src/plugins/git/utils/gitmenumanager.cpp @@ -56,17 +56,9 @@ void GitMenuManager::initialize(dpfservice::WindowService *service) if (!service) return; - auto initAction = [&](QAction *action, const QString &id = QString(), - const QString &description = QString(), - const QKeySequence &key = QKeySequence()) -> AbstractAction * { - auto actionImpl = new AbstractAction(action, this); - if (!key.isEmpty()) - actionImpl->setShortCutInfo(id, description, key); - return actionImpl; - }; - + winSrv = service; gitAct = new QAction("&Git", this); - auto gitActImpl = initAction(gitAct); + auto gitActImpl = new AbstractAction(gitAct, this); service->addAction(MWM_TOOLS, gitActImpl); createGitSubMenu(); @@ -141,12 +133,15 @@ void GitMenuManager::createFileSubMenu() { fileLogAct = new QAction(this); connect(fileLogAct, &QAction::triggered, this, std::bind(&GitMenuManager::actionHandler, this, fileLogAct, GitLog)); + registerShortcut(fileLogAct, "Git.log", tr("Git Log"), QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_L)); fileBlameAct = new QAction(this); connect(fileBlameAct, &QAction::triggered, this, std::bind(&GitMenuManager::actionHandler, this, fileBlameAct, GitBlame)); + registerShortcut(fileBlameAct, "Git.blame", tr("Git Blame"), QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_B)); fileDiffAct = new QAction(this); connect(fileDiffAct, &QAction::triggered, this, std::bind(&GitMenuManager::actionHandler, this, fileDiffAct, GitDiff)); + registerShortcut(fileDiffAct, "Git.diff", tr("Git Diff"), QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_D)); fileSubMenu.addAction(fileLogAct); fileSubMenu.addAction(fileBlameAct); @@ -166,3 +161,10 @@ void GitMenuManager::createProjectSubMenu() projectSubMenu.addAction(projectLogAct); projectSubMenu.addAction(projectDiffAct); } + +void GitMenuManager::registerShortcut(QAction *act, const QString &id, const QString &description, const QKeySequence &shortCut) +{ + auto actImpl = new AbstractAction(act, qApp); + actImpl->setShortCutInfo(id, description, shortCut); + winSrv->addAction(tr("&Git"), actImpl); +} diff --git a/src/plugins/git/utils/gitmenumanager.h b/src/plugins/git/utils/gitmenumanager.h index ec9ebb6e8..82f9c76b1 100644 --- a/src/plugins/git/utils/gitmenumanager.h +++ b/src/plugins/git/utils/gitmenumanager.h @@ -34,9 +34,11 @@ class GitMenuManager : public QObject void createGitSubMenu(); void createFileSubMenu(); void createProjectSubMenu(); + void registerShortcut(QAction *act, const QString &id, const QString &description, const QKeySequence &shortCut); private: dpfservice::EditorService *editSrv { nullptr }; + dpfservice::WindowService *winSrv { nullptr }; QMenu gitSubMenu; QMenu fileSubMenu; diff --git a/src/plugins/java/CMakeLists.txt b/src/plugins/java/CMakeLists.txt index 4ef5e1d43..c69ae8242 100644 --- a/src/plugins/java/CMakeLists.txt +++ b/src/plugins/java/CMakeLists.txt @@ -24,6 +24,7 @@ set(CXX_CPP gradle/project/gradleprojectgenerator.cpp gradle/project/properties/gradleconfigpropertywidget.cpp gradle/project/properties/gradleconfigutil.cpp + gradle/project/transceiver/projectgradlereciver.cpp gradle/option/gradlewidget.cpp gradle/option/gradleoptionwidget.cpp gradle/option/optiongradlegenerator.cpp @@ -57,6 +58,7 @@ set(CXX_H gradle/project/gradleprojectgenerator.h gradle/project/properties/gradleconfigpropertywidget.h gradle/project/properties/gradleconfigutil.h + gradle/project/transceiver/projectgradlereciver.h gradle/option/gradlewidget.h gradle/option/gradleoptionwidget.h gradle/option/optiongradlegenerator.h diff --git a/src/plugins/java/gradle/gradlegenerator.cpp b/src/plugins/java/gradle/gradlegenerator.cpp index 105c0da3c..56251df8b 100644 --- a/src/plugins/java/gradle/gradlegenerator.cpp +++ b/src/plugins/java/gradle/gradlegenerator.cpp @@ -122,6 +122,7 @@ dpfservice::RunCommandInfo GradleGenerator::getRunArguments(const dpfservice::Pr runCommandInfo.program = "java"; runCommandInfo.arguments << JavaUtil::getMainClass(mainClassPath, packageDirName); runCommandInfo.workingDir = JavaUtil::getPackageDir(mainClassPath, packageDirName); + runCommandInfo.runInTerminal = false; // todo : config "run in terminal" return runCommandInfo; } diff --git a/src/plugins/java/gradle/project/gradleprojectgenerator.cpp b/src/plugins/java/gradle/project/gradleprojectgenerator.cpp index dccebb25b..6138f1a1b 100644 --- a/src/plugins/java/gradle/project/gradleprojectgenerator.cpp +++ b/src/plugins/java/gradle/project/gradleprojectgenerator.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "gradleprojectgenerator.h" +#include "transceiver/projectgradlereciver.h" #include "gradleasynparse.h" #include "properties/gradleconfigpropertywidget.h" @@ -53,6 +54,7 @@ class GradleProjectGeneratorPrivate QMenu *gradleMenu {nullptr}; QProcess *menuGenProcess {nullptr}; QHash projectParses {}; + QSet toExpand; private: GradleTasks parseTasks(const QByteArray &data) @@ -89,6 +91,17 @@ GradleProjectGenerator::GradleProjectGenerator() qCritical() << "Failed, not found service : projectService"; abort(); } + QObject::connect(ProjectGradleProxy::instance(), + &ProjectGradleProxy::nodeExpanded, + this, [this](const QString &filePath){ + d->toExpand.insert(filePath); + }); + + QObject::connect(ProjectGradleProxy::instance(), + &ProjectGradleProxy::nodeCollapsed, + this, [this](const QString &filePath){ + d->toExpand.remove(filePath); + }); } GradleProjectGenerator::~GradleProjectGenerator() @@ -149,10 +162,15 @@ QStandardItem *GradleProjectGenerator::createRootItem(const dpfservice::ProjectI QStandardItem * rootItem = ProjectGenerator::createRootItem(info); dpfservice::ProjectInfo::set(rootItem, info); d->projectParses[rootItem] = new GradleAsynParse(); - QObject::connect(d->projectParses[rootItem], &GradleAsynParse::itemsModified, - this, &GradleProjectGenerator::doProjectChildsModified); + + QObject::connect(d->projectParses[rootItem], &GradleAsynParse::itemsModified, this, [=](const QList &items){ + doProjectChildsModified(items); + auto prjService = dpfGetService(ProjectService); + prjService->expandItemByFile(d->toExpand.toList()); + }); QMetaObject::invokeMethod(d->projectParses[rootItem], "parseProject", Q_ARG(const dpfservice::ProjectInfo &, info)); + return rootItem; } @@ -328,6 +346,7 @@ void GradleProjectGenerator::doGradleTaskActionTriggered() void GradleProjectGenerator::actionProperties(const dpfservice::ProjectInfo &info, QStandardItem *item) { PropertiesDialog dlg; + dlg.setCurrentTitle(info.currentProgram() + " - " + tr("Project Properties")); GradleConfigPropertyWidget *property = new GradleConfigPropertyWidget(info, item); dlg.insertPropertyPanel("Config", property); dlg.exec(); diff --git a/src/plugins/java/gradle/project/transceiver/projectgradlereciver.cpp b/src/plugins/java/gradle/project/transceiver/projectgradlereciver.cpp new file mode 100644 index 000000000..f601c52bf --- /dev/null +++ b/src/plugins/java/gradle/project/transceiver/projectgradlereciver.cpp @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "projectgradlereciver.h" +#include "common/common.h" + +#include "services/project/projectservice.h" +#include "services/window/windowelement.h" +#include "services/project/projectgenerator.h" +#include "services/builder/builderglobals.h" + +ProjectGradleReceiver::ProjectGradleReceiver(QObject *parent) + : dpf::EventHandler (parent) + , dpf::AutoEventHandlerRegister () +{ + +} + +dpf::EventHandler::Type ProjectGradleReceiver::type() +{ + return dpf::EventHandler::Type::Sync; +} + +QStringList ProjectGradleReceiver::topics() +{ + return { T_BUILDER, project.topic}; +} + +void ProjectGradleReceiver::eventProcess(const dpf::Event &event) +{ + if (event.data() == project.projectNodeExpanded.name) { + auto index = event.property("modelIndex").value(); + auto filePath = index.data(Qt::ToolTipRole).toString(); + emit ProjectGradleProxy::instance()->nodeExpanded(filePath); + } + + if (event.data() == project.projectNodeCollapsed.name) { + auto index = event.property("modelIndex").value(); + auto filePath = index.data(Qt::ToolTipRole).toString(); + emit ProjectGradleProxy::instance()->nodeCollapsed(filePath); + } +} + +ProjectGradleProxy *ProjectGradleProxy::instance() +{ + static ProjectGradleProxy ins; + return &ins; +} diff --git a/src/plugins/java/gradle/project/transceiver/projectgradlereciver.h b/src/plugins/java/gradle/project/transceiver/projectgradlereciver.h new file mode 100644 index 000000000..ce35580a9 --- /dev/null +++ b/src/plugins/java/gradle/project/transceiver/projectgradlereciver.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef PROJECTGRADLERECEIVER_H +#define PROJECTGRADLERECEIVER_H + +#include "common/project/projectinfo.h" +#include "services/builder/builderglobals.h" + +#include + +class ProjectGradleReceiver : public dpf::EventHandler, + dpf::AutoEventHandlerRegister +{ + Q_OBJECT + friend class dpf::AutoEventHandlerRegister; +public: + explicit ProjectGradleReceiver(QObject * parent = nullptr); + + static Type type(); + + static QStringList topics(); + + virtual void eventProcess(const dpf::Event& event) override; +}; + +class ProjectGradleProxy : public QObject +{ + Q_OBJECT + ProjectGradleProxy(){} + ProjectGradleProxy(const ProjectGradleProxy&) = delete; + +public: + static ProjectGradleProxy* instance(); + +signals: + void nodeExpanded(const QString &filePath); + void nodeCollapsed(const QString &filePath); + +}; + +#endif // PROJECTGRADLERECEIVER_H diff --git a/src/plugins/java/javaplugin.json b/src/plugins/java/javaplugin.json index cde2f8de0..f63714a64 100644 --- a/src/plugins/java/javaplugin.json +++ b/src/plugins/java/javaplugin.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Languages", "Description" : "The java plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=664d969d685f572b03c2aca9&id=664d9716685f572b03c2acb1", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=664d969d685f572b03c2aca9&id=664d9716685f572b03c2acb1", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/java/javautil.cpp b/src/plugins/java/javautil.cpp index 6c5ce1f9c..505dd560c 100644 --- a/src/plugins/java/javautil.cpp +++ b/src/plugins/java/javautil.cpp @@ -3,9 +3,11 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "javautil.h" +#include "services/window/windowservice.h" #include #include +#include QString JavaUtil::getMainClassPath(const QDir &dir) { @@ -26,6 +28,12 @@ QString JavaUtil::getMainClassPath(const QDir &dir) if (entry.suffix().toLower() == "class") { qInfo() << entry.fileName(); + if (QStandardPaths::findExecutable("javap").isEmpty()) { + auto winSrv = dpfGetService(dpfservice::WindowService); + winSrv->notify(2, "Java", tr("Unable to find the javap application. Please install openjdk-11-jdk or another version first."), {}); + return {}; + } + QProcess process; auto temp = entry.filePath(); process.start("javap " + entry.filePath()); diff --git a/src/plugins/java/maven/mavengenerator.cpp b/src/plugins/java/maven/mavengenerator.cpp index 4f099961a..684bf66af 100644 --- a/src/plugins/java/maven/mavengenerator.cpp +++ b/src/plugins/java/maven/mavengenerator.cpp @@ -118,6 +118,7 @@ RunCommandInfo MavenGenerator::getRunArguments(const ProjectInfo &projectInfo, c runCommandInfo.program = "java"; runCommandInfo.arguments << JavaUtil::getMainClass(mainClassPath, packageDirName); runCommandInfo.workingDir = JavaUtil::getPackageDir(mainClassPath, packageDirName); + runCommandInfo.runInTerminal = false; // todo : config "run in terminal" return runCommandInfo; } diff --git a/src/plugins/java/maven/project/mavenprojectgenerator.cpp b/src/plugins/java/maven/project/mavenprojectgenerator.cpp index e729e1335..d4e05ff03 100644 --- a/src/plugins/java/maven/project/mavenprojectgenerator.cpp +++ b/src/plugins/java/maven/project/mavenprojectgenerator.cpp @@ -214,6 +214,7 @@ void MavenProjectGenerator::doActionTriggered() void MavenProjectGenerator::actionProperties(const dpfservice::ProjectInfo &info, QStandardItem *item) { PropertiesDialog dlg; + dlg.setCurrentTitle(info.currentProgram() + " - " + tr("Project Properties")); MavenConfigPropertyWidget *property = new MavenConfigPropertyWidget(info, item); dlg.insertPropertyPanel("Config", property); dlg.exec(); diff --git a/src/plugins/javascript/debugger/jsdebugger.cpp b/src/plugins/javascript/debugger/jsdebugger.cpp index 62dae5b21..c247ded94 100644 --- a/src/plugins/javascript/debugger/jsdebugger.cpp +++ b/src/plugins/javascript/debugger/jsdebugger.cpp @@ -67,6 +67,11 @@ void JSDebugger::startDebugRemote(const RemoteInfo &info) Q_UNUSED(info) } +void JSDebugger::attachDebug(const QString &processId) +{ + Q_UNUSED(processId); +} + void JSDebugger::detachDebug() { } diff --git a/src/plugins/javascript/debugger/jsdebugger.h b/src/plugins/javascript/debugger/jsdebugger.h index 7169b6724..82db0edea 100644 --- a/src/plugins/javascript/debugger/jsdebugger.h +++ b/src/plugins/javascript/debugger/jsdebugger.h @@ -29,6 +29,7 @@ class JSDebugger : public AbstractDebugger void startDebug() override; void startDebugRemote(const RemoteInfo &info) override; + void attachDebug(const QString &processId) override; void detachDebug() override; void interruptDebug() override; diff --git a/src/plugins/javascript/javascriptplugin.json b/src/plugins/javascript/javascriptplugin.json index acfa8fb18..9b23dbea1 100644 --- a/src/plugins/javascript/javascriptplugin.json +++ b/src/plugins/javascript/javascriptplugin.json @@ -3,12 +3,12 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2023 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Languages", "Description" : "The javascript plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=664d969d685f572b03c2aca9&id=664d9734685f572b03c2acb4", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=664d969d685f572b03c2aca9&id=664d9734685f572b03c2acb4", "Depends" : [{"Name" : "debugger"}] } diff --git a/src/plugins/javascript/jsgenerator.cpp b/src/plugins/javascript/jsgenerator.cpp index d5fce4036..9fcbade12 100644 --- a/src/plugins/javascript/jsgenerator.cpp +++ b/src/plugins/javascript/jsgenerator.cpp @@ -131,5 +131,5 @@ RunCommandInfo JSGenerator::getRunArguments(const ProjectInfo &projectInfo, cons if (!QFile::exists(currentFile)) { OutputPane::instance()->appendText(tr("Please open a JS file in editor!"), OutputPane::ErrorMessage); } - return {"node", {currentFile}, ""}; + return {"node", {currentFile}, "", {}, false}; } diff --git a/src/plugins/linglong/CMakeLists.txt b/src/plugins/linglong/CMakeLists.txt new file mode 100644 index 000000000..582c0e30f --- /dev/null +++ b/src/plugins/linglong/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.0.2) + +project(linglong) + +FILE(GLOB_RECURSE PROJECT_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/*/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/*/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/*.json" +) + +find_package(yaml-cpp REQUIRED) + +add_library(${PROJECT_NAME} + SHARED + ${PROJECT_SOURCES} + resource.qrc + ) + +target_link_libraries(${PROJECT_NAME} + framework + base + services + common + ${QtUseModules} + ${PkgUserModules} + yaml-cpp + ) + +install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${PLUGIN_INSTALL_PATH}) + + diff --git a/src/plugins/linglong/builder/mainframe/llbuildergenerator.cpp b/src/plugins/linglong/builder/mainframe/llbuildergenerator.cpp new file mode 100644 index 000000000..e4aeb6ade --- /dev/null +++ b/src/plugins/linglong/builder/mainframe/llbuildergenerator.cpp @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "llbuildergenerator.h" +#include "builder/parser/llparser.h" +#include "services/window/windowservice.h" +#include "services/builder/builderservice.h" +#include "services/option/optionmanager.h" + +using namespace dpfservice; +using DTK_WIDGET_NAMESPACE::DDialog; + +LLBuilderGenerator::LLBuilderGenerator() +{ +} + +LLBuilderGenerator::~LLBuilderGenerator() +{ +} + +BuildCommandInfo LLBuilderGenerator::getMenuCommand(const BuildMenuType buildMenuType, const dpfservice::ProjectInfo &projectInfo) +{ + Q_UNUSED(buildMenuType) + BuildCommandInfo info; + info.kitName = projectInfo.kitName(); + info.workingDir = projectInfo.workspaceFolder(); + info.program = "ll-builder"; + info.arguments.append("build"); + + return info; +} + +void LLBuilderGenerator::appendOutputParser(std::unique_ptr &outputParser) +{ + if (outputParser) { + outputParser->takeOutputParserChain(); + outputParser->appendOutputParser(new LLParser()); + } +} + +bool LLBuilderGenerator::checkCommandValidity(const BuildCommandInfo &info, QString &retMsg) +{ + QProcess process; + process.start("which", { "ll-builder" }); + process.waitForFinished(); + if (process.exitCode() != 0) { + DDialog dialog; + dialog.setWindowTitle("Warning"); + dialog.setIcon(QIcon::fromTheme("dialog-warning")); + dialog.setMessage(tr("Can`t find linglong-builder tool")); + dialog.exec(); + return false; + } + + if (!QFileInfo(info.workingDir.trimmed()).exists()) { + retMsg = tr("The path of \"%1\" is not exist! " + "please check and reopen the project.") + .arg(info.workingDir); + return false; + } + + return true; +} diff --git a/src/plugins/linglong/builder/mainframe/llbuildergenerator.h b/src/plugins/linglong/builder/mainframe/llbuildergenerator.h new file mode 100644 index 000000000..268f4c2bb --- /dev/null +++ b/src/plugins/linglong/builder/mainframe/llbuildergenerator.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LLBUILDERGENERATOR_H +#define LLBUILDERGENERATOR_H + +#include "llglobal.h" +#include "services/builder/buildergenerator.h" + +class LLBuilderGenerator : public dpfservice::BuilderGenerator +{ + Q_OBJECT +public: + explicit LLBuilderGenerator(); + virtual ~LLBuilderGenerator() override; + + static QString toolKitName() { return LL_TOOLKIT; } + + BuildCommandInfo getMenuCommand(const BuildMenuType buildMenuType, const dpfservice::ProjectInfo &projectInfo) override; + void appendOutputParser(std::unique_ptr &outputParser) override; + bool checkCommandValidity(const BuildCommandInfo &info, QString &retMsg) override; +}; + +#endif // LLBUILDERGENERATOR_H diff --git a/src/plugins/linglong/builder/parser/llparser.cpp b/src/plugins/linglong/builder/parser/llparser.cpp new file mode 100644 index 000000000..e8d6618f3 --- /dev/null +++ b/src/plugins/linglong/builder/parser/llparser.cpp @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "llparser.h" + +#include "common/type/task.h" +#include "common/util/fileutils.h" + +const char TASK_CATEGORY_BUILDSYSTEM[] = "Task.Category.Buildsystem"; + +LLParser::LLParser() +{ + setObjectName(QLatin1String("LLParser")); +} + +void LLParser::stdOutput(const QString &line, OutputPane::OutputFormat format) +{ + QString newContent = line; + emit outputAdded(newContent, format); + + AbstractOutputParser::stdOutput(newContent, format); +} + +void LLParser::stdError(const QString &line) +{ + QString newContent = line; + QRegExp exp("/.*:(\\d*):"); + int ret = newContent.indexOf(exp); + QString filePath; + int lineNumber = -1; + if (ret != -1) { + QStringList list = newContent.split(":"); + if (list.count() > 1) { + filePath = list.at(0); + lineNumber = list.at(1).toInt(); + } + } + + Utils::FileName fileName; + if (QFileInfo(filePath).isFile()) { + fileName = Utils::FileName::fromUserInput(filePath); + } else { + fileName = Utils::FileName(); + } + + taskAdded(Task(Task::Error, + line, + fileName, + lineNumber, + TASK_CATEGORY_BUILDSYSTEM), + 1, 0); +} + +void LLParser::taskAdded(const Task &task, int linkedLines, int skippedLines) +{ + emit addTask(task, linkedLines, skippedLines); +} diff --git a/src/plugins/linglong/builder/parser/llparser.h b/src/plugins/linglong/builder/parser/llparser.h new file mode 100644 index 000000000..85083204a --- /dev/null +++ b/src/plugins/linglong/builder/parser/llparser.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LLPARSER_H +#define LLPARSER_H + +#include "base/abstractoutputparser.h" + +class LLParser : public AbstractOutputParser +{ + Q_OBJECT + +public: + explicit LLParser(); + + void stdOutput(const QString &line, OutputPane::OutputFormat format) override; + void stdError(const QString &line) override; + void taskAdded(const Task &task, int linkedLines, int skippedLines) override; + +private: +}; + +#endif // NINJAPARSER_H diff --git a/src/plugins/linglong/builtin/texts/linglong_22px.svg b/src/plugins/linglong/builtin/texts/linglong_22px.svg new file mode 100644 index 000000000..735e60d28 --- /dev/null +++ b/src/plugins/linglong/builtin/texts/linglong_22px.svg @@ -0,0 +1,7 @@ + + + ICON / sidebar /linglong_22px + + + + \ No newline at end of file diff --git a/src/plugins/linglong/gui/generatedialog.cpp b/src/plugins/linglong/gui/generatedialog.cpp new file mode 100644 index 000000000..6855a17be --- /dev/null +++ b/src/plugins/linglong/gui/generatedialog.cpp @@ -0,0 +1,254 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "generatedialog.h" +#include "common/util/eventdefinitions.h" +#include "llglobal.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +const static QString Depends_Version_V20 = "V20"; +const static QString Depends_Version_V23 = "V23"; + +DWIDGET_USE_NAMESPACE +class GenerateDialogPrivate +{ + friend class GenerateDialog; + + DLineEdit *pathEdit { nullptr }; + DPushButton *browse { nullptr }; + DLineEdit *idEdit { nullptr }; + DLineEdit *nameEdit { nullptr }; + DLineEdit *versionEdit { nullptr }; + DComboBox *dependsCbBox { nullptr }; + DComboBox *kindCbBox { nullptr }; + DLineEdit *descriptionEdit { nullptr }; + DLineEdit *commandEdit { nullptr }; + DRadioButton *localSource { nullptr }; + DRadioButton *remoteSource { nullptr }; + QFormLayout *formLayout { nullptr }; + + DDialog *newSourceDialog { nullptr }; + + DPushButton *cancel { nullptr }; + DSuggestButton *confirm { nullptr }; +}; + +GenerateDialog::GenerateDialog(QWidget *parent) + : DDialog(parent), d(new GenerateDialogPrivate()) +{ + setWindowTitle(tr("New LingLong Project")); + setIcon(QIcon::fromTheme("ide")); + + initUi(); + initConnection(); +} + +void GenerateDialog::initUi() +{ + auto mainWidget = new DWidget(this); + auto mainLayout = new QVBoxLayout(mainWidget); + + d->formLayout = new QFormLayout; + d->formLayout->setSpacing(10); + + d->pathEdit = new DLineEdit(this); + d->browse = new DPushButton(this); + d->browse->setIcon(DStyle::standardIcon(style(), DStyle::SP_SelectElement)); + auto pathLayout = new QHBoxLayout; + pathLayout->setSpacing(10); + pathLayout->addWidget(d->pathEdit); + pathLayout->addWidget(d->browse); + + d->idEdit = new DLineEdit(this); + d->idEdit->setPlaceholderText("org.deepin.hello"); + d->nameEdit = new DLineEdit(this); + d->nameEdit->setPlaceholderText(tr("Please input application`s name")); + d->versionEdit = new DLineEdit(this); + d->versionEdit->setPlaceholderText(tr("0.0.0.1")); + d->kindCbBox = new DComboBox(this); + d->kindCbBox->addItem("App"); + d->dependsCbBox = new DComboBox(this); + d->dependsCbBox->addItems({ Depends_Version_V20, Depends_Version_V23 }); + d->descriptionEdit = new DLineEdit(this); + d->descriptionEdit->setPlaceholderText(tr("Please input description")); + d->commandEdit = new DLineEdit(this); + d->commandEdit->setText("echo,-e,hello world"); + + auto hLayout = new QHBoxLayout; + hLayout->setSpacing(20); + hLayout->addWidget(d->versionEdit); + hLayout->addWidget(new QLabel(tr("Kind:"), this)); + hLayout->addWidget(d->kindCbBox); + + auto sourceLayout = new QVBoxLayout; + d->localSource = new DRadioButton(tr("Local\nneed to manually copy the source code to this path"), this); + d->remoteSource = new DRadioButton(tr("Remote"), this); + sourceLayout->addWidget(d->localSource); + sourceLayout->addWidget(d->remoteSource); + d->remoteSource->setChecked(true); + + d->formLayout->addRow(new QLabel(tr("ID: "), this), d->idEdit); + d->formLayout->addRow(new QLabel(tr("Name "), this), d->nameEdit); + d->formLayout->addRow(new QLabel(tr("Project Path: "), this), pathLayout); + d->formLayout->addRow(new QLabel(tr("Version: "), this), hLayout); + d->formLayout->addRow(new QLabel(tr("Description: "), this), d->descriptionEdit); + d->formLayout->addRow(new QLabel(tr("Depends : "), this), d->dependsCbBox); + d->formLayout->addRow(new QLabel(tr("Execute Command: "), this), d->commandEdit); + d->formLayout->addRow(new QLabel(tr("Source Type: "), this), sourceLayout); + + mainLayout->addLayout(d->formLayout); + + auto btnLayout = new QHBoxLayout; + btnLayout->setContentsMargins(0, 20, 0, 0); + d->cancel = new DPushButton(tr("Cancel"), this); + d->confirm = new DSuggestButton(tr("Confirm"), this); + DVerticalLine *vLine = new DVerticalLine(this); + vLine->setFixedHeight(30); + btnLayout->addWidget(d->cancel); + btnLayout->addWidget(vLine); + btnLayout->addWidget(d->confirm); + mainLayout->addLayout(btnLayout, Qt::AlignCenter); + + addContent(mainWidget); +} + +void GenerateDialog::initConnection() +{ + connect(d->confirm, &DSuggestButton::clicked, this, &GenerateDialog::generate); + connect(d->cancel, &QPushButton::clicked, this, &GenerateDialog::reject); + connect(d->browse, &QPushButton::clicked, this, [=]() { + QString path = DFileDialog::getExistingDirectory(this, tr("Choose path"), QDir::homePath()); + if (!path.isEmpty()) + d->pathEdit->setText(path); + }); +} + +void GenerateDialog::generate() +{ + if (!checkFiledsInfo()) + return; + + YAML::Node version; + version["version"] = "\"1\""; + + YAML::Node package; + YAML::Node content; + content["id"] = d->idEdit->text().toStdString(); + content["name"] = d->nameEdit->text().toStdString(); + content["version"] = d->versionEdit->text().toStdString(); + content["kind"] = d->kindCbBox->currentText().toStdString(); + content["description"] = d->descriptionEdit->text().toStdString(); + package["package"] = content; + + YAML::Node base; + YAML::Node runtime; + if (d->dependsCbBox->currentText() == Depends_Version_V20) { + base["base"] = "org.deepin.foundation/20.0.0"; + runtime["runtime"] = "org.deepin.Runtime/20.0.0"; + } else { + base["base"] = "org.deepin.foundation/23.0.0"; + runtime["runtime"] = "org.deepin.Runtime/23.0.1"; + } + YAML::Node command; + for (auto cmd : d->commandEdit->text().split(",")) + command["command"].push_back(cmd.toStdString()); + + YAML::Node build; + build["build"] = "echo 'hello'"; + + YAML::Node sources; + YAML::Node depend; + depend["kind"] = "file"; + depend["url"] = "https://pools.uniontech.com/deepin-beige/pool/main/a/acl/libacl1_2.3.1-1_amd64.deb"; + depend["digest"] = "f06e936eb913b8e9271c17e6d8b94d9e4f0aa558d7debdc324c9484908ee8dc8"; + YAML::Node source; + source["kind"] = "git"; + source["url"] = "https://github.com/linuxdeepin/linglong-builder-demo.git"; + source["version"] = "master"; + source["commit"] = "a3b89c3aa34c1aff8d7f823f0f4a87d5da8d4dc0"; + sources["source"].push_back(depend); + + if (d->remoteSource->isChecked()) + sources["source"].push_back(source); + + QString path = d->pathEdit->text() + QDir::separator() + d->idEdit->text(); + QDir newDir(path); + if (newDir.exists()) { + DDialog dialog; + dialog.setMessage(tr("%1 is already existed").arg(d->idEdit->text())); + dialog.setIcon(QIcon::fromTheme("dialog-warning")); + dialog.exec(); + return; + } + + if (!QDir().mkdir(path)) { + qWarning() << "Failed to create: " << path; + return; + } + + auto yamlFilePath = path + "/linglong.yaml"; + std::ofstream file(yamlFilePath.toStdString()); + file << "version: " + << "\"1\"" + << "\n\n" + << package << "\n\n" + << base << "\n" + << "#" << runtime << "\n\n" + << command << "\n\n" + << "#set the sources if you need, a simple example as follows\n" + << sources << "\n\n" + << build << " #some operation to build this project"; + file.close(); + + project.openProject(LL_TOOLKIT, LL_LANGUAGE, path); + accept(); +} + +bool GenerateDialog::checkFiledsInfo() +{ + QList editList { d->idEdit, d->nameEdit, d->versionEdit, d->descriptionEdit, d->commandEdit }; + bool ret = true; + for (auto edit : editList) { + if (edit->text().isEmpty()) { + edit->setPlaceholderText(tr("This item can not be empty")); + ret = false; + } + } + + DDialog dialog; + dialog.setIcon(QIcon::fromTheme("dialog-warning")); + auto path = d->pathEdit->text(); + QDir dir(path); + if (path.isEmpty() || !dir.exists()) { + d->pathEdit->setText(""); + dialog.setMessage(tr("Project Path is empty or not exist")); + dialog.exec(); + ret = false; + return ret; + } + + QRegularExpression regex("^(\\d+\\.){3}\\d+$"); + auto match = regex.match(d->versionEdit->text()); + if (!match.hasMatch()) { + dialog.setMessage(tr("Version must be in the format of *.*.*.*")); + ret = false; + dialog.exec(); + } + + return ret; +} diff --git a/src/plugins/linglong/gui/generatedialog.h b/src/plugins/linglong/gui/generatedialog.h new file mode 100644 index 000000000..8ad19b529 --- /dev/null +++ b/src/plugins/linglong/gui/generatedialog.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef GENERATEDIALOG_H +#define GENERATEDIALOG_H + +#include + +class GenerateDialogPrivate; +class GenerateDialog : public DTK_WIDGET_NAMESPACE::DDialog +{ + Q_OBJECT +public: + GenerateDialog(QWidget *parent = nullptr); + +private: + GenerateDialogPrivate *d; + + bool checkFiledsInfo(); + void initUi(); + void initConnection(); + void generate(); +}; + +#endif // GENERATEDIALOG_H diff --git a/src/plugins/linglong/gui/mainframe.cpp b/src/plugins/linglong/gui/mainframe.cpp new file mode 100644 index 000000000..8a095dd3d --- /dev/null +++ b/src/plugins/linglong/gui/mainframe.cpp @@ -0,0 +1,396 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "mainframe.h" +#include "generatedialog.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +DWIDGET_USE_NAMESPACE +class MainFramePrivate +{ + friend class MainFrame; + + DPushButton *generateBtn { nullptr }; + GenerateDialog genDialog; + DButtonBox *btnBox { nullptr }; + DButtonBoxButton *installedBtn { nullptr }; + DButtonBoxButton *repositoryBtn { nullptr }; + DButtonBoxButton *runningBtn { nullptr }; + DStackedWidget *stackWidget { nullptr }; + + DWidget *search { nullptr }; + DLineEdit *searchEdit { nullptr }; + DPushButton *searchBtn { nullptr }; + + DTableWidget *installedTabel { nullptr }; + DTableWidget *repTable { nullptr }; + DTableWidget *runningTable { nullptr }; + std::unique_ptr process; +}; + +bool MainFrame::checkToolInstalled(const QString &tool) +{ + QProcess process; + process.start("which", QStringList() << tool); + process.waitForFinished(); + if (process.exitCode() != 0) { + QString message = QString("Can not find tool named: %1\n").arg(tool); + AppOutputPane::instance()->defaultPane()->appendText(message, OutputPane::ErrorMessage); + message = "Check the repository source, and install the linglong-builder, linglong-box, and linglong-bin tools.\n"; + AppOutputPane::instance()->defaultPane()->appendText(message, OutputPane::ErrorMessage); + return false; + } + return true; +} + +MainFrame::MainFrame(QWidget *parent) + : DFrame(parent), + d(new MainFramePrivate()) +{ + initUi(); + initConnection(); +} + +MainFrame::~MainFrame() +{ + if (d) + delete d; +} + +void MainFrame::initUi() +{ + setLineWidth(0); + auto mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + DStyle::setFrameRadius(this, 0); + + d->generateBtn = new DPushButton(tr("New"), this); + d->generateBtn->setFixedWidth(80); + + DFrame *topWidget = new DFrame(this); + topWidget->setLineWidth(0); + DStyle::setFrameRadius(topWidget, 0); + auto topLayout = new QHBoxLayout(topWidget); + topLayout->setContentsMargins(18, 18, 18, 18); + + //init button box + d->btnBox = new DButtonBox(this); + d->installedBtn = new DButtonBoxButton(tr("Installed"), this); + d->repositoryBtn = new DButtonBoxButton(tr("Repository"), this); + d->runningBtn = new DButtonBoxButton(tr("Running"), this); + + d->btnBox->setButtonList({ d->installedBtn, d->repositoryBtn, d->runningBtn }, true); + d->btnBox->setFixedWidth(360); + d->installedBtn->setChecked(true); + + //init search + d->search = new DWidget(this); + d->searchEdit = new DLineEdit(this); + d->searchEdit->setFixedSize(265, 36); + d->searchBtn = new DPushButton(tr("Search"), this); + d->searchBtn->setEnabled(false); + auto searchLayout = new QHBoxLayout(d->search); + searchLayout->setContentsMargins(0, 0, 0, 0); + searchLayout->addWidget(d->searchEdit); + searchLayout->addWidget(d->searchBtn); + d->search->hide(); + + topLayout->addWidget(d->btnBox, 1, Qt::AlignLeft); + topLayout->addWidget(d->search, 0, Qt::AlignRight); + topLayout->addWidget(d->generateBtn, 0, Qt::AlignRight); + + d->stackWidget = new DStackedWidget(this); + d->stackWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + mainLayout->addWidget(topWidget); + mainLayout->addWidget(d->stackWidget); + + //init table widget + initTable(); +} + +void MainFrame::initTable() +{ + auto initTable = [](QTableWidget *table) { + table->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + table->verticalHeader()->hide(); + table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); + table->horizontalHeader()->setStretchLastSection(true); + table->horizontalHeader()->setFixedHeight(36); + table->setAlternatingRowColors(true); + table->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows); + table->setEditTriggers(QTableWidget::NoEditTriggers); + table->setFrameShape(QFrame::NoFrame); + table->setShowGrid(false); + table->setContextMenuPolicy(Qt::ActionsContextMenu); + }; + + d->repTable = new QTableWidget(0, 7, this); + initTable(d->repTable); + d->repTable->setHorizontalHeaderLabels({ "appid", "name", "version", "arch", "channel", "module", "description" }); + d->installedTabel = new QTableWidget(0, 7, this); + d->installedTabel->setHorizontalHeaderLabels({ "appid", "name", "version", "arch", "channel", "module", "description" }); + initTable(d->installedTabel); + d->runningTable = new QTableWidget(0, 4, this); + d->runningTable->setHorizontalHeaderLabels({ "App", "Pid", "Path", "ContainerID" }); + initTable(d->runningTable); + + d->stackWidget->addWidget(d->installedTabel); + d->stackWidget->addWidget(d->repTable); + d->stackWidget->addWidget(d->runningTable); + d->stackWidget->setCurrentWidget(d->repTable); + + auto runAction = new QAction(tr("Run"), this); + auto installAction = new QAction(tr("Install"), this); + auto uninstallAction = new QAction(tr("UnInstall"), this); + // todo : asyn / process output + runAction->setEnabled(false); + installAction->setEnabled(false); + uninstallAction->setEnabled(false); + d->repTable->addAction(installAction); + d->installedTabel->addAction(runAction); + d->installedTabel->addAction(uninstallAction); + + connect(runAction, &QAction::triggered, this, [=]() { + auto selectedRow = d->installedTabel->selectionModel()->currentIndex().row(); + auto index = d->installedTabel->model()->index(selectedRow, 0); + auto appId = d->installedTabel->model()->data(index).toString(); + runApp(appId); + }); + connect(installAction, &QAction::triggered, this, [=]() { + auto selectedRow = d->repTable->selectionModel()->currentIndex().row(); + auto index = d->repTable->model()->index(selectedRow, 0); + auto appId = d->repTable->model()->data(index).toString(); + installApp(appId); + }); + connect(uninstallAction, &QAction::triggered, this, [=]() { + auto selectedRow = d->installedTabel->selectionModel()->currentIndex().row(); + auto index = d->installedTabel->model()->index(selectedRow, 0); + auto appId = d->installedTabel->model()->data(index).toString(); + uninstallApp(appId); + }); + + updateInstalled(); +} + +void MainFrame::initConnection() +{ + connect(d->generateBtn, &QPushButton::clicked, this, [=]() { + d->genDialog.exec(); + }); + connect(d->searchEdit, &DLineEdit::textChanged, this, [=](const QString &text) { + d->searchBtn->setEnabled(text.isEmpty() ? false : true); + }); + connect(d->searchBtn, &QPushButton::clicked, this, [=]() { + updateRepository(d->searchEdit->text()); + }); + connect(d->installedBtn, &DButtonBoxButton::clicked, this, [=]() { + d->stackWidget->setCurrentWidget(d->installedTabel); + d->search->hide(); + updateInstalled(); + }); + connect(d->runningBtn, &DButtonBoxButton::clicked, this, [=]() { + d->stackWidget->setCurrentWidget(d->runningTable); + d->search->hide(); + updateRunning(); + }); + connect(d->repositoryBtn, &DButtonBoxButton::clicked, this, [=]() { + d->stackWidget->setCurrentWidget(d->repTable); + d->search->show(); + }); +} + +void MainFrame::updateInstalled() +{ + d->installedTabel->clearContents(); + d->installedTabel->setRowCount(0); + + d->process.reset(new QProcess(this)); + d->process->setProgram("ll-cli"); + QStringList args; + args.append("list"); + args.append("--json"); + d->process->setArguments(args); + + connect(d->process.get(), &QProcess::readyRead, this, [=]() { + auto data = d->process->readAll(); + + QJsonParseError error; + QJsonDocument jsonDocument = QJsonDocument::fromJson(data, &error); + + if (error.error != QJsonParseError::NoError) { + qCritical() << "JSON parse error: " << error.errorString(); + return; + } + + QJsonArray arr = jsonDocument.array(); + int row = 0; + for (auto line : arr) { + d->installedTabel->setRowCount(row + 1); + auto jsonObject = line.toObject(); + d->installedTabel->setItem(row, 0, new QTableWidgetItem(jsonObject["appid"].toString())); + d->installedTabel->setItem(row, 1, new QTableWidgetItem(jsonObject["name"].toString())); + d->installedTabel->setItem(row, 2, new QTableWidgetItem(jsonObject["version"].toString())); + d->installedTabel->setItem(row, 3, new QTableWidgetItem(jsonObject["arch"].toString())); + d->installedTabel->setItem(row, 4, new QTableWidgetItem(jsonObject["channel"].toString())); + d->installedTabel->setItem(row, 5, new QTableWidgetItem(jsonObject["module"].toString())); + d->installedTabel->setItem(row, 6, new QTableWidgetItem(jsonObject["description"].toString())); + row++; + } + }); + + d->process->start(); + d->process->waitForFinished(); +} + +void MainFrame::updateRepository(const QString &text) +{ + if (!checkToolInstalled("ll-cli")) + return; + + d->repTable->clearContents(); + d->repTable->setRowCount(0); + + d->process.reset(new QProcess(this)); + d->process->setProgram("ll-cli"); + QStringList args; + args.append("search"); + args.append(text); + args.append("--json"); + d->process->setArguments(args); + + connect(d->process.get(), &QProcess::readyRead, this, [=]() { + auto data = d->process->readAll(); + + QJsonParseError error; + QJsonDocument jsonDocument = QJsonDocument::fromJson(data, &error); + + if (error.error != QJsonParseError::NoError) { + qCritical() << "JSON parse error: " << error.errorString(); + return; + } + + QJsonArray arr = jsonDocument.array(); + int row = 0; + for (auto line : arr) { + d->repTable->setRowCount(row + 1); + auto jsonObject = line.toObject(); + d->repTable->setItem(row, 0, new QTableWidgetItem(jsonObject["appid"].toString())); + d->repTable->setItem(row, 1, new QTableWidgetItem(jsonObject["name"].toString())); + d->repTable->setItem(row, 2, new QTableWidgetItem(jsonObject["version"].toString())); + d->repTable->setItem(row, 3, new QTableWidgetItem(jsonObject["arch"].toString())); + d->repTable->setItem(row, 4, new QTableWidgetItem(jsonObject["channel"].toString())); + d->repTable->setItem(row, 5, new QTableWidgetItem(jsonObject["module"].toString())); + d->repTable->setItem(row, 6, new QTableWidgetItem(jsonObject["description"].toString())); + row++; + } + }); + + d->process->start(); + d->process->waitForFinished(); +} + +void MainFrame::updateRunning() +{ + d->runningTable->clearContents(); + d->runningTable->setRowCount(0); + + d->process.reset(new QProcess(this)); + d->process->setProgram("ll-cli"); + QStringList args; + args.append("ps"); + args.append("--json"); + d->process->setArguments(args); + + connect(d->process.get(), &QProcess::readyRead, this, [=]() { + auto data = d->process->readAll(); + + QJsonParseError error; + QJsonDocument jsonDocument = QJsonDocument::fromJson(data, &error); + + if (error.error != QJsonParseError::NoError) { + qCritical() << "JSON parse error: " << error.errorString(); + return; + } + + QJsonArray arr = jsonDocument.array(); + int row = 0; + for (auto line : arr) { + d->runningTable->setRowCount(row + 1); + auto jsonObject = line.toObject(); + d->runningTable->setItem(row, 0, new QTableWidgetItem(jsonObject["package"].toString())); + d->runningTable->setItem(row, 1, new QTableWidgetItem(QString::number(jsonObject["pid"].toInt()))); + d->runningTable->setItem(row, 2, new QTableWidgetItem(jsonObject["path"].toString())); + d->runningTable->setItem(row, 3, new QTableWidgetItem(jsonObject["id"].toString())); + row++; + } + }); + + d->process->start(); + d->process->waitForFinished(); +} + +void MainFrame::runApp(const QString &appId) +{ + d->process.reset(new QProcess(this)); + d->process->setProgram("ll-cli"); + QStringList args; + args.append("run"); + args.append(appId); + d->process->setArguments(args); + + d->process->start(); + d->process->waitForFinished(); +} + +void MainFrame::installApp(const QString &appId) +{ + d->process.reset(new QProcess(this)); + d->process->setProgram("ll-cli"); + QStringList args; + args.append("install"); + args.append(appId); + d->process->setArguments(args); + + d->process->start(); + d->process->waitForFinished(); + + updateInstalled(); +} + +void MainFrame::uninstallApp(const QString &appId) +{ + d->process.reset(new QProcess(this)); + d->process->setProgram("ll-cli"); + QStringList args; + args.append("uninstall"); + args.append(appId); + d->process->setArguments(args); + + d->process->start(); + d->process->waitForFinished(); + + updateInstalled(); +} + +void MainFrame::output(const QString &text, OutputPane::OutputFormat format) +{ + AppOutputPane::instance()->defaultPane()->appendText(text, format); +} diff --git a/src/plugins/linglong/gui/mainframe.h b/src/plugins/linglong/gui/mainframe.h new file mode 100644 index 000000000..172befdb9 --- /dev/null +++ b/src/plugins/linglong/gui/mainframe.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef MAINFRAME_H +#define MAINFRAME_H + +#include "common/widget/appoutputpane.h" + +#include + +class MainFramePrivate; +class MainFrame : public DTK_WIDGET_NAMESPACE::DFrame +{ + Q_OBJECT +public: + MainFrame(QWidget *parent = nullptr); + ~MainFrame(); + static bool checkToolInstalled(const QString &tool); + +private slots: + void runApp(const QString &appId); + void installApp(const QString &appId); + void uninstallApp(const QString &appId); + +private: + MainFramePrivate *d { nullptr }; + + void initUi(); + void initConnection(); + void initTable(); + void updateInstalled(); + void updateRepository(const QString &text); + void updateRunning(); + + void output(const QString &text, OutputPane::OutputFormat format = OutputPane::NormalMessage); +}; + +#endif // MAINFRAME_H diff --git a/src/plugins/linglong/linglong.json b/src/plugins/linglong/linglong.json new file mode 100644 index 000000000..af70ed5b0 --- /dev/null +++ b/src/plugins/linglong/linglong.json @@ -0,0 +1,15 @@ +{ + "Name" : "linglong", + "Version" : "0.0.1", + "CompatVersion" : "0.0.1", + "Vendor" : "The Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2024 Uniontech Software Technology Co., Ltd.", + "License" : [ + "GPL-3.0-or-later" + ], + "Category" : "Utils", + "Description" : "The linglong plugin for the unioncode.", + "UrlLink" : "https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e", + "Depends" : [ + ] +} diff --git a/src/plugins/linglong/linglong.svg b/src/plugins/linglong/linglong.svg new file mode 100644 index 000000000..735e60d28 --- /dev/null +++ b/src/plugins/linglong/linglong.svg @@ -0,0 +1,7 @@ + + + ICON / sidebar /linglong_22px + + + + \ No newline at end of file diff --git a/src/plugins/linglong/linglongplugin.cpp b/src/plugins/linglong/linglongplugin.cpp new file mode 100644 index 000000000..df4e44c5b --- /dev/null +++ b/src/plugins/linglong/linglongplugin.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "linglongplugin.h" +#include "llglobal.h" +#include "llgenerator.h" +#include "project/llprojectgenerator.h" +#include "builder/mainframe/llbuildergenerator.h" + +#include "base/abstractaction.h" +#include "base/abstractwidget.h" +#include "services/language/languageservice.h" +#include "services/project/projectservice.h" +#include "services/window/windowservice.h" +#include "services/builder/builderservice.h" + +#include "gui/mainframe.h" + +using namespace dpfservice; + +void LinglongPlugin::initialize() +{ + qInfo() << __FUNCTION__; +} + +bool LinglongPlugin::start() +{ + qInfo() << __FUNCTION__; + // language register. + LanguageService *languageService = dpfGetService(LanguageService); + if (languageService) { + QString errorString; + bool ret = languageService->regClass(LLGenerator::toolKitName(), &errorString); + if (!ret) { + qCritical() << errorString; + } else { + ret = languageService->create(LLGenerator::toolKitName(), &errorString); + if (!ret) { + qCritical() << errorString; + } + } + } + + // project register. + ProjectService *projectService = dpfGetService(ProjectService); + if (projectService) { + QString errorString; + projectService->implGenerator(LLProjectGenerator::toolKitName(), &errorString); + } + + //builder register + BuilderService *builderService = dpfGetService(BuilderService); + if (builderService) { + QString errorString; + bool ret = builderService->regClass(LLBuilderGenerator::toolKitName(), &errorString); + if (ret) { + builderService->create(LLBuilderGenerator::toolKitName(), &errorString); + } + } + + QAction *action = new QAction(QIcon::fromTheme("linglong"), LL_NAME, this); + windowService = dpfGetService(WindowService); + if (!windowService) + return false; + + windowService->addNavigationItem(new AbstractAction(action), Priority::low); + auto mainFrame = new AbstractWidget(new MainFrame()); + // mainFrame will setParent when register to windowService + windowService->registerWidget(LL_NAME, mainFrame); + + connect(action, &QAction::triggered, this, [=]() { + windowService->showWidgetAtPosition(LL_NAME, Position::FullWindow, true); + windowService->showContextWidget(); + MainFrame::checkToolInstalled("ll-cli"); + }, + Qt::DirectConnection); + + return true; +} + +dpf::Plugin::ShutdownFlag LinglongPlugin::stop() +{ + qInfo() << __FUNCTION__; + return Sync; +} diff --git a/src/plugins/linglong/linglongplugin.h b/src/plugins/linglong/linglongplugin.h new file mode 100644 index 000000000..705255576 --- /dev/null +++ b/src/plugins/linglong/linglongplugin.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LINGLONG_PLUGIN_H +#define LINGLONG_PLUGIN_H + +#include + +namespace dpfservice { + class WindowService; +} + +class LinglongPlugin : public dpf::Plugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.deepin.plugin.unioncode" FILE "linglong.json") +public: + virtual void initialize() override; + virtual bool start() override; + virtual dpf::Plugin::ShutdownFlag stop() override; + +signals: + +private: + dpfservice::WindowService *windowService = nullptr; +}; + +#endif // LINGLONG_PLUGIN_H diff --git a/src/plugins/linglong/llgenerator.cpp b/src/plugins/linglong/llgenerator.cpp new file mode 100644 index 000000000..9c3417018 --- /dev/null +++ b/src/plugins/linglong/llgenerator.cpp @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "llgenerator.h" +#include "common/widget/outputpane.h" + +using namespace dpfservice; + +LLGenerator::LLGenerator() +{ +} + +LLGenerator::~LLGenerator() +{ +} + +QString LLGenerator::debugger() +{ + return ""; +} + +bool LLGenerator::prepareDebug(const QMap ¶m, QString &retMsg) +{ + Q_UNUSED(param) + Q_UNUSED(retMsg) + return true; +} + +bool LLGenerator::requestDAPPort(const QString &ppid, const QMap ¶m, QString &retMsg) +{ + retMsg = tr("debug of Linglong project is not supported"); + return false; +} + +bool LLGenerator::isNeedBuild() +{ + return false; +} + +bool LLGenerator::isTargetReady() +{ + return true; +} + +bool LLGenerator::isLaunchNotAttach() +{ + return true; +} + +dap::LaunchRequest LLGenerator::launchDAP(const QMap ¶m) +{ + dap::LaunchRequest request; + return request; +} + +QString LLGenerator::build(const QString &projectPath) +{ + Q_UNUSED(projectPath) + return ""; +} + +QString LLGenerator::getProjectFile(const QString &projectPath) +{ + Q_UNUSED(projectPath) + return ""; +} + +QMap LLGenerator::getDebugArguments(const dpfservice::ProjectInfo &projectInfo, + const QString ¤tFile) +{ + Q_UNUSED(currentFile) + + QMap param; + //param.insert("workspace", projectInfo.workspaceFolder()); + + return param; +} + +RunCommandInfo LLGenerator::getRunArguments(const ProjectInfo &projectInfo, const QString ¤tFile) +{ + Q_UNUSED(currentFile) + RunCommandInfo info; + info.program = "ll-builder"; + info.arguments.append("run"); + info.workingDir = projectInfo.workspaceFolder(); + info.runInTerminal = false; + + return info; +} diff --git a/src/plugins/linglong/llgenerator.h b/src/plugins/linglong/llgenerator.h new file mode 100644 index 000000000..3f420f67a --- /dev/null +++ b/src/plugins/linglong/llgenerator.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LLGENERATOR_H +#define LLGENERATOR_H + +#include "llglobal.h" +#include "services/language/languagegenerator.h" + +class LLGenerator : public dpfservice::LanguageGenerator +{ + Q_OBJECT +public: + explicit LLGenerator(); + virtual ~LLGenerator() override; + + static QString toolKitName() { return LL_TOOLKIT; } + + //disabled debug function for now + QString debugger() override; + bool prepareDebug(const QMap ¶m, QString &retMsg) override; + bool requestDAPPort(const QString &ppid, const QMap ¶m, QString &retMsg) override; + bool isNeedBuild() override; + bool isTargetReady() override; + bool isLaunchNotAttach() override; + dap::LaunchRequest launchDAP(const QMap ¶m) override; + QString build(const QString &projectPath) override; + QString getProjectFile(const QString &projectPath) override; + QMap getDebugArguments(const dpfservice::ProjectInfo &projectInfo, + const QString ¤tFile) override; + + dpfservice::RunCommandInfo getRunArguments(const dpfservice::ProjectInfo &projectInfo, + const QString ¤tFile) override; +}; + +#endif // LLGENERATOR_H diff --git a/src/plugins/linglong/llglobal.h b/src/plugins/linglong/llglobal.h new file mode 100644 index 000000000..afd82dd47 --- /dev/null +++ b/src/plugins/linglong/llglobal.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LLGLOBAL_H +#define LLGLOBAL_H + +#include + +static const QString LL_NAME = "linglong"; +static const QString LL_TOOLKIT = "linglong"; +static const QString LL_LANGUAGE = "LingLong"; + +#endif // LLGLOBAL_H diff --git a/src/plugins/linglong/project/llasynparse.cpp b/src/plugins/linglong/project/llasynparse.cpp new file mode 100644 index 000000000..1c4b3a913 --- /dev/null +++ b/src/plugins/linglong/project/llasynparse.cpp @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "llasynparse.h" + +class LLAsynParsePrivate +{ + friend class LLAsynParse; + QThread *thread { nullptr }; + QString rootPath; + QSet fileList {}; + QList rows {}; + + QStandardItem *rootItem { nullptr }; + + bool stop { false }; +}; + +LLAsynParse::LLAsynParse(QStandardItem *root) + : d(new LLAsynParsePrivate) +{ + d->rootItem = root; + QObject::connect(this, &QFileSystemWatcher::directoryChanged, + this, &LLAsynParse::doDirectoryChanged); + d->thread = new QThread(); + this->moveToThread(d->thread); + d->thread->start(); +} + +LLAsynParse::~LLAsynParse() +{ + if (d) { + if (d->thread) { + d->stop = true; + if (d->thread->isRunning()) + d->thread->quit(); + d->thread->wait(); + d->thread->deleteLater(); + d->thread = nullptr; + } + delete d; + } +} + +void LLAsynParse::parseProject(const dpfservice::ProjectInfo &info) +{ + createRows(info.workspaceFolder()); + emit itemsModified(d->rows); +} + +QSet LLAsynParse::getFilelist() +{ + return d->fileList; +} + +void LLAsynParse::doDirectoryChanged(const QString &path) +{ + if (!path.startsWith(d->rootPath)) + return; + + if (d->rootItem) { + while (d->rootItem->hasChildren()) + d->rootItem->takeRow(0); + } + + d->rows.clear(); + + createRows(d->rootPath); + + emit itemsModified(d->rows); +} + +QString LLAsynParse::itemDisplayName(const QStandardItem *item) const +{ + if (!item) + return ""; + return item->data(Qt::DisplayRole).toString(); +} + +void LLAsynParse::createRows(const QString &path) +{ + QString rootPath = path; + d->fileList.clear(); + if (rootPath.endsWith(QDir::separator())) { + int separatorSize = QString(QDir::separator()).size(); + rootPath = rootPath.remove(rootPath.size() - separatorSize, separatorSize); + } + + d->rootPath = rootPath; + QFileSystemWatcher::addPath(d->rootPath); + + { + QDir dir; + dir.setPath(rootPath); + dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs); + dir.setSorting(QDir::Name); + QDirIterator dirItera(dir, QDirIterator::Subdirectories); + while (dirItera.hasNext()) { + if (d->stop) + return; + QString childPath = dirItera.next().remove(0, rootPath.size()); + QFileSystemWatcher::addPath(dirItera.filePath()); + QStandardItem *item = findItem(childPath); + auto newItem = new QStandardItem(dirItera.fileName()); + newItem->setToolTip(dirItera.filePath()); + if (item) + item->appendRow(newItem); + else + d->rootItem->appendRow(newItem); + d->rows.append(newItem); + } + } + { + QDir dir; + dir.setPath(rootPath); + dir.setFilter(QDir::NoDotAndDotDot | QDir::Files); + dir.setSorting(QDir::Name); + QDirIterator fileItera(dir, QDirIterator::Subdirectories); + while (fileItera.hasNext()) { + if (d->stop) + return; + QString childPath = fileItera.next().remove(0, rootPath.size()); + QStandardItem *item = findItem(childPath); + auto newItem = new QStandardItem(fileItera.fileName()); + newItem->setToolTip(fileItera.filePath()); + if (item) + item->appendRow(newItem); + else + d->rootItem->appendRow(newItem); + d->rows.append(newItem); + d->fileList.insert(fileItera.filePath()); + } + } +} + +QList LLAsynParse::rows(const QStandardItem *item) const +{ + QList result; + for (int i = 0; i < item->rowCount(); i++) { + result << item->child(i); + } + return result; +} + +QStandardItem *LLAsynParse::findItem(const QString &path, + QStandardItem *parent) const +{ + QString pathTemp = path; + if (pathTemp.endsWith(QDir::separator())) { + pathTemp = pathTemp.remove(pathTemp.size() - separatorSize(), separatorSize()); + } + + if (pathTemp.startsWith(QDir::separator())) + pathTemp.remove(0, separatorSize()); + + if (pathTemp.endsWith(QDir::separator())) + pathTemp.remove(pathTemp.size() - separatorSize(), separatorSize()); + + if (pathTemp.isEmpty()) + return parent; + + QStringList splitPaths = pathTemp.split(QDir::separator()); + QString name = splitPaths.takeFirst(); + + QList currRows {}; + if (parent) { + currRows = rows(parent); + } else { + currRows = d->rows; + } + + for (int i = 0; i < currRows.size(); i++) { + QStandardItem *child = currRows[i]; + if (name == itemDisplayName(child)) { + if (splitPaths.isEmpty()) { + return child; + } else { + return findItem(splitPaths.join(QDir::separator()), child); + } + } + } + return parent; +} + +int LLAsynParse::separatorSize() const +{ + return QString(QDir::separator()).size(); +} diff --git a/src/plugins/linglong/project/llasynparse.h b/src/plugins/linglong/project/llasynparse.h new file mode 100644 index 000000000..d4eb09b0d --- /dev/null +++ b/src/plugins/linglong/project/llasynparse.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LLASYNPARSE_H +#define LLASYNPARSE_H + +#include "common/common.h" + +class LLAsynParsePrivate; +class LLAsynParse : public QFileSystemWatcher +{ + Q_OBJECT + LLAsynParsePrivate *const d; + +public: + template + struct ParseInfo + { + T result; + bool isNormal = true; + }; + + QSet getFilelist(); + + LLAsynParse(QStandardItem *rootItem); + virtual ~LLAsynParse(); + +signals: + void itemsModified(const QList &info); + void parsedError(const ParseInfo &info); + +public slots: + void parseProject(const dpfservice::ProjectInfo &info); + +private slots: + void doDirectoryChanged(const QString &path); + +private: + void createRows(const QString &path); + QString itemDisplayName(const QStandardItem *item) const; + QStandardItem *findItem(const QString &path, QStandardItem *parent = nullptr) const; + QList rows(const QStandardItem *item) const; + int separatorSize() const; +}; + +#endif // LLASYNPARSE_H diff --git a/src/plugins/linglong/project/llprojectgenerator.cpp b/src/plugins/linglong/project/llprojectgenerator.cpp new file mode 100644 index 000000000..38777c590 --- /dev/null +++ b/src/plugins/linglong/project/llprojectgenerator.cpp @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "llprojectgenerator.h" +#include "common/dialog/propertiesdialog.h" + +using namespace dpfservice; + +class LLProjectGeneratorPrivate +{ + friend class LLProjectGenerator; + QHash projectParses {}; +}; + +LLProjectGenerator::LLProjectGenerator() + : d(new LLProjectGeneratorPrivate) +{ +} + +LLProjectGenerator::~LLProjectGenerator() +{ + if (d) { + for (auto parser : d->projectParses.values()) { + if (parser) + delete parser; + } + delete d; + } +} + +QStringList LLProjectGenerator::supportLanguages() +{ + return { LL_LANGUAGE }; +} + +QDialog *LLProjectGenerator::configureWidget(const QString &language, + const QString &projectPath) +{ + using namespace dpfservice; + + ProjectInfo info; + info.setLanguage(language); + info.setKitName(LLProjectGenerator::toolKitName()); + info.setWorkspaceFolder(projectPath); + info.setExePrograms({ "LingLong" }); + + configure(info); + + return nullptr; +} + +bool LLProjectGenerator::configure(const dpfservice::ProjectInfo &projectInfo) +{ + dpfservice::ProjectGenerator::configure(projectInfo); + + auto root = createRootItem(projectInfo); + ProjectService *projectService = dpfGetService(ProjectService); + if (projectService && root) { + projectService->addRootItem(root); + projectService->expandedDepth(root, 1); + } + + return true; +} + +QStandardItem *LLProjectGenerator::createRootItem(const dpfservice::ProjectInfo &info) +{ + using namespace dpfservice; + + QStandardItem *rootItem = ProjectGenerator::createRootItem(info); + d->projectParses[rootItem] = new LLAsynParse(rootItem); + QObject::connect(d->projectParses[rootItem], + &LLAsynParse::itemsModified, + this, &LLProjectGenerator::doProjectChildsModified, + Qt::ConnectionType::UniqueConnection); + QMetaObject::invokeMethod(d->projectParses[rootItem], "parseProject", + Q_ARG(const dpfservice::ProjectInfo &, info)); + auto sourceFiles = d->projectParses[rootItem]->getFilelist(); + dpfservice::ProjectInfo tempInfo = info; + tempInfo.setSourceFiles(sourceFiles); + dpfservice::ProjectInfo::set(rootItem, tempInfo); + + return rootItem; +} + +void LLProjectGenerator::removeRootItem(QStandardItem *root) +{ + if (!root) + return; + auto parser = d->projectParses[root]; + + while (root->hasChildren()) { + root->takeRow(0); + } + d->projectParses.remove(root); + + delete root; + + if (parser) + delete parser; +} + +QMenu *LLProjectGenerator::createItemMenu(const QStandardItem *item) +{ + if (item->parent()) + return nullptr; + + QMenu *menu = new QMenu(); + // dpfservice::ProjectInfo info = dpfservice::ProjectInfo::get(item); + // if (info.isEmpty()) + // return nullptr; + + // QStandardItem *itemTemp = const_cast(item); + // if (!itemTemp) + // return nullptr; + + // QAction *action = new QAction(tr("Properties")); + // menu->addAction(action); + // QObject::connect(action, &QAction::triggered, [=]() { + // actionProperties(info, itemTemp); + // }); + + return menu; +} + +void LLProjectGenerator::doProjectChildsModified(const QList &items) +{ + auto rootItem = d->projectParses.key(qobject_cast(sender())); + if (!rootItem) + return; + + for (auto &item : items) { + item->setIcon(CustomIcons::icon(item->toolTip())); + } + + rootItem->setData(ParsingState::Done, Parsing_State_Role); +} + +void LLProjectGenerator::actionProperties(const ProjectInfo &info, QStandardItem *item) +{ + //PropertiesDialog dlg; + // ConfigPropertyWidget *property = new ConfigPropertyWidget(info, item); + // dlg.insertPropertyPanel("Config", property); + // dlg.exec(); +} diff --git a/src/plugins/linglong/project/llprojectgenerator.h b/src/plugins/linglong/project/llprojectgenerator.h new file mode 100644 index 000000000..5ccb5c59a --- /dev/null +++ b/src/plugins/linglong/project/llprojectgenerator.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LLPROJECTGENERATOR_H +#define LLPROJECTGENERATOR_H + +#include "llasynparse.h" +#include "llglobal.h" +#include "services/project/projectservice.h" + +class LLProjectGeneratorPrivate; +class LLProjectGenerator : public dpfservice::ProjectGenerator +{ + Q_OBJECT +public: + explicit LLProjectGenerator(); + virtual ~LLProjectGenerator(); + + static QString toolKitName() { return LL_TOOLKIT; } + virtual QStringList supportLanguages() override; + virtual QDialog *configureWidget(const QString &language, + const QString &projectPath) override; + virtual bool configure(const dpfservice::ProjectInfo &info = {}) override; + virtual QStandardItem *createRootItem(const dpfservice::ProjectInfo &items) override; + virtual void removeRootItem(QStandardItem *root) override; + virtual QMenu *createItemMenu(const QStandardItem *item) override; + +private slots: + void doProjectChildsModified(const QList &items); + void actionProperties(const dpfservice::ProjectInfo &info, QStandardItem *item); + +private: + LLProjectGeneratorPrivate *const d; +}; + +#endif // LLPROJECTGENERATOR_H diff --git a/src/plugins/linglong/resource.qrc b/src/plugins/linglong/resource.qrc new file mode 100644 index 000000000..0c1c7eeb9 --- /dev/null +++ b/src/plugins/linglong/resource.qrc @@ -0,0 +1,6 @@ + + + + builtin/texts/linglong_22px.svg + + diff --git a/src/plugins/option/optioncore/optioncore.json b/src/plugins/option/optioncore/optioncore.json index 069023d26..8c8f3d403 100644 --- a/src/plugins/option/optioncore/optioncore.json +++ b/src/plugins/option/optioncore/optioncore.json @@ -9,7 +9,7 @@ ], "Category" : "Core Plugins", "Description" : "The optioncore plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document2?dirid=656d40a9bd766615b0b02e5e", + "UrlLink" : "https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/performance/performance.json b/src/plugins/performance/performance.json index 970dac4e8..ea45a60cd 100644 --- a/src/plugins/performance/performance.json +++ b/src/plugins/performance/performance.json @@ -9,7 +9,7 @@ ], "Category" : "Code Analyzer", "Description" : "The performance analysis tools plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document2?dirid=656d40a9bd766615b0b02e5e", + "UrlLink" : "https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/project/CMakeLists.txt b/src/plugins/project/CMakeLists.txt index 254e23e4b..079bc9b2c 100644 --- a/src/plugins/project/CMakeLists.txt +++ b/src/plugins/project/CMakeLists.txt @@ -9,6 +9,9 @@ set(CXX_CPP mainframe/projectinfodialog.cpp mainframe/projecttree.cpp mainframe/projectkeeper.cpp + mainframe/recent/recentopenitemdelegate.cpp + mainframe/recent/recentopenview.cpp + mainframe/recent/recentopenwidget.cpp locator/allprojectfilelocator.cpp locator/currentprojectlocator.cpp transceiver/projectcorereceiver.cpp @@ -24,6 +27,9 @@ set(CXX_H mainframe/projectinfodialog.h mainframe/projecttree.h mainframe/projectkeeper.h + mainframe/recent/recentopenitemdelegate.h + mainframe/recent/recentopenview.h + mainframe/recent/recentopenwidget.h transceiver/projectcorereceiver.h transceiver/sendevents.h locator/allprojectfilelocator.h diff --git a/src/plugins/project/locator/allprojectfilelocator.h b/src/plugins/project/locator/allprojectfilelocator.h index 8d5c69527..3aca07651 100644 --- a/src/plugins/project/locator/allprojectfilelocator.h +++ b/src/plugins/project/locator/allprojectfilelocator.h @@ -12,6 +12,7 @@ class AllProjectFileLocator : public baseFileLocator { + Q_OBJECT public: AllProjectFileLocator(QObject *parent = nullptr); diff --git a/src/plugins/project/locator/currentprojectlocator.h b/src/plugins/project/locator/currentprojectlocator.h index 8e48f93e8..dd0ba634e 100644 --- a/src/plugins/project/locator/currentprojectlocator.h +++ b/src/plugins/project/locator/currentprojectlocator.h @@ -12,6 +12,7 @@ class CurrentProjectLocator : public baseFileLocator { + Q_OBJECT public: CurrentProjectLocator(QObject *parent = nullptr); diff --git a/src/plugins/project/mainframe/projectdelegate.cpp b/src/plugins/project/mainframe/projectdelegate.cpp index d10f4a1f5..a493aeb3b 100644 --- a/src/plugins/project/mainframe/projectdelegate.cpp +++ b/src/plugins/project/mainframe/projectdelegate.cpp @@ -71,3 +71,9 @@ QSize ProjectDelegate::sizeHint(const QStyleOptionViewItem &option, const QModel Q_UNUSED(index) return { option.rect.width(), 24 }; } + +void ProjectDelegate::hideSpinner() +{ + d->spinner->hide(); + d->spinner->stop(); +} diff --git a/src/plugins/project/mainframe/projectdelegate.h b/src/plugins/project/mainframe/projectdelegate.h index ad4dccbd9..a65889222 100644 --- a/src/plugins/project/mainframe/projectdelegate.h +++ b/src/plugins/project/mainframe/projectdelegate.h @@ -17,6 +17,7 @@ class ProjectDelegate : public BaseItemDelegate ~ProjectDelegate() override; void setActiveProject(const QModelIndex &root); const QModelIndex &getActiveProject() const; + void hideSpinner(); protected: void paint(QPainter *painter, diff --git a/src/plugins/project/mainframe/projecttree.cpp b/src/plugins/project/mainframe/projecttree.cpp index 64b9ba6af..01e5d8254 100644 --- a/src/plugins/project/mainframe/projecttree.cpp +++ b/src/plugins/project/mainframe/projecttree.cpp @@ -26,6 +26,7 @@ #include #include #include +#include DWIDGET_USE_NAMESPACE using namespace dpfservice; @@ -33,34 +34,36 @@ using namespace dpfservice; class ProjectTreePrivate { friend class ProjectTree; - ProjectModel *itemModel {nullptr}; - ProjectSelectionModel *sectionModel {nullptr}; - ProjectDelegate *delegate {nullptr}; + ProjectModel *itemModel { nullptr }; + ProjectSelectionModel *sectionModel { nullptr }; + ProjectDelegate *delegate { nullptr }; DDialog *messDialog = nullptr; QPoint startPos; + QString currentFile = ""; + bool autoFocusState = true; int itemDepth(const QStandardItem *item) { int depth = 0; const QStandardItem *current = item; while (current->parent()) { current = current->parent(); - depth ++; + depth++; } return depth; } }; ProjectTree::ProjectTree(QWidget *parent) - : DTreeView (parent) - , d(new ProjectTreePrivate) + : DTreeView(parent), d(new ProjectTreePrivate) { setLineWidth(0); setContentsMargins(0, 0, 0, 0); DStyle::setFrameRadius(this, 0); + setIconSize(QSize(16, 16)); - setEditTriggers(DTreeView::NoEditTriggers); //节点不能编辑 - setSelectionBehavior(DTreeView::SelectRows); //一次选中整行 - setSelectionMode(DTreeView::SingleSelection); //单选,配合上面的整行就是一次选单行 + setEditTriggers(DTreeView::NoEditTriggers); //节点不能编辑 + setSelectionBehavior(DTreeView::SelectRows); //一次选中整行 + setSelectionMode(DTreeView::SingleSelection); //单选,配合上面的整行就是一次选单行 this->header()->hide(); d->itemModel = new ProjectModel(this); @@ -74,6 +77,12 @@ ProjectTree::ProjectTree(QWidget *parent) QObject::connect(this, &ProjectTree::doubleClicked, this, &ProjectTree::doDoubleClicked); + QObject::connect(this, &ProjectTree::expanded, + this, [=](const QModelIndex &index) { SendEvents::projectNodeExpanded(index); }); + + QObject::connect(this, &ProjectTree::collapsed, + this, [=](const QModelIndex &index) { SendEvents::projectNodeCollapsed(index); }); + d->sectionModel = new ProjectSelectionModel(d->itemModel); setSelectionModel(d->sectionModel); @@ -94,13 +103,13 @@ ProjectTree::~ProjectTree() void ProjectTree::activeProjectInfo(const ProjectInfo &info) { int rowCount = d->itemModel->rowCount(); - for (int currRow = 0; currRow < rowCount; currRow ++) { + for (int currRow = 0; currRow < rowCount; currRow++) { auto currItem = d->itemModel->item(currRow, 0); if (currItem) { auto currInfo = ProjectInfo::get(ProjectGenerator::root(currItem)); if (currInfo.language() == info.language() - && currInfo.workspaceFolder() == info.workspaceFolder() - && currInfo.kitName() == info.kitName()) { + && currInfo.workspaceFolder() == info.workspaceFolder() + && currInfo.kitName() == info.kitName()) { doActiveProject(currItem); } } @@ -112,13 +121,13 @@ void ProjectTree::activeProjectInfo(const QString &kitName, const QString &workspace) { int rowCount = d->itemModel->rowCount(); - for (int currRow = 0; currRow < rowCount; currRow ++) { + for (int currRow = 0; currRow < rowCount; currRow++) { auto currItem = d->itemModel->item(currRow, 0); if (currItem) { auto currInfo = ProjectInfo::get(ProjectGenerator::root(currItem)); if (currInfo.language() == language - && currInfo.workspaceFolder() == workspace - && currInfo.kitName() == kitName) { + && currInfo.workspaceFolder() == workspace + && currInfo.kitName() == kitName) { doActiveProject(currItem); } } @@ -135,11 +144,11 @@ void ProjectTree::appendRootItem(QStandardItem *root) auto info = ProjectInfo::get(ProjectGenerator::root(root)); // 添加工程节点 - QStandardItemModel *model = static_cast(DTreeView::model()); + QStandardItemModel *model = static_cast(DTreeView::model()); if (model) model->appendRow(root); - if (root->data(Parsing_State_Role).value() != ParsingState::Done) //avoid appent root item after complete parse + if (root->data(Parsing_State_Role).value() != ParsingState::Done) //avoid appent root item after complete parse root->setData(ParsingState::Wait, Parsing_State_Role); // 发送工程节点已创建信号 @@ -174,7 +183,7 @@ void ProjectTree::removeRootItem(QStandardItem *root) // 始终保持首选项 int rowCount = d->itemModel->rowCount(); - if ( 0 < rowCount) { // 存在其他工程时 + if (0 < rowCount) { // 存在其他工程时 auto index = d->itemModel->index(0, 0); doActiveProject(d->itemModel->itemFromIndex(index)); } @@ -187,11 +196,15 @@ void ProjectTree::takeRootItem(QStandardItem *root) // 从展示的模型中删除 QModelIndex index = d->itemModel->indexFromItem(root); d->itemModel->takeRow(index.row()); + emit itemDeleted(root); + + if (d->itemModel->rowCount() == 0) + d->delegate->hideSpinner(); } void ProjectTree::doItemMenuRequest(QStandardItem *item, QContextMenuEvent *event) { - if(!item) + if (!item) return; auto rootItem = ProjectGenerator::root(item); @@ -209,10 +222,15 @@ void ProjectTree::doItemMenuRequest(QStandardItem *item, QContextMenuEvent *even if (info.isDir()) { menu->addSeparator(); QAction *newDocAction = new QAction(tr("New Document"), this); - QObject::connect(newDocAction, &QAction::triggered, this, [=](){ + QObject::connect(newDocAction, &QAction::triggered, this, [=]() { actionNewDocument(item); }); + QAction *newDirAction = new QAction(tr("New Directory"), this); + QObject::connect(newDirAction, &QAction::triggered, this, [=]() { + actionNewDirectory(item); + }); menu->addAction(newDocAction); + menu->addAction(newDirAction); } } else { menu = childMenu(rootItem, item); @@ -220,15 +238,19 @@ void ProjectTree::doItemMenuRequest(QStandardItem *item, QContextMenuEvent *even // add action that show contain folder. menu->addSeparator(); - QAction *showContainFolder = new QAction(tr("Show Contain Folder"), this); - connect(showContainFolder, &QAction::triggered, [=](){ + QAction *showContainFolder = new QAction(tr("Show Containing Folder"), this); + connect(showContainFolder, &QAction::triggered, [=]() { QString filePath = item->toolTip(); - QFileInfo fileInfo(filePath); - QString openCommand = "xdg-open " + fileInfo.dir().path(); - QProcess::startDetached(openCommand); + QFileInfo info(filePath); + QDesktopServices::openUrl(QUrl::fromLocalFile(info.absolutePath())); }); menu->addAction(showContainFolder); + connect(this, &ProjectTree::itemDeleted, menu, [=](QStandardItem *deletedItem) { + if (item == deletedItem || ProjectGenerator::root(item) == deletedItem) + menu->close(); + }); + if (menu) { menu->move(event->globalPos()); menu->exec(); @@ -241,10 +263,10 @@ void ProjectTree::expandedProjectDepth(const QStandardItem *root, int depth) if (!root) return; - if (d->itemDepth(root) < depth) { //满足深度 + if (d->itemDepth(root) < depth) { //满足深度 expand(d->itemModel->indexFromItem(root)); - for(int i = 0; i < root->rowCount(); i++) { - QStandardItem * childitem = root->child(i); + for (int i = 0; i < root->rowCount(); i++) { + QStandardItem *childitem = root->child(i); if (root->hasChildren()) { expandedProjectDepth(childitem, depth); } @@ -259,19 +281,71 @@ void ProjectTree::expandedProjectAll(const QStandardItem *root) expand(d->itemModel->indexFromItem(root)); if (root->hasChildren()) { - for(int i = 0; i < root->rowCount(); i++) { - QStandardItem * childitem = root->child(i); + for (int i = 0; i < root->rowCount(); i++) { + QStandardItem *childitem = root->child(i); expandedProjectAll(childitem); } } } +void ProjectTree::selectProjectFile(const QString &file) +{ + d->currentFile = file; + if (!d->autoFocusState) + return; + focusCurrentFile(); +} + +void ProjectTree::expandItemByFile(const QStringList &filePaths) +{ + QModelIndex root = d->itemModel->index(0, 0); + + if (!root.isValid()) { + return; + } + for (auto filePath : filePaths) { + // hints = 2 : project`s root and lib-node might use the same filePath + QModelIndexList indices = model()->match(root, Qt::ToolTipRole, filePath, 2, Qt::MatchExactly | Qt::MatchRecursive); + if (!indices.isEmpty()) { + for (auto index : indices) + expand(index); + } + } +} + +void ProjectTree::focusCurrentFile() +{ + QModelIndex root = d->itemModel->index(0, 0); + if (!root.isValid()) { + return; + } + if (d->currentFile.isEmpty()) { + clearSelection(); + return; + } + QModelIndexList indices = model()->match(root, Qt::ToolTipRole, d->currentFile, 1, Qt::MatchExactly | Qt::MatchRecursive); + if (!indices.isEmpty()) { + QModelIndex index = indices.first(); + setCurrentIndex(index); + } +} + +void ProjectTree::setAutoFocusState(bool state) +{ + d->autoFocusState = state; +} + +bool ProjectTree::getAutoFocusState() const +{ + return d->autoFocusState; +} + QList ProjectTree::getAllProjectInfo() { using namespace dpfservice; QList result; for (int row = 0; row < d->itemModel->rowCount(); row++) { - result << ProjectInfo::get(d->itemModel->index(row, 0)); + result << ProjectInfo::get(d->itemModel->index(row, 0)); } return result; } @@ -293,7 +367,7 @@ ProjectInfo ProjectTree::getActiveProjectInfo() const { ProjectInfo projectInfo; auto activeProject = d->delegate->getActiveProject(); - if(activeProject.isValid()) { + if (activeProject.isValid()) { projectInfo = ProjectInfo::get(activeProject); } return projectInfo; @@ -309,13 +383,14 @@ bool ProjectTree::updateProjectInfo(ProjectInfo &projectInfo) return true; } } + return false; } bool ProjectTree::hasProjectInfo(const ProjectInfo &info) const { - ProjectInfo projectInfo = getProjectInfo(info.kitName(), info.workspaceFolder()); - return !projectInfo.isEmpty(); + ProjectInfo projectInfo = getProjectInfo(info.kitName(), info.workspaceFolder()); + return !projectInfo.isEmpty(); } void ProjectTree::contextMenuEvent(QContextMenuEvent *event) @@ -357,13 +432,18 @@ DMenu *ProjectTree::childMenu(const QStandardItem *root, QStandardItem *childIte if (!menu) menu = new DMenu(); + QAction *newDirAction = new QAction(tr("New Directory"), this); + QObject::connect(newDirAction, &QAction::triggered, this, [=]() { + actionNewDirectory(childItem); + }); + QAction *newDocAction = new QAction(tr("New Document"), this); - QObject::connect(newDocAction, &QAction::triggered, this, [=](){ + QObject::connect(newDocAction, &QAction::triggered, this, [=]() { actionNewDocument(childItem); }); QAction *renameDocAction = new QAction(tr("Rename"), this); - QObject::connect(renameDocAction, &QAction::triggered, this, [=](){ + QObject::connect(renameDocAction, &QAction::triggered, this, [=]() { actionRenameDocument(childItem); }); @@ -373,18 +453,20 @@ DMenu *ProjectTree::childMenu(const QStandardItem *root, QStandardItem *childIte // add open in terminal menu item. QAction *openInTerminal = new QAction(tr("Open In Terminal"), this); menu->addAction(openInTerminal); - connect(openInTerminal, &QAction::triggered, [=](){ + connect(openInTerminal, &QAction::triggered, [=]() { actionOpenInTerminal(childItem); }); - if (info.isDir()) + if (info.isDir()) { menu->addAction(newDocAction); + menu->addAction(newDirAction); + } if (info.isFile()) { newDocAction->setEnabled(false); // add delete file menu item. QAction *deleteDocAction = new QAction(tr("Delete Document"), this); - QObject::connect(deleteDocAction, &QAction::triggered, this, [=](){ + QObject::connect(deleteDocAction, &QAction::triggered, this, [=]() { actionDeleteDocument(childItem); }); deleteDocAction->setEnabled(true); @@ -398,7 +480,7 @@ DMenu *ProjectTree::childMenu(const QStandardItem *root, QStandardItem *childIte QMenu *ProjectTree::rootMenu(QStandardItem *root) { - DMenu * menu = nullptr; + DMenu *menu = nullptr; QString toolKitName = ProjectInfo::get(root).kitName(); // 获取支持右键菜单生成器 auto &ctx = dpfInstance.serviceContext(); @@ -409,12 +491,12 @@ QMenu *ProjectTree::rootMenu(QStandardItem *root) if (!menu) menu = new DMenu(); - QAction* activeProjectAction = new QAction(QAction::tr("Project Active"), menu); - QAction* closeAction = new QAction(QAction::tr("Project Close"), menu); - QAction* propertyAction = new QAction(QAction::tr("Project Info"), menu); - QObject::connect(activeProjectAction, &QAction::triggered, activeProjectAction, [=](){doActiveProject(root);}); - QObject::connect(closeAction, &QAction::triggered, closeAction, [=](){doCloseProject(root);}); - QObject::connect(propertyAction, &QAction::triggered, propertyAction, [=](){doShowProjectInfo(root);}); + QAction *activeProjectAction = new QAction(QAction::tr("Project Active"), menu); + QAction *closeAction = new QAction(QAction::tr("Project Close"), menu); + QAction *propertyAction = new QAction(QAction::tr("Project Info"), menu); + QObject::connect(activeProjectAction, &QAction::triggered, activeProjectAction, [=]() { doActiveProject(root); }); + QObject::connect(closeAction, &QAction::triggered, closeAction, [=]() { doCloseProject(root); }); + QObject::connect(propertyAction, &QAction::triggered, propertyAction, [=]() { doShowProjectInfo(root); }); menu->insertAction(nullptr, activeProjectAction); menu->insertAction(nullptr, closeAction); menu->insertAction(nullptr, propertyAction); @@ -467,7 +549,7 @@ void ProjectTree::doCloseProject(QStandardItem *root) { if (!root && root != ProjectGenerator::root(root)) return; - auto info = ProjectInfo::get(root); + this->removeRootItem(root); } @@ -493,7 +575,7 @@ void ProjectTree::actionRenameDocument(const QStandardItem *item) dialog->addContent(inputEdit); dialog->addButton(tr("Ok"), true, DDialog::ButtonRecommend); - QObject::connect(dialog, &DDialog::buttonClicked, dialog, [=](){ + QObject::connect(dialog, &DDialog::buttonClicked, dialog, [=]() { if (!inputEdit->text().isEmpty()) { renameDocument(item, inputEdit->text()); } @@ -535,12 +617,11 @@ void ProjectTree::renameDocument(const QStandardItem *item, const QString &newFi d->messDialog->insertButton(0, tr("Cancel")); d->messDialog->insertButton(1, tr("Ok"), true, DDialog::ButtonWarning); } - connect(d->messDialog, &DDialog::buttonClicked, [=](int index) { - if (index == 0){ + if (index == 0) { d->messDialog->reject(); - } else if (index == 1){ + } else if (index == 1) { okCallBack(); QFile::remove(newFilePath); d->messDialog->accept(); @@ -556,6 +637,34 @@ void ProjectTree::renameDocument(const QStandardItem *item, const QString &newFi file.rename(newFilePath); } +void ProjectTree::actionNewDirectory(const QStandardItem *item) +{ + auto dialog = new DDialog(this); + auto inputEdit = new DLineEdit(dialog); + + inputEdit->setPlaceholderText(tr("New Dirctory Name")); + inputEdit->lineEdit()->setAlignment(Qt::AlignLeft); + + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowTitle(tr("New Dirctory")); + + dialog->addContent(inputEdit); + dialog->addButton(tr("Ok"), true, DDialog::ButtonRecommend); + dialog->setOnButtonClickedClose(false); + + QObject::connect(dialog, &DDialog::buttonClicked, dialog, [=]() { + auto ret = false; + if (!inputEdit->text().isEmpty()) { + ret = createNewDirectory(item, inputEdit->text()); + } + + if (ret) + dialog->close(); + }); + + dialog->exec(); +} + void ProjectTree::actionNewDocument(const QStandardItem *item) { auto dialog = new DDialog(this); @@ -570,7 +679,7 @@ void ProjectTree::actionNewDocument(const QStandardItem *item) dialog->addContent(inputEdit); dialog->addButton(tr("Ok"), true, DDialog::ButtonRecommend); - QObject::connect(dialog, &DDialog::buttonClicked, dialog, [=](){ + QObject::connect(dialog, &DDialog::buttonClicked, dialog, [=]() { if (!inputEdit->text().isEmpty()) { creatNewDocument(item, inputEdit->text()); } @@ -616,13 +725,53 @@ void ProjectTree::actionOpenInTerminal(const QStandardItem *menuItem) QModelIndex index = d->itemModel->indexFromItem(menuItem); QFileInfo fileInfo(index.data(Qt::ToolTipRole).toString()); - QString dirPath = fileInfo.dir().path(); + + QString dirPath; + if (fileInfo.isFile()) + dirPath = fileInfo.dir().path(); + else if (fileInfo.isDir()) + dirPath = fileInfo.filePath(); + auto terminalService = dpfGetService(TerminalService); if (terminalService) { - terminalService->executeCommand(QString("cd %1\n").arg(dirPath)); - terminalService->executeCommand(QString("clear\n")); - uiController.switchContext(CONSOLE_TAB_TEXT); + terminalService->sendCommand(QString("cd %1\n").arg(dirPath)); + terminalService->sendCommand(QString("clear\n")); + uiController.switchContext(TERMINAL_TAB_TEXT); + } +} + +bool ProjectTree::createNewDirectory(const QStandardItem *item, const QString &dirName) +{ + QModelIndex index = d->itemModel->indexFromItem(item); + QFileInfo info(index.data(Qt::ToolTipRole).toString()); + + auto root = ProjectGenerator::root(const_cast(item)); + QString toolKitName = ProjectInfo::get(root).kitName(); + + QString directoryPath; + if (info.isDir()) { + directoryPath = info.filePath() + QDir::separator() + dirName; + } else if (info.isFile()) { + directoryPath = info.path() + QDir::separator() + dirName; + } + + if (QFile::exists(directoryPath)) { + d->messDialog = new DDialog(); + d->messDialog->setIcon(QIcon::fromTheme("dialog-warning")); + d->messDialog->setMessage(tr("A directory with name %1 already exists. please reanme it").arg(directoryPath)); + d->messDialog->insertButton(0, tr("Cancel")); + d->messDialog->insertButton(1, tr("Ok"), true, DDialog::ButtonWarning); + + d->messDialog->exec(); + return false; + } + + auto &ctx = dpfInstance.serviceContext(); + ProjectService *projectService = ctx.service(ProjectService::name()); + if (projectService->supportGeneratorName().contains(toolKitName)) { + projectService->createGenerator(toolKitName)->createDirectory(item, directoryPath); } + return true; } void ProjectTree::creatNewDocument(const QStandardItem *item, const QString &fileName) @@ -653,9 +802,9 @@ void ProjectTree::creatNewDocument(const QStandardItem *item, const QString &fil d->messDialog->insertButton(1, tr("Ok"), true, DDialog::ButtonWarning); connect(d->messDialog, &DDialog::buttonClicked, [=](int index) { - if (index == 0){ + if (index == 0) { d->messDialog->reject(); - } else if (index == 1){ + } else if (index == 1) { okCallBack(); QFile::remove(filePath); d->messDialog->accept(); diff --git a/src/plugins/project/mainframe/projecttree.h b/src/plugins/project/mainframe/projecttree.h index 9854d9560..9d2f67809 100644 --- a/src/plugins/project/mainframe/projecttree.h +++ b/src/plugins/project/mainframe/projecttree.h @@ -29,6 +29,11 @@ class ProjectTree : public DTreeView void takeRootItem(QStandardItem *root); void expandedProjectDepth(const QStandardItem *root, int depth); void expandedProjectAll(const QStandardItem *root); + void selectProjectFile(const QString &file); + void setAutoFocusState(bool state); + bool getAutoFocusState() const; + void expandItemByFile(const QStringList &filePaths); + void focusCurrentFile(); QList getAllProjectInfo(); dpfservice::ProjectInfo getProjectInfo(const QString &kitName, const QString &workspace) const; dpfservice::ProjectInfo getActiveProjectInfo() const; @@ -37,6 +42,7 @@ class ProjectTree : public DTreeView Q_SIGNALS: void itemMenuRequest(QStandardItem *item, QContextMenuEvent *event); + void itemDeleted(QStandardItem *item); protected: void contextMenuEvent(QContextMenuEvent *event) override; @@ -57,10 +63,12 @@ private slots: void doCloseProject(QStandardItem *root); void doShowProjectInfo(QStandardItem *root); void doActiveProject(QStandardItem *root); + void actionNewDirectory(const QStandardItem *item); void actionNewDocument(const QStandardItem *item); void actionRenameDocument(const QStandardItem *item); void actionDeleteDocument(QStandardItem *item); void actionOpenInTerminal(const QStandardItem *item); + bool createNewDirectory(const QStandardItem *item, const QString &dirName); void creatNewDocument(const QStandardItem *item, const QString &fileName); void renameDocument(const QStandardItem *item, const QString &fileName); }; diff --git a/src/plugins/project/mainframe/recent/recentopenitemdelegate.cpp b/src/plugins/project/mainframe/recent/recentopenitemdelegate.cpp new file mode 100644 index 000000000..2d90c0da8 --- /dev/null +++ b/src/plugins/project/mainframe/recent/recentopenitemdelegate.cpp @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "recentopenitemdelegate.h" + +#include + +#include +#include +#include + +RecentOpenItemDelegate::RecentOpenItemDelegate(QObject *parent) : + BaseItemDelegate(parent) +{ +} + +void RecentOpenItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + BaseItemDelegate::paint(painter, option, index); + + if (option.state & QStyle::StateFlag::State_MouseOver) { + const QIcon icon(QIcon::fromTheme("edit-closeBtn").pixmap(16, 16)); + + QRect iconRect(option.rect.right() - option.rect.height() - 15, + option.rect.top(), + option.rect.height(), + option.rect.height()); + + icon.paint(painter, iconRect, Qt::AlignRight | Qt::AlignVCenter); + } +} + +bool RecentOpenItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent && mouseEvent->button() == Qt::LeftButton) { + QRect buttonRect(option.rect.right() - option.rect.height() - 15, + option.rect.top(), + option.rect.height(), + option.rect.height()); + + if (buttonRect.contains(mouseEvent->pos())) { + emit closeBtnClicked(index); + return true; + } + } + } + return QStyledItemDelegate::editorEvent(event, model, option, index); +} + +QSize RecentOpenItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_UNUSED(index) + return { option.rect.width(), 24 }; +} diff --git a/src/plugins/project/mainframe/recent/recentopenitemdelegate.h b/src/plugins/project/mainframe/recent/recentopenitemdelegate.h new file mode 100644 index 000000000..87edd1be2 --- /dev/null +++ b/src/plugins/project/mainframe/recent/recentopenitemdelegate.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RECENTOPENITEMDELEGATE_H +#define RECENTOPENITEMDELEGATE_H + +#include "base/baseitemdelegate.h" + +#include + +#include +#include + +class RecentOpenItemDelegate : public BaseItemDelegate +{ + Q_OBJECT +public: + explicit RecentOpenItemDelegate(QObject *parent = nullptr); + +signals: + void closeBtnClicked(const QModelIndex &index); + +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +#endif // RECENTOPENLISTDELEGATE_H diff --git a/src/plugins/project/mainframe/recent/recentopenview.cpp b/src/plugins/project/mainframe/recent/recentopenview.cpp new file mode 100644 index 000000000..fd7acc136 --- /dev/null +++ b/src/plugins/project/mainframe/recent/recentopenview.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "recentopenview.h" + +#include + +RecentOpenView::RecentOpenView(QWidget *parent) + : QTreeView(parent) +{ +} + +RecentOpenView::~RecentOpenView() +{ +} + +void RecentOpenView::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace + && event->modifiers() == 0) { + emit closeActivated(currentIndex()); + } else { + QTreeView::keyPressEvent(event); + } +} diff --git a/src/plugins/project/mainframe/recent/recentopenview.h b/src/plugins/project/mainframe/recent/recentopenview.h new file mode 100644 index 000000000..1805981e0 --- /dev/null +++ b/src/plugins/project/mainframe/recent/recentopenview.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RECENTOPENVIEW_H +#define RECENTOPENVIEW_H + +#include + +class RecentOpenView : public Dtk::Widget::DTreeView +{ + Q_OBJECT +public: + explicit RecentOpenView(QWidget *parent = nullptr); + ~RecentOpenView(); + +signals: + void closeActivated(const QModelIndex &index); + +protected: + void keyPressEvent(QKeyEvent *event) override; +}; + +#endif // RECENTOPENVIEW_H diff --git a/src/plugins/project/mainframe/recent/recentopenwidget.cpp b/src/plugins/project/mainframe/recent/recentopenwidget.cpp new file mode 100644 index 000000000..a446c5381 --- /dev/null +++ b/src/plugins/project/mainframe/recent/recentopenwidget.cpp @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "recentopenwidget.h" +#include "common/util/customicons.h" + +#include + +#include +#include +#include + +DWIDGET_USE_NAMESPACE + +RecentOpenWidget::RecentOpenWidget(QWidget *parent) + : DFrame(parent) +{ + initUI(); +} + +RecentOpenWidget::~RecentOpenWidget() +{ +} + +void RecentOpenWidget::initUI() +{ + QHBoxLayout *mainLayout = new QHBoxLayout(); + mainLayout->setContentsMargins(0, 0, 0, 0); + + listView = new RecentOpenView(this); + listView->setEditTriggers(QAbstractItemView::NoEditTriggers); + listView->setLineWidth(0); + listView->setHeaderHidden(true); + listView->setContentsMargins(0, 0, 0, 0); + listView->setRootIsDecorated(false); + listView->setIconSize(QSize(16, 16)); + delegate = new RecentOpenItemDelegate(listView); + listView->setItemDelegate(delegate); + listView->setSelectionMode(QAbstractItemView::SingleSelection); + + model = new QStandardItemModel(this); + proxyModel = new QSortFilterProxyModel(this); + proxyModel->setSourceModel(model); + proxyModel->setSortRole(Qt::DisplayRole); + proxyModel->sort(0); + listView->setModel(proxyModel); + + connect(listView, &RecentOpenView::clicked, this, &RecentOpenWidget::triggered); + connect(listView, &RecentOpenView::closeActivated, this, &RecentOpenWidget::closePage); + connect(delegate, &RecentOpenItemDelegate::closeBtnClicked, this, &RecentOpenWidget::closePage); + + mainLayout->addWidget(listView); + this->setLineWidth(0); + this->setLayout(mainLayout); + DStyle::setFrameRadius(this, 0); +} + +void RecentOpenWidget::setOpenedFiles(const QVector &list) +{ + model->clear(); + QSet fileNamesSet; + + for (const QString &absolutePath : list) { + QFileInfo info(absolutePath); + QString fileName = info.fileName(); + QStandardItem *item = new QStandardItem(fileName); + item->setData(absolutePath, RecentOpenedUserRole::FilePathRole); + item->setIcon(CustomIcons::icon(info)); + model->appendRow(item); + } + + for (int i = 0; i < model->rowCount(); ++i) { + QStandardItem *item = model->item(i); + QString fileName = item->text(); + if (fileNamesSet.contains(fileName)) { + for (int j = 0; j < model->rowCount(); ++j) { + QStandardItem *nextItem = model->item(j); + if (nextItem->text() == fileName) { + QString dirName = QFileInfo(nextItem->data(RecentOpenedUserRole::FilePathRole).toString()).absolutePath(); + dirName = dirName.mid(dirName.lastIndexOf('/') + 1); + QString newName = dirName + "/" + fileName; + nextItem->setText(newName); + } + } + } else { + fileNamesSet.insert(fileName); + } + } +} + +void RecentOpenWidget::setListViewSelection(int num) +{ + if (num < 0 || num >= proxyModel->rowCount() || !listView || !proxyModel) { + return; + } + + QModelIndex index = proxyModel->index(num, 0); + listView->setCurrentIndex(index); +} + +void RecentOpenWidget::setListViewSelection(const QString &file) +{ + if (!listView || !proxyModel) { + return; + } + int rowCount = proxyModel->rowCount(); + for (int i = 0; i < rowCount; ++i) { + QModelIndex proxyIndex = proxyModel->index(i, 0); + QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex); + if (file == model->itemData(sourceIndex).value(RecentOpenedUserRole::FilePathRole)) { + listView->setCurrentIndex(proxyIndex); + break; + } + } +} + +void RecentOpenWidget::setFocusListView() +{ + listView->setFocus(); +} diff --git a/src/plugins/project/mainframe/recent/recentopenwidget.h b/src/plugins/project/mainframe/recent/recentopenwidget.h new file mode 100644 index 000000000..7269ea2c5 --- /dev/null +++ b/src/plugins/project/mainframe/recent/recentopenwidget.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RECENTOPENWIDGET_H +#define RECENTOPENWIDGET_H + +#include "recentopenview.h" +#include "recentopenitemdelegate.h" + +#include + +#include +#include +#include + +class RecentOpenWidget : public Dtk::Widget::DFrame +{ + Q_OBJECT + +public: + enum RecentOpenedUserRole { + FilePathRole = Qt::UserRole + 1 + }; + explicit RecentOpenWidget(QWidget *parent = nullptr); + ~RecentOpenWidget(); + + void initUI(); + void setOpenedFiles(const QVector &list); + void setListViewSelection(int index); + void setListViewSelection(const QString &file); + void setFocusListView(); + +signals: + void triggered(const QModelIndex &index); + void closePage(const QModelIndex &index); + +private: + RecentOpenView *listView = nullptr; + QStandardItemModel *model = nullptr; + QSortFilterProxyModel *proxyModel = nullptr; + RecentOpenItemDelegate *delegate = nullptr; +}; + +#endif // RECENTOPENWIDGET_H diff --git a/src/plugins/project/projectcore.cpp b/src/plugins/project/projectcore.cpp index b6fcb0613..12a598f3b 100644 --- a/src/plugins/project/projectcore.cpp +++ b/src/plugins/project/projectcore.cpp @@ -7,15 +7,17 @@ #include "transceiver/projectcorereceiver.h" #include "mainframe/projectkeeper.h" #include "mainframe/projecttree.h" +#include "mainframe/recent/recentopenwidget.h" #include "common/common.h" #include "base/abstractmenu.h" #include "base/abstractaction.h" #include "base/abstractwidget.h" -#include "services/window/windowservice.h" #include "services/project/projectservice.h" #include "services/locator/locatorservice.h" +#include "services/editor/editorservice.h" #include "locator/allprojectfilelocator.h" #include "locator/currentprojectlocator.h" +#include "base/abstractwidget.h" #include #include @@ -23,6 +25,9 @@ #include using namespace dpfservice; +static const QString openFilesWidgetName = "openFilesWidget"; +static bool openFileWidgetInited = false; + void ProjectCore::initialize() { qInfo() << __FUNCTION__; @@ -44,19 +49,17 @@ bool ProjectCore::start() if (windowService->addWidgetWorkspace) { auto view = new AbstractWidget(ProjectKeeper::instance()->treeView()); windowService->addWidgetWorkspace(MWCWT_PROJECTS, view, "project"); + + DToolButton *focusFile = new DToolButton(ProjectKeeper::instance()->treeView()); + DToolButton *autoFocusSwitcher = new DToolButton(ProjectKeeper::instance()->treeView()); DToolButton *projectProperty = new DToolButton(ProjectKeeper::instance()->treeView()); - projectProperty->setIcon(QIcon::fromTheme("settings")); - projectProperty->setToolTip(tr("Open activted project`s property dialog")); - connect(projectProperty, &DToolButton::clicked, this, [=](){ - project.openProjectPropertys(ProjectKeeper::instance()->treeView()->getActiveProjectInfo()); - }, Qt::DirectConnection); - // todo(zta :temp only supprt cmake project do other kit later - connect(ProjectProxy::instance(), &ProjectProxy::projectActivated, this, [=](const ProjectInfo &prjInfo){ - if (prjInfo.kitName() != "cmake") - projectProperty->setEnabled(false); - else - projectProperty->setEnabled(true); - }, Qt::DirectConnection); + + addRecentOpenWidget(windowService); + addAutoFocusSwitcher(windowService, autoFocusSwitcher, focusFile); + addProjectProperty(windowService, projectProperty); + + windowService->registerToolBtnToWorkspaceWidget(focusFile, MWCWT_PROJECTS); + windowService->registerToolBtnToWorkspaceWidget(autoFocusSwitcher, MWCWT_PROJECTS); windowService->registerToolBtnToWorkspaceWidget(projectProperty, MWCWT_PROJECTS); } } @@ -64,6 +67,103 @@ bool ProjectCore::start() QObject::connect(&dpf::Listener::instance(), &dpf::Listener::pluginsStarted, this, &ProjectCore::pluginsStartedMain, Qt::DirectConnection); + initProject(ctx); + initLocator(ctx); + + return true; +} + +void ProjectCore::addRecentOpenWidget(WindowService *windowService) +{ + RecentOpenWidget *openedWidget = new RecentOpenWidget(); + auto editSrv = dpfGetService(EditorService); + connect(ProjectProxy::instance(), &ProjectProxy::switchedFile, this, [=](const QString &file) { + openedWidget->setOpenedFiles(editSrv->openedFiles().toVector()); + openedWidget->setListViewSelection(file); + }, + Qt::DirectConnection); + connect(openedWidget, &RecentOpenWidget::triggered, [=](const QModelIndex &index) { + QFileInfo info(index.data(RecentOpenWidget::RecentOpenedUserRole::FilePathRole).toString()); + if (info.exists() && info.isFile()) { + editor.openFile(QString(), info.filePath()); + } + }); + connect(openedWidget, &RecentOpenWidget::closePage, [=](const QModelIndex &index) { + QFileInfo info(index.data(RecentOpenWidget::RecentOpenedUserRole::FilePathRole).toString()); + if (info.exists() && info.isFile()) { + editor.closeFile(info.filePath()); + } + }); + connect(ProjectProxy::instance(), &ProjectProxy::modeRaised, this, [=](const QString &mode) { + if (mode != CM_EDIT || openFileWidgetInited) + return; + initOpenFilesWidget(windowService); + }, + Qt::DirectConnection); + auto openFilesWidget = new AbstractWidget(openedWidget); + openFilesWidget->setDisplayIcon(QIcon::fromTheme("opened_files")); + windowService->registerWidgetToMode(openFilesWidgetName, openFilesWidget, CM_EDIT, Position::Left, false, true); + windowService->setDockHeaderName(openFilesWidgetName, tr("Opened Files")); +} + +void ProjectCore::addAutoFocusSwitcher(WindowService *windowService, DToolButton *autoFocusSwitcher, DToolButton *focusFile) +{ + autoFocusSwitcher->setToolTip(tr("Auto Focus")); + autoFocusSwitcher->setIcon(QIcon::fromTheme("focus_auto")); + autoFocusSwitcher->setCheckable(true); + autoFocusSwitcher->setChecked(true); + + focusFile->setToolTip(tr("Focus File")); + focusFile->setIcon(QIcon::fromTheme("focus")); + + connect(focusFile, &DToolButton::clicked, this, []() { + ProjectKeeper::instance()->treeView()->focusCurrentFile(); + }, + Qt::DirectConnection); + focusFile->hide(); + + connect(autoFocusSwitcher, &DToolButton::clicked, this, [=]() { + bool state = ProjectKeeper::instance()->treeView()->getAutoFocusState(); + ProjectKeeper::instance()->treeView()->setAutoFocusState(!state); + if (state) { + focusFile->show(); + } else { + focusFile->hide(); + } + }, + Qt::DirectConnection); +} + +void ProjectCore::addProjectProperty(WindowService *windowService, DToolButton *projectProperty) +{ + projectProperty->setIcon(QIcon::fromTheme("settings")); + projectProperty->setToolTip(tr("Open activted project`s property dialog")); + + connect(projectProperty, &DToolButton::clicked, this, [=]() { + project.openProjectPropertys(ProjectKeeper::instance()->treeView()->getActiveProjectInfo()); + }, + Qt::DirectConnection); + // todo(zta :temp only supprt cmake project do other kit later + connect(ProjectProxy::instance(), &ProjectProxy::projectActivated, this, [=](const ProjectInfo &prjInfo) { + if (prjInfo.kitName() != "cmake") + projectProperty->setEnabled(false); + else + projectProperty->setEnabled(true); + }, + Qt::DirectConnection); +} + +void ProjectCore::initLocator(dpf::PluginServiceContext &ctx) +{ + LocatorService *locatorService = ctx.service(LocatorService::name()); + AllProjectFileLocator *allProjectFileLocator = new AllProjectFileLocator(this); + CurrentProjectLocator *currentProjectLocator = new CurrentProjectLocator(this); + locatorService->registerLocator(allProjectFileLocator); + locatorService->registerLocator(currentProjectLocator); +} + +void ProjectCore::initProject(dpf::PluginServiceContext &ctx) +{ using namespace std::placeholders; ProjectService *projectService = ctx.service(ProjectService::name()); if (projectService) { @@ -95,16 +195,10 @@ bool ProjectCore::start() if (!projectService->updateProjectInfo) { projectService->updateProjectInfo = std::bind(&ProjectTree::updateProjectInfo, treeView, _1); } + if (!projectService->expandItemByFile) { + projectService->expandItemByFile = std::bind(&ProjectTree::expandItemByFile, treeView, _1); + } } - - //init locator - LocatorService *locatorService = ctx.service(LocatorService::name()); - AllProjectFileLocator *allProjectFileLocator = new AllProjectFileLocator(this); - CurrentProjectLocator *currentProjectLocator = new CurrentProjectLocator(this); - locatorService->registerLocator(allProjectFileLocator); - locatorService->registerLocator(currentProjectLocator); - - return true; } dpf::Plugin::ShutdownFlag ProjectCore::stop() @@ -135,3 +229,30 @@ void ProjectCore::pluginsStartedMain() } } } + +void ProjectCore::initOpenFilesWidget(dpfservice::WindowService *windowService) +{ + // adjust position + windowService->splitWidgetOrientation(WN_WORKSPACE, openFilesWidgetName, Qt::Vertical); + + QStringList allLeftDocks = windowService->getCurrentDockName(Position::Left); + auto dockCount = allLeftDocks.size(); + + if (!allLeftDocks.contains(openFilesWidgetName) || dockCount <= 1) + return; + + allLeftDocks.removeOne(openFilesWidgetName); + + QStringList docks { openFilesWidgetName }; + // Set the height of the widget to 25% of the total height. + QList sizes { 25 }; + auto size = 75 / (dockCount - 1); + for (auto dock : allLeftDocks) { + sizes.append(size); + docks.append(dock); + } + + windowService->resizeDocks(docks, sizes, Qt::Vertical); + + openFileWidgetInited = true; +} diff --git a/src/plugins/project/projectcore.h b/src/plugins/project/projectcore.h index bfd3a3e66..14926c958 100644 --- a/src/plugins/project/projectcore.h +++ b/src/plugins/project/projectcore.h @@ -5,6 +5,10 @@ #ifndef PROJECTCORE_H #define PROJECTCORE_H +#include "services/window/windowservice.h" + +#include + #include class ProjectCore : public dpf::Plugin @@ -15,8 +19,19 @@ class ProjectCore : public dpf::Plugin virtual void initialize() override; virtual bool start() override; virtual dpf::Plugin::ShutdownFlag stop() override; + + void addRecentOpenWidget(dpfservice::WindowService *windowservice); + void addAutoFocusSwitcher(dpfservice::WindowService *windowService, + Dtk::Widget::DToolButton *autoFocusSwitcher, Dtk::Widget::DToolButton *focusFile); + void addProjectProperty(dpfservice::WindowService *windowService, + Dtk::Widget::DToolButton *projectProperty); + + void initLocator(dpf::PluginServiceContext& ctx); + void initProject(dpf::PluginServiceContext& ctx); + private slots: void pluginsStartedMain(); + void initOpenFilesWidget(dpfservice::WindowService *windowService); }; #endif // PROJECTCORE_H diff --git a/src/plugins/project/projectcore.json b/src/plugins/project/projectcore.json index b1f5232b0..85dbd96cb 100644 --- a/src/plugins/project/projectcore.json +++ b/src/plugins/project/projectcore.json @@ -3,7 +3,7 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], diff --git a/src/plugins/project/resource.qrc b/src/plugins/project/resource.qrc index 77c43e0a6..27fce2621 100644 --- a/src/plugins/project/resource.qrc +++ b/src/plugins/project/resource.qrc @@ -2,5 +2,8 @@ texts/project_16px.svg texts/settings_16px.svg + texts/focus_16px.svg + texts/focus_auto_16px.svg + texts/opened_files_16px.svg diff --git a/src/plugins/project/texts/focus_16px.svg b/src/plugins/project/texts/focus_16px.svg new file mode 100644 index 000000000..39335fe42 --- /dev/null +++ b/src/plugins/project/texts/focus_16px.svg @@ -0,0 +1,7 @@ + + + ICON / list /focus + + + + \ No newline at end of file diff --git a/src/plugins/project/texts/focus_auto_16px.svg b/src/plugins/project/texts/focus_auto_16px.svg new file mode 100644 index 000000000..e309c9500 --- /dev/null +++ b/src/plugins/project/texts/focus_auto_16px.svg @@ -0,0 +1,7 @@ + + + ICON / list /focus_auto + + + + \ No newline at end of file diff --git a/src/plugins/project/texts/opened_files_16px.svg b/src/plugins/project/texts/opened_files_16px.svg new file mode 100644 index 000000000..bbdc1a5f6 --- /dev/null +++ b/src/plugins/project/texts/opened_files_16px.svg @@ -0,0 +1,7 @@ + + + ICON / sidebar /opened_file + + + + \ No newline at end of file diff --git a/src/plugins/project/transceiver/projectcorereceiver.cpp b/src/plugins/project/transceiver/projectcorereceiver.cpp index 18fba6917..7a569b444 100644 --- a/src/plugins/project/transceiver/projectcorereceiver.cpp +++ b/src/plugins/project/transceiver/projectcorereceiver.cpp @@ -10,10 +10,8 @@ #include "services/window/windowelement.h" ProjectCoreReceiver::ProjectCoreReceiver(QObject *parent) - : dpf::EventHandler (parent) - , dpf::AutoEventHandlerRegister () + : dpf::EventHandler(parent), dpf::AutoEventHandlerRegister() { - } dpf::EventHandler::Type ProjectCoreReceiver::type() @@ -23,7 +21,7 @@ dpf::EventHandler::Type ProjectCoreReceiver::type() QStringList ProjectCoreReceiver::topics() { - return {project.topic, workspace.topic}; //绑定menu 事件 + return { project.topic, workspace.topic, editor.topic, uiController.topic }; //绑定menu 事件 } void ProjectCoreReceiver::eventProcess(const dpf::Event &event) @@ -31,9 +29,9 @@ void ProjectCoreReceiver::eventProcess(const dpf::Event &event) using namespace dpfservice; if (event.data() == project.activeProject.name) { auto infos = ProjectKeeper::instance()->treeView()->getAllProjectInfo(); - QString kitName = event.property(project.openProject.pKeys[0]).toString(); - QString language = event.property(project.openProject.pKeys[1]).toString(); - QString workspace = event.property(project.openProject.pKeys[2]).toString(); + QString kitName = event.property("kitName").toString(); + QString language = event.property("language").toString(); + QString workspace = event.property("workspace").toString(); ProjectKeeper::instance()->treeView()->activeProjectInfo(kitName, language, workspace); } else if (event.data() == project.openProject.name) { uiController.doSwitch(dpfservice::MWNA_EDIT); @@ -60,12 +58,16 @@ void ProjectCoreReceiver::eventProcess(const dpf::Event &event) ProjectKeeper::instance()->treeView()->expandAll(); } else if (event.data() == workspace.foldAll.name) { ProjectKeeper::instance()->treeView()->collapseAll(); - } else if (event.data() == project.activedProject.name) { + } else if (event.data() == project.activatedProject.name) { QVariant proInfoVar = event.property("projectInfo"); dpfservice::ProjectInfo projectInfo = qvariant_cast(proInfoVar); emit ProjectProxy::instance()->projectActivated(projectInfo); + } else if (event.data() == editor.switchedFile.name) { + emit ProjectProxy::instance()->switchedFile(event.property("fileName").toString()); + ProjectKeeper::instance()->treeView()->selectProjectFile(event.property("fileName").toString()); + } else if (event.data() == uiController.modeRaised.name) { + auto mode = event.property("mode").toString(); + emit ProjectProxy::instance()->modeRaised(mode); } } - - diff --git a/src/plugins/project/transceiver/projectcorereceiver.h b/src/plugins/project/transceiver/projectcorereceiver.h index 1443a20d4..25e1a078d 100644 --- a/src/plugins/project/transceiver/projectcorereceiver.h +++ b/src/plugins/project/transceiver/projectcorereceiver.h @@ -35,7 +35,9 @@ class ProjectProxy : public QObject return &ins; } signals: + void modeRaised(const QString &mode); void projectActivated(const dpfservice::ProjectInfo prjInfo); + void switchedFile(const QString &file); }; #endif // PROJECTCORERECEIVER_H diff --git a/src/plugins/project/transceiver/sendevents.cpp b/src/plugins/project/transceiver/sendevents.cpp index 49fb22205..a4e810e13 100644 --- a/src/plugins/project/transceiver/sendevents.cpp +++ b/src/plugins/project/transceiver/sendevents.cpp @@ -8,7 +8,7 @@ void SendEvents::projectActived(const dpfservice::ProjectInfo &info) { - project.activedProject(info); + project.activatedProject(info); } void SendEvents::projectCreated(const dpfservice::ProjectInfo &info) @@ -21,6 +21,16 @@ void SendEvents::projectDeleted(const dpfservice::ProjectInfo &info) project.deletedProject(info); } +void SendEvents::projectNodeExpanded(const QModelIndex &index) +{ + project.projectNodeExpanded(index); +} + +void SendEvents::projectNodeCollapsed(const QModelIndex &index) +{ + project.projectNodeCollapsed(index); +} + void SendEvents::collaboratorsOpenRepos(const QString &workspace) { dpf::Event event; diff --git a/src/plugins/project/transceiver/sendevents.h b/src/plugins/project/transceiver/sendevents.h index daf9e1cfd..ecedff101 100644 --- a/src/plugins/project/transceiver/sendevents.h +++ b/src/plugins/project/transceiver/sendevents.h @@ -16,6 +16,8 @@ class SendEvents final static void projectActived(const dpfservice::ProjectInfo &info); static void projectCreated(const dpfservice::ProjectInfo &info); static void projectDeleted(const dpfservice::ProjectInfo &info); + static void projectNodeExpanded(const QModelIndex &index); + static void projectNodeCollapsed(const QModelIndex &index); static void collaboratorsOpenRepos(const QString &workspace); }; diff --git a/src/plugins/python/python/project/properties/configpropertywidget.cpp b/src/plugins/python/python/project/properties/configpropertywidget.cpp index e1d1dd40f..309e4f156 100644 --- a/src/plugins/python/python/project/properties/configpropertywidget.cpp +++ b/src/plugins/python/python/project/properties/configpropertywidget.cpp @@ -11,6 +11,7 @@ #include #include +#include #include @@ -18,14 +19,15 @@ using namespace config; class DetailPropertyWidgetPrivate { friend class DetailPropertyWidget; - DComboBox *pyVersionComboBox{nullptr}; - DComboBox *executeFileComboBox{nullptr}; + DComboBox *pyVersionComboBox { nullptr }; + DComboBox *executeFileComboBox { nullptr }; + DCheckBox *runInTerminal {nullptr}; + QSharedPointer toolChainData; }; DetailPropertyWidget::DetailPropertyWidget(QWidget *parent) - : QWidget(parent) - , d(new DetailPropertyWidgetPrivate()) + : QWidget(parent), d(new DetailPropertyWidgetPrivate()) { setupUI(); initData(); @@ -60,6 +62,14 @@ void DetailPropertyWidget::setupUI() hLayout->addWidget(d->executeFileComboBox); vLayout->addLayout(hLayout); + hLayout = new QHBoxLayout; + label = new DLabel(tr("Run in terminal: "), this); + label->setFixedWidth(120); + d->runInTerminal = new DCheckBox(this); + hLayout->addWidget(label); + hLayout->addWidget(d->runInTerminal); + vLayout->addLayout(hLayout); + vLayout->addStretch(10); } @@ -95,6 +105,7 @@ QStringList getPythonAllVersion() QString pattern = "((\\d)|(\\d+.\\d+))($|\\s)"; QStringList versions = findAll(pattern, pythonList.join(" "), true); + return versions; } @@ -119,7 +130,7 @@ void DetailPropertyWidget::initData() int index = 0; for (auto version : data) { ToolChainData::ToolChainParam param; - param.name = QString("python%1").arg(version); + param.name = QString("python%1").arg(version); param.path = "/usr/bin/" + param.name; QString text = param.name + "(" + param.path + ")"; comboBox->insertItem(index, text); @@ -143,22 +154,26 @@ void DetailPropertyWidget::setValues(const config::ProjectConfigure *param) auto globalToolPath = OptionManager::getInstance()->getPythonToolPath(); for (int i = 0; i < count; i++) { ToolChainData::ToolChainParam toolChainParam = qvariant_cast(d->pyVersionComboBox->itemData(i, Qt::UserRole + 1)); - if ((param->pythonVersion.name == toolChainParam.name - && param->pythonVersion.path == toolChainParam.path) - || globalToolPath == toolChainParam.path) { + if (param->pythonVersion.name == toolChainParam.name + && param->pythonVersion.path == toolChainParam.path) { d->pyVersionComboBox->setCurrentIndex(i); break; } + + if (globalToolPath == toolChainParam.path) + d->pyVersionComboBox->setCurrentIndex(i); } count = d->executeFileComboBox->count(); for (int i = 0; i < count; i++) { if (param->executeFile == ExecuteFile::CURRENTFILE) { d->executeFileComboBox->setCurrentIndex(0); - } else if(param->executeFile == ExecuteFile::ENTRYFILE) { + } else if (param->executeFile == ExecuteFile::ENTRYFILE) { d->executeFileComboBox->setCurrentIndex(1); } } + + d->runInTerminal->setChecked(param->runInTerminal); } void DetailPropertyWidget::getValues(config::ProjectConfigure *param) @@ -175,25 +190,26 @@ void DetailPropertyWidget::getValues(config::ProjectConfigure *param) } index = d->executeFileComboBox->currentIndex(); - if (index == 0) { //current file + if (index == 0) { //current file param->executeFile = ExecuteFile::CURRENTFILE; - } else if (index == 1) { //Entry file + } else if (index == 1) { //Entry file param->executeFile = ExecuteFile::ENTRYFILE; } + + param->runInTerminal = d->runInTerminal->isChecked(); } class ConfigPropertyWidgetPrivate { friend class ConfigPropertyWidget; - DetailPropertyWidget *detail{nullptr}; - QStandardItem *item{nullptr}; + DetailPropertyWidget *detail { nullptr }; + QStandardItem *item { nullptr }; dpfservice::ProjectInfo projectInfo; }; ConfigPropertyWidget::ConfigPropertyWidget(const dpfservice::ProjectInfo &projectInfo, QStandardItem *item, DWidget *parent) - : PageWidget(parent) - , d(new ConfigPropertyWidgetPrivate()) + : PageWidget(parent), d(new ConfigPropertyWidgetPrivate()) { d->item = item; d->projectInfo = projectInfo; diff --git a/src/plugins/python/python/project/properties/configutil.cpp b/src/plugins/python/python/project/properties/configutil.cpp index c3fb0ffd1..5b4a68bbc 100644 --- a/src/plugins/python/python/project/properties/configutil.cpp +++ b/src/plugins/python/python/project/properties/configutil.cpp @@ -88,6 +88,7 @@ void ConfigUtil::updateProjectInfo(dpfservice::ProjectInfo &info, const ProjectC info.setWorkspaceFolder(param->projectPath); info.setBuildFolder(param->projectPath); info.setBuildProgram(param->pythonVersion.path); + info.setRunInTerminal(param->runInTerminal); info.setCurrentProgram(param->executeFile == ExecuteFile::CURRENTFILE ? exeCurrent : exeEntry); } diff --git a/src/plugins/python/python/project/properties/configutil.h b/src/plugins/python/python/project/properties/configutil.h index eab074149..991847294 100644 --- a/src/plugins/python/python/project/properties/configutil.h +++ b/src/plugins/python/python/project/properties/configutil.h @@ -54,6 +54,7 @@ struct ProjectConfigure { QString projectPath; ItemInfo pythonVersion; ExecuteFile executeFile; + bool runInTerminal; friend QDataStream &operator<<(QDataStream &stream, const ProjectConfigure &data) { @@ -62,6 +63,7 @@ struct ProjectConfigure { stream << data.projectPath; stream << data.pythonVersion; stream << static_cast(data.executeFile); + stream << data.runInTerminal; return stream; } @@ -75,6 +77,7 @@ struct ProjectConfigure { qint8 temp; stream >> temp; data.executeFile = static_cast(temp); + stream >> data.runInTerminal; return stream; } diff --git a/src/plugins/python/python/pythondebug.cpp b/src/plugins/python/python/pythondebug.cpp index 86ea9f75d..fee95d63f 100644 --- a/src/plugins/python/python/pythondebug.cpp +++ b/src/plugins/python/python/pythondebug.cpp @@ -4,6 +4,7 @@ #include "pythondebug.h" +#include "project/properties/configutil.h" #include "services/option/optionmanager.h" #include "common/util/custompaths.h" @@ -15,6 +16,7 @@ class PythonDebugPrivate { friend class PythonDebug; + QString interpreterPath; }; PythonDebug::PythonDebug(QObject *parent) @@ -37,8 +39,10 @@ bool PythonDebug::prepareDebug(const QString &fileName, QString &retMsg) return false; } - QString pythonTool = OptionManager::getInstance()->getPythonToolPath(); - if (!pythonTool.contains("python3")) { + d->interpreterPath = config::ConfigUtil::instance()->getConfigureParamPointer()->pythonVersion.path; + if (d->interpreterPath.isEmpty()) //project has not set interpreter to config. use default interpreter + d->interpreterPath = OptionManager::getInstance()->getPythonToolPath(); + if (!d->interpreterPath.contains("python3")) { retMsg = tr("The python3 is needed, please select it in options dialog or install it."); return false; } @@ -69,7 +73,7 @@ bool PythonDebug::requestDAPPort(const QString &uuid, const QString &kit, QString projectCachePath = CustomPaths::projectCachePath(projectPath); msg << uuid << kit - << OptionManager::getInstance()->getPythonToolPath() + << d->interpreterPath << fileName << projectPath << projectCachePath; diff --git a/src/plugins/python/python/pythongenerator.cpp b/src/plugins/python/python/pythongenerator.cpp index 9eb9f3e1e..cbd51c5a6 100644 --- a/src/plugins/python/python/pythongenerator.cpp +++ b/src/plugins/python/python/pythongenerator.cpp @@ -141,6 +141,7 @@ RunCommandInfo PythonGenerator::getRunArguments(const ProjectInfo &projectInfo, } } runCommandInfo.arguments << runFilePath; + runCommandInfo.runInTerminal = ConfigUtil::instance()->getConfigureParamPointer()->runInTerminal; return runCommandInfo; } diff --git a/src/plugins/python/pythonplugin.h b/src/plugins/python/pythonplugin.h index 57d66af76..f878cd009 100644 --- a/src/plugins/python/pythonplugin.h +++ b/src/plugins/python/pythonplugin.h @@ -16,8 +16,6 @@ class PythonPlugin : public dpf::Plugin virtual bool start() override; virtual dpf::Plugin::ShutdownFlag stop() override; -signals: - private: void registEditorService(); }; diff --git a/src/plugins/python/pythonplugin.json b/src/plugins/python/pythonplugin.json index 431e15982..697edf9ef 100644 --- a/src/plugins/python/pythonplugin.json +++ b/src/plugins/python/pythonplugin.json @@ -3,14 +3,23 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Languages", "Description" : "The python plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=664d969d685f572b03c2aca9&id=664d96f8685f572b03c2acae", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=664d969d685f572b03c2aca9&id=664d96f8685f572b03c2acae", "Depends" : [ {"Name" : "codeeditor"} + ], + "InstallDepends" : [ + { + "InstallerName" : "pip", + "Packages" : [ + "debugpy", + "python-language-server" + ] + } ] } diff --git a/src/plugins/recent/mainframe/recentdisplay.cpp b/src/plugins/recent/mainframe/recentdisplay.cpp index c948a4faa..7947839bf 100644 --- a/src/plugins/recent/mainframe/recentdisplay.cpp +++ b/src/plugins/recent/mainframe/recentdisplay.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -209,11 +210,11 @@ class RecentDisplayPrivate DisplayProjectView *proView{nullptr}; DisplayDocView *docView{nullptr}; DLabel *proLabel{nullptr}; - DPushButton *proClear{nullptr}; + DToolButton *proClear{nullptr}; DDialog *clearProConfirm{nullptr}; DLabel *docLabel{nullptr}; - DPushButton *docClear{nullptr}; + DToolButton *docClear{nullptr}; DDialog *clearDocConfirm{nullptr}; DFrame *navFrame{nullptr}; @@ -461,16 +462,15 @@ void RecentDisplay::initializeUi() //recent open document d->docFrame->setLineWidth(0); DStyle::setFrameRadius(d->docFrame, 0); - d->docView = new DisplayDocView(); + d->docView = new DisplayDocView(this); d->docLabel = new DLabel(tr("Documents")); d->docLabel->setForegroundRole(QPalette::BrightText); d->docLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); d->docLabel->setContentsMargins(10, 5, 0, 10); - d->docClear = new DPushButton(); + d->docClear = new DToolButton(this); d->docClear->setIcon(QIcon::fromTheme("ide_recent_delete")); - d->docClear->setFlat(true); d->docClear->setToolTip(tr("clear all")); d->clearDocConfirm = new DDialog(this); @@ -494,16 +494,15 @@ void RecentDisplay::initializeUi() //recent open projects d->proFrame->setLineWidth(0); DStyle::setFrameRadius(d->proFrame, 0); - d->proView = new DisplayProjectView(); + d->proView = new DisplayProjectView(this); - d->proLabel = new DLabel(tr("Projects")); + d->proLabel = new DLabel(tr("Projects"), this); d->proLabel->setForegroundRole(QPalette::BrightText); d->proLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); d->proLabel->setContentsMargins(10, 5, 0, 10); - d->proClear = new DPushButton(); + d->proClear = new DToolButton(this); d->proClear->setIcon(QIcon::fromTheme("ide_recent_delete")); - d->proClear->setFlat(true); d->proClear->setToolTip(tr("clear all")); d->clearProConfirm = new DDialog(this); diff --git a/src/plugins/recent/recent.json b/src/plugins/recent/recent.json index c3af11330..47ee62f51 100644 --- a/src/plugins/recent/recent.json +++ b/src/plugins/recent/recent.json @@ -3,7 +3,7 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], diff --git a/src/plugins/reversedebug/reversedebug.json b/src/plugins/reversedebug/reversedebug.json index 72f364245..dae4fc852 100644 --- a/src/plugins/reversedebug/reversedebug.json +++ b/src/plugins/reversedebug/reversedebug.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Utils", "Description" : "realize reverse debug.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=656d40c5bd766615b0b02e64&id=656d4152bd766615b0b02e76", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=656d40c5bd766615b0b02e64&id=656d4152bd766615b0b02e76", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/symbol/CMakeLists.txt b/src/plugins/symbol/CMakeLists.txt index 397219e6b..1939be331 100644 --- a/src/plugins/symbol/CMakeLists.txt +++ b/src/plugins/symbol/CMakeLists.txt @@ -13,6 +13,7 @@ set(CXX_CPP transceiver/symbolreceiver.cpp clangparser/clangcursor.cpp clangparser/clangparser.cpp + util/util.cpp symbol.cpp symbol.json ) @@ -26,6 +27,7 @@ set(CXX_H transceiver/symbolreceiver.h clangparser/clangcursor.h clangparser/clangparser.h + util/util.h symbol.h ) diff --git a/src/plugins/symbol/symbol.cpp b/src/plugins/symbol/symbol.cpp index 0b226c068..cb656bd6c 100644 --- a/src/plugins/symbol/symbol.cpp +++ b/src/plugins/symbol/symbol.cpp @@ -5,6 +5,8 @@ #include "symbol.h" #include "mainframe/symbolkeeper.h" #include "mainframe/symboltreeview.h" +#include "util/util.h" + #include "common/common.h" #include "base/abstractmenu.h" #include "base/abstractaction.h" @@ -19,14 +21,10 @@ using namespace dpfservice; void Symbol::initialize() { - QProcess process; - process.start("pip3 show esprima"); - process.waitForFinished(); - - QString output = process.readAllStandardOutput(); - if(output.isEmpty()) { - process.start("pip3 install esprima"); - process.waitForFinished(); + QStringList dependenceList {"esprima", "clang-5"}; + for (const auto &dependence : dependenceList) { + if (!Util::checkPackageValid(dependence)) + Util::installPackage(dependence); } } diff --git a/src/plugins/symbol/symbol.json b/src/plugins/symbol/symbol.json index b8a305543..887a8dedd 100644 --- a/src/plugins/symbol/symbol.json +++ b/src/plugins/symbol/symbol.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Core Plugins", "Description" : "Display symbol in source code", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document2?dirid=656d40a9bd766615b0b02e5e", + "UrlLink" : "https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/plugins/symbol/transceiver/symbolreceiver.cpp b/src/plugins/symbol/transceiver/symbolreceiver.cpp index 8e318623e..d5575dee6 100644 --- a/src/plugins/symbol/transceiver/symbolreceiver.cpp +++ b/src/plugins/symbol/transceiver/symbolreceiver.cpp @@ -32,8 +32,8 @@ QStringList SymbolReceiver::topics() void SymbolReceiver::eventProcess(const dpf::Event &event) { - if (event.data() == project.activedProject.name) { - QString projectInfoKey = project.activedProject.pKeys[0]; + if (event.data() == project.activatedProject.name) { + QString projectInfoKey = project.activatedProject.pKeys[0]; dpfservice::ProjectInfo info = qvariant_cast (event.property(projectInfoKey)); QString workspace = info.workspaceFolder(); diff --git a/src/plugins/symbol/util/util.cpp b/src/plugins/symbol/util/util.cpp new file mode 100644 index 000000000..28b4d9e49 --- /dev/null +++ b/src/plugins/symbol/util/util.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "util.h" + +#include + +bool Util::checkPackageValid(const QString &package) +{ + QProcess process; + QString cmd("pip3 show %1"); + process.start(cmd.arg(package)); + process.waitForFinished(); + + QString output = process.readAllStandardOutput(); + return !output.isEmpty(); +} + +void Util::installPackage(const QString &package) +{ + QProcess process; + QString cmd("pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple %1"); + process.start(cmd.arg(package)); + process.waitForFinished(); +} diff --git a/src/plugins/symbol/util/util.h b/src/plugins/symbol/util/util.h new file mode 100644 index 000000000..431733c5f --- /dev/null +++ b/src/plugins/symbol/util/util.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef UTIL_H +#define UTIL_H + +#include + +class Util +{ +public: + static bool checkPackageValid(const QString &package); + static void installPackage(const QString &package); +}; + +#endif // UTIL_H diff --git a/src/plugins/template/templateplugin.json b/src/plugins/template/templateplugin.json index 5d13382ca..a502289eb 100644 --- a/src/plugins/template/templateplugin.json +++ b/src/plugins/template/templateplugin.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Core Plugins", "Description" : "The template plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document2?dirid=656d40a9bd766615b0b02e5e", + "UrlLink" : "https://uosdn.uniontech.com/#document2?dirid=656d40a9bd766615b0b02e5e", "Depends" : [ {"Name" : "core"} ] diff --git a/src/plugins/template/wizard/abstractpane.h b/src/plugins/template/wizard/abstractpane.h index 6a2352d43..3b6a2e033 100644 --- a/src/plugins/template/wizard/abstractpane.h +++ b/src/plugins/template/wizard/abstractpane.h @@ -13,8 +13,9 @@ class AbstractPane : public DWidget public: explicit AbstractPane(DWidget *parent = nullptr) {} virtual QMap getValue() = 0; + private: virtual void setupUi() = 0; }; -#endif // ABSTRACTPANE_H +#endif // ABSTRACTPANE_H diff --git a/src/plugins/template/wizard/fieldspane.h b/src/plugins/template/wizard/fieldspane.h index d2492a353..d01852e51 100644 --- a/src/plugins/template/wizard/fieldspane.h +++ b/src/plugins/template/wizard/fieldspane.h @@ -13,9 +13,11 @@ using DTK_WIDGET_NAMESPACE::DWidget; class FieldsPane : public AbstractPane { + Q_OBJECT public: explicit FieldsPane(const templateMgr::Page &pageInfo, DWidget *parent = nullptr); QMap getValue() override; + private: void setupUi() override; @@ -23,4 +25,4 @@ class FieldsPane : public AbstractPane QMap value; }; -#endif // FIELDSPANE_H +#endif // FIELDSPANE_H diff --git a/src/plugins/template/wizard/kitspane.h b/src/plugins/template/wizard/kitspane.h index cf93135d9..3f38ad43e 100644 --- a/src/plugins/template/wizard/kitspane.h +++ b/src/plugins/template/wizard/kitspane.h @@ -13,9 +13,11 @@ using DTK_WIDGET_NAMESPACE::DWidget; class KitsPane : public AbstractPane { + Q_OBJECT public: explicit KitsPane(const templateMgr::Page &pageInfo, DWidget *parent = nullptr); QMap getValue() override; + private: void setupUi() override; @@ -23,4 +25,4 @@ class KitsPane : public AbstractPane QMap value; }; -#endif // KITSPANE_H +#endif // KITSPANE_H diff --git a/src/plugins/template/wizard/maindialog.cpp b/src/plugins/template/wizard/maindialog.cpp index d79e69444..21e86ac5f 100644 --- a/src/plugins/template/wizard/maindialog.cpp +++ b/src/plugins/template/wizard/maindialog.cpp @@ -33,17 +33,16 @@ using namespace dpfservice; class MainDialogPrivate { friend class MainDialog; - QMap detailWidgetMap; + QMap detailWidgetMap; DStackedWidget *detailStackedWidget = nullptr; DStackedWidget *StackedWidget = nullptr; DFrame *blankWidget = nullptr; }; MainDialog::MainDialog(DWidget *parent) - : DAbstractDialog(parent) - , d(new MainDialogPrivate()) + : DAbstractDialog(parent), d(new MainDialogPrivate()) { - setMinimumSize(611, 427); + setMinimumSize(800, 600); TemplateVector templateVec; TemplateParser::readTemplateConfig(templateVec); setupUI(templateVec); @@ -64,7 +63,7 @@ void MainDialog::setupUI(TemplateVector &templateVec) DButtonBoxButton *newFileButton = new DButtonBoxButton(QObject::tr("New File"), this); DButtonBoxButton *newProjectButton = new DButtonBoxButton(QObject::tr("New Project"), this); DButtonBox *btnbox = new DButtonBox(this); - QList list { newFileButton, newProjectButton}; + QList list { newFileButton, newProjectButton }; btnbox->setButtonList(list, true); btnbox->setFixedWidth(widthPerBtn * btnbox->buttonList().size()); titleBar->addWidget(btnbox); @@ -86,7 +85,7 @@ void MainDialog::setupUI(TemplateVector &templateVec) d->StackedWidget->setLineWidth(0); DFrame *leftFrame = new DFrame(this); - DTreeView * treeView = new DTreeView(leftFrame); + DTreeView *treeView = new DTreeView(leftFrame); QHBoxLayout *leftFrameLayout = new QHBoxLayout(); treeView->setHeaderHidden(true); @@ -104,7 +103,7 @@ void MainDialog::setupUI(TemplateVector &templateVec) //deafult new file显示 QStandardItemModel *StandardModel = new QStandardItemModel(); - QStandardItem * rootItem = StandardModel->invisibleRootItem(); + QStandardItem *rootItem = StandardModel->invisibleRootItem(); auto iterTpl = templateVec.begin(); @@ -128,9 +127,9 @@ void MainDialog::setupUI(TemplateVector &templateVec) treeView->expandAll(); connect(btnbox, &DButtonBox::buttonClicked, this, [=](QAbstractButton *button) { - if (button == newFileButton){ + if (button == newFileButton) { QStandardItemModel *StandardModel = new QStandardItemModel(); - QStandardItem * rootItem = StandardModel->invisibleRootItem(); + QStandardItem *rootItem = StandardModel->invisibleRootItem(); auto iterTpl = templateVec.begin(); @@ -156,8 +155,8 @@ void MainDialog::setupUI(TemplateVector &templateVec) } if (button == newProjectButton) { QStandardItemModel *StandardModel = new QStandardItemModel(); - QStandardItem * rootItem = StandardModel->invisibleRootItem(); - auto iterTpl = templateVec.begin() +1 ; + QStandardItem *rootItem = StandardModel->invisibleRootItem(); + auto iterTpl = templateVec.begin() + 1; QVector tplVec = iterTpl->templateVec; for (auto iterCate = tplVec.begin(); iterCate != tplVec.end(); ++iterCate) { @@ -183,15 +182,15 @@ void MainDialog::setupUI(TemplateVector &templateVec) } }); - connect(treeView, &DTreeView::clicked, [=](){ + connect(treeView, &DTreeView::clicked, [=]() { QModelIndex index = treeView->selectionModel()->currentIndex(); - if (!index.isValid()){ + if (!index.isValid()) { d->detailStackedWidget->setCurrentWidget(d->blankWidget); return; } QVariant varDetail = index.data(Qt::UserRole + 1); - if (!varDetail.isValid()){ + if (!varDetail.isValid()) { d->detailStackedWidget->setCurrentWidget(d->blankWidget); return; } @@ -203,32 +202,31 @@ void MainDialog::setupUI(TemplateVector &templateVec) } QVariant varUuid = index.data(Qt::UserRole + 2); - if (!varUuid.isValid()){ + if (!varUuid.isValid()) { d->detailStackedWidget->setCurrentWidget(d->blankWidget); return; } QString uuid = varUuid.value(); if (d->detailWidgetMap.contains(uuid)) { - DetailWidget * detailWidget = d->detailWidgetMap.value(uuid); + DetailWidget *detailWidget = d->detailWidgetMap.value(uuid); if (detailWidget) { d->detailStackedWidget->setCurrentWidget(detailWidget); } } else { - DetailWidget * detailWidget = new DetailWidget(detail.path, this); + DetailWidget *detailWidget = new DetailWidget(detail.path, this); d->detailWidgetMap.insert(uuid, detailWidget); d->detailStackedWidget->addWidget(detailWidget); d->detailStackedWidget->setCurrentWidget(detailWidget); //关闭主窗口 - connect(detailWidget, &DetailWidget::closeSignal, this, [this](){ + connect(detailWidget, &DetailWidget::closeSignal, this, [this]() { close(); }); } }); - QVBoxLayout *leftLayout = new QVBoxLayout(); leftLayout->addWidget(leftFrame); diff --git a/src/plugins/template/wizard/projectpane.cpp b/src/plugins/template/wizard/projectpane.cpp index ee4b14f8f..28fdcd293 100644 --- a/src/plugins/template/wizard/projectpane.cpp +++ b/src/plugins/template/wizard/projectpane.cpp @@ -23,7 +23,7 @@ void ProjectPane::setupUi() { DLabel *title = new DLabel(this); title->setText(shortTitle); - + QFormLayout *mainLayout = new QFormLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(10); @@ -32,25 +32,24 @@ void ProjectPane::setupUi() DLabel *project = new DLabel(tr("project Name :"), this); projectEdit = new DLineEdit(this); mainLayout->addRow(project, projectEdit); - + DLabel *pathChooser = new DLabel(tr("Location :"), this); locationEdit = new DLineEdit(this); locationEdit->lineEdit()->setReadOnly(true); - + DSuggestButton *browse = new DSuggestButton("...", this); browse->setFixedSize(36, 36); - locationEdit->lineEdit()->setAlignment(Qt::AlignCenter); - + connect(browse, &DSuggestButton::clicked, [=]() { QString path = DFileDialog::getExistingDirectory(this, tr("Choose path"), QDir::homePath()); if (!path.isEmpty()) locationEdit->setText(path); }); - + auto hLayout = new QHBoxLayout(); hLayout->addWidget(locationEdit); hLayout->addWidget(browse); - + mainLayout->addRow(pathChooser, hLayout); } diff --git a/src/plugins/template/wizard/projectpane.h b/src/plugins/template/wizard/projectpane.h index 62be70916..7133402c3 100644 --- a/src/plugins/template/wizard/projectpane.h +++ b/src/plugins/template/wizard/projectpane.h @@ -13,9 +13,11 @@ using DTK_WIDGET_NAMESPACE::DWidget; class ProjectPane : public AbstractPane { + Q_OBJECT public: explicit ProjectPane(const QString &title, DWidget *parent = nullptr); QMap getValue() override; + private: void setupUi() override; @@ -25,4 +27,4 @@ class ProjectPane : public AbstractPane QMap value; }; -#endif // PROJECTPANE_H +#endif // PROJECTPANE_H diff --git a/src/plugins/valgrind/mainframe/valgrindrunner.cpp b/src/plugins/valgrind/mainframe/valgrindrunner.cpp index 95527ddcc..7ee9f737b 100644 --- a/src/plugins/valgrind/mainframe/valgrindrunner.cpp +++ b/src/plugins/valgrind/mainframe/valgrindrunner.cpp @@ -24,7 +24,7 @@ class ValgrindRunnerPrivate QStringList ValgrindArgs; dpfservice::ProjectInfo projectInfo; - QString activedProjectKitName; + QString activatedProjectKitName; QString workingDir; QString currentFilePath; QString targetPath; @@ -73,7 +73,7 @@ void ValgrindRunner::initialize() }); }); - setActionsStatus(d->activedProjectKitName); + setActionsStatus(d->activatedProjectKitName); } void ValgrindRunner::runValgrind(const QString &type) @@ -157,13 +157,13 @@ void ValgrindRunner::setActionsStatus(const QString &kitName) void ValgrindRunner::saveCurrentProjectInfo(const ProjectInfo &projectInfo) { d->projectInfo = projectInfo; - d->activedProjectKitName = d->projectInfo.kitName(); - setActionsStatus(d->activedProjectKitName); + d->activatedProjectKitName = d->projectInfo.kitName(); + setActionsStatus(d->activatedProjectKitName); } void ValgrindRunner::removeProjectInfo() { - d->activedProjectKitName.clear(); + d->activatedProjectKitName.clear(); setActionsStatus(""); } @@ -182,14 +182,14 @@ void ValgrindRunner::runBuilding() auto &ctx = dpfInstance.serviceContext(); LanguageService *service = ctx.service(LanguageService::name()); if (service) { - auto generator = service->create(d->activedProjectKitName); + auto generator = service->create(d->activatedProjectKitName); if (generator) { if (generator->isNeedBuild()) { generator->build(d->projectInfo.workspaceFolder()); } RunCommandInfo args = generator->getRunArguments(d->projectInfo, d->currentFilePath); d->targetPath = args.program.trimmed(); - d->workingDir = args.workingDir.trimmed(); + d->workingDir = d->projectInfo.workspaceFolder(); } } } diff --git a/src/plugins/valgrind/transceiver/valgrindreceiver.cpp b/src/plugins/valgrind/transceiver/valgrindreceiver.cpp index c7a2e7ab9..cf85d45d1 100644 --- a/src/plugins/valgrind/transceiver/valgrindreceiver.cpp +++ b/src/plugins/valgrind/transceiver/valgrindreceiver.cpp @@ -30,8 +30,8 @@ QStringList ValgrindReceiver::topics() void ValgrindReceiver::eventProcess(const dpf::Event &event) { - if (event.data() == project.activedProject.name) { - QVariant proInfoVar = event.property(project.activedProject.pKeys[0]); + if (event.data() == project.activatedProject.name) { + QVariant proInfoVar = event.property(project.activatedProject.pKeys[0]); dpfservice::ProjectInfo projectInfo = qvariant_cast(proInfoVar); QString buildFolder = projectInfo.buildFolder(); ValgrindRunner::instance()->saveCurrentProjectInfo(projectInfo); diff --git a/src/plugins/valgrind/valgrind.json b/src/plugins/valgrind/valgrind.json index 0f3654411..f39246169 100644 --- a/src/plugins/valgrind/valgrind.json +++ b/src/plugins/valgrind/valgrind.json @@ -3,13 +3,13 @@ "Version" : "4.8.2", "CompatVersion" : "4.8.0", "Vendor" : "The Uniontech Software Technology Co., Ltd.", - "Copyright" : "Copyright (C) 2020 ~ 2022 Uniontech Software Technology Co., Ltd.", + "Copyright" : "Copyright (C) 2020 ~ 2024 Uniontech Software Technology Co., Ltd.", "License" : [ "GPL-3.0-or-later" ], "Category" : "Code Analyzer", "Description" : "The codedetection plugin for the unioncode.", - "UrlLink" : "https://ecology.chinauos.com/adaptidentification/doc_new/#document3?dirid=656d40c5bd766615b0b02e64&id=656d4172bd766615b0b02e7c", + "UrlLink" : "https://uosdn.uniontech.com/#document3?dirid=656d40c5bd766615b0b02e64&id=656d4172bd766615b0b02e7c", "Depends" : [ {"Name" : "codeeditor"} ] diff --git a/src/services/CMakeLists.txt b/src/services/CMakeLists.txt index e3bdec309..10783c16b 100644 --- a/src/services/CMakeLists.txt +++ b/src/services/CMakeLists.txt @@ -3,6 +3,9 @@ project(services) set(CPPDAP_DIR ${CMAKE_SOURCE_DIR}/3rdparty/cppdap) include_directories(${CPPDAP_DIR}/include) +add_definitions( + -DSERVICES_LIBRARY + ) FILE(GLOB SERVICES_FILES "${CMAKE_CURRENT_SOURCE_DIR}/window/*.h" @@ -21,6 +24,7 @@ FILE(GLOB SERVICES_FILES "${CMAKE_CURRENT_SOURCE_DIR}/editor/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/terminal/*.h" "${CMAKE_CURRENT_SOURCE_DIR}/locator/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" ) add_library( diff --git a/src/services/builder/buildergenerator.h b/src/services/builder/buildergenerator.h index 130a5c788..8b9f04b4a 100644 --- a/src/services/builder/buildergenerator.h +++ b/src/services/builder/buildergenerator.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef BUILDERGENERATOR_H #define BUILDERGENERATOR_H @@ -8,12 +8,13 @@ #include "builderglobals.h" #include "common/common.h" #include "base/abstractoutputparser.h" +#include "services/services_global.h" #include #include namespace dpfservice { -class BuilderGenerator : public Generator +class SERVICE_EXPORT BuilderGenerator : public Generator { Q_OBJECT public: diff --git a/src/services/builder/builderglobals.h b/src/services/builder/builderglobals.h index 58bfd0c3e..99d49ae59 100644 --- a/src/services/builder/builderglobals.h +++ b/src/services/builder/builderglobals.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef BUILDERGLOBALS_H #define BUILDERGLOBALS_H diff --git a/src/services/builder/builderservice.h b/src/services/builder/builderservice.h index 5371837ba..040d7d42d 100644 --- a/src/services/builder/builderservice.h +++ b/src/services/builder/builderservice.h @@ -1,17 +1,19 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef BUILDERSERVICE_H #define BUILDERSERVICE_H #include "buildergenerator.h" #include "builderglobals.h" +#include "services/services_global.h" + #include namespace dpfservice { -class BuilderService final : public dpf::PluginService, +class SERVICE_EXPORT BuilderService final : public dpf::PluginService, dpf::AutoServiceRegister, dpf::QtClassFactory, dpf::QtClassManager diff --git a/src/services/container/containerservice.h b/src/services/container/containerservice.h index 357227f5d..547cccf1e 100644 --- a/src/services/container/containerservice.h +++ b/src/services/container/containerservice.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef CONTAINERSERVICE_H #define CONTAINERSERVICE_H diff --git a/src/services/debugger/debuggerservice.h b/src/services/debugger/debuggerservice.h index eaea59a2b..2484f187e 100644 --- a/src/services/debugger/debuggerservice.h +++ b/src/services/debugger/debuggerservice.h @@ -1,16 +1,17 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef DEBUGGERSERVICE_H #define DEBUGGERSERVICE_H #include #include "base/abstractdebugger.h" +#include "services/services_global.h" namespace dpfservice { -class DebuggerService final : public dpf::PluginService, +class SERVICE_EXPORT DebuggerService final : public dpf::PluginService, dpf::AutoServiceRegister { Q_OBJECT diff --git a/src/services/editor/editorservice.h b/src/services/editor/editorservice.h index 13272f4c9..3fc65f754 100644 --- a/src/services/editor/editorservice.h +++ b/src/services/editor/editorservice.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef EDITORSERVICE_H #define EDITORSERVICE_H @@ -41,6 +41,11 @@ class EditorService final : public dpf::PluginService, DPF_INTERFACE(void, setText, const QString &text); DPF_INTERFACE(void, setCompletion, const QString &info, const QIcon &icon, const QKeySequence &key); DPF_INTERFACE(QString, currentFile); + DPF_INTERFACE(QStringList, openedFiles); + DPF_INTERFACE(QString, fileText, const QString &file); + DPF_INTERFACE(void, replaceAll, const QString &file, const QString &oldText, + const QString &newText, bool caseSensitive, bool wholeWords); + DPF_INTERFACE(void, replaceRange, const QString &file, int line, int index, int length, const QString &after); DPF_INTERFACE(void, registerSciLexerProxy, const QString &language, AbstractLexerProxy *proxy); DPF_INTERFACE(void, registerWidget, const QString &id, AbstractEditWidget *widget); diff --git a/src/services/language/languagegenerator.h b/src/services/language/languagegenerator.h index 2f16d1af0..a48d14fa5 100644 --- a/src/services/language/languagegenerator.h +++ b/src/services/language/languagegenerator.h @@ -1,12 +1,13 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef LANGUAGEGENERATOR_H #define LANGUAGEGENERATOR_H #include "common/common.h" #include "dap/protocol.h" +#include "services/services_global.h" namespace dpfservice { @@ -15,9 +16,10 @@ struct RunCommandInfo { QStringList arguments; QString workingDir; QStringList envs; + bool runInTerminal; }; -class LanguageGenerator : public Generator +class SERVICE_EXPORT LanguageGenerator : public Generator { Q_OBJECT public: diff --git a/src/services/language/languageservice.h b/src/services/language/languageservice.h index bac5ff6ba..254fb882d 100644 --- a/src/services/language/languageservice.h +++ b/src/services/language/languageservice.h @@ -1,16 +1,17 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef LANGUAGESERVICE_H #define LANGUAGESERVICE_H #include "languagegenerator.h" #include +#include "services/services_global.h" namespace dpfservice { -class LanguageService final : public dpf::PluginService, +class SERVICE_EXPORT LanguageService final : public dpf::PluginService, dpf::AutoServiceRegister, dpf::QtClassFactory, dpf::QtClassManager diff --git a/src/services/locator/locatorservice.h b/src/services/locator/locatorservice.h index b839d6727..d78ced27a 100644 --- a/src/services/locator/locatorservice.h +++ b/src/services/locator/locatorservice.h @@ -1,17 +1,17 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef LOCATORSERVICE_H #define LOCATORSERVICE_H #include "base/abstractlocator.h" - +#include "services/services_global.h" #include namespace dpfservice { -class LocatorService final : public dpf::PluginService, +class SERVICE_EXPORT LocatorService final : public dpf::PluginService, dpf::AutoServiceRegister { Q_OBJECT diff --git a/src/services/option/optiondatastruct.h b/src/services/option/optiondatastruct.h index 98055706a..30fa5bbbb 100644 --- a/src/services/option/optiondatastruct.h +++ b/src/services/option/optiondatastruct.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef OPTIONDATASTRUCT_H #define OPTIONDATASTRUCT_H diff --git a/src/services/option/optiongenerator.h b/src/services/option/optiongenerator.h index 986241230..419f8a0ea 100644 --- a/src/services/option/optiongenerator.h +++ b/src/services/option/optiongenerator.h @@ -1,17 +1,18 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef OPTIONGENERATOR_H #define OPTIONGENERATOR_H #include "common/common.h" +#include "services/services_global.h" #include namespace dpfservice { -class OptionGenerator : public Generator +class SERVICE_EXPORT OptionGenerator : public Generator { Q_OBJECT public: diff --git a/src/services/option/optionmanager.cpp b/src/services/option/optionmanager.cpp index a5cc362ea..aeecbc0a4 100644 --- a/src/services/option/optionmanager.cpp +++ b/src/services/option/optionmanager.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "optionmanager.h" #include "optionutils.h" diff --git a/src/services/option/optionmanager.h b/src/services/option/optionmanager.h index 65c1a7bb1..effb895de 100644 --- a/src/services/option/optionmanager.h +++ b/src/services/option/optionmanager.h @@ -1,16 +1,18 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef OPTIONMANAGER_H #define OPTIONMANAGER_H #include "optiondatastruct.h" +#include "services/services_global.h" + #include #include class OptionManagerPrivate; -class OptionManager : public QObject +class SERVICE_EXPORT OptionManager : public QObject { Q_OBJECT public: diff --git a/src/services/option/optionservice.h b/src/services/option/optionservice.h index 1dc7e2697..334244450 100644 --- a/src/services/option/optionservice.h +++ b/src/services/option/optionservice.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef OPTIONSERVICE_H #define OPTIONSERVICE_H @@ -8,12 +8,12 @@ #include "optiongenerator.h" #include "base/abstractwidget.h" - +#include "services/services_global.h" #include "framework/framework.h" namespace dpfservice { -class OptionService final : public dpf::PluginService, +class SERVICE_EXPORT OptionService final : public dpf::PluginService, dpf::AutoServiceRegister, dpf::QtClassFactory, dpf::QtClassManager diff --git a/src/services/option/optionutils.cpp b/src/services/option/optionutils.cpp index 1828d04a5..53193c440 100644 --- a/src/services/option/optionutils.cpp +++ b/src/services/option/optionutils.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "optionutils.h" #include "common/util/custompaths.h" diff --git a/src/services/option/optionutils.h b/src/services/option/optionutils.h index 7cb51235b..43074cbe5 100644 --- a/src/services/option/optionutils.h +++ b/src/services/option/optionutils.h @@ -1,13 +1,14 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef OPTIONUTILS_H #define OPTIONUTILS_H +#include "services/services_global.h" #include -class OptionUtils : public QObject +class SERVICE_EXPORT OptionUtils : public QObject { Q_OBJECT public: diff --git a/src/services/option/toolchaindata.cpp b/src/services/option/toolchaindata.cpp index 1d6557c7e..e3d7924a4 100644 --- a/src/services/option/toolchaindata.cpp +++ b/src/services/option/toolchaindata.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "toolchaindata.h" #include "common/util/custompaths.h" diff --git a/src/services/option/toolchaindata.h b/src/services/option/toolchaindata.h index 2e31db9c1..5e2ae9247 100644 --- a/src/services/option/toolchaindata.h +++ b/src/services/option/toolchaindata.h @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef TOOLCHAINDATA_H #define TOOLCHAINDATA_H +#include "services/services_global.h" + #include #include #include @@ -25,7 +27,7 @@ static const QString kNameItem{"name"}; static const QString kPathItem{"path"}; } -class ToolChainData +class SERVICE_EXPORT ToolChainData { public: struct ToolChainParam diff --git a/src/services/project/projectgenerator.cpp b/src/services/project/projectgenerator.cpp index bede30b4f..a216bff95 100644 --- a/src/services/project/projectgenerator.cpp +++ b/src/services/project/projectgenerator.cpp @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #include "projectgenerator.h" #include "projectservice.h" @@ -211,6 +211,13 @@ QMenu *dpfservice::ProjectGenerator::createItemMenu(const QStandardItem *item) return nullptr; } +void dpfservice::ProjectGenerator::createDirectory(const QStandardItem *item, const QString &dirPath) +{ + Q_UNUSED(item) + QDir directory(dirPath); + directory.mkpath(dirPath); +} + void dpfservice::ProjectGenerator::createDocument(const QStandardItem *item, const QString &filePath) { Q_UNUSED(item) diff --git a/src/services/project/projectgenerator.h b/src/services/project/projectgenerator.h index 95c0288fb..222e235a0 100644 --- a/src/services/project/projectgenerator.h +++ b/src/services/project/projectgenerator.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef PROJECTGENERATOR_H #define PROJECTGENERATOR_H @@ -8,6 +8,7 @@ #include "common/common.h" #include +#include "services/services_global.h" #include #include @@ -15,7 +16,7 @@ class QFileDialog; namespace dpfservice { -class ProjectGenerator : public Generator +class SERVICE_EXPORT ProjectGenerator : public Generator { Q_OBJECT public: @@ -34,6 +35,7 @@ class ProjectGenerator : public Generator virtual QStandardItem *createRootItem(const ProjectInfo &info); virtual void removeRootItem(QStandardItem *root); virtual QMenu* createItemMenu(const QStandardItem *item); + virtual void createDirectory(const QStandardItem *item, const QString &filePath); virtual void createDocument(const QStandardItem *item, const QString &filePath); static QStandardItem *root(QStandardItem *child); static const QModelIndex root(const QModelIndex &child); diff --git a/src/services/project/projectservice.h b/src/services/project/projectservice.h index 86a2f0415..5a9bdc4a3 100644 --- a/src/services/project/projectservice.h +++ b/src/services/project/projectservice.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef PROJECTSERVICE_H #define PROJECTSERVICE_H @@ -53,7 +53,7 @@ enum TargetType { kActiveExecTarget }; -class ProjectService final : public dpf::PluginService, +class SERVICE_EXPORT ProjectService final : public dpf::PluginService, dpf::AutoServiceRegister, dpf::QtClassFactory, dpf::QtClassManager @@ -161,6 +161,12 @@ class ProjectService final : public dpf::PluginService, */ DPF_INTERFACE(bool, updateProjectInfo, ProjectInfo &projectInfo); + /*! + * \brief expandItemByFile + * \param filepath + */ + DPF_INTERFACE(void, expandItemByFile, const QStringList &filePaths); + /** * @brief getActiveProjectInfo */ diff --git a/src/services/services_global.h b/src/services/services_global.h new file mode 100644 index 000000000..ccad5e52d --- /dev/null +++ b/src/services/services_global.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef SERVICES_GLOBAL_H +#define SERVICES_GLOBAL_H + +#if defined(SERVICES_LIBRARY) +# define SERVICE_EXPORT Q_DECL_EXPORT +#else +# define SERVICE_EXPORT Q_DECL_IMPORT +#endif + + +#endif // SERVICES_GLOBAL_H diff --git a/src/services/terminal/terminalservice.h b/src/services/terminal/terminalservice.h index cf96e2af2..c3cb1fad7 100644 --- a/src/services/terminal/terminalservice.h +++ b/src/services/terminal/terminalservice.h @@ -1,15 +1,16 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef TERMINALSERVICE_H #define TERMINALSERVICE_H +#include "services/services_global.h" #include namespace dpfservice { // service interface -class TerminalService final : public dpf::PluginService, dpf::AutoServiceRegister +class SERVICE_EXPORT TerminalService final : public dpf::PluginService, dpf::AutoServiceRegister { Q_OBJECT Q_DISABLE_COPY(TerminalService) @@ -26,10 +27,11 @@ class TerminalService final : public dpf::PluginService, dpf::AutoServiceRegiste } /** - * @brief execute command by terminal + * @brief send command to terminal * @param command */ - DPF_INTERFACE(void, executeCommand, const QString &command); + DPF_INTERFACE(void, sendCommand, const QString &command); + DPF_INTERFACE(void, executeCommand, const QString &name, const QString &program, const QStringList &args, const QString &workingDir, const QStringList &env); }; } // namespace dpfservice diff --git a/src/services/window/windowcontroller.h b/src/services/window/windowcontroller.h index f75f8354e..b990313bf 100644 --- a/src/services/window/windowcontroller.h +++ b/src/services/window/windowcontroller.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef WINDOWCONTROLLER_H #define WINDOWCONTROLLER_H diff --git a/src/services/window/windowelement.h b/src/services/window/windowelement.h index 9865e50c8..17d2cce1b 100644 --- a/src/services/window/windowelement.h +++ b/src/services/window/windowelement.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef WINDOWELEMENT_H #define WINDOWELEMENT_H @@ -21,6 +21,15 @@ inline const QString MWNA_GIT { QObject::tr("Git") }; inline const QString MWNA_SVN { QObject::tr("Svn") }; inline const QString MWNA_RUNTIME { QObject::tr("Runtime") }; inline const QString MWNA_CODEGEEX { QObject::tr("CodeGeeX") }; +inline const QString MWNA_ADVANCEDSEARCH { QObject::tr("Advanced Search") }; + +// MWTG = MainWindow TopTool group +inline const QString MWTG_EDIT { "Edit" }; +inline const QString MWTG_DEBUG { "Debug" }; + +// WN = window name +inline const QString WN_CONTEXTWIDGET = "contextWidget"; +inline const QString WN_WORKSPACE = "workspaceWidget"; // MWTG = MainWindow TopTool group inline const QString MWTG_EDIT { "Edit" }; @@ -48,11 +57,12 @@ inline const QString MWM_BUILD { QMenu::tr("&Build") }; inline const QString MWMBA_BUILD { QAction::tr("Build") }; inline const QString MWMBA_REBUILD { QAction::tr("Rebuild") }; inline const QString MWMBA_CLEAN { QAction::tr("Clean Cache") }; -inline const QString MWMBA_CANCEL { QAction::tr("Cancel") }; +inline const QString MWMBA_CANCEL { QAction::tr("Cancel Build") }; // MWMDA = MWM Debug Action inline const QString MWM_DEBUG { QMenu::tr("&Debug") }; inline const QString MWMDA_START_DEBUG { QAction::tr("Start Debugging") }; +inline const QString MWMDA_ATTACH_DEBUG { QAction::tr("Attaching to Running Program") }; inline const QString MWMDA_RUNNING { QAction::tr("Running") }; inline const QString MWMDA_INTERRUPT { QAction::tr("Interrupt") }; inline const QString MWMDA_CONTINUE { QAction::tr("Continue") }; @@ -79,7 +89,7 @@ inline const QString MWM_HELP_DOCUMENTS { QAction::tr("Help Documents") }; inline const QString MWM_ABOUT_PLUGINS { QAction::tr("About Plugins")}; // Others -inline const QString CONSOLE_TAB_TEXT { QTabWidget::tr("&Console") }; +inline const QString TERMINAL_TAB_TEXT { QTabWidget::tr("&Terminal") }; // MWCWT = MW CodeEditor Window Title inline const QString MWCWT_SYMBOL {QTabWidget::tr("Symbol")}; diff --git a/src/services/window/windowservice.h b/src/services/window/windowservice.h index baed9f740..271ed2981 100644 --- a/src/services/window/windowservice.h +++ b/src/services/window/windowservice.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: LGPL-3.0-or-later #ifndef WINDOWSERVICE_H #define WINDOWSERVICE_H @@ -20,6 +20,15 @@ class Core; class AbstractAction; class AbstractMenu; class AbstractWidget; +class AbstractInstaller; + +namespace Priority { +const quint8 lowest = 255; +const quint8 low = 150; +const quint8 medium = 100; +const quint8 high = 50; +const quint8 highest = 5; +} namespace Priority { const quint8 lowest = 255; @@ -73,6 +82,7 @@ class WindowService final : public dpf::PluginService, dpf::AutoServiceRegister< DPF_INTERFACE(void, insertWidget, const QString &name, Position pos, Qt::Orientation orientation); DPF_INTERFACE(void, hideWidget, const QString &name); + /*! * \brief register Widget to mode, when switch to this mode, automatically show widget. * \param widget name @@ -91,7 +101,9 @@ class WindowService final : public dpf::PluginService, dpf::AutoServiceRegister< */ DPF_INTERFACE(void, registerWidget, const QString &name, AbstractWidget *abstractWidget); DPF_INTERFACE(void, showWidgetAtPosition, const QString &name, Position pos, bool replace); - DPF_INTERFACE(QString, getCurrentDockName, Position pos); + DPF_INTERFACE(QString, getCentralWidgetName); + DPF_INTERFACE(QStringList, getCurrentDockName, Position pos); + DPF_INTERFACE(void, resizeDocks, const QList &docks, const QList &sizes, Qt::Orientation orientation); /*! * \brief split two windows in the specified direction @@ -104,6 +116,7 @@ class WindowService final : public dpf::PluginService, dpf::AutoServiceRegister< DPF_INTERFACE(void, setDockWidgetFeatures, const QString &name, QDockWidget::DockWidgetFeatures feature); DPF_INTERFACE(void, setDockHeaderName, const QString &dockName, const QString &headerName); + DPF_INTERFACE(void, setDockHeaderList, const QString &dockName, const QList &headers); DPF_INTERFACE(void, deleteDockHeader, const QString &name); DPF_INTERFACE(void, addToolBtnToDockHeader, const QString &name, DTK_WIDGET_NAMESPACE::DToolButton *btn); /*! @@ -114,6 +127,7 @@ class WindowService final : public dpf::PluginService, dpf::AutoServiceRegister< DPF_INTERFACE(void, addNavigationItem, AbstractAction *action, quint8 priority); DPF_INTERFACE(void, addNavigationItemToBottom, AbstractAction *action, quint8 priority); DPF_INTERFACE(void, switchWidgetNavigation, const QString &navName); + DPF_INTERFACE(void, bindWidgetToNavigation, const QString &dockName, AbstractAction *action); DPF_INTERFACE(QStringList, getAllNavigationItemName); DPF_INTERFACE(quint8, getPriorityOfNavigationItem, const QString &itemName); @@ -173,6 +187,7 @@ class WindowService final : public dpf::PluginService, dpf::AutoServiceRegister< DPF_INTERFACE(void, addTopToolItem, AbstractAction *action, bool addSeparator, quint8 priority); DPF_INTERFACE(void, addTopToolItemToRight, AbstractAction *action, bool addSeparator, quint8 priority); DPF_INTERFACE(void, removeTopToolItem, AbstractAction *action); + DPF_INTERFACE(void, setTopToolItemVisible, AbstractAction *action, bool visible); DPF_INTERFACE(void, showTopToolBar); DPF_INTERFACE(void, hideTopToolBar); @@ -187,6 +202,7 @@ class WindowService final : public dpf::PluginService, dpf::AutoServiceRegister< * \param widget */ DPF_INTERFACE(void, addWidgetWorkspace, const QString &title, AbstractWidget *widget, const QString &iconName); + DPF_INTERFACE(void, registerWidgetToDockHeader,const QString &dockName, QWidget *widget); DPF_INTERFACE(void, registerToolBtnToWorkspaceWidget, Dtk::Widget::DToolButton *btn, const QString &title); DPF_INTERFACE(void, switchWorkspaceArea, const QString &title); @@ -201,6 +217,17 @@ class WindowService final : public dpf::PluginService, dpf::AutoServiceRegister< * @param actions {id, text, id, text, ...} */ DPF_INTERFACE(void, notify, uint type, const QString &name, const QString &msg, const QStringList &actions); + using NotifyCallback = std::function; + DPF_INTERFACE(void, notifyWithCallback, uint type, const QString &name, const QString &msg, const QStringList &actions, NotifyCallback cb); + + DPF_INTERFACE(void, registerInstaller, const QString &name, AbstractInstaller *installer); + /** + * @param plugin: plugin name + * @param name: installer name, with apt and pip by default + * @param packageList: installation package list + * @param error: error message + */ + DPF_INTERFACE(bool, installPackages, const QString &plugin, const QString &name, const QStringList &packageList, QString *error); }; } // namespace dpfservice diff --git a/src/tools/debugadapter/dapsession.cpp b/src/tools/debugadapter/dapsession.cpp index 8d30c90c0..3d7b9a79a 100644 --- a/src/tools/debugadapter/dapsession.cpp +++ b/src/tools/debugadapter/dapsession.cpp @@ -347,6 +347,13 @@ void DapSession::registerHanlder() } } d->debugger->initDebugger(request.name.value().c_str(), arguments); + if (request.environment.has_value()) { + QStringList envList; + for (auto envItem : request.environment.value()) + envList.append(QString::fromStdString(envItem)); + d->debugger->setEnvironment(envList); + } + } emit DapProxy::instance()->sigStart(); auto message = QString{"gdb process started with pid: %1\n"}.arg(d->debugger->getProcessId()); @@ -366,6 +373,13 @@ void DapSession::registerHanlder() Q_UNUSED(request) Log("<-- Server received attach reqeust from client\n") d->isDebuggeIsStartWithAttachRequest = true; + if (request.name.has_value()) { + auto processId = request.connect.value().at("processId").get(); + QStringList arg = {"attach", QString::fromStdString(processId)}; + d->debugger->initDebugger(request.name.value().c_str(), arg); + } + + emit DapProxy::instance()->sigStart(); dap::AttachResponse response; Log("--> Server sent attach response to client\n") return response; diff --git a/src/tools/debugadapter/debugger/gdbmi/gdbdebugger.cpp b/src/tools/debugadapter/debugger/gdbmi/gdbdebugger.cpp index 4b13bde0d..7a83e4fe6 100644 --- a/src/tools/debugadapter/debugger/gdbmi/gdbdebugger.cpp +++ b/src/tools/debugadapter/debugger/gdbmi/gdbdebugger.cpp @@ -505,8 +505,7 @@ void GDBDebugger::sendStoppedNotify(const gdbmi::AsyncContext &ctx) { if (gdbmi::AsyncContext::Reason::exitedNormally == ctx.reason || gdbmi::AsyncContext::Reason::exitedSignalled == ctx.reason - || gdbmi::AsyncContext::Reason::Unknown == ctx.reason - || gdbmi::AsyncContext::Reason::exited == ctx.reason) { + || gdbmi::AsyncContext::Reason::exited == ctx.reason) { dap::ExitedEvent exitedEvent; emit asyncExited(exitedEvent); } diff --git a/src/tools/debugadapter/debugmanager.cpp b/src/tools/debugadapter/debugmanager.cpp index 76dbf5fe5..5717e1697 100644 --- a/src/tools/debugadapter/debugmanager.cpp +++ b/src/tools/debugadapter/debugmanager.cpp @@ -28,6 +28,7 @@ class DebugManagerPrivate { QHash resposeExpected; QStringList arguments; + QStringList environment; int tokenCounter = 0; }; @@ -123,6 +124,11 @@ void DebugManager::initDebugger(const QString &program, const QStringList &argum } } +void DebugManager::setEnvironment(const QStringList &env) +{ + d->environment = env; +} + qint64 DebugManager::getProcessId() { return d->process->processId(); @@ -162,6 +168,7 @@ void DebugManager::execute() } d->process->setArguments(d->arguments); d->process->setProgram(d->debugger->program()); + d->process->setEnvironment(d->environment); d->process->start(); //init after process started diff --git a/src/tools/debugadapter/debugmanager.h b/src/tools/debugadapter/debugmanager.h index 091988657..f27bfb0dd 100644 --- a/src/tools/debugadapter/debugmanager.h +++ b/src/tools/debugadapter/debugmanager.h @@ -32,6 +32,7 @@ class DebugManager : public QObject }; void initDebugger(const QString &program, const QStringList &arguments); + void setEnvironment(const QStringList &env); void quit(); void terminate(); diff --git a/src/tools/languageadapter/main.cpp b/src/tools/languageadapter/main.cpp index 14a75ea35..6fcd2bc12 100644 --- a/src/tools/languageadapter/main.cpp +++ b/src/tools/languageadapter/main.cpp @@ -40,6 +40,7 @@ QProcess *createCxxServ(const newlsp::ProjectKey &key) procAs << QString("--compile-commands-dir=%0").arg(compileDB_Path); procAs << "--clang-tidy"; procAs << "--completion-style=bundled"; + procAs << "--limit-results=500"; procAs << "-j=$(nproc)"; auto proc = new QProcess();