diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt new file mode 100644 index 0000000..2d2bab1 --- /dev/null +++ b/LICENSES/BSD-2-Clause.txt @@ -0,0 +1,22 @@ +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 0000000..1d80ac3 --- /dev/null +++ b/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,319 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software +is covered by the GNU Lesser General Public License instead.) You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you +wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must give the recipients all the rights that you have. You +must make sure that they, too, receive or can get the source code. And you +must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If +the software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or translated +into another language. (Hereinafter, translation is included without limitation +in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program +is not restricted, and the output from the Program is covered only if its +contents constitute a work based on the Program (independent of having been +made by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute verbatim copies of the Program's source code +as you receive it, in any medium, provided that you conspicuously and appropriately +publish on each copy an appropriate copyright notice and disclaimer of warranty; +keep intact all the notices that refer to this License and to the absence +of any warranty; and give any other recipients of the Program a copy of this +License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or +in part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print +such an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Program, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Program. + +In addition, mere aggregation of another work not based on the Program with +the Program (or with a work based on the Program) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under Section +2) in object code or executable form under the terms of Sections 1 and 2 above +provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, +which must be distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for noncommercial +distribution and only if you received the program in object code or executable +form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with +the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except +as expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Program +(or any work based on the Program), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor +to copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of +the rights granted herein. You are not responsible for enforcing compliance +by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed +on you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of +this License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Program at all. For example, if a +patent license would not permit royalty-free redistribution of the Program +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system, which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of +the General Public License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Program does not specify a version number of this License, you may choose +any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing and reuse +of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE +OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES +OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + + +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 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when +it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author Gnomovision comes +with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, +and you are welcome to redistribute it under certain conditions; type `show +c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than `show w' and `show c'; they could even be mouse-clicks +or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' +(which makes passes at compilers) written by James Hacker. + +, 1 April 1989 Ty Coon, President of Vice This General +Public License does not permit incorporating your program into proprietary +programs. If your program is a subroutine library, you may consider it more +useful to permit linking proprietary applications with the library. If this +is what you want to do, use the GNU Lesser General Public License instead +of this License. diff --git a/diff_ext_for_kdiff3/class_factory.cpp b/diff_ext_for_kdiff3/class_factory.cpp index 619d38b..ad2ee58 100644 --- a/diff_ext_for_kdiff3/class_factory.cpp +++ b/diff_ext_for_kdiff3/class_factory.cpp @@ -1,94 +1,74 @@ /* - * Copyright (c) 2003, Sergey Zorin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ + SPDX-FileCopyrightText: 2003-2006, Sergey Zorin. All rights reserved. + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: BSD-2-Clause +*/ #include "class_factory.h" #include "diff_ext.h" #include "server.h" CLASS_FACTORY::CLASS_FACTORY() { _ref_count = 0L; SERVER::instance()->lock(); } CLASS_FACTORY::~CLASS_FACTORY() { SERVER::instance()->release(); } STDMETHODIMP CLASS_FACTORY::QueryInterface(REFIID riid, void** ppv) { HRESULT ret = E_NOINTERFACE; *ppv = 0; if(IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory)) { *ppv = static_cast(this); AddRef(); ret = NOERROR; } return ret; } STDMETHODIMP_(ULONG) CLASS_FACTORY::AddRef() { return InterlockedIncrement((LPLONG)&_ref_count); } STDMETHODIMP_(ULONG) CLASS_FACTORY::Release() { ULONG ret = 0L; if(InterlockedDecrement((LPLONG)&_ref_count) != 0) ret = _ref_count; else delete this; return ret; } STDMETHODIMP CLASS_FACTORY::CreateInstance(IUnknown* outer, REFIID refiid, void** obj) { HRESULT ret = CLASS_E_NOAGGREGATION; *obj = 0; // Shell extensions typically don't support aggregation (inheritance) if(outer == 0) { DIFF_EXT* ext = new DIFF_EXT(); if(ext == 0) ret = E_OUTOFMEMORY; else ret = ext->QueryInterface(refiid, obj); } return ret; } STDMETHODIMP CLASS_FACTORY::LockServer(BOOL) { return NOERROR; } diff --git a/diff_ext_for_kdiff3/class_factory.h b/diff_ext_for_kdiff3/class_factory.h index 173b4f1..c4e6bcd 100644 --- a/diff_ext_for_kdiff3/class_factory.h +++ b/diff_ext_for_kdiff3/class_factory.h @@ -1,51 +1,31 @@ /* - * Copyright (c) 2003, Sergey Zorin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ + SPDX-FileCopyrightText: 2003-2006, Sergey Zorin. All rights reserved. + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: BSD-2-Clause +*/ #ifndef class_factory_h #define class_factory_h #include #include class CLASS_FACTORY : public IClassFactory { public: CLASS_FACTORY(); virtual ~CLASS_FACTORY(); //IUnknown members STDMETHODIMP QueryInterface(REFIID, void**); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); //ICLASS_FACTORY members STDMETHODIMP CreateInstance(IUnknown*, REFIID, void**); STDMETHODIMP LockServer(BOOL); private: ULONG _ref_count; }; #endif //class_factory_h diff --git a/diff_ext_for_kdiff3/diff_ext.cpp b/diff_ext_for_kdiff3/diff_ext.cpp index d77a007..38d042f 100644 --- a/diff_ext_for_kdiff3/diff_ext.cpp +++ b/diff_ext_for_kdiff3/diff_ext.cpp @@ -1,480 +1,460 @@ /* - * Copyright (c) 2003-2006, Sergey Zorin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ + SPDX-FileCopyrightText: 2003-2006, Sergey Zorin. All rights reserved. + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: BSD-2-Clause +*/ #define _CRT_SECURE_NO_DEPRECATE #include "diff_ext.h" #include #include #include #include #include DIFF_EXT::DIFF_EXT() : m_nrOfSelectedFiles(0), _ref_count(0L), m_recentFiles( SERVER::instance()->recent_files() ) { LOG(); _resource = SERVER::instance()->handle(); SERVER::instance()->lock(); } DIFF_EXT::~DIFF_EXT() { LOG(); if(_resource != SERVER::instance()->handle()) { FreeLibrary(_resource); } SERVER::instance()->release(); } STDMETHODIMP DIFF_EXT::QueryInterface(REFIID refiid, void** ppv) { HRESULT ret = E_NOINTERFACE; *ppv = 0; if(IsEqualIID(refiid, IID_IShellExtInit) || IsEqualIID(refiid, IID_IUnknown)) { *ppv = static_cast(this); } else if (IsEqualIID(refiid, IID_IContextMenu)) { *ppv = static_cast(this); } if(*ppv != 0) { AddRef(); ret = NOERROR; } return ret; } STDMETHODIMP_(ULONG) DIFF_EXT::AddRef() { return InterlockedIncrement((LPLONG)&_ref_count); } STDMETHODIMP_(ULONG) DIFF_EXT::Release() { ULONG ret = 0L; if(InterlockedDecrement((LPLONG)&_ref_count) != 0) { ret = _ref_count; } else { delete this; } return ret; } STDMETHODIMP DIFF_EXT::Initialize(LPCITEMIDLIST /*folder not used*/, IDataObject* data, HKEY /*key not used*/) { LOG(); FORMATETC format = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM medium; medium.tymed = TYMED_HGLOBAL; HRESULT ret = E_INVALIDARG; if(data->GetData(&format, &medium) == S_OK) { HDROP drop = (HDROP)medium.hGlobal; m_nrOfSelectedFiles = DragQueryFile(drop, 0xFFFFFFFF, 0, 0); TCHAR tmp[MAX_PATH]; if (m_nrOfSelectedFiles >= 1 && m_nrOfSelectedFiles <= 3) { DragQueryFile(drop, 0, tmp, MAX_PATH); _file_name1 = tmp; if(m_nrOfSelectedFiles >= 2) { DragQueryFile(drop, 1, tmp, MAX_PATH); _file_name2 = tmp; } if( m_nrOfSelectedFiles == 3) { DragQueryFile(drop, 2, tmp, MAX_PATH); _file_name3 = tmp; } ret = S_OK; } } else { SYSERRORLOG(TEXT("GetData")); } return ret; } static int insertMenuItemHelper( HMENU menu, UINT id, UINT position, const tstring& text, UINT fState = MFS_ENABLED, HMENU hSubMenu=0 ) { MENUITEMINFO item_info; ZeroMemory(&item_info, sizeof(item_info)); item_info.cbSize = sizeof(MENUITEMINFO); item_info.wID = id; if (text.empty()) { // Separator item_info.fMask = MIIM_TYPE; item_info.fType = MFT_SEPARATOR; item_info.dwTypeData = 0; } else { item_info.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | (hSubMenu!=0 ? MIIM_SUBMENU : 0); item_info.fType = MFT_STRING; item_info.fState = fState; item_info.dwTypeData = (LPTSTR)text.c_str(); item_info.hSubMenu = hSubMenu; } if ( 0 == InsertMenuItem(menu, position, TRUE, &item_info) ) SYSERRORLOG(TEXT("InsertMenuItem")); return id; } STDMETHODIMP DIFF_EXT::QueryContextMenu(HMENU menu, UINT position, UINT first_cmd, UINT /*last_cmd not used*/, UINT flags) { LOG(); SERVER::instance()->recent_files(); // updates recent files list (reads from registry) m_id_Diff = UINT(-1); m_id_DiffWith = UINT(-1); m_id_DiffLater = UINT(-1); m_id_MergeWith = UINT(-1); m_id_Merge3 = UINT(-1); m_id_Diff3 = UINT(-1); m_id_DiffWith_Base = UINT(-1); m_id_ClearList = UINT(-1); m_id_About = UINT(-1); HRESULT ret = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0); if(!(flags & CMF_DEFAULTONLY)) { /* Menu structure: KDiff3 -> (1 File selected): Save 'selection' for later comparison (push onto history stack) Compare 'selection' with first file on history stack. Compare 'selection' with -> choice from history stack Merge 'selection' with first file on history stack. Merge 'selection' with last two files on history stack. (2 Files selected): Compare 's1' with 's2' Merge 's1' with 's2' (3 Files selected): Compare 's1', 's2' and 's3' */ HMENU subMenu = CreateMenu(); UINT id = first_cmd; m_id_FirstCmd = first_cmd; insertMenuItemHelper( menu, id++, position++, TEXT("") ); // begin separator tstring menuString; UINT pos2=0; if(m_nrOfSelectedFiles == 1) { size_t nrOfRecentFiles = m_recentFiles.size(); tstring menuStringCompare; tstring menuStringMerge; tstring firstFileName; if( nrOfRecentFiles>=1 ) { firstFileName = TEXT("'") + cut_to_length( m_recentFiles.front() ) + TEXT("'"); } menuStringCompare = i18n("Compare with %1", firstFileName); menuStringMerge = i18n("Merge with %1", firstFileName); m_id_DiffWith = insertMenuItemHelper( subMenu, id++, pos2++, menuStringCompare, nrOfRecentFiles >=1 ? MFS_ENABLED : MFS_DISABLED ); m_id_MergeWith = insertMenuItemHelper( subMenu, id++, pos2++, menuStringMerge, nrOfRecentFiles >=1 ? MFS_ENABLED : MFS_DISABLED ); //if( nrOfRecentFiles>=2 ) //{ // tstring firstFileName = cut_to_length( m_recentFiles.front() ); // tstring secondFileName = cut_to_length( *(++m_recentFiles.begin()) ); //} m_id_Merge3 = insertMenuItemHelper( subMenu, id++, pos2++, fromQString(i18n("3-way merge with base")), nrOfRecentFiles >=2 ? MFS_ENABLED : MFS_DISABLED ); menuString = fromQString(i18n("Save '%1' for later", toQString(_file_name1))); m_id_DiffLater = insertMenuItemHelper( subMenu, id++, pos2++, menuString ); HMENU file_list = CreateMenu(); std::list::iterator i; m_id_DiffWith_Base = id; int n = 0; for( i = m_recentFiles.begin(); i!=m_recentFiles.end(); ++i ) { tstring s = cut_to_length( *i ); insertMenuItemHelper( file_list, id++, n, s ); ++n; } insertMenuItemHelper( subMenu, id++, pos2++, fromQString(i18n("Compare with ...")), nrOfRecentFiles > 0 ? MFS_ENABLED : MFS_DISABLED, file_list ); m_id_ClearList = insertMenuItemHelper( subMenu, id++, pos2++, fromQString(i18n("Clear list")), nrOfRecentFiles >=1 ? MFS_ENABLED : MFS_DISABLED ); } else if(m_nrOfSelectedFiles == 2) { //= "Diff " + cut_to_length(_file_name1, 20)+" and "+cut_to_length(_file_name2, 20); m_id_Diff = insertMenuItemHelper( subMenu, id++, pos2++, fromQString(i18n("Compare")) ); } else if ( m_nrOfSelectedFiles == 3 ) { m_id_Diff3 = insertMenuItemHelper( subMenu, id++, pos2++, fromQString(i18n("3 way comparison")) ); } else { // More than 3 files selected? } m_id_About = insertMenuItemHelper( subMenu, id++, pos2++, fromQString(i18n("About Diff-Ext ...")) ); insertMenuItemHelper( menu, id++, position++, TEXT("KDiff3"), MFS_ENABLED, subMenu ); insertMenuItemHelper( menu, id++, position++, TEXT("") ); // final separator ret = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, id-first_cmd); } return ret; } STDMETHODIMP DIFF_EXT::InvokeCommand(LPCMINVOKECOMMANDINFO ici) { HRESULT ret = NOERROR; _hwnd = ici->hwnd; if(HIWORD(ici->lpVerb) == 0) { UINT id = m_id_FirstCmd + LOWORD(ici->lpVerb); if(id == m_id_Diff) { LOG(); diff( TEXT("\"") + _file_name1 + TEXT("\" \"") + _file_name2 + TEXT("\"") ); } else if(id == m_id_Diff3) { LOG(); diff( TEXT("\"") + _file_name1 + TEXT("\" \"") + _file_name2 + TEXT("\" \"") + _file_name3 + TEXT("\"") ); } else if(id == m_id_Merge3) { LOG(); std::list< tstring >::iterator iFrom = m_recentFiles.begin(); std::list< tstring >::iterator iBase = iFrom; ++iBase; diff( TEXT("-m \"") + *iBase + TEXT("\" \"") + *iFrom + TEXT("\" \"") + _file_name1 + TEXT("\"") ); } else if(id == m_id_DiffWith) { LOG(); diff_with(0, false); } else if(id == m_id_MergeWith) { LOG(); diff_with(0, true); } else if(id == m_id_ClearList) { LOG(); m_recentFiles.clear(); SERVER::instance()->save_history(); } else if(id == m_id_DiffLater) { MESSAGELOG(TEXT("Diff Later: ")+_file_name1); m_recentFiles.remove( _file_name1 ); m_recentFiles.push_front( _file_name1 ); SERVER::instance()->save_history(); } else if(id >= m_id_DiffWith_Base && id < m_id_DiffWith_Base+m_recentFiles.size()) { LOG(); diff_with(id-m_id_DiffWith_Base, false); } else if(id == m_id_About) { LOG(); QString sBits = i18n("(32 Bit)"); if (sizeof(void*)==8) MessageBox( _hwnd, (fromQString(i18n("Diff-Ext Copyright (c) 2003-2006, Sergey Zorin. All rights reserved.\n") + i18n("This software is distributable under the BSD-2-Clause license.\n") + i18n("Some extensions for KDiff3 (c) 2006-2013 by Joachim Eibl.\n") + i18n("Homepage for Diff-Ext: http://diff-ext.sourceforge.net\n"))).c_str() , fromQString(i18n("About Diff-Ext for KDiff3 ")+sBits).c_str(), MB_OK ); } else { ret = E_INVALIDARG; TCHAR verb[80]; _sntprintf(verb, 79, TEXT("Command id: %d"), LOWORD(ici->lpVerb)); verb[79]=0; ERRORLOG(verb); } } else { ret = E_INVALIDARG; } return ret; } STDMETHODIMP DIFF_EXT::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT*, LPSTR pszName, UINT cchMax) { // LOG(); // Gets called very often HRESULT ret = NOERROR; if(uFlags == GCS_HELPTEXT) { QString helpString; if( idCmd == m_id_Diff ) { helpString = i18n("Compare selected files"); } else if( idCmd == m_id_DiffWith ) { if(!m_recentFiles.empty()) { helpString = i18n("Compare '%1' with '%2'", toQString(_file_name1), toQString(m_recentFiles.front())); } } else if(idCmd == m_id_DiffLater) { helpString = i18n("Save '%1' for later operation", _file_name1); } else if((idCmd >= m_id_DiffWith_Base) && (idCmd < m_id_DiffWith_Base+m_recentFiles.size())) { if( !m_recentFiles.empty() ) { unsigned int num = idCmd - m_id_DiffWith_Base; std::list::iterator i = m_recentFiles.begin(); for(unsigned int j = 0; j < num && i != m_recentFiles.end(); j++) i++; if ( i!=m_recentFiles.end() ) { helpString = i18n("Compare '%1' with '%2'", toQString(_file_name1), toQString(*i)); } } } lstrcpyn( (LPTSTR)pszName, fromQString(helpString).c_str(), cchMax ); } else { ret = E_INVALIDARG; } return ret; } void DIFF_EXT::diff( const tstring& arguments ) { LOG(); STARTUPINFO si; PROCESS_INFORMATION pi; bool bError = true; tstring command = SERVER::instance()->getRegistryKeyString( TEXT(""), TEXT("diffcommand") ); tstring commandLine = TEXT("\"") + command + TEXT("\" ") + arguments; if ( ! command.empty() ) { ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); if (CreateProcess(command.c_str(), (LPTSTR)commandLine.c_str(), 0, 0, FALSE, 0, 0, 0, &si, &pi) == 0) { SYSERRORLOG(TEXT("CreateProcess") + command); } else { bError = false; CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); } } if (bError) { tstring message = fromQString(i18n("Could not start KDiff3. Please rerun KDiff3 installation.")); message += TEXT("\n") + fromQString(i18n("Command")) + TEXT(": ") + command; message += TEXT("\n") + fromQString(i18n("CommandLine")) + TEXT(": ") + commandLine; MessageBox(_hwnd, message.c_str(), fromQString(i18n("Diff-Ext For KDiff3")).c_str(), MB_OK); } } void DIFF_EXT::diff_with(unsigned int num, bool bMerge) { LOG(); std::list::iterator i = m_recentFiles.begin(); for(unsigned int j = 0; j < num && i!=m_recentFiles.end(); j++) { i++; } if ( i!=m_recentFiles.end() ) _file_name2 = *i; diff( (bMerge ? TEXT("-m \"") : TEXT("\"") ) + _file_name2 + TEXT("\" \"") + _file_name1 + TEXT("\"") ); } tstring DIFF_EXT::cut_to_length(const tstring& in, size_t max_len) { tstring ret; if( in.length() > max_len) { ret = in.substr(0, (max_len-3)/2); ret += TEXT("..."); ret += in.substr( in.length()-(max_len-3)/2 ); } else { ret = in; } return ret; } diff --git a/diff_ext_for_kdiff3/diff_ext.h b/diff_ext_for_kdiff3/diff_ext.h index 8262587..699bcd3 100644 --- a/diff_ext_for_kdiff3/diff_ext.h +++ b/diff_ext_for_kdiff3/diff_ext.h @@ -1,85 +1,65 @@ /* - * Copyright (c) 2003-2004, Sergey Zorin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ + SPDX-FileCopyrightText: 2003-2006, Sergey Zorin. All rights reserved. + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: BSD-2-Clause +*/ #ifndef diff_ext_h #define diff_ext_h #include #include #include #include "server.h" // this is the actual OLE Shell context menu handler class DIFF_EXT : public IContextMenu, IShellExtInit { public: DIFF_EXT(); virtual ~DIFF_EXT(); //IUnknown members STDMETHODIMP QueryInterface(REFIID interface_id, void** result); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); //IShell members STDMETHODIMP QueryContextMenu(HMENU menu, UINT index, UINT cmd_first, UINT cmd_last, UINT flags); STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO info); STDMETHODIMP GetCommandString(UINT_PTR cmd, UINT flags, UINT* reserved, LPSTR name, UINT name_length); //IShellExtInit methods STDMETHODIMP Initialize(LPCITEMIDLIST folder, IDataObject* subj, HKEY key); private: void diff( const tstring& arguments ); void diff_with(unsigned int num, bool bMerge); tstring cut_to_length(const tstring&, size_t length = 64); private: UINT m_nrOfSelectedFiles; tstring _file_name1; tstring _file_name2; tstring _file_name3; HINSTANCE _resource; HWND _hwnd; ULONG _ref_count; std::list< tstring >& m_recentFiles; UINT m_id_FirstCmd; UINT m_id_Diff; UINT m_id_DiffWith; UINT m_id_DiffLater; UINT m_id_MergeWith; UINT m_id_Merge3; UINT m_id_Diff3; UINT m_id_DiffWith_Base; UINT m_id_About; UINT m_id_ClearList; }; #endif // __diff_ext_h__ diff --git a/diff_ext_for_kdiff3/server.cpp b/diff_ext_for_kdiff3/server.cpp index 68b7342..ec7719d 100644 --- a/diff_ext_for_kdiff3/server.cpp +++ b/diff_ext_for_kdiff3/server.cpp @@ -1,483 +1,463 @@ /* - * Copyright (c) 2003-2005, Sergey Zorin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ + SPDX-FileCopyrightText: 2003-2006, Sergey Zorin. All rights reserved. + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: BSD-2-Clause +*/ #define _WIN32_WINNT 0x0502 #define _CRT_NON_CONFORMING_SWPRINTFS #define _CRT_SECURE_NO_DEPRECATE #include "server.h" #include #include #include #include #include #include #include #include #include #include "class_factory.h" #define DllExport __declspec( dllexport ) // registry key util struct struct REGSTRUCT { LPCTSTR subkey; LPCTSTR name; LPCTSTR value; }; SERVER* SERVER::_instance = 0; static HINSTANCE server_instance; // Handle to this DLL itself. //DEFINE_GUID(CLSID_DIFF_EXT, 0xA0482097, 0xC69D, 0x4DEC, 0x8A, 0xB6, 0xD3, 0xA2, 0x59, 0xAC, 0xC1, 0x51); // New class id for DIFF_EXT for KDiff3 #ifdef Q_OS_WIN64 // {34471FFB-4002-438b-8952-E4588D0C0FE9} DEFINE_GUID( CLSID_DIFF_EXT, 0x34471FFB, 0x4002, 0x438b, 0x89, 0x52, 0xE4, 0x58, 0x8D, 0x0C, 0x0F, 0xE9 ); #else DEFINE_GUID( CLSID_DIFF_EXT, 0x9f8528e4, 0xab20, 0x456e, 0x84, 0xe5, 0x3c, 0xe6, 0x9d, 0x87, 0x20, 0xf3 ); #endif tstring SERVER::getRegistryKeyString( const tstring& subKey, const tstring& value ) { tstring keyName = m_registryBaseName; if (!subKey.empty()) keyName += TEXT("\\")+subKey; HKEY key; HKEY baseKey = HKEY_CURRENT_USER; tstring result; for(;;) { if( RegOpenKeyEx( baseKey, keyName.c_str(), 0, KEY_READ | KEY_WOW64_64KEY, &key ) == ERROR_SUCCESS ) { DWORD neededSizeInBytes = 0; if (RegQueryValueEx(key, value.c_str(), 0, 0, 0, &neededSizeInBytes) == ERROR_SUCCESS) { DWORD length = neededSizeInBytes / sizeof( TCHAR ); result.resize( length ); if ( RegQueryValueEx( key, value.c_str(), 0, 0, (LPBYTE)&result[0], &neededSizeInBytes ) == ERROR_SUCCESS) { //Everything is ok, but we want to cut off the terminating 0-character result.resize( length - 1 ); RegCloseKey(key); return result; } else { result.resize(0); } } RegCloseKey(key); } if (baseKey==HKEY_LOCAL_MACHINE) break; baseKey = HKEY_LOCAL_MACHINE; } // Error { LPTSTR message; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &message, 0, 0); ERRORLOG( (tstring(TEXT("RegOpenKeyEx: ")+keyName+TEXT("->")+value) + TEXT(": ")) + message ); \ LocalFree(message); } return result; } STDAPI DllCanUnloadNow(void) { HRESULT ret = S_FALSE; if(SERVER::instance()->reference_count() == 0) { ret = S_OK; } return ret; } extern "C" int APIENTRY DllMain(HINSTANCE instance, DWORD reason, LPVOID /* reserved */) { // char str[1024]; // char* reason_string[] = {"DLL_PROCESS_DETACH", "DLL_PROCESS_ATTACH", "DLL_THREAD_ATTACH", "DLL_THREAD_DETACH"}; // sprintf(str, "instance: %x; reason: '%s'", instance, reason_string[reason]); // MessageBox(0, str, TEXT("Info"), MB_OK); switch (reason) { case DLL_PROCESS_ATTACH: server_instance = instance; SERVER::instance()->save_history(); MESSAGELOG(TEXT("DLL_PROCESS_ATTACH")); break; case DLL_PROCESS_DETACH: MESSAGELOG(TEXT("DLL_PROCESS_DETACH")); SERVER::instance()->save_history(); break; } return 1; } STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** class_object) { HRESULT ret = CLASS_E_CLASSNOTAVAILABLE; *class_object = 0; if (IsEqualIID(rclsid, CLSID_DIFF_EXT)) { CLASS_FACTORY* pcf = new CLASS_FACTORY(); ret = pcf->QueryInterface(riid, class_object); } return ret; } /*extern "C" HRESULT STDAPICALLTYPE*/ STDAPI DllRegisterServer() { return SERVER::instance()->do_register(); } STDAPI DllUnregisterServer() { return SERVER::instance()->do_unregister(); } SERVER* SERVER::instance() { if(_instance == 0) { _instance = new SERVER(); _instance->initLogging(); MESSAGELOG(TEXT("New Server instance")); } return _instance; } SERVER::SERVER() : _reference_count(0) { m_registryBaseName = TEXT("Software\\KDiff3\\diff-ext"); m_pRecentFiles = 0; m_pLogFile = 0; } void SERVER::initLogging() { tstring logFileName = getRegistryKeyString( TEXT(""), TEXT("LogFile") ); if ( !logFileName.empty() ) { m_pLogFile = _tfopen( logFileName.c_str(), TEXT("a+, ccs=UTF-8") ); if (m_pLogFile) { _ftprintf( m_pLogFile, TEXT("\nSERVER::SERVER()\n") ); } } } SERVER::~SERVER() { if ( m_pLogFile ) { _ftprintf( m_pLogFile, TEXT("SERVER::~SERVER()\n\n") ); fclose( m_pLogFile ); } delete m_pRecentFiles; } HINSTANCE SERVER::handle() const { return server_instance; } void SERVER::lock() { InterlockedIncrement(&_reference_count); } void SERVER::release() { InterlockedDecrement(&_reference_count); //if(InterlockedDecrement((LPLONG)&_reference_count) == 0) // delete this; } void SERVER::logMessage( const char* function, const char* file, int line, const tstring& msg ) { SERVER* pServer = SERVER::instance(); if ( pServer && pServer->m_pLogFile ) { SYSTEMTIME st; GetSystemTime( &st ); _ftprintf( pServer->m_pLogFile, TEXT("%04d/%02d/%02d %02d:%02d:%02d ") #ifdef UNICODE TEXT("%S (%S:%d) %s\n"), // integrate char-string into wchar_t string #else TEXT("%s (%s:%d) %s\n"), #endif st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, function, file, line, msg.c_str() ); fflush(pServer->m_pLogFile); } } std::list& SERVER::recent_files() { LOG(); if ( m_pRecentFiles==0 ) { m_pRecentFiles = new std::list; } else { m_pRecentFiles->clear(); } MESSAGELOG(TEXT("Reading history from registry...")); for( int i=0; i<32; ++i ) // Max history size { TCHAR numAsString[10]; _sntprintf( numAsString, 10, TEXT("%d"), i ); tstring historyItem = getRegistryKeyString( TEXT("history"), numAsString ); if ( ! historyItem.empty() ) m_pRecentFiles->push_back( historyItem ); } return *m_pRecentFiles; } void SERVER::save_history() const { if( m_pRecentFiles ) { HKEY key; if( RegCreateKeyEx(HKEY_CURRENT_USER, (m_registryBaseName + TEXT("\\history")).c_str(), 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_WOW64_64KEY, 0, &key, 0) == ERROR_SUCCESS ) { LOG(); //DWORD len = MAX_PATH; int n = 0; std::list::const_iterator i; for(i = m_pRecentFiles->begin(); i!=m_pRecentFiles->end(); ++i, ++n ) { tstring str = *i; TCHAR numAsString[10]; _sntprintf( numAsString, 10, TEXT("%d"), n ); if(RegSetValueEx(key, numAsString, 0, REG_SZ, (const BYTE*)str.c_str(), (DWORD)(str.size()+1)*sizeof(TCHAR) ) != ERROR_SUCCESS) { LPTSTR message; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &message, 0, 0); MessageBox(0, message, TEXT("KDiff3-diff-ext: Save history failed"), MB_OK | MB_ICONINFORMATION); LocalFree(message); } } for(; n<32; ++n ) { TCHAR numAsString[10]; _sntprintf( numAsString, 10, TEXT("%d"), n ); RegDeleteValue(key, numAsString ); } RegCloseKey(key); } else { SYSERRORLOG(TEXT("RegOpenKeyEx")); } } } HRESULT SERVER::do_register() { LOG(); TCHAR class_id[MAX_PATH]; LPWSTR tmp_guid; HRESULT ret = SELFREG_E_CLASS; if (StringFromIID(CLSID_DIFF_EXT, &tmp_guid) == S_OK) { #ifdef UNICODE _tcsncpy(class_id, tmp_guid, MAX_PATH); #else wcstombs(class_id, tmp_guid, MAX_PATH); #endif CoTaskMemFree((void*)tmp_guid); TCHAR subkey[MAX_PATH]; TCHAR server_path[MAX_PATH]; HKEY key; LRESULT result = NOERROR; DWORD dwDisp; GetModuleFileName(SERVER::instance()->handle(), server_path, MAX_PATH); REGSTRUCT entry[] = { {TEXT("Software\\Classes\\CLSID\\%s"), 0, TEXT("diff-ext-for-kdiff3")}, {TEXT("Software\\Classes\\CLSID\\%s\\InProcServer32"), 0, TEXT("%s")}, {TEXT("Software\\Classes\\CLSID\\%s\\InProcServer32"), TEXT("ThreadingModel"), TEXT("Apartment")} }; for(unsigned int i = 0; (i < sizeof(entry)/sizeof(entry[0])) && (result == NOERROR); i++) { _sntprintf(subkey, MAX_PATH, entry[i].subkey, class_id); result = RegCreateKeyEx(HKEY_CURRENT_USER, subkey, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, &dwDisp); if(result == NOERROR) { TCHAR szData[MAX_PATH]; _sntprintf(szData, MAX_PATH, entry[i].value, server_path); szData[MAX_PATH-1]=0; result = RegSetValueEx(key, entry[i].name, 0, REG_SZ, (LPBYTE)szData, DWORD(_tcslen(szData)*sizeof(TCHAR))); } RegCloseKey(key); } if(result == NOERROR) { result = RegCreateKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Classes\\*\\shellex\\ContextMenuHandlers\\diff-ext-for-kdiff3"), 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, &dwDisp); if(result == NOERROR) { result = RegSetValueEx(key, 0, 0, REG_SZ, (LPBYTE)class_id, DWORD(_tcslen(class_id)*sizeof(TCHAR))); RegCloseKey(key); //If running on NT, register the extension as approved. OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(osvi); GetVersionEx(&osvi); // NT needs to have shell extensions "approved". if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) { result = RegCreateKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"), 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, &dwDisp); if(result == NOERROR) { TCHAR szData[MAX_PATH]; lstrcpy(szData, TEXT("diff-ext")); result = RegSetValueEx(key, class_id, 0, REG_SZ, (LPBYTE)szData, DWORD(_tcslen(szData)*sizeof(TCHAR))); RegCloseKey(key); ret = S_OK; } else if (result == ERROR_ACCESS_DENIED) { TCHAR msg[] = TEXT("Warning! You have unsufficient rights to write to a specific registry key.\n") TEXT("The application may work anyway, but it is advised to register this module ") TEXT("again while having administrator rights."); MessageBox(0, msg, TEXT("Warning"), MB_ICONEXCLAMATION); ret = S_OK; } } else { ret = S_OK; } } } } return ret; } HRESULT SERVER::do_unregister() { LOG(); TCHAR class_id[MAX_PATH]; LPWSTR tmp_guid; HRESULT ret = SELFREG_E_CLASS; if (StringFromIID(CLSID_DIFF_EXT, &tmp_guid) == S_OK) { #ifdef UNICODE _tcsncpy(class_id, tmp_guid, MAX_PATH); #else wcstombs(class_id, tmp_guid, MAX_PATH); #endif CoTaskMemFree((void*)tmp_guid); LRESULT result = NOERROR; TCHAR subkey[MAX_PATH]; REGSTRUCT entry[] = { {TEXT("Software\\Classes\\CLSID\\%s\\InProcServer32"), 0, 0}, {TEXT("Software\\Classes\\CLSID\\%s"), 0, 0} }; for(unsigned int i = 0; (i < sizeof(entry)/sizeof(entry[0])) && (result == NOERROR); i++) { _stprintf(subkey, entry[i].subkey, class_id); result = RegDeleteKey(HKEY_CURRENT_USER, subkey); } if(result == NOERROR) { result = RegDeleteKey(HKEY_CURRENT_USER, TEXT("Software\\Classes\\*\\shellex\\ContextMenuHandlers\\diff-ext-for-kdiff3")); if(result == NOERROR) { //If running on NT, register the extension as approved. OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(osvi); GetVersionEx(&osvi); // NT needs to have shell extensions "approved". if(osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) { HKEY key; RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"), 0, KEY_ALL_ACCESS, &key); result = RegDeleteValue(key, class_id); RegCloseKey(key); if(result == ERROR_SUCCESS) { ret = S_OK; } } else { ret = S_OK; } } } } return ret; } diff --git a/diff_ext_for_kdiff3/server.h b/diff_ext_for_kdiff3/server.h index e694169..9aeb056 100644 --- a/diff_ext_for_kdiff3/server.h +++ b/diff_ext_for_kdiff3/server.h @@ -1,101 +1,81 @@ /* - * Copyright (c) 2003-2005, Sergey Zorin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ + SPDX-FileCopyrightText: 2003-2006, Sergey Zorin. All rights reserved. + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: BSD-2-Clause +*/ #ifndef server_h #define server_h #include // std::list //#include #include #include // std::wstring #ifdef UNICODE typedef std::wstring tstring; #define toQString(s) QString::fromStdWString(s) #define fromQString(s) (s).toStdWString() #else typedef std::string tstring; #define toQString(s) { QString::fromStdString(s);} #define fromQString(s) { (s).toStdString();} #endif #define MESSAGELOG( msg ) SERVER::logMessage( __FUNCTION__, __FILE__, __LINE__, msg ) #define LOG() MESSAGELOG( TEXT("") ) #define ERRORLOG( msg ) MESSAGELOG( TEXT("Error: ")+tstring(msg) ) #define SYSERRORLOG( msg ) \ { \ LPTSTR message; \ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, \ GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &message, 0, 0); \ ERRORLOG( (tstring(msg) + TEXT(": ")) + message ); \ LocalFree(message); \ } class SERVER { public: static SERVER* instance(); void initLogging(); public: virtual ~SERVER(); tstring getRegistryKeyString( const tstring& subKey, const tstring& value ); HINSTANCE handle() const; HRESULT do_register(); HRESULT do_unregister(); void lock(); void release(); ULONG reference_count() const { return _reference_count; } std::list< tstring >& recent_files(); void save_history() const; static void logMessage( const char* function, const char* file, int line, const tstring& msg ); private: SERVER(); SERVER(const SERVER&) {} private: LONG _reference_count; std::list* m_pRecentFiles; static SERVER* _instance; tstring m_registryBaseName; FILE* m_pLogFile; }; #endif // __server_h__ diff --git a/kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp b/kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp index d4e98d4..b9ef9dd 100644 --- a/kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp +++ b/kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp @@ -1,293 +1,281 @@ -/* This file is part of the KDiff3 project - - Copyright (C) 2008 Joachim Eibl - - 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; version 2 - of the License. - - 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; see the file COPYING. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "kdiff3fileitemaction.h" #include #include #include #include #include #include #include #include #include #include #include //#include static QStringList* s_pHistory=nullptr; class KDiff3PluginHistory { KConfig* m_pConfig; KConfigGroup* m_pConfigGroup; public: KDiff3PluginHistory() { m_pConfig = nullptr; if (s_pHistory==nullptr) { //std::cout << "New History: " << instanceName << std::endl; s_pHistory = new QStringList; m_pConfig = new KConfig( "kdiff3fileitemactionrc", KConfig::SimpleConfig ); m_pConfigGroup = new KConfigGroup( m_pConfig, "KDiff3Plugin" ); *s_pHistory = m_pConfigGroup->readEntry("HistoryStack", QStringList() ); } } ~KDiff3PluginHistory() { //std::cout << "Delete History" << std::endl; if ( s_pHistory && m_pConfigGroup ) m_pConfigGroup->writeEntry("HistoryStack",*s_pHistory); delete s_pHistory; delete m_pConfigGroup; delete m_pConfig; s_pHistory = nullptr; m_pConfig = nullptr; } }; KDiff3PluginHistory s_history; K_PLUGIN_FACTORY_WITH_JSON(KDiff3FileItemActionFactory, "kdiff3fileitemaction.json", registerPlugin();) #include "kdiff3fileitemaction.moc" KDiff3FileItemAction::KDiff3FileItemAction (QObject* pParent, const QVariantList & /*args*/) : KAbstractFileItemActionPlugin(pParent) { } QList KDiff3FileItemAction::actions( const KFileItemListProperties& fileItemInfos, QWidget* pParentWidget ) { QList< QAction* > actions; if (QStandardPaths::findExecutable("kdiff3").isEmpty ()) return actions; //m_fileItemInfos = fileItemInfos; m_pParentWidget = pParentWidget; QAction *pMenuAction = new QAction(QIcon::fromTheme(QStringLiteral("kdiff3")), i18n("KDiff3..."), this); QMenu *pActionMenu = new QMenu(); pMenuAction->setMenu( pActionMenu ); // remember currently selected files m_list = fileItemInfos.urlList(); /* Menu structure: KDiff3 -> (1 File selected): Save 'selection' for later comparison (push onto history stack) Compare 'selection' with first file on history stack. Compare 'selection' with -> choice from history stack Merge 'selection' with first file on history stack. Merge 'selection' with last two files on history stack. (2 Files selected): Compare 's1' with 's2' Merge 's1' with 's2' (3 Files selected): Compare 's1', 's2' and 's3' */ QAction* pAction = nullptr; QString s; if(m_list.count() == 1) { int historyCount = s_pHistory ? s_pHistory->count() : 0; s = i18n("Compare with %1", (historyCount>0 ? s_pHistory->first() : QString()) ); pAction = new QAction ( s,this ); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotCompareWith); pAction->setEnabled( m_list.count()>0 && historyCount>0 ); pActionMenu->addAction(pAction); s = i18n("Merge with %1", historyCount>0 ? s_pHistory->first() : QString() ); pAction = new QAction( s, this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotMergeWith); pAction->setEnabled( m_list.count()>0 && historyCount>0 ); pActionMenu->addAction (pAction); s = i18n("Save '%1' for later", ( m_list.first().toDisplayString(QUrl::PreferLocalFile) ) ); pAction = new QAction ( s, this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotSaveForLater); pAction->setEnabled( m_list.count()>0 ); pActionMenu->addAction(pAction); pAction = new QAction (i18n("3-way merge with base"), this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotMergeThreeWay); pAction->setEnabled( m_list.count()>0 && historyCount>=2 ); pActionMenu->addAction (pAction); if (s_pHistory && !s_pHistory->empty()) { QAction* pHistoryMenuAction = new QAction( i18n("Compare with..."), this ); QMenu* pHistoryMenu = new QMenu(); pHistoryMenuAction->setMenu( pHistoryMenu ); pHistoryMenu->setEnabled( m_list.count()>0 && historyCount>0 ); pActionMenu->addAction(pHistoryMenuAction); for(const QString& file : qAsConst(*s_pHistory)) { pAction = new QAction(file, this); pAction->setData(file); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotCompareWithHistoryItem); pHistoryMenu->addAction(pAction); } pAction = new QAction(i18n("Clear list"), this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotClearList); pActionMenu->addAction(pAction); pAction->setEnabled( historyCount>0 ); } } else if(m_list.count() == 2) { pAction = new QAction (i18n("Compare"), this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotCompareTwoFiles); pActionMenu->addAction (pAction); } else if ( m_list.count() == 3 ) { pAction = new QAction (i18n("3 way comparison"), this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotCompareThreeFiles); pActionMenu->addAction (pAction); } pAction = new QAction (i18n("About KDiff3 menu plugin..."), this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotAbout); pActionMenu->addAction (pAction); //pMenu->addSeparator(); //pMenu->addAction( pActionMenu ); //pMenu->addSeparator(); actions << pMenuAction; return actions; } KDiff3FileItemAction::~KDiff3FileItemAction () { } void KDiff3FileItemAction::slotCompareWith() { if ( m_list.count() > 0 && s_pHistory && ! s_pHistory->empty() ) { QStringList args; args << s_pHistory->first(); args << m_list.first().toDisplayString(QUrl::PreferLocalFile); KProcess::startDetached("kdiff3", args); } } void KDiff3FileItemAction::slotCompareWithHistoryItem() { const QAction* pAction = dynamic_cast( sender() ); if (!m_list.isEmpty() && pAction) { QStringList args; args << pAction->data().toString(); args << m_list.first().toDisplayString(QUrl::PreferLocalFile); KProcess::startDetached ("kdiff3", args); } } void KDiff3FileItemAction::slotCompareTwoFiles() { if (m_list.count() == 2) { QStringList args; args << m_list.first().toDisplayString(QUrl::PreferLocalFile); args << m_list.last().toDisplayString(QUrl::PreferLocalFile); KProcess::startDetached ("kdiff3", args); } } void KDiff3FileItemAction::slotCompareThreeFiles() { if ( m_list.count() == 3 ) { QStringList args; args << m_list.at(0).toDisplayString(QUrl::PreferLocalFile); args << m_list.at(1).toDisplayString(QUrl::PreferLocalFile); args << m_list.at(2).toDisplayString(QUrl::PreferLocalFile); KProcess::startDetached ("kdiff3", args); } } void KDiff3FileItemAction::slotMergeWith() { if ( m_list.count() > 0 && s_pHistory && ! s_pHistory->empty() ) { QStringList args; args << s_pHistory->first(); args << m_list.first().toDisplayString(QUrl::PreferLocalFile); args << ( "-o" + m_list.first().toDisplayString(QUrl::PreferLocalFile) ); KProcess::startDetached ("kdiff3", args); } } void KDiff3FileItemAction::slotMergeThreeWay() { if ( m_list.count() > 0 && s_pHistory && s_pHistory->count()>=2 ) { QStringList args; args << s_pHistory->at(1); args << s_pHistory->at(0); args << m_list.first().toDisplayString(QUrl::PreferLocalFile); args << ("-o" + m_list.first().toDisplayString(QUrl::PreferLocalFile)); KProcess::startDetached ("kdiff3", args); } } void KDiff3FileItemAction::slotSaveForLater() { if (!m_list.isEmpty() && s_pHistory) { while (s_pHistory->count()>=10) { s_pHistory->removeLast(); } const QString file = m_list.first().toDisplayString(QUrl::PreferLocalFile); s_pHistory->removeAll(file); s_pHistory->prepend(file); } } void KDiff3FileItemAction::slotClearList() { if (s_pHistory) { s_pHistory->clear(); } } void KDiff3FileItemAction::slotAbout() { QString s = i18n("KDiff3 File Item Action Plugin: Copyright (C) 2011 Joachim Eibl\n"); s += i18n("Using the context menu extension:\n" "For simple comparison of two selected files choose \"Compare\".\n" "If the other file is somewhere else \"Save\" the first file for later. " "It will appear in the \"Compare with...\" submenu. " "Then use \"Compare With\" on the second file.\n" "For a 3-way merge first \"Save\" the base file, then the branch to merge and " "choose \"3-way merge with base\" on the other branch which will be used as destination.\n" "Same also applies to folder comparison and merge."); KMessageBox::information(m_pParentWidget, s, i18n("About KDiff3 File Item Action Plugin") ); } diff --git a/kdiff3fileitemactionplugin/kdiff3fileitemaction.h b/kdiff3fileitemactionplugin/kdiff3fileitemaction.h index 6af4033..8af0b25 100644 --- a/kdiff3fileitemactionplugin/kdiff3fileitemaction.h +++ b/kdiff3fileitemactionplugin/kdiff3fileitemaction.h @@ -1,52 +1,40 @@ -/* This file is part of the KDiff3 project - - Copyright (C) 2008 Joachim Eibl - - 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; version 2 - of the License. - - 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; see the file COPYING. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef KDIFF3FILEITEMACTIONPLUGIN_H #define KDIFF3FILEITEMACTIONPLUGIN_H #include #include class KDiff3FileItemAction : public KAbstractFileItemActionPlugin { Q_OBJECT public: KDiff3FileItemAction (QObject* pParent, const QVariantList & args); ~KDiff3FileItemAction() override; // implement pure virtual method from KonqPopupMenuPlugin QList actions( const KFileItemListProperties& fileItemInfos, QWidget* pParentWidget ) override; private Q_SLOTS: void slotCompareWith(); void slotCompareTwoFiles(); void slotCompareThreeFiles(); void slotMergeWith(); void slotMergeThreeWay(); void slotSaveForLater(); void slotClearList(); void slotCompareWithHistoryItem(); void slotAbout(); private: QList m_list; QWidget* m_pParentWidget = nullptr; //KFileItemListProperties m_fileItemInfos; }; #endif diff --git a/src/CommentParser.cpp b/src/CommentParser.cpp index 180142e..88f1be1 100644 --- a/src/CommentParser.cpp +++ b/src/CommentParser.cpp @@ -1,167 +1,155 @@ -/** - * Copyright (C) 2019 Michael Reeves - * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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. +/* + * KDiff3 - Text Diff And Merge Tool * - * You should have received a copy of the GNU General Public License - * along with KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2019-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "CommentParser.h" #include #include #include #include void DefaultCommentParser::processChar(const QString &line, const QChar &inChar) { if(!bIsEscaped) { switch(inChar.unicode()) { case '\\': if(bInString) bIsEscaped = true; break; case '\'': case '"': if(!inComment()) { if(!bInString) { bInString = true; mStartChar = inChar; } else if(mStartChar == inChar) { bInString = false; } } break; case '/': if(bInString) break; if(!inComment() && mLastChar == '/') { mCommentType = singleLine; mIsPureComment = line.startsWith(QLatin1String("//")); lastComment.startOffset = offset - 1; } else if(mLastChar == '*' && mCommentType == multiLine) { //ending multi-line comment mCommentType = none; lastComment.endOffset = offset + 1; //include last char in offset comments.push_back(lastComment); if(!isFirstLine) mIsPureComment = line.endsWith(QLatin1String("*/")) ? true : mIsPureComment; } break; case '*': if(bInString) break; if(mLastChar == '/' && !inComment()) { mCommentType = multiLine; mIsPureComment = line.startsWith(QLatin1String("/*")) ? true : mIsPureComment; isFirstLine = true; lastComment.startOffset = offset - 1; } break; case '\n': if(mCommentType == singleLine) { mCommentType = none; lastComment.endOffset = offset; comments.push_back(lastComment); } if(mCommentType == multiLine && !isFirstLine) { mIsPureComment = true; } if(lastComment.startOffset > 0 && lastComment.endOffset == 0) { lastComment.endOffset = offset; comments.push_back(lastComment); } isFirstLine = false; break; default: if(inComment()) { break; } mIsPureComment = false; break; } mLastChar = inChar; } else { bIsEscaped = false; mLastChar = QChar(); } ++offset; }; /* Find comments if any and set is pure comment flag it it has nothing but whitespace and comments. */ void DefaultCommentParser::processLine(const QString &line) { offset = line.indexOf(QRegularExpression("[\\S]", QRegularExpression::UseUnicodePropertiesOption)); lastComment.startOffset = lastComment.endOffset = 0; //reset these for each line comments.clear(); //remove trailing and ending spaces. const QString trimmedLine = line.trimmed(); for(const QChar &c : trimmedLine) { processChar(trimmedLine, c); } processChar(trimmedLine, '\n'); } /* Modifies the input data, and replaces comments with whitespace when the line contains other data too. */ void DefaultCommentParser::removeComment(QString &line) { if(isPureComment() || lastComment.startOffset == lastComment.endOffset) return; for(const CommentRange &range : comments) { /* Q_ASSERT isn't useful during auto testing as it causes the QTest not to show the actual line that the test failed on. */ #ifndef AUTOTEST Q_ASSERT(range.endOffset <= line.length() && range.startOffset <= line.length()); Q_ASSERT(range.endOffset >= range.startOffset); #endif qint32 size = range.endOffset - range.startOffset; line.replace(range.startOffset, size, QString(" ").repeated(size)); } } diff --git a/src/CommentParser.h b/src/CommentParser.h index 5a0a4ee..9cd3ffd 100644 --- a/src/CommentParser.h +++ b/src/CommentParser.h @@ -1,77 +1,65 @@ -/** - * Copyright (C) 2019 Michael Reeves +/* + * KDiff3 - Text Diff And Merge Tool * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2019-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef COMMENTPARSER_H #define COMMENTPARSER_H #include #include #include class CommentParser { public: virtual void processChar(const QString &line, const QChar &inChar) = 0; virtual void processLine(const QString &line) = 0; virtual void removeComment(QString &line) = 0; virtual bool inComment() const = 0; virtual bool isPureComment() const = 0; virtual ~CommentParser(){}; }; class DefaultCommentParser : public CommentParser { private: typedef enum {none, singleLine, multiLine}CommentType; public: void processLine(const QString &line) override; inline bool inComment() const override { return mCommentType != none; }; inline bool isPureComment() const override { return mIsPureComment; }; void removeComment(QString &line) override; protected: friend class CommentParserTest; void processChar(const QString &line, const QChar &inChar) override; //For tests only. inline bool isEscaped() const{ return bIsEscaped; } inline bool inString() const{ return bInString; } private: QChar mLastChar, mStartChar; struct CommentRange { qint32 startOffset = 0; qint32 endOffset = 0; }; qint32 offset = -1; CommentRange lastComment; std::vector comments; bool isFirstLine = false; bool mIsPureComment = false; bool bInString = false; bool bIsEscaped = false; CommentType mCommentType = none; }; #endif // !COMMENTPASER_H diff --git a/src/ConfigValueMap.h b/src/ConfigValueMap.h index 91d3039..b4a9dfc 100644 --- a/src/ConfigValueMap.h +++ b/src/ConfigValueMap.h @@ -1,111 +1,100 @@ -/** - * Copyright (C) 2019 Michael Reeves - * Copyright (C) 2002-2009 Joachim Eibl, joachim.eibl at gmx.de +/* + * This file is part of KDiff3. * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ + #ifndef CONFIGVALUEMAP_H #define CONFIGVALUEMAP_H #include "common.h" #include #include #include #include #include #include class ConfigValueMap : public ValueMap { private: KConfigGroup m_config; public: explicit ConfigValueMap(const KConfigGroup& config) : m_config(config) {} void writeEntry(const QString& s, const QFont& v) override { m_config.writeEntry(s, v); } void writeEntry(const QString& s, const QColor& v) override { m_config.writeEntry(s, v); } void writeEntry(const QString& s, const QSize& v) override { m_config.writeEntry(s, v); } void writeEntry(const QString& s, const QPoint& v) override { m_config.writeEntry(s, v); } void writeEntry(const QString& s, int v) override { m_config.writeEntry(s, v); } void writeEntry(const QString& s, bool v) override { m_config.writeEntry(s, v); } void writeEntry(const QString& s, const QString& v) override { m_config.writeEntry(s, v); } void writeEntry(const QString& s, const char* v) override { m_config.writeEntry(s, v); } private: QFont readFontEntry(const QString& s, const QFont* defaultVal) override { return m_config.readEntry(s, *defaultVal); } QColor readColorEntry(const QString& s, const QColor* defaultVal) override { return m_config.readEntry(s, *defaultVal); } QSize readSizeEntry(const QString& s, const QSize* defaultVal) override { return m_config.readEntry(s, *defaultVal); } QPoint readPointEntry(const QString& s, const QPoint* defaultVal) override { return m_config.readEntry(s, *defaultVal); } bool readBoolEntry(const QString& s, bool defaultVal) override { return m_config.readEntry(s, defaultVal); } int readNumEntry(const QString& s, int defaultVal) override { return m_config.readEntry(s, defaultVal); } QString readStringEntry(const QString& s, const QString& defaultVal) override { return m_config.readEntry(s, defaultVal); } void writeEntry(const QString& s, const QStringList& v) override { m_config.writeEntry(s, v); } QStringList readListEntry(const QString& s, const QStringList& def) override { return m_config.readEntry(s, def); } }; #endif // !CONFIGVALUEMAP_H diff --git a/src/DirectoryInfo.h b/src/DirectoryInfo.h index 4dc01b7..9576679 100644 --- a/src/DirectoryInfo.h +++ b/src/DirectoryInfo.h @@ -1,94 +1,82 @@ -/** - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com - * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef DIRECTORYINFO_H #define DIRECTORYINFO_H #include "fileaccess.h" #include "options.h" class DirectoryInfo { public: explicit DirectoryInfo(){}; DirectoryInfo(FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest) { m_dirA = dirA; m_dirB = dirB; m_dirC = dirC; m_dirDest = dirDest; m_dirListA.clear(); m_dirListB.clear(); m_dirListC.clear(); } inline FileAccess dirA() const { return m_dirA; } inline FileAccess dirB() const { return m_dirB; } inline FileAccess dirC() const { return m_dirC; } inline FileAccess destDir() const { if(m_dirDest.isValid()) return m_dirDest; else return m_dirC.isValid() ? m_dirC : m_dirB; } inline bool allowSyncMode() { return !m_dirC.isValid() && !m_dirDest.isValid(); } inline bool listDirA(const Options& options) { return m_dirA.listDir(&m_dirListA, options.m_bDmRecursiveDirs, options.m_bDmFindHidden, options.m_DmFilePattern, options.m_DmFileAntiPattern, options.m_DmDirAntiPattern, options.m_bDmFollowDirLinks, options.m_bDmUseCvsIgnore); } inline bool listDirB(const Options& options) { return m_dirB.listDir(&m_dirListB, options.m_bDmRecursiveDirs, options.m_bDmFindHidden, options.m_DmFilePattern, options.m_DmFileAntiPattern, options.m_DmDirAntiPattern, options.m_bDmFollowDirLinks, options.m_bDmUseCvsIgnore); } inline bool listDirC(const Options& options) { return m_dirC.listDir(&m_dirListC, options.m_bDmRecursiveDirs, options.m_bDmFindHidden, options.m_DmFilePattern, options.m_DmFileAntiPattern, options.m_DmDirAntiPattern, options.m_bDmFollowDirLinks, options.m_bDmUseCvsIgnore); } t_DirectoryList& getDirListA() { return m_dirListA; } t_DirectoryList& getDirListB() { return m_dirListB; } t_DirectoryList& getDirListC() { return m_dirListC; } private: FileAccess m_dirA, m_dirB, m_dirC; t_DirectoryList m_dirListA; t_DirectoryList m_dirListB; t_DirectoryList m_dirListC; FileAccess m_dirDest; }; #endif diff --git a/src/FileNameLineEdit.cpp b/src/FileNameLineEdit.cpp index adaab7c..9c935ac 100644 --- a/src/FileNameLineEdit.cpp +++ b/src/FileNameLineEdit.cpp @@ -1,43 +1,31 @@ -/** - * Copyright (C) 2019 Michael Reeves +/* + * KDiff3 - Text Diff And Merge Tool * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2019-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "FileNameLineEdit.h" #include #include #include #include void FileNameLineEdit::dropEvent(QDropEvent* event) { Q_ASSERT(event->mimeData()->hasUrls());//Debugging aid in case FileNameLineEdit::dragEnterEvent is changed to accept other types. QList lst = event->mimeData()->urls(); if(lst.count() > 0) { setText(lst[0].toString()); setFocus(); Q_EMIT returnPressed(); } } void FileNameLineEdit::dragEnterEvent(QDragEnterEvent* e) { e->setAccepted(e->mimeData()->hasUrls()); } diff --git a/src/FileNameLineEdit.h b/src/FileNameLineEdit.h index f3673a1..0c47434 100644 --- a/src/FileNameLineEdit.h +++ b/src/FileNameLineEdit.h @@ -1,32 +1,20 @@ -/** - * Copyright (C) 2019 Michael Reeves +/* + * KDiff3 - Text Diff And Merge Tool * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2019-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef FILENAMELINEEDIT_H #include class FileNameLineEdit : public QLineEdit { public: using QLineEdit::QLineEdit; void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* e) override; }; #endif // !FILENAMELINEEDIT_H diff --git a/src/LineRef.h b/src/LineRef.h index 0e9c22b..fe7c05f 100644 --- a/src/LineRef.h +++ b/src/LineRef.h @@ -1,97 +1,83 @@ -/** - * - * Copyright (C) 2018 Michael Reeves - * - * This file is part of Kdiff3. - * - * Kdiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Kdiff3 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 Kdiff3. If not, see . - * - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef LINEREF_H #define LINEREF_H #include "TypeUtils.h" #include #include #include class LineRef { public: typedef qint32 LineType; inline LineRef() = default; inline LineRef(const LineType i) { mLineNumber = i; } inline LineRef(const qint64 i) { if(i <= TYPE_MAX(LineType)) mLineNumber = (LineType)i; else mLineNumber = -1; } inline operator LineType() const { return mLineNumber; } inline LineRef& operator=(const LineType lineIn) { mLineNumber = lineIn; return *this; } inline LineRef& operator+=(const LineType& inLine) { mLineNumber += inLine; return *this; }; LineRef& operator++() { ++mLineNumber; return *this; }; const LineRef operator++(int) { LineRef line(*this); ++mLineNumber; return line; }; LineRef& operator--() { --mLineNumber; return *this; }; const LineRef operator--(int) { LineRef line(*this); --mLineNumber; return line; }; inline void invalidate() { mLineNumber = -1; } inline bool isValid() const { return mLineNumber != -1; } private: LineType mLineNumber = -1; }; static_assert(std::is_copy_constructible::value, "LineRef must be copy constuctible."); static_assert(std::is_copy_assignable::value, "LineRef must copy assignable."); static_assert(std::is_move_constructible::value, "LineRef must be move constructible."); static_assert(std::is_move_assignable::value, "LineRef must be move assignable."); static_assert(std::is_convertible::value, "Can not convert LineRef to int."); static_assert(std::is_convertible::value, "Can not convert int to LineRef."); typedef LineRef::LineType LineCount; typedef LineRef::LineType LineIndex; #endif diff --git a/src/Logging.cpp b/src/Logging.cpp index 05ddd6b..29522ce 100644 --- a/src/Logging.cpp +++ b/src/Logging.cpp @@ -1,25 +1,13 @@ -/** - * Copyright (C) 2019 Michael Reeves +/* + * KDiff3 - Text Diff And Merge Tool * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2019-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "Logging.h" Q_LOGGING_CATEGORY(kdiffMain, "org.kde.kdiff3") Q_LOGGING_CATEGORY(kdiffFileAccess, "org.kde.kdiff3.fileAccess") //The following is very noisey if debug is turned on and not really useful unless your making changes in the core data processing. Q_LOGGING_CATEGORY(kdiffCore, "org.kde.kdiff3.core", QtWarningMsg) diff --git a/src/Logging.h b/src/Logging.h index bc2e356..1bcf3dd 100644 --- a/src/Logging.h +++ b/src/Logging.h @@ -1,29 +1,17 @@ -/** - * Copyright (C) 2019 Michael Reeves +/* + * KDiff3 - Text Diff And Merge Tool * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2019-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef LOGGING_H #define LOGGING_H #include Q_DECLARE_LOGGING_CATEGORY(kdiffMain) Q_DECLARE_LOGGING_CATEGORY(kdiffFileAccess); Q_DECLARE_LOGGING_CATEGORY(kdiffCore) //very noisey shows internal state information for kdiffs core. #endif // !LOGGING_H diff --git a/src/MergeEditLine.cpp b/src/MergeEditLine.cpp index 096c2dc..065521b 100644 --- a/src/MergeEditLine.cpp +++ b/src/MergeEditLine.cpp @@ -1,51 +1,48 @@ -/*************************************************************************** - * Copyright (C) 2002-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "MergeEditLine.h" QString MergeEditLine::getString(const QVector* pLineDataA, const QVector* pLineDataB, const QVector* pLineDataC) { if(isRemoved()) { return QString(); } if(!isModified()) { int src = m_src; if(src == 0) { return QString(); } const Diff3Line& d3l = *m_id3l; const LineData* pld = nullptr; Q_ASSERT(src == A || src == B || src == C); if(src == A && d3l.getLineA().isValid()) pld = &(*pLineDataA)[d3l.getLineA()]; else if(src == B && d3l.getLineB().isValid()) pld = &(*pLineDataB)[d3l.getLineB()]; else if(src == C && d3l.getLineC().isValid()) pld = &(*pLineDataC)[d3l.getLineC()]; //Not an error. if(pld == nullptr) { return QString(); } return pld->getLine(); } else { return m_str; } return QString(); } diff --git a/src/MergeEditLine.h b/src/MergeEditLine.h index f9e0f4c..0373266 100644 --- a/src/MergeEditLine.h +++ b/src/MergeEditLine.h @@ -1,138 +1,135 @@ -/*************************************************************************** - * Copyright (C) 2002-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef MERGEEDITLINE_H #include "diff.h" #include #include class MergeEditLine { public: explicit MergeEditLine(const Diff3LineList::const_iterator& i, e_SrcSelector src = None) { m_id3l = i; m_src = src; m_bLineRemoved = false; } void setConflict() { m_src = None; m_bLineRemoved = false; m_str = QString(); } bool isConflict() { return m_src == None && !m_bLineRemoved && m_str.isEmpty(); } void setRemoved(e_SrcSelector src = None) { m_src = src; m_bLineRemoved = true; m_str = QString(); } bool isRemoved() const { return m_bLineRemoved; } bool isEditableText() { return !isConflict() && !isRemoved(); } void setString(const QString& s) { m_str = s; m_bLineRemoved = false; m_src = None; } QString getString(const QVector* pLineDataA, const QVector* pLineDataB, const QVector* pLineDataC); bool isModified() { return !m_str.isEmpty() || (m_bLineRemoved && m_src == None); } void setSource(e_SrcSelector src, bool bLineRemoved) { m_src = src; m_bLineRemoved = bLineRemoved; } e_SrcSelector src() { return m_src; } Diff3LineList::const_iterator id3l() { return m_id3l; } private: Diff3LineList::const_iterator m_id3l; e_SrcSelector m_src; // 1, 2 or 3 for A, B or C respectively, or 0 when line is from neither source. QString m_str; // String when modified by user or null-string when orig data is used. bool m_bLineRemoved; }; class MergeEditLineList :public std::list { // I want to know the size immediately! private: typedef std::list BASE; public: typedef std::list::iterator iterator; typedef std::list::reverse_iterator reverse_iterator; typedef std::list::const_iterator const_iterator; int size() { return (int)BASE::size(); } }; class MergeLine { public: Diff3LineList::const_iterator id3l; LineIndex d3lLineIdx = -1; // Needed to show the correct window pos. LineCount srcRangeLength = 0; // how many src-lines have this properties e_MergeDetails mergeDetails = eDefault; bool bConflict = false; bool bWhiteSpaceConflict = false; bool bDelta = false; e_SrcSelector srcSelect = None; MergeEditLineList mergeEditLineList; void split(MergeLine& ml2, int d3lLineIdx2) // The caller must insert the ml2 after this ml in the m_mergeLineList { if(d3lLineIdx2 < d3lLineIdx || d3lLineIdx2 >= d3lLineIdx + srcRangeLength) return; //Error ml2.mergeDetails = mergeDetails; ml2.bConflict = bConflict; ml2.bWhiteSpaceConflict = bWhiteSpaceConflict; ml2.bDelta = bDelta; ml2.srcSelect = srcSelect; ml2.d3lLineIdx = d3lLineIdx2; ml2.srcRangeLength = srcRangeLength - (d3lLineIdx2 - d3lLineIdx); srcRangeLength = d3lLineIdx2 - d3lLineIdx; // current MergeLine controls fewer lines ml2.id3l = id3l; for(int i = 0; i < srcRangeLength; ++i) ++ml2.id3l; ml2.mergeEditLineList.clear(); // Search for best place to splice for(MergeEditLineList::iterator i = mergeEditLineList.begin(); i != mergeEditLineList.end(); ++i) { if(i->id3l() == ml2.id3l) { ml2.mergeEditLineList.splice(ml2.mergeEditLineList.begin(), mergeEditLineList, i, mergeEditLineList.end()); return; } } ml2.mergeEditLineList.push_back(MergeEditLine(ml2.id3l)); } void join(MergeLine& ml2) // The caller must remove the ml2 from the m_mergeLineList after this call { srcRangeLength += ml2.srcRangeLength; ml2.mergeEditLineList.clear(); mergeEditLineList.clear(); mergeEditLineList.push_back(MergeEditLine(id3l)); // Create a simple conflict if(ml2.bConflict) bConflict = true; if(!ml2.bWhiteSpaceConflict) bWhiteSpaceConflict = false; if(ml2.bDelta) bDelta = true; } }; typedef std::list MergeLineList; #endif diff --git a/src/MergeFileInfos.cpp b/src/MergeFileInfos.cpp index 27a7520..5fabf98 100644 --- a/src/MergeFileInfos.cpp +++ b/src/MergeFileInfos.cpp @@ -1,578 +1,574 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "MergeFileInfos.h" #include "DirectoryInfo.h" #include "directorymergewindow.h" #include "fileaccess.h" #include "progress.h" #include #include QSharedPointer MergeFileInfos::m_dirInfo; MergeFileInfos::MergeFileInfos() { m_bEqualAB = false; m_bEqualAC = false; m_bEqualBC = false; m_pParent = nullptr; m_bOperationComplete = false; m_bSimOpComplete = false; m_eMergeOperation = eNoOperation; m_eOpStatus = eOpStatusNone; m_ageA = eNotThere; m_ageB = eNotThere; m_ageC = eNotThere; m_bConflictingAges = false; m_pFileInfoA = nullptr; m_pFileInfoB = nullptr; m_pFileInfoC = nullptr; m_dirInfo.clear(); } MergeFileInfos::~MergeFileInfos() { m_children.clear(); } void MergeFileInfos::updateParents() { MergeFileInfos* current = parent(); while(current != nullptr) { bool bChange = false; if(!isEqualAB() && current->isEqualAB()) { current->m_bEqualAB = false; bChange = true; } if(!isEqualAC() && current->isEqualAC()) { current->m_bEqualAC = false; bChange = true; } if(!isEqualBC() && current->isEqualBC()) { current->m_bEqualBC = false; bChange = true; } if(bChange) current->updateAge(); else break; current = current->parent(); } } /* Check for directories or links marked as not equal and mark them equal. */ void MergeFileInfos::updateDirectoryOrLink() { bool bChange = false; if(!isEqualAB() && isDirA() == isDirB() && isLinkA() == isLinkB()) { m_bEqualAB = true; bChange = true; } if(!isEqualBC() && isDirC() == isDirB() && isLinkC() == isLinkB()) { m_bEqualBC = true; bChange = true; } if(!isEqualAC() && isDirA() == isDirC() && isLinkA() == isLinkC()) { m_bEqualAC = true; bChange = true; } if(bChange) updateAge(); } QString MergeFileInfos::subPath() const { if(m_pFileInfoA != nullptr && m_pFileInfoA->exists()) return m_pFileInfoA->fileRelPath(); else if(m_pFileInfoB != nullptr && m_pFileInfoB->exists()) return m_pFileInfoB->fileRelPath(); else if(m_pFileInfoC != nullptr && m_pFileInfoC->exists()) return m_pFileInfoC->fileRelPath(); return QString(""); } QString MergeFileInfos::fileName() const { if(m_pFileInfoA != nullptr && m_pFileInfoA->exists()) return m_pFileInfoA->fileName(); else if(m_pFileInfoB != nullptr && m_pFileInfoB->exists()) return m_pFileInfoB->fileName(); else if(m_pFileInfoC != nullptr && m_pFileInfoC->exists()) return m_pFileInfoC->fileName(); return QString(""); } bool MergeFileInfos::conflictingFileTypes() { if((m_pFileInfoA != nullptr && !m_pFileInfoA->isNormal()) || (m_pFileInfoB != nullptr && !m_pFileInfoB->isNormal()) || (m_pFileInfoC != nullptr && !m_pFileInfoC->isNormal())) return true; // Now check if file/dir-types fit. if(isLinkA() || isLinkB() || isLinkC()) { if((existsInA() && !isLinkA()) || (existsInB() && !isLinkB()) || (existsInC() && !isLinkC())) { return true; } } if(isDirA() || isDirB() || isDirC()) { if((existsInA() && !isDirA()) || (existsInB() && !isDirB()) || (existsInC() && !isDirC())) { return true; } } return false; } QString MergeFileInfos::fullNameA() const { if(existsInA()) return getFileInfoA()->absoluteFilePath(); return m_dirInfo->dirA().absoluteFilePath() + '/' + subPath(); } QString MergeFileInfos::fullNameB() const { if(existsInB()) return getFileInfoB()->absoluteFilePath(); return m_dirInfo->dirB().absoluteFilePath() + '/' + subPath(); } QString MergeFileInfos::fullNameC() const { if(existsInC()) return getFileInfoC()->absoluteFilePath(); return m_dirInfo->dirC().absoluteFilePath() + '/' + subPath(); } void MergeFileInfos::sort(Qt::SortOrder order) { std::sort(m_children.begin(), m_children.end(), MfiCompare(order)); for(int i = 0; i < m_children.count(); ++i) m_children[i]->sort(order); } QString MergeFileInfos::fullNameDest() const { if(m_dirInfo->destDir().prettyAbsPath() == m_dirInfo->dirC().prettyAbsPath()) return fullNameC(); else if(m_dirInfo->destDir().prettyAbsPath() == m_dirInfo->dirB().prettyAbsPath()) return fullNameB(); else return m_dirInfo->destDir().absoluteFilePath() + '/' + subPath(); } bool MergeFileInfos::compareFilesAndCalcAges(QStringList& errors, QSharedPointer const pOptions, DirectoryMergeWindow* pDMW) { std::map dateMap; if(existsInA()) { dateMap[getFileInfoA()->lastModified()] = 0; } if(existsInB()) { dateMap[getFileInfoB()->lastModified()] = 1; } if(existsInC()) { dateMap[getFileInfoC()->lastModified()] = 2; } if(pOptions->m_bDmFullAnalysis) { if((existsInA() && isDirA()) || (existsInB() && isDirB()) || (existsInC() && isDirC())) { // If any input is a directory, don't start any comparison. m_bEqualAB = existsInA() && existsInB(); m_bEqualAC = existsInA() && existsInC(); m_bEqualBC = existsInB() && existsInC(); } else { Q_EMIT pDMW->startDiffMerge( existsInA() ? getFileInfoA()->absoluteFilePath() : QString(""), existsInB() ? getFileInfoB()->absoluteFilePath() : QString(""), existsInC() ? getFileInfoC()->absoluteFilePath() : QString(""), "", "", "", "", &diffStatus()); int nofNonwhiteConflicts = diffStatus().getNonWhitespaceConflicts(); if(pOptions->m_bDmWhiteSpaceEqual && nofNonwhiteConflicts == 0) { m_bEqualAB = existsInA() && existsInB(); m_bEqualAC = existsInA() && existsInC(); m_bEqualBC = existsInB() && existsInC(); } else { m_bEqualAB = diffStatus().isBinaryEqualAB(); m_bEqualBC = diffStatus().isBinaryEqualBC(); m_bEqualAC = diffStatus().isBinaryEqualAC(); } } } else { bool bError = false; QString eqStatus; if(existsInA() && existsInB()) { if(isDirA()) m_bEqualAB = true; else m_bEqualAB = fastFileComparison(*getFileInfoA(), *getFileInfoB(), bError, eqStatus, pOptions); } if(existsInA() && existsInC()) { if(isDirA()) m_bEqualAC = true; else m_bEqualAC = fastFileComparison(*getFileInfoA(), *getFileInfoC(), bError, eqStatus, pOptions); } if(existsInB() && existsInC()) { if(m_bEqualAB && m_bEqualAC) m_bEqualBC = true; else { if(isDirB()) m_bEqualBC = true; else m_bEqualBC = fastFileComparison(*getFileInfoB(), *getFileInfoC(), bError, eqStatus, pOptions); } } if(bError) { //Limit size of error list in memmory. if(errors.size() < 30) errors.append(eqStatus); return false; } } if(isLinkA() != isLinkB()) m_bEqualAB = false; if(isLinkA() != isLinkC()) m_bEqualAC = false; if(isLinkB() != isLinkC()) m_bEqualBC = false; if(isDirA() != isDirB()) m_bEqualAB = false; if(isDirA() != isDirC()) m_bEqualAC = false; if(isDirB() != isDirC()) m_bEqualBC = false; Q_ASSERT(eNew == 0 && eMiddle == 1 && eOld == 2); // The map automatically sorts the keys. int age = eNew; std::map::reverse_iterator i; for(i = dateMap.rbegin(); i != dateMap.rend(); ++i) { int n = i->second; if(n == 0 && getAgeA() == eNotThere) { setAgeA((e_Age)age); ++age; if(m_bEqualAB) { setAgeB(getAgeA()); ++age; } if(m_bEqualAC) { setAgeC(getAgeA()); ++age; } } else if(n == 1 && getAgeB() == eNotThere) { setAgeB((e_Age)age); ++age; if(m_bEqualAB) { setAgeA(getAgeB()); ++age; } if(m_bEqualBC) { setAgeC(getAgeB()); ++age; } } else if(n == 2 && getAgeC() == eNotThere) { setAgeC((e_Age)age); ++age; if(m_bEqualAC) { setAgeA(getAgeC()); ++age; } if(m_bEqualBC) { setAgeB(getAgeC()); ++age; } } } // The checks below are necessary when the dates of the file are equal but the // files are not. One wouldn't expect this to happen, yet it happens sometimes. if(existsInC() && getAgeC() == eNotThere) { setAgeC((e_Age)age); ++age; m_bConflictingAges = true; } if(existsInB() && getAgeB() == eNotThere) { setAgeB((e_Age)age); ++age; m_bConflictingAges = true; } if(existsInA() && getAgeA() == eNotThere) { setAgeA((e_Age)age); ++age; m_bConflictingAges = true; } if(getAgeA() != eOld && getAgeB() != eOld && getAgeC() != eOld) { if(getAgeA() == eMiddle) setAgeA(eOld); if(getAgeB() == eMiddle) setAgeB(eOld); if(getAgeC() == eMiddle) setAgeC(eOld); } return true; } bool MergeFileInfos::fastFileComparison( FileAccess& fi1, FileAccess& fi2, bool& bError, QString& status, QSharedPointer const pOptions) { ProgressProxy pp; bool bEqual = false; status = ""; bError = true; if(fi1.isNormal() != fi2.isNormal()) { status = i18n("Unable to compare non-normal file with normal file."); return false; } if(!fi1.isNormal()) { bError = false; return false; } if(!pOptions->m_bDmFollowFileLinks) { if(fi1.isSymLink() != fi2.isSymLink()) { status = i18n("Mix of links and normal files."); return bEqual; } else if(fi1.isSymLink() && fi2.isSymLink()) { bError = false; bEqual = fi1.readLink() == fi2.readLink(); status = i18n("Link: "); return bEqual; } } if(fi1.size() != fi2.size()) { bError = false; bEqual = false; status = i18n("Size. "); return bEqual; } else if(pOptions->m_bDmTrustSize) { bEqual = true; bError = false; return bEqual; } if(pOptions->m_bDmTrustDate) { bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); bError = false; status = i18n("Date & Size: "); return bEqual; } if(pOptions->m_bDmTrustDateFallbackToBinary) { bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); if(bEqual) { bError = false; status = i18n("Date & Size: "); return bEqual; } } std::vector buf1(100000); std::vector buf2(buf1.size()); if(!fi1.open(QIODevice::ReadOnly)) { status = fi1.errorString(); return bEqual; } if(!fi2.open(QIODevice::ReadOnly)) { fi1.close(); status = fi2.errorString(); return bEqual; } pp.setInformation(i18n("Comparing file..."), 0, false); typedef qint64 t_FileSize; t_FileSize fullSize = fi1.size(); t_FileSize sizeLeft = fullSize; pp.setMaxNofSteps(fullSize / buf1.size()); while(sizeLeft > 0 && !pp.wasCancelled()) { qint64 len = std::min(sizeLeft, (t_FileSize)buf1.size()); if(len != fi1.read(&buf1[0], len)) { status = fi1.errorString(); fi1.close(); fi2.close(); return bEqual; } if(len != fi2.read(&buf2[0], len)) { status = fi2.errorString(); fi1.close(); fi2.close(); return bEqual; } if(memcmp(&buf1[0], &buf2[0], len) != 0) { bError = false; fi1.close(); fi2.close(); return bEqual; } sizeLeft -= len; //pp.setCurrent(double(fullSize-sizeLeft)/fullSize, false ); pp.step(); } fi1.close(); fi2.close(); // If the program really arrives here, then the files are really equal. bError = false; bEqual = true; return bEqual; } void MergeFileInfos::updateAge() { if(isDirA() || isDirB() || isDirC()) { setAgeA(eNotThere); setAgeB(eNotThere); setAgeC(eNotThere); e_Age age = eNew; if(existsInC()) { setAgeC(age); if(m_bEqualAC) setAgeA(age); if(m_bEqualBC) setAgeB(age); age = eMiddle; } if(existsInB() && getAgeB() == eNotThere) { setAgeB(age); if(m_bEqualAB) setAgeA(age); age = eOld; } if(existsInA() && getAgeA() == eNotThere) { setAgeA(age); } if(getAgeA() != eOld && getAgeB() != eOld && getAgeC() != eOld) { if(getAgeA() == eMiddle) setAgeA(eOld); if(getAgeB() == eMiddle) setAgeB(eOld); if(getAgeC() == eMiddle) setAgeC(eOld); } } } QTextStream& operator<<(QTextStream& ts, MergeFileInfos& mfi) { ts << "{\n"; ValueMap vm; vm.writeEntry("SubPath", mfi.subPath()); vm.writeEntry("ExistsInA", mfi.existsInA()); vm.writeEntry("ExistsInB", mfi.existsInB()); vm.writeEntry("ExistsInC", mfi.existsInC()); vm.writeEntry("EqualAB", mfi.isEqualAB()); vm.writeEntry("EqualAC", mfi.isEqualAC()); vm.writeEntry("EqualBC", mfi.isEqualBC()); vm.writeEntry("MergeOperation", (int)mfi.getOperation()); vm.writeEntry("DirA", mfi.isDirA()); vm.writeEntry("DirB", mfi.isDirB()); vm.writeEntry("DirC", mfi.isDirC()); vm.writeEntry("LinkA", mfi.isLinkA()); vm.writeEntry("LinkB", mfi.isLinkB()); vm.writeEntry("LinkC", mfi.isLinkC()); vm.writeEntry("OperationComplete", !mfi.isOperationRunning()); vm.writeEntry("AgeA", (int)mfi.getAgeA()); vm.writeEntry("AgeB", (int)mfi.getAgeB()); vm.writeEntry("AgeC", (int)mfi.getAgeC()); vm.writeEntry("ConflictingAges", mfi.conflictingAges()); // Equal age but files are not! vm.save(ts); ts << "}\n"; return ts; } diff --git a/src/MergeFileInfos.h b/src/MergeFileInfos.h index 5736cab..4e57868 100644 --- a/src/MergeFileInfos.h +++ b/src/MergeFileInfos.h @@ -1,232 +1,228 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef MERGEFILEINFO_H #define MERGEFILEINFO_H #include "DirectoryInfo.h" #include "diff.h" #include "fileaccess.h" #include //class DirectoryInfo; enum e_MergeOperation { eTitleId, eNoOperation, // Operations in sync mode (with only two directories): eCopyAToB, eCopyBToA, eDeleteA, eDeleteB, eDeleteAB, eMergeToA, eMergeToB, eMergeToAB, // Operations in merge mode (with two or three directories) eCopyAToDest, eCopyBToDest, eCopyCToDest, eDeleteFromDest, eMergeABCToDest, eMergeABToDest, eConflictingFileTypes, // Error eChangedAndDeleted, // Error eConflictingAges // Equal age but files are not! }; enum e_Age { eNew, eMiddle, eOld, eNotThere, eAgeEnd }; enum e_OperationStatus { eOpStatusNone, eOpStatusDone, eOpStatusError, eOpStatusSkipped, eOpStatusNotSaved, eOpStatusInProgress, eOpStatusToDo }; class DirectoryMergeWindow; class MergeFileInfos { public: MergeFileInfos(); ~MergeFileInfos(); QString subPath() const; QString fileName() const; bool isDirA() const { return m_pFileInfoA != nullptr ? m_pFileInfoA->isDir() : false; } bool isDirB() const { return m_pFileInfoB != nullptr ? m_pFileInfoB->isDir() : false; } bool isDirC() const { return m_pFileInfoC != nullptr ? m_pFileInfoC->isDir() : false; } bool hasDir() const { return isDirA() || isDirB() || isDirC(); } bool isLinkA() const { return m_pFileInfoA != nullptr ? m_pFileInfoA->isSymLink() : false; } bool isLinkB() const { return m_pFileInfoB != nullptr ? m_pFileInfoB->isSymLink() : false; } bool isLinkC() const { return m_pFileInfoC != nullptr ? m_pFileInfoC->isSymLink() : false; } bool hasLink() const { return isLinkA() || isLinkB() || isLinkC(); } bool existsInA() const { return m_pFileInfoA != nullptr; } bool existsInB() const { return m_pFileInfoB != nullptr; } bool existsInC() const { return m_pFileInfoC != nullptr; } bool conflictingFileTypes(); void sort(Qt::SortOrder order); inline MergeFileInfos* parent() const { return m_pParent; } inline void setParent(MergeFileInfos* inParent) { m_pParent = inParent; } inline const QList& children() const { return m_children; } inline void addChild(MergeFileInfos* child) { m_children.push_back(child); } inline void clear() { m_children.clear(); } FileAccess* getFileInfoA() const { return m_pFileInfoA; } FileAccess* getFileInfoB() const { return m_pFileInfoB; } FileAccess* getFileInfoC() const { return m_pFileInfoC; } void setFileInfoA(FileAccess* newInfo) { m_pFileInfoA = newInfo; } void setFileInfoB(FileAccess* newInfo) { m_pFileInfoB = newInfo; } void setFileInfoC(FileAccess* newInfo) { m_pFileInfoC = newInfo; } QString fullNameA() const; QString fullNameB() const; QString fullNameC() const; QString fullNameDest() const; inline QSharedPointer getDirectoryInfo() const { return m_dirInfo; } void setDirectoryInfo(const QSharedPointer& dirInfo) { m_dirInfo = dirInfo; } inline QString getDirNameA() const { return getDirectoryInfo()->dirA().prettyAbsPath(); } inline QString getDirNameB() const { return getDirectoryInfo()->dirB().prettyAbsPath(); } inline QString getDirNameC() const { return getDirectoryInfo()->dirC().prettyAbsPath(); } inline QString getDirNameDest() const { return getDirectoryInfo()->destDir().prettyAbsPath(); } inline TotalDiffStatus& diffStatus() { return m_totalDiffStatus; } inline e_MergeOperation getOperation() const { return m_eMergeOperation; } inline void setOperation(const e_MergeOperation op) { m_eMergeOperation = op; } inline e_OperationStatus getOpStatus() const { return m_eOpStatus; } inline void setOpStatus(const e_OperationStatus eOpStatus) { m_eOpStatus = eOpStatus; } inline e_Age getAgeA() const { return m_ageA; } inline e_Age getAgeB() const { return m_ageB; } inline e_Age getAgeC() const { return m_ageC; } inline bool isEqualAB() const { return m_bEqualAB; } inline bool isEqualAC() const { return m_bEqualAC; } inline bool isEqualBC() const { return m_bEqualBC; } bool compareFilesAndCalcAges(QStringList& errors, QSharedPointer const pOptions, DirectoryMergeWindow* pDMW); void updateAge(); void updateParents(); void updateDirectoryOrLink(); inline void startSimOp() { m_bSimOpComplete = false; } inline bool isSimOpRunning() const { return !m_bOperationComplete; } inline void endSimOp() { m_bSimOpComplete = true; } inline void startOperation() { m_bOperationComplete = false; }; inline bool isOperationRunning() const { return !m_bOperationComplete; } inline void endOperation() { m_bOperationComplete = true; }; inline bool isThreeWay() const { if(getDirectoryInfo() == nullptr) return false; return getDirectoryInfo()->dirC().isValid(); } inline bool existsEveryWhere() const { return existsInA() && existsInB() && (existsInC() || !isThreeWay()); } inline int existsCount() const { return (existsInA() ? 1 : 0) + (existsInB() ? 1 : 0) + (existsInC() ? 1 : 0); } inline bool onlyInA() const { return existsInA() && !existsInB() && !existsInC(); } inline bool onlyInB() const { return !existsInA() && existsInB() && !existsInC(); } inline bool onlyInC() const { return !existsInA() && !existsInB() && existsInC(); } bool conflictingAges() const { return m_bConflictingAges; } private: bool fastFileComparison(FileAccess& fi1, FileAccess& fi2, bool& bError, QString& status, QSharedPointer const pOptions); inline void setAgeA(const e_Age inAge) { m_ageA = inAge; } inline void setAgeB(const e_Age inAge) { m_ageB = inAge; } inline void setAgeC(const e_Age inAge) { m_ageC = inAge; } MergeFileInfos* m_pParent; QList m_children; FileAccess* m_pFileInfoA; FileAccess* m_pFileInfoB; FileAccess* m_pFileInfoC; static QSharedPointer m_dirInfo; TotalDiffStatus m_totalDiffStatus; e_MergeOperation m_eMergeOperation; e_OperationStatus m_eOpStatus; e_Age m_ageA; e_Age m_ageB; e_Age m_ageC; bool m_bOperationComplete; bool m_bSimOpComplete; bool m_bEqualAB; bool m_bEqualAC; bool m_bEqualBC; bool m_bConflictingAges; // Equal age but files are not! }; QTextStream& operator<<(QTextStream& ts, MergeFileInfos& mfi); class MfiCompare { Qt::SortOrder mOrder; public: explicit MfiCompare(Qt::SortOrder order) { mOrder = order; } bool operator()(MergeFileInfos* pMFI1, MergeFileInfos* pMFI2) { bool bDir1 = pMFI1->isDirA() || pMFI1->isDirB() || pMFI1->isDirC(); bool bDir2 = pMFI2->isDirA() || pMFI2->isDirB() || pMFI2->isDirC(); if(bDir1 == bDir2) { if(mOrder == Qt::AscendingOrder) { return pMFI1->fileName().compare(pMFI2->fileName(), Qt::CaseInsensitive) < 0; } else { return pMFI1->fileName().compare(pMFI2->fileName(), Qt::CaseInsensitive) > 0; } } else return bDir1; } }; #endif // !MERGEFILEINFO_H diff --git a/src/OptionItems.h b/src/OptionItems.h index 54f4357..819fa31 100644 --- a/src/OptionItems.h +++ b/src/OptionItems.h @@ -1,190 +1,178 @@ -/** - * Copyright (C) 2002-2009 Joachim Eibl, joachim.eibl at gmx.de - * Copyright (C) 2018 Michael Reeves - * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef OPTIONITEMS_H #define OPTIONITEMS_H #include "common.h" #include #include #include #include #include #include #include class OptionItemBase { public: explicit OptionItemBase(const QString& saveName) { m_saveName = saveName; m_bPreserved = false; } virtual ~OptionItemBase(){}; virtual void setToDefault() = 0; virtual void setToCurrent() = 0; virtual void apply() = 0; virtual void write(ValueMap*) const = 0; virtual void read(ValueMap*) = 0; void doPreserve() { if(!m_bPreserved) { m_bPreserved = true; preserve(); } } void doUnpreserve() { if(m_bPreserved) { unpreserve(); } } QString getSaveName() const { return m_saveName; } protected: virtual void preserve() = 0; virtual void unpreserve() = 0; bool m_bPreserved; QString m_saveName; Q_DISABLE_COPY(OptionItemBase) }; template class Option : public OptionItemBase { public: explicit Option(const QString& saveName) : OptionItemBase(saveName) { } explicit Option(T* pVar, const QString& saveName):OptionItemBase(saveName) { m_pVar = pVar; } explicit Option(const T& defaultVal, const QString& saveName, T* pVar) : Option(pVar, defaultVal, saveName) { } explicit Option(T* pVar, const T& defaultValue, const QString& saveName) : OptionItemBase(saveName) { m_pVar = pVar; m_defaultVal = defaultValue; } void setToDefault() override {}; void setToCurrent() override {}; const T& getDefault() const { return m_defaultVal; }; const T getCurrent() const { return *m_pVar; }; virtual void setCurrent(const T inValue) { *m_pVar = inValue; } void apply() override {}; virtual void apply(const T& inValue) { *m_pVar = inValue; } void write(ValueMap* config) const override { config->writeEntry(m_saveName, *m_pVar); } void read(ValueMap* config) override { *m_pVar = config->readEntry(m_saveName, m_defaultVal); } protected: void preserve() override { m_preservedVal = *m_pVar; } void unpreserve() override { *m_pVar = m_preservedVal; } T* m_pVar; T m_preservedVal; T m_defaultVal; private: Q_DISABLE_COPY(Option) }; template class OptionNum : public Option { public: using Option::Option; explicit OptionNum(T* pVar, const QString& saveName) : Option(pVar, saveName) { } explicit OptionNum(T* pVar, const T& defaultValue, const QString& saveName) : Option(pVar, defaultValue, saveName) { } void setCurrent(const T inValue) override { Option::setCurrent(inValue); } static const QString toString(const T inValue) { //QString::setNum does not use locale formatting instead it always use QLocale::C. return QLocale().toString(inValue); } const QString getString() const { //QString::setNum does not use locale formatting instead it always use QLocale::C. return QLocale().toString(Option::getCurrent()); } private: Q_DISABLE_COPY(OptionNum) }; typedef Option OptionToggleAction; typedef OptionNum OptionInt; typedef Option OptionPoint; typedef Option OptionSize; typedef Option OptionStringList; typedef Option OptionBool; typedef Option OptionFont; typedef Option OptionColor; typedef Option OptionString; class OptionCodec : public OptionString { public: using OptionString::Option; void setCurrent(const QString name) override { OptionString::setCurrent(name); }; void setCurrent(const QByteArray& name) { OptionString::setCurrent(QString::fromLatin1(name)); } const QString& defaultName() const { return mDefaultName; } void saveDefaultIndex(const int i) { defaultIndex = i; }; int getDefaultIndex() const { return defaultIndex; } private: const QString mDefaultName = QLatin1String(QTextCodec::codecForLocale()->name()); int defaultIndex = 0; Q_DISABLE_COPY(OptionCodec) }; #endif // !OPTIONITEMS_H diff --git a/src/Options.cpp b/src/Options.cpp index df2f8f6..d05ab39 100644 --- a/src/Options.cpp +++ b/src/Options.cpp @@ -1,146 +1,134 @@ -/** - * Copyright (C) 2019 Michael Reeves +/* + * KDiff3 - Text Diff And Merge Tool * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2019-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "options.h" #include "ConfigValueMap.h" #include "OptionItems.h" #include #define KDIFF3_CONFIG_GROUP "KDiff3 Options" void Options::init() { /* TODO manage toolbar positioning */ addOptionItem(new OptionNum( Qt::TopToolBarArea, "ToolBarPos", (int*)&m_toolBarPos)); addOptionItem(new OptionSize(QSize(600, 400), "Geometry", &m_geometry)); addOptionItem(new OptionPoint(QPoint(0, 22), "Position", &m_position)); addOptionItem(new OptionToggleAction(false, "WindowStateMaximised", &m_bMaximised)); addOptionItem(new OptionToggleAction(true, "Show Toolbar", &m_bShowToolBar)); addOptionItem(new OptionToggleAction(true, "Show Statusbar", &m_bShowStatusBar)); } void Options::apply() { for(OptionItemBase* item : mOptionItemList) { item->apply(); } } void Options::resetToDefaults() { for(OptionItemBase* item : mOptionItemList) { item->setToDefault(); } } void Options::setToCurrent() { for(OptionItemBase* item : mOptionItemList) { item->setToCurrent(); } } void Options::saveOptions(const KSharedConfigPtr config) { // No i18n()-Translations here! ConfigValueMap cvm(config->group(KDIFF3_CONFIG_GROUP)); for(OptionItemBase* item : mOptionItemList) { item->doUnpreserve(); item->write(&cvm); } } void Options::readOptions(const KSharedConfigPtr config) { // No i18n()-Translations here! ConfigValueMap cvm(config->group(KDIFF3_CONFIG_GROUP)); for(OptionItemBase* item : mOptionItemList) { item->read(&cvm); } } const QString Options::parseOptions(const QStringList& optionList) { QString result; for(const QString &optionString : optionList) { int pos = optionString.indexOf('='); if(pos > 0) // seems not to have a tag { const QString key = optionString.left(pos); const QString val = optionString.mid(pos + 1); bool bFound = false; for(OptionItemBase* item : mOptionItemList) { if(item->getSaveName() == key) { item->doPreserve(); ValueMap config; config.writeEntry(key, val); // Write the value as a string and item->read(&config); // use the internal conversion from string to the needed value. bFound = true; break; } } if(!bFound) { result += "No config item named \"" + key + "\"\n"; } } else { result += "No '=' found in \"" + optionString + "\"\n"; } } return result; } QString Options::calcOptionHelp() { ValueMap config; for(OptionItemBase* item : mOptionItemList) { item->write(&config); } return config.getAsString(); } void Options::addOptionItem(OptionItemBase* inItem) { mOptionItemList.push_back(inItem); } diff --git a/src/Overview.cpp b/src/Overview.cpp index c00910a..6b8b863 100644 --- a/src/Overview.cpp +++ b/src/Overview.cpp @@ -1,306 +1,303 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "Overview.h" #include "diff.h" #include "options.h" #include // for max #include #include #include #include #include Overview::Overview(const QSharedPointer &pOptions) //: QWidget( pParent, 0, Qt::WNoAutoErase ) { m_pDiff3LineList = nullptr; m_pOptions = pOptions; m_bTripleDiff = false; m_eOverviewMode = eOMNormal; m_nofLines = 1; setUpdatesEnabled(false); m_firstLine = 0; m_pageHeight = 0; setFixedWidth(20); } void Overview::init(Diff3LineList* pDiff3LineList, bool bTripleDiff) { m_pDiff3LineList = pDiff3LineList; m_bTripleDiff = bTripleDiff; m_pixmap = QPixmap(QSize(0, 0)); // make sure that a redraw happens update(); } void Overview::reset() { m_pDiff3LineList = nullptr; } void Overview::slotRedraw() { m_pixmap = QPixmap(QSize(0, 0)); // make sure that a redraw happens update(); } void Overview::setRange(QtNumberType firstLine, QtNumberType pageHeight) { m_firstLine = firstLine; m_pageHeight = pageHeight; update(); } void Overview::setFirstLine(QtNumberType firstLine) { m_firstLine = firstLine; update(); } void Overview::setOverviewMode(e_OverviewMode eOverviewMode) { m_eOverviewMode = eOverviewMode; slotRedraw(); } Overview::e_OverviewMode Overview::getOverviewMode() { return m_eOverviewMode; } void Overview::mousePressEvent(QMouseEvent* e) { int h = height() - 1; int h1 = h * m_pageHeight / std::max(1, m_nofLines) + 3; if(h > 0) Q_EMIT setLine((e->y() - h1 / 2) * m_nofLines / h); } void Overview::mouseMoveEvent(QMouseEvent* e) { mousePressEvent(e); } void Overview::setPaintingAllowed(bool bAllowPainting) { if(updatesEnabled() != bAllowPainting) { setUpdatesEnabled(bAllowPainting); if(bAllowPainting) update(); else reset(); } } void Overview::drawColumn(QPainter& p, e_OverviewMode eOverviewMode, int x, int w, int h, int nofLines) { p.setPen(Qt::black); p.drawLine(x, 0, x, h); if(nofLines == 0) return; int line = 0; int oldY = 0; int oldConflictY = -1; int wrapLineIdx = 0; Diff3LineList::const_iterator i; for(i = m_pDiff3LineList->begin(); i != m_pDiff3LineList->end();) { const Diff3Line& d3l = *i; int y = h * (line + 1) / nofLines; e_MergeDetails md; bool bConflict; bool bLineRemoved; e_SrcSelector src; d3l.mergeOneLine(md, bConflict, bLineRemoved, src, !m_bTripleDiff); QColor c = m_pOptions->m_bgColor; bool bWhiteSpaceChange = false; //if( bConflict ) c=m_pOptions->m_colorForConflict; //else if(eOverviewMode == eOMNormal) { switch(md) { case eDefault: case eNoChange: c = m_pOptions->m_bgColor; break; case eBAdded: case eBDeleted: case eBChanged: c = bConflict ? m_pOptions->m_colorForConflict : m_pOptions->m_colorB; bWhiteSpaceChange = d3l.isEqualAB() || (d3l.isWhiteLine(A) && d3l.isWhiteLine(B)); break; case eCAdded: case eCDeleted: case eCChanged: bWhiteSpaceChange = d3l.isEqualAC() || (d3l.isWhiteLine(A) && d3l.isWhiteLine(C)); c = bConflict ? m_pOptions->m_colorForConflict : m_pOptions->m_colorC; break; case eBCChanged: // conflict case eBCChangedAndEqual: // possible conflict case eBCDeleted: // possible conflict case eBChanged_CDeleted: // conflict case eCChanged_BDeleted: // conflict case eBCAdded: // conflict case eBCAddedAndEqual: // possible conflict c = m_pOptions->m_colorForConflict; break; default: Q_ASSERT(true); break; } } else if(eOverviewMode == eOMAvsB) { switch(md) { case eDefault: case eNoChange: case eCAdded: case eCDeleted: case eCChanged: break; default: c = m_pOptions->m_colorForConflict; bWhiteSpaceChange = d3l.isEqualAB() || (d3l.isWhiteLine(A) && d3l.isWhiteLine(B)); break; } } else if(eOverviewMode == eOMAvsC) { switch(md) { case eDefault: case eNoChange: case eBAdded: case eBDeleted: case eBChanged: break; default: c = m_pOptions->m_colorForConflict; bWhiteSpaceChange = d3l.isEqualAC() || (d3l.isWhiteLine(A) && d3l.isWhiteLine(C)); break; } } else if(eOverviewMode == eOMBvsC) { switch(md) { case eDefault: case eNoChange: case eBCChangedAndEqual: case eBCDeleted: case eBCAddedAndEqual: break; default: c = m_pOptions->m_colorForConflict; bWhiteSpaceChange = d3l.isEqualBC() || (d3l.isWhiteLine(B) && d3l.isWhiteLine(C)); break; } } int x2 = x; int w2 = w; if(!m_bTripleDiff) { if(!d3l.getLineA().isValid() && d3l.getLineB().isValid()) { c = m_pOptions->m_colorA; x2 = w / 2; w2 = x2; } if(d3l.getLineA().isValid() && !d3l.getLineB().isValid()) { c = m_pOptions->m_colorB; w2 = w / 2; } } if(!bWhiteSpaceChange || m_pOptions->m_bShowWhiteSpace) { // Make sure that lines with conflict are not overwritten. if(c == m_pOptions->m_colorForConflict) { p.fillRect(x2 + 1, oldY, w2, std::max(1, y - oldY), bWhiteSpaceChange ? QBrush(c, Qt::Dense4Pattern) : QBrush(c)); oldConflictY = oldY; } else if(c != m_pOptions->m_bgColor && oldY > oldConflictY) { p.fillRect(x2 + 1, oldY, w2, std::max(1, y - oldY), bWhiteSpaceChange ? QBrush(c, Qt::Dense4Pattern) : QBrush(c)); } } oldY = y; ++line; if(m_pOptions->m_bWordWrap) { ++wrapLineIdx; if(wrapLineIdx >= d3l.linesNeededForDisplay()) { wrapLineIdx = 0; ++i; } } else { ++i; } } } void Overview::paintEvent(QPaintEvent*) { if(m_pDiff3LineList == nullptr) return; int h = height() - 1; int w = width(); if(m_pixmap.size() != size()) { m_nofLines = m_pDiff3LineList->numberOfLines(m_pOptions->m_bWordWrap); m_pixmap = QPixmap(size()); QPainter p(&m_pixmap); p.fillRect(rect(), m_pOptions->m_bgColor); if(!m_bTripleDiff || m_eOverviewMode == eOMNormal) { drawColumn(p, eOMNormal, 0, w, h, m_nofLines); } else { drawColumn(p, eOMNormal, 0, w / 2, h, m_nofLines); drawColumn(p, m_eOverviewMode, w / 2, w / 2, h, m_nofLines); } } QPainter painter(this); painter.drawPixmap(0, 0, m_pixmap); int y1=0, h1=0; if(m_nofLines > 0) { y1 = h * m_firstLine / m_nofLines - 1; h1 = h * m_pageHeight / m_nofLines + 3; } painter.setPen(Qt::black); painter.drawRect(1, y1, w - 1, h1); } diff --git a/src/Overview.h b/src/Overview.h index bae508d..972d97e 100644 --- a/src/Overview.h +++ b/src/Overview.h @@ -1,68 +1,65 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef OVERVIEW_H #define OVERVIEW_H #include "LineRef.h" // for LineRef #include "TypeUtils.h" // for QtNumberType #include // for QString #include #include class Diff3LineList; class Options; class Overview : public QWidget { Q_OBJECT public: enum e_OverviewMode { eOMNormal, eOMAvsB, eOMAvsC, eOMBvsC }; explicit Overview(const QSharedPointer &pOptions); void init(Diff3LineList* pDiff3LineList, bool bTripleDiff); void reset(); void setRange(QtNumberType firstLine, QtNumberType pageHeight); void setPaintingAllowed(bool bAllowPainting); void setOverviewMode(e_OverviewMode eOverviewMode); e_OverviewMode getOverviewMode(); public Q_SLOTS: void setFirstLine(QtNumberType firstLine); void slotRedraw(); Q_SIGNALS: void setLine(LineRef); private: const Diff3LineList* m_pDiff3LineList; QSharedPointer m_pOptions; bool m_bTripleDiff; LineRef m_firstLine; int m_pageHeight; QPixmap m_pixmap; e_OverviewMode m_eOverviewMode; int m_nofLines; void paintEvent(QPaintEvent* e) override; void mousePressEvent(QMouseEvent* e) override; void mouseMoveEvent(QMouseEvent* e) override; void drawColumn(QPainter& p, e_OverviewMode eOverviewMode, int x, int w, int h, int nofLines); }; #endif // !OVERVIEW_H diff --git a/src/PixMapUtils.cpp b/src/PixMapUtils.cpp index 14f8027..e4ba3b2 100644 --- a/src/PixMapUtils.cpp +++ b/src/PixMapUtils.cpp @@ -1,170 +1,158 @@ -/** - * Copyright (C) 2003-2007 by Joachim Eibl - * Copyright (C) 2018 Michael Reeves - * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include #include #include #include "MergeFileInfos.h" namespace PixMapUtils { namespace{ QPixmap* s_pm_dir = nullptr; QPixmap* s_pm_file = nullptr; QPixmap* pmNotThere; QPixmap* pmNew = nullptr; QPixmap* pmOld; QPixmap* pmMiddle; QPixmap* pmLink; QPixmap* pmDirLink; QPixmap* pmFileLink; QPixmap* pmNewLink; QPixmap* pmOldLink; QPixmap* pmMiddleLink; QPixmap* pmNewDir; QPixmap* pmMiddleDir; QPixmap* pmOldDir; QPixmap* pmNewDirLink; QPixmap* pmMiddleDirLink; QPixmap* pmOldDirLink; } QPixmap colorToPixmap(const QColor &inColor) { QPixmap pm(16, 16); QPainter p(&pm); p.setPen(Qt::black); p.setBrush(inColor); p.drawRect(0, 0, pm.width(), pm.height()); return pm; } // Copy pm2 onto pm1, but preserve the alpha value from pm1 where pm2 is transparent. QPixmap pixCombiner(const QPixmap* pm1, const QPixmap* pm2) { QImage img1 = pm1->toImage().convertToFormat(QImage::Format_ARGB32); QImage img2 = pm2->toImage().convertToFormat(QImage::Format_ARGB32); for(int y = 0; y < img1.height(); y++) { quint32* line1 = reinterpret_cast(img1.scanLine(y)); quint32* line2 = reinterpret_cast(img2.scanLine(y)); for(int x = 0; x < img1.width(); x++) { if(qAlpha(line2[x]) > 0) line1[x] = (line2[x] | 0xff000000); } } return QPixmap::fromImage(img1); } // like pixCombiner but let the pm1 color shine through QPixmap pixCombiner2(const QPixmap* pm1, const QPixmap* pm2) { QPixmap pix = *pm1; QPainter p(&pix); p.setOpacity(0.5); p.drawPixmap(0, 0, *pm2); p.end(); return pix; } void initPixmaps(const QColor& newest, const QColor& oldest, const QColor& middle, const QColor& notThere) { if(s_pm_dir == nullptr || s_pm_file == nullptr) { #include "xpm/file.xpm" #include "xpm/folder.xpm" s_pm_dir = new QPixmap(KIconLoader::global()->loadIcon("folder", KIconLoader::Small)); if(s_pm_dir->size() != QSize(16, 16)) { delete s_pm_dir; s_pm_dir = new QPixmap(folder_pm); } s_pm_file = new QPixmap(file_pm); } if(pmNew == nullptr) { #include "xpm/link_arrow.xpm" pmNotThere = new QPixmap; pmNew = new QPixmap; pmOld = new QPixmap; pmMiddle = new QPixmap; pmLink = new QPixmap(link_arrow); pmDirLink = new QPixmap; pmFileLink = new QPixmap; pmNewLink = new QPixmap; pmOldLink = new QPixmap; pmMiddleLink = new QPixmap; pmNewDir = new QPixmap; pmMiddleDir = new QPixmap; pmOldDir = new QPixmap; pmNewDirLink = new QPixmap; pmMiddleDirLink = new QPixmap; pmOldDirLink = new QPixmap; } *pmNotThere = colorToPixmap(notThere); *pmNew = colorToPixmap(newest); *pmOld = colorToPixmap(oldest); *pmMiddle = colorToPixmap(middle); *pmDirLink = pixCombiner(s_pm_dir, pmLink); *pmFileLink = pixCombiner(s_pm_file, pmLink); *pmNewLink = pixCombiner(pmNew, pmLink); *pmOldLink = pixCombiner(pmOld, pmLink); *pmMiddleLink = pixCombiner(pmMiddle, pmLink); *pmNewDir = pixCombiner2(pmNew, s_pm_dir); *pmMiddleDir = pixCombiner2(pmMiddle, s_pm_dir); *pmOldDir = pixCombiner2(pmOld, s_pm_dir); *pmNewDirLink = pixCombiner(pmNewDir, pmLink); *pmMiddleDirLink = pixCombiner(pmMiddleDir, pmLink); *pmOldDirLink = pixCombiner(pmOldDir, pmLink); } QPixmap getOnePixmap(e_Age eAge, bool bLink, bool bDir) { QPixmap* ageToPm[] = {pmNew, pmMiddle, pmOld, pmNotThere, s_pm_file}; QPixmap* ageToPmLink[] = {pmNewLink, pmMiddleLink, pmOldLink, pmNotThere, pmFileLink}; QPixmap* ageToPmDir[] = {pmNewDir, pmMiddleDir, pmOldDir, pmNotThere, s_pm_dir}; QPixmap* ageToPmDirLink[] = {pmNewDirLink, pmMiddleDirLink, pmOldDirLink, pmNotThere, pmDirLink}; QPixmap** ppPm = bDir ? (bLink ? ageToPmDirLink : ageToPmDir) : (bLink ? ageToPmLink : ageToPm); return *ppPm[eAge]; } } // namespace PixMapUtils diff --git a/src/PixMapUtils.h b/src/PixMapUtils.h index 04009df..02beadb 100644 --- a/src/PixMapUtils.h +++ b/src/PixMapUtils.h @@ -1,42 +1,30 @@ -/** - * Copyright (C) 2003-2007 by Joachim Eibl - * Copyright (C) 2018 Michael Reeves - * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef PIXMAPUTILSH #define PIXMAPUTILSH #include "MergeFileInfos.h" class QPixmap; class QColor; namespace PixMapUtils { QPixmap colorToPixmap(const QColor &inColor); // Copy pm2 onto pm1, but preserve the alpha value from pm1 where pm2 is transparent. QPixmap pixCombiner(const QPixmap* pm1, const QPixmap* pm2); // like pixCombiner but let the pm1 color shine through QPixmap pixCombiner2(const QPixmap* pm1, const QPixmap* pm2); void initPixmaps(const QColor& newest, const QColor& oldest, const QColor& middle, const QColor& notThere); QPixmap getOnePixmap(e_Age eAge, bool bLink, bool bDir); } // namespace PixMapUtils #endif diff --git a/src/ProgressProxyExtender.cpp b/src/ProgressProxyExtender.cpp index f03db5c..e3ec707 100644 --- a/src/ProgressProxyExtender.cpp +++ b/src/ProgressProxyExtender.cpp @@ -1,37 +1,23 @@ -/** - * Copyright (C) 2003-2007 by Joachim Eibl - * joachim.eibl at gmx.de - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com - * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - * - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "ProgressProxyExtender.h" #include void ProgressProxyExtender::slotListDirInfoMessage(KJob*, const QString& msg) { setInformation(msg, 0); } void ProgressProxyExtender::slotPercent(KJob*, unsigned long percent) { setCurrent(percent); } diff --git a/src/ProgressProxyExtender.h b/src/ProgressProxyExtender.h index 76ef7ca..e7f889a 100644 --- a/src/ProgressProxyExtender.h +++ b/src/ProgressProxyExtender.h @@ -1,40 +1,26 @@ -/** - * Copyright (C) 2003-2007 by Joachim Eibl - * joachim.eibl at gmx.de - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com - * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - * - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef PROGRESSPROXYEXTENDER_H #define PROGRESSPROXYEXTENDER_H #include "progress.h" #include class KJob; class ProgressProxyExtender: public ProgressProxy { Q_OBJECT public: ProgressProxyExtender() { setMaxNofSteps(100); } public Q_SLOTS: void slotListDirInfoMessage( KJob*, const QString& msg ); void slotPercent( KJob*, unsigned long percent ); }; #endif diff --git a/src/RLPainter.h b/src/RLPainter.h index ce68b77..e7113dd 100644 --- a/src/RLPainter.h +++ b/src/RLPainter.h @@ -1,72 +1,69 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef RLPAINTER_H #define RLPAINTER_H #include // Helper class that swaps left and right for some commands. class RLPainter : public QPainter { int m_factor; int m_xOffset; int m_fontWidth; public: RLPainter(QPaintDevice* pd, bool bRTL, int width, int fontWidth) : QPainter(pd) { if(bRTL) { m_fontWidth = fontWidth; m_factor = -1; m_xOffset = width - 1; } else { m_fontWidth = 0; m_factor = 1; m_xOffset = 0; } } void fillRect(int x, int y, int w, int h, const QBrush& b) { if(m_factor == 1) QPainter::fillRect(m_xOffset + x, y, w, h, b); else QPainter::fillRect(m_xOffset - x - w, y, w, h, b); } void drawText(int x, int y, const QString& s, bool bAdapt = false) { Qt::LayoutDirection ld = (m_factor == 1 || !bAdapt) ? Qt::LeftToRight : Qt::RightToLeft; //QPainter::setLayoutDirection( ld ); if(ld == Qt::RightToLeft) // Reverse the text { QString s2; for(int i = s.length() - 1; i >= 0; --i) { s2 += s[i]; } QPainter::drawText(m_xOffset - m_fontWidth * s.length() + m_factor * x, y, s2); return; } QPainter::drawText(m_xOffset - m_fontWidth * s.length() + m_factor * x, y, s); } void drawLine(int x1, int y1, int x2, int y2) { QPainter::drawLine(m_xOffset + m_factor * x1, y1, m_xOffset + m_factor * x2, y2); } }; #endif diff --git a/src/SourceData.cpp b/src/SourceData.cpp index c0a8944..53331cf 100644 --- a/src/SourceData.cpp +++ b/src/SourceData.cpp @@ -1,792 +1,790 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ /* Features of class SourceData: - Read a file (from the given URL) or accept data via a string. - Allocate and free buffers as necessary. - Run a preprocessor, when specified. - Run the line-matching preprocessor, when specified. - Run other preprocessing steps: Uppercase, ignore comments, remove carriage return, ignore numbers. Order of operation: 1. If data was given via a string then save it to a temp file. (see setData()) 2. If the specified file is nonlocal (URL) copy it to a temp file. 3. If a preprocessor was specified, run the input file through it. 4. Read the output of the preprocessor. 5. If Uppercase was specified: Turn the read data to uppercase. 6. Write the result to a temp file. 7. If a line-matching preprocessor was specified, run the temp file through it. 8. Read the output of the line-matching preprocessor. 9. If ignore numbers was specified, strip the LMPP-output of all numbers. 10. If ignore comments was specified, strip the LMPP-output of comments. Optimizations: Skip unneeded steps. */ #include "SourceData.h" #include "CommentParser.h" #include "Utils.h" #include "diff.h" #include "Logging.h" #include #include #include #include #include #include #include #include SourceData::SourceData() { m_pOptions = nullptr; reset(); } SourceData::~SourceData() { reset(); } void SourceData::reset() { m_pEncoding = nullptr; m_fileAccess = FileAccess(); m_normalData.reset(); m_lmppData.reset(); if(!m_tempInputFileName.isEmpty()) { m_tempFile.remove(); m_tempInputFileName = ""; } } void SourceData::setFilename(const QString& filename) { if(filename.isEmpty()) { reset(); } else { FileAccess fa(filename); setFileAccess(fa); } } bool SourceData::isEmpty() const { return getFilename().isEmpty(); } bool SourceData::hasData() const { return m_normalData.m_pBuf != nullptr; } bool SourceData::isValid() const { return isEmpty() || hasData(); } void SourceData::setOptions(const QSharedPointer &pOptions) { m_pOptions = pOptions; } QString SourceData::getFilename() const { return m_fileAccess.absoluteFilePath(); } QString SourceData::getAliasName() const { return m_aliasName.isEmpty() ? m_fileAccess.prettyAbsPath() : m_aliasName; } void SourceData::setAliasName(const QString& name) { m_aliasName = name; } void SourceData::setFileAccess(const FileAccess& fileAccess) { m_fileAccess = fileAccess; m_aliasName = QString(); if(!m_tempInputFileName.isEmpty()) { m_tempFile.remove(); m_tempInputFileName = ""; } } void SourceData::setEncoding(QTextCodec* pEncoding) { m_pEncoding = pEncoding; } const QString SourceData::setData(const QString& data) { // Create a temp file for preprocessing: if(m_tempInputFileName.isEmpty()) { FileAccess::createTempFile(m_tempFile); m_tempInputFileName = m_tempFile.fileName(); } FileAccess f(m_tempInputFileName); QByteArray ba = QTextCodec::codecForName("UTF-8")->fromUnicode(data); bool bSuccess = f.writeFile(ba.constData(), ba.length()); if(!bSuccess) { return i18n("Writing clipboard data to temp file failed."); } else { m_aliasName = i18n("From Clipboard"); m_fileAccess = FileAccess(); // Insure m_fileAccess is not valid } return QLatin1String(""); } const QVector* SourceData::getLineDataForDiff() const { if(m_lmppData.m_pBuf == nullptr) return m_normalData.m_v.size() > 0 ? &m_normalData.m_v : nullptr; else return m_lmppData.m_v.size() > 0 ? &m_lmppData.m_v : nullptr; } const QVector* SourceData::getLineDataForDisplay() const { return m_normalData.m_v.size() > 0 ? &m_normalData.m_v : nullptr; } LineRef SourceData::getSizeLines() const { return (LineRef)(m_normalData.m_vSize); } qint64 SourceData::getSizeBytes() const { return m_normalData.m_size; } const char* SourceData::getBuf() const { return m_normalData.m_pBuf; } const QString& SourceData::getText() const { return *m_normalData.m_unicodeBuf; } bool SourceData::isText() const { return m_normalData.isText() || m_normalData.isEmpty(); } bool SourceData::isIncompleteConversion() const { return m_normalData.m_bIncompleteConversion; } bool SourceData::isFromBuffer() const { return !m_fileAccess.isValid(); } bool SourceData::isBinaryEqualWith(const QSharedPointer& other) const { return m_fileAccess.exists() && other->m_fileAccess.exists() && getSizeBytes() == other->getSizeBytes() && (getSizeBytes() == 0 || memcmp(getBuf(), other->getBuf(), getSizeBytes()) == 0); } void SourceData::FileData::reset() { delete[](char*) m_pBuf; m_pBuf = nullptr; m_v.clear(); m_size = 0; m_vSize = 0; m_bIsText = false; m_bIncompleteConversion = false; m_eLineEndStyle = eLineEndStyleUndefined; } bool SourceData::FileData::readFile(FileAccess& file) { reset(); if(file.fileName().isEmpty()) { return true; } //FileAccess fa(filename); if(!file.isNormal()) return true; m_size = file.sizeForReading(); char* pBuf; m_pBuf = pBuf = new char[m_size + 100]; // Alloc 100 byte extra: Safety hack, not nice but does no harm. // Some extra bytes at the end of the buffer are needed by // the diff algorithm. See also GnuDiff::diff_2_files(). bool bSuccess = file.readFile(pBuf, m_size); if(!bSuccess) { delete[] pBuf; m_pBuf = nullptr; m_size = 0; } else { //null terminate buffer pBuf[m_size + 1] = 0; pBuf[m_size + 2] = 0; pBuf[m_size + 3] = 0; pBuf[m_size + 4] = 0; } return bSuccess; } bool SourceData::FileData::readFile(const QString& filename) { reset(); if(filename.isEmpty()) { return true; } FileAccess fa(filename); if(!fa.isNormal()) return true; m_size = fa.sizeForReading(); char* pBuf; m_pBuf = pBuf = new char[m_size + 100]; // Alloc 100 byte extra: Safety hack, not nice but does no harm. // Some extra bytes at the end of the buffer are needed by // the diff algorithm. See also GnuDiff::diff_2_files(). bool bSuccess = fa.readFile(pBuf, m_size); if(!bSuccess) { delete[] pBuf; m_pBuf = nullptr; m_size = 0; } return bSuccess; } bool SourceData::saveNormalDataAs(const QString& fileName) { return m_normalData.writeFile(fileName); } bool SourceData::FileData::writeFile(const QString& filename) { if(filename.isEmpty()) { return true; } FileAccess fa(filename); bool bSuccess = fa.writeFile(m_pBuf, m_size); return bSuccess; } //Depriated void SourceData::FileData::copyBufFrom(const FileData& src) //TODO: Remove me. { reset(); char* pBuf; m_size = src.m_size; m_pBuf = pBuf = new char[m_size + 100]; Q_ASSERT(src.m_pBuf != nullptr); memcpy(pBuf, src.m_pBuf, m_size); } QTextCodec* SourceData::detectEncoding(const QString& fileName, QTextCodec* pFallbackCodec) { QFile f(fileName); if(f.open(QIODevice::ReadOnly)) { char buf[200]; qint64 size = f.read(buf, sizeof(buf)); qint64 skipBytes = 0; QTextCodec* pCodec = detectEncoding(buf, size, skipBytes); if(pCodec) return pCodec; } return pFallbackCodec; } QStringList SourceData::readAndPreprocess(QTextCodec* pEncoding, bool bAutoDetectUnicode) { m_pEncoding = pEncoding; QTemporaryFile fileIn1, fileOut1; QString fileNameIn1; QString fileNameOut1; QString fileNameIn2; QString fileNameOut2; QStringList errors; if(m_fileAccess.isValid() && !m_fileAccess.isNormal()) { errors.append(i18n("%1 is not a normal file.", m_fileAccess.prettyAbsPath())); return errors; } bool bTempFileFromClipboard = !m_fileAccess.isValid(); // Detect the input for the preprocessing operations if(!bTempFileFromClipboard) { if(m_fileAccess.isLocal()) { fileNameIn1 = m_fileAccess.absoluteFilePath(); } else // File is not local: create a temporary local copy: { if(m_tempInputFileName.isEmpty()) { m_fileAccess.createLocalCopy(); m_tempInputFileName = m_fileAccess.getTempName(); } fileNameIn1 = m_tempInputFileName; } if(bAutoDetectUnicode) { m_pEncoding = detectEncoding(fileNameIn1, pEncoding); } } else // The input was set via setData(), probably from clipboard. { fileNameIn1 = m_tempInputFileName; m_pEncoding = QTextCodec::codecForName("UTF-8"); } QTextCodec* pEncoding1 = m_pEncoding; QTextCodec* pEncoding2 = m_pEncoding; m_normalData.reset(); m_lmppData.reset(); FileAccess faIn(fileNameIn1); qint64 fileInSize = faIn.size(); if(faIn.exists()) { // Run the first preprocessor if(m_pOptions->m_PreProcessorCmd.isEmpty()) { // No preprocessing: Read the file directly: if(!m_normalData.readFile(faIn)) { errors.append(faIn.getStatusText()); return errors; } } else { QTemporaryFile tmpInPPFile; QString fileNameInPP = fileNameIn1; if(pEncoding1 != m_pOptions->m_pEncodingPP) { // Before running the preprocessor convert to the format that the preprocessor expects. FileAccess::createTempFile(tmpInPPFile); fileNameInPP = tmpInPPFile.fileName(); pEncoding1 = m_pOptions->m_pEncodingPP; convertFileEncoding(fileNameIn1, pEncoding, fileNameInPP, pEncoding1); } QString ppCmd = m_pOptions->m_PreProcessorCmd; FileAccess::createTempFile(fileOut1); fileNameOut1 = fileOut1.fileName(); QProcess ppProcess; ppProcess.setStandardInputFile(fileNameInPP); ppProcess.setStandardOutputFile(fileNameOut1); QString program; QStringList args; QString errorReason = Utils::getArguments(ppCmd, program, args); if(errorReason.isEmpty()) { ppProcess.start(program, args); ppProcess.waitForFinished(-1); } else errorReason = "\n(" + errorReason + ')'; bool bSuccess = errorReason.isEmpty() && m_normalData.readFile(fileNameOut1); if(fileInSize > 0 && (!bSuccess || m_normalData.m_size == 0)) { //Don't fail the preprocessor command if the file cann't be read. if(!m_normalData.readFile(faIn)) { errors.append(faIn.getStatusText()); errors.append(i18n(" Temp file is: %1", fileNameIn1)); return errors; } errors.append( i18n("Preprocessing possibly failed. Check this command:\n\n %1" "\n\nThe preprocessing command will be disabled now.", ppCmd) + errorReason); m_pOptions->m_PreProcessorCmd = ""; pEncoding1 = m_pEncoding; } } if(!m_normalData.preprocess(pEncoding1, false)) { errors.append(i18n("File %1 too large to process. Skipping.", fileNameIn1)); return errors; } //exit early for non text data further processing assumes a text file as input if(!m_normalData.isText()) return errors; // LineMatching Preprocessor if(!m_pOptions->m_LineMatchingPreProcessorCmd.isEmpty()) { QTemporaryFile tempOut2, fileInPP; fileNameIn2 = fileNameOut1.isEmpty() ? fileNameIn1 : fileNameOut1; QString fileNameInPP = fileNameIn2; pEncoding2 = pEncoding1; if(pEncoding2 != m_pOptions->m_pEncodingPP) { // Before running the preprocessor convert to the format that the preprocessor expects. FileAccess::createTempFile(fileInPP); fileNameInPP = fileInPP.fileName(); pEncoding2 = m_pOptions->m_pEncodingPP; convertFileEncoding(fileNameIn2, pEncoding1, fileNameInPP, pEncoding2); } QString ppCmd = m_pOptions->m_LineMatchingPreProcessorCmd; FileAccess::createTempFile(tempOut2); fileNameOut2 = tempOut2.fileName(); QProcess ppProcess; ppProcess.setStandardInputFile(fileNameInPP); ppProcess.setStandardOutputFile(fileNameOut2); QString program; QStringList args; QString errorReason = Utils::getArguments(ppCmd, program, args); if(errorReason.isEmpty()) { ppProcess.start(program, args); ppProcess.waitForFinished(-1); } else errorReason = "\n(" + errorReason + ')'; bool bSuccess = errorReason.isEmpty() && m_lmppData.readFile(fileNameOut2); if(FileAccess(fileNameIn2).size() > 0 && (!bSuccess || m_lmppData.m_size == 0)) { errors.append( i18n("The line-matching-preprocessing possibly failed. Check this command:\n\n %1" "\n\nThe line-matching-preprocessing command will be disabled now.", ppCmd) + errorReason); m_pOptions->m_LineMatchingPreProcessorCmd = ""; if(!m_lmppData.readFile(fileNameIn2)) { errors.append(i18n("Failed to read file: %1", fileNameIn2)); return errors; } } } else if(m_pOptions->m_bIgnoreComments || m_pOptions->m_bIgnoreCase) { // We need a copy of the normal data. m_lmppData.copyBufFrom(m_normalData); } } else { //exit early for nonexistent files return errors; } if(!m_lmppData.preprocess(pEncoding2, true)) { errors.append(i18n("File %1 too large to process. Skipping.", fileNameIn1)); return errors; } Q_ASSERT(m_lmppData.isText()); //TODO: Needed? if(m_lmppData.m_vSize < m_normalData.m_vSize) { // Preprocessing command may result in smaller data buffer so adjust size for(qint64 i = m_lmppData.m_vSize; i < m_normalData.m_vSize; ++i) { // Set all empty lines to point to the end of the buffer. m_lmppData.m_v.push_back(LineData(m_lmppData.m_unicodeBuf, m_lmppData.m_unicodeBuf->length())); } m_lmppData.m_vSize = m_normalData.m_vSize; } // Ignore comments if(m_pOptions->m_bIgnoreComments && hasData()) { qint64 vSize = std::min(m_normalData.m_vSize, m_lmppData.m_vSize); Q_ASSERT(vSize < TYPE_MAX(qint32)); //Perform explcit cast to insure well defined behavior comparing 32-bit to a 64-bit value for(qint32 i = 0; (qint64)i < vSize; ++i) { m_normalData.m_v[i].setPureComment(m_lmppData.m_v[i].isPureComment()); } } return errors; } /** Prepare the linedata vector for every input line.*/ bool SourceData::FileData::preprocess(QTextCodec* pEncoding, bool removeComments) { if(m_pBuf == nullptr) return true; if(pEncoding == nullptr) return false; QString line; QChar curChar; LineCount lineCount = 0; qint64 lastOffset = 0; qint64 skipBytes = 0; QScopedPointer parser(new DefaultCommentParser()); // detect line end style QVector vOrigDataLineEndStyle; m_eLineEndStyle = eLineEndStyleUndefined; QTextCodec* pCodec = detectEncoding(m_pBuf, m_size, skipBytes); if(pCodec != pEncoding) skipBytes = 0; if(m_size - skipBytes > TYPE_MAX(QtNumberType)) return false; const QByteArray ba = QByteArray::fromRawData(m_pBuf + skipBytes, (int)(m_size - skipBytes)); QTextStream ts(ba, QIODevice::ReadOnly); //Don't use text mode we need to see the actual line ending. ts.setCodec(pEncoding); ts.setAutoDetectUnicode(false); m_bIncompleteConversion = false; m_unicodeBuf->clear(); Q_ASSERT(m_unicodeBuf->length() == 0); while(!ts.atEnd()) { line.clear(); if(lineCount >= TYPE_MAX(LineCount) - 5) return false; ts >> curChar; quint32 firstNonwhite=0; //QTextStream::readLine doesn't tell us abount line endings. while(curChar != '\n' && curChar != '\r') { if(curChar.isNull() || curChar.isNonCharacter()) return true; if(curChar == QChar::ReplacementCharacter) m_bIncompleteConversion = true; if(!curChar.isSpace()) firstNonwhite = line.length(); line.append(curChar); if(ts.atEnd()) break; ts >> curChar; } ++lineCount; switch(curChar.unicode()) { case '\n': vOrigDataLineEndStyle.push_back(eLineEndStyleUnix); break; case '\r': if(lastOffset < m_size) { //workaround for lack of peak API in QTextStream. qint64 j; for(j = 0; j < 4 && lastOffset + j < m_size; ++j) { if(m_pBuf[lastOffset + j] != '\0') break; } if(m_pBuf[lastOffset + j] == '\n') { ts >> curChar; vOrigDataLineEndStyle.push_back(eLineEndStyleDos); lastOffset = ts.pos(); break; } } //old mac style ending. vOrigDataLineEndStyle.push_back(eLineEndStyleUndefined); break; } parser->processLine(line); if(removeComments) parser->removeComment(line); //kdiff3 internally uses only unix style endings for simplicity. m_v.push_back(LineData(m_unicodeBuf, lastOffset, line.length(), firstNonwhite, parser->isPureComment())); m_unicodeBuf->append(line).append('\n'); lastOffset = m_unicodeBuf->length(); } m_v.push_back(LineData(m_unicodeBuf, lastOffset)); Q_ASSERT(m_v.size() < 2 || m_v[m_v.size() - 1].getOffset() != m_v[m_v.size() - 2].getOffset()); m_bIsText = true; if(!vOrigDataLineEndStyle.isEmpty()) m_eLineEndStyle = vOrigDataLineEndStyle[0]; m_vSize = lineCount; return true; } // Convert the input file from input encoding to output encoding and write it to the output file. bool SourceData::convertFileEncoding(const QString& fileNameIn, QTextCodec* pCodecIn, const QString& fileNameOut, QTextCodec* pCodecOut) { QFile in(fileNameIn); if(!in.open(QIODevice::ReadOnly)) return false; QTextStream inStream(&in); inStream.setCodec(pCodecIn); inStream.setAutoDetectUnicode(false); QFile out(fileNameOut); if(!out.open(QIODevice::WriteOnly)) return false; QTextStream outStream(&out); outStream.setCodec(pCodecOut); QString data = inStream.readAll(); outStream << data; return true; } QTextCodec* SourceData::getEncodingFromTag(const QByteArray& s, const QByteArray& encodingTag) { int encodingPos = s.indexOf(encodingTag); if(encodingPos >= 0) { int apostrophPos = s.indexOf('"', encodingPos + encodingTag.length()); int apostroph2Pos = s.indexOf('\'', encodingPos + encodingTag.length()); char apostroph = '"'; if(apostroph2Pos >= 0 && (apostrophPos < 0 || apostroph2Pos < apostrophPos)) { apostroph = '\''; apostrophPos = apostroph2Pos; } int encodingEnd = s.indexOf(apostroph, apostrophPos + 1); if(encodingEnd >= 0) // e.g.: or { QByteArray encoding = s.mid(apostrophPos + 1, encodingEnd - (apostrophPos + 1)); return QTextCodec::codecForName(encoding); } else // e.g.: { QByteArray encoding = s.mid(encodingPos + encodingTag.length(), apostrophPos - (encodingPos + encodingTag.length())); return QTextCodec::codecForName(encoding); } } return nullptr; } QTextCodec* SourceData::detectEncoding(const char* buf, qint64 size, qint64& skipBytes) { if(size >= 2) { if(buf[0] == '\xFF' && buf[1] == '\xFE') { skipBytes = 2; return QTextCodec::codecForName("UTF-16LE"); } if(buf[0] == '\xFE' && buf[1] == '\xFF') { skipBytes = 2; return QTextCodec::codecForName("UTF-16BE"); } } if(size >= 3) { if(buf[0] == '\xEF' && buf[1] == '\xBB' && buf[2] == '\xBF') { skipBytes = 3; return QTextCodec::codecForName("UTF-8-BOM"); } } skipBytes = 0; QByteArray s; /* We don't need the whole file here just the header. ] */ if(size <= 5000) s = QByteArray(buf, (int)size); else s = QByteArray(buf, 5000); int xmlHeaderPos = s.indexOf("= 0) { int xmlHeaderEnd = s.indexOf("?>", xmlHeaderPos); if(xmlHeaderEnd >= 0) { QTextCodec* pCodec = getEncodingFromTag(s.mid(xmlHeaderPos, xmlHeaderEnd - xmlHeaderPos), "encoding="); if(pCodec) return pCodec; } } else // HTML { int metaHeaderPos = s.indexOf("= 0) { int metaHeaderEnd = s.indexOf(">", metaHeaderPos); if(metaHeaderEnd >= 0) { QTextCodec* pCodec = getEncodingFromTag(s.mid(metaHeaderPos, metaHeaderEnd - metaHeaderPos), "charset="); if(pCodec) return pCodec; metaHeaderPos = s.indexOf(" * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef SOURCEDATA_H #define SOURCEDATA_H #include "options.h" #include "fileaccess.h" #include "LineRef.h" #include #include #include #include class LineData; class SourceData: public QObject { Q_OBJECT; public: SourceData(); virtual ~SourceData(); void setOptions(const QSharedPointer &pOptions); LineRef getSizeLines() const; qint64 getSizeBytes() const; const char* getBuf() const; const QString& getText() const; const QVector* getLineDataForDisplay() const; const QVector* getLineDataForDiff() const; void setFilename(const QString& filename); void setFileAccess(const FileAccess& fileAccess); QString getFilename() const; void setAliasName(const QString& name); QString getAliasName() const; bool isEmpty() const; // File was set bool hasData() const; // Data was readable bool isText() const; // is it pure text (vs. binary data) bool isIncompleteConversion() const; // true if some replacement characters were found bool isFromBuffer() const; // was it set via setData() (vs. setFileAccess() or setFilename()) const QString setData(const QString& data); bool isValid() const; // Either no file is specified or reading was successful // Returns a list of error messages if anything went wrong QStringList readAndPreprocess(QTextCodec* pEncoding, bool bAutoDetectUnicode); bool saveNormalDataAs(const QString& fileName); bool isBinaryEqualWith(const QSharedPointer& other) const; void reset(); QTextCodec* getEncoding() const { return m_pEncoding; } e_LineEndStyle getLineEndStyle() const { return m_normalData.m_eLineEndStyle; } public Q_SLOTS: void setEncoding(QTextCodec* pEncoding); private: bool convertFileEncoding(const QString& fileNameIn, QTextCodec* pCodecIn, const QString& fileNameOut, QTextCodec* pCodecOut); static QTextCodec* detectEncoding(const char* buf, qint64 size, qint64& skipBytes); static QTextCodec* getEncodingFromTag(const QByteArray& s, const QByteArray& encodingTag); QTextCodec* detectEncoding(const QString& fileName, QTextCodec* pFallbackCodec); QString m_aliasName; FileAccess m_fileAccess; QSharedPointer m_pOptions; QString m_tempInputFileName; QTemporaryFile m_tempFile; //Created from clipboard content. class FileData { private: friend SourceData; const char* m_pBuf = nullptr; //TODO: Phase out needlessly wastes memmory and time by keeping second copy of file data. qint64 m_size = 0; qint64 m_vSize = 0; // Nr of lines in m_pBuf1 and size of m_v1, m_dv12 and m_dv13 QSharedPointer m_unicodeBuf=QSharedPointer::create(); QVector m_v; bool m_bIsText = false; bool m_bIncompleteConversion = false; e_LineEndStyle m_eLineEndStyle = eLineEndStyleUndefined; public: ~FileData() { reset(); } bool readFile(FileAccess& file); bool readFile(const QString& filename); bool writeFile(const QString& filename); bool preprocess(QTextCodec* pEncoding, bool removeComments); void reset(); void copyBufFrom(const FileData& src); bool isEmpty() const { return m_size == 0; } bool isText() const { return m_bIsText || isEmpty(); } }; FileData m_normalData; FileData m_lmppData; QTextCodec* m_pEncoding = nullptr; }; #endif // !SOURCEDATA_H diff --git a/src/TypeUtils.h b/src/TypeUtils.h index f08c71f..a3b4be0 100644 --- a/src/TypeUtils.h +++ b/src/TypeUtils.h @@ -1,37 +1,23 @@ -/** - * - * Copyright (C) 2018 Michael Reeves - * - * This file is part of Kdiff3. - * - * Kdiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Kdiff3 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 Kdiff3. If not, see . - * - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef TYPEUTILS_H #define TYPEUTILS_H #include #include #include #define TYPE_MAX(x) std::numeric_limits::max() #define TYPE_MIN(x) std::numeric_limits::min() typedef size_t PtrDiffRef; typedef qint32 QtNumberType;//Qt insists on one type for all but does not create a typedef for it. static_assert(sizeof(int) >= sizeof(qint32), "Legacy LP32 systems/compilers not supported"); // e.g. Windows 16-bit #endif diff --git a/src/Utils.cpp b/src/Utils.cpp index 95c3668..e88f8d2 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -1,173 +1,160 @@ -/** - * Copyright (C) 2003-2007 by Joachim Eibl - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com - * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - * - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "Utils.h" #include #include #include #include #include #include /* Split the command line into arguments. * Normally split at white space separators except when quoting with " or '. * Backslash is treated as meta character within single quotes ' only. * Detect parsing errors like unclosed quotes. * The first item in the list will be the command itself. * Returns the error reason as string or an empty string on success. * Eg. >"1" "2"< => >1<, >2< * Eg. >'\'\\'< => >'\< backslash is a meta character between single quotes * Eg. > "\\" < => >\\< but not between double quotes * Eg. >"c:\sed" 's/a/\' /g'< => >c:\sed<, >s/a/' /g< */ QString Utils::getArguments(QString cmd, QString& program, QStringList& args) { program = QString(); args.clear(); for(int i = 0; i < cmd.length(); ++i) { while(i < cmd.length() && cmd[i].isSpace()) { ++i; } if(cmd[i] == '"' || cmd[i] == '\'') // argument beginning with a quote { QChar quoteChar = cmd[i]; ++i; int argStart = i; bool bSkip = false; while(i < cmd.length() && (cmd[i] != quoteChar || bSkip)) { if(bSkip) { bSkip = false; //Don't emulate bash here we are not talking to it. //For us all quotes are the same. if(cmd[i] == '\\' || cmd[i] == '\'' || cmd[i] == '"') { cmd.remove(i - 1, 1); // remove the backslash '\' continue; } } else if(cmd[i] == '\\') bSkip = true; ++i; } if(i < cmd.length()) { args << cmd.mid(argStart, i - argStart); if(i + 1 < cmd.length() && !cmd[i + 1].isSpace()) return i18n("Expecting space after closing quote."); } else return i18n("Unmatched quote."); continue; } else { int argStart = i; while(i < cmd.length() && (!cmd[i].isSpace() /*|| bSkip*/)) { if(cmd[i] == '"' || cmd[i] == '\'') return i18n("Unexpected quote character within argument."); ++i; } args << cmd.mid(argStart, i - argStart); } } if(args.isEmpty()) return i18n("No program specified."); else { program = args[0]; args.pop_front(); } return QString(); } bool Utils::wildcardMultiMatch(const QString& wildcard, const QString& testString, bool bCaseSensitive) { static QHash s_patternMap; const QStringList regExpList = wildcard.split(QChar(';')); for(const QString& regExp : regExpList) { QHash::iterator patIt = s_patternMap.find(regExp); if(patIt == s_patternMap.end()) { QRegExp pattern(regExp, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::Wildcard); patIt = s_patternMap.insert(regExp, pattern); } if(patIt.value().exactMatch(testString)) return true; } return false; } bool Utils::isCTokenChar(QChar c) { return (c == '_') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); } /// Calculate where a token starts and ends, given the x-position on screen. void Utils::calcTokenPos(const QString& s, int posOnScreen, int& pos1, int& pos2) { int pos = std::max(0, posOnScreen); if(pos >= (int)s.length()) { pos1 = s.length(); pos2 = s.length(); return; } pos1 = pos; pos2 = pos + 1; if(isCTokenChar(s[pos1])) { while(pos1 >= 0 && isCTokenChar(s[pos1])) --pos1; ++pos1; while(pos2 < (int)s.length() && isCTokenChar(s[pos2])) ++pos2; } } QString Utils::calcHistoryLead(const QString& s) { // Return the start of the line until the first white char after the first non white char. int i = s.indexOf(QRegularExpression("\\S")); if(i == -1) return QString(""); i = s.indexOf(QRegularExpression("\\s"), i); if(Q_UNLIKELY(i == -1)) return s;// Very unlikely return s.left(i); } diff --git a/src/Utils.h b/src/Utils.h index 36f267e..76b4bcf 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -1,61 +1,48 @@ -/** - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com - * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - * - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef UTILS_H #define UTILS_H #include #include #include #include class Utils{ public: static bool wildcardMultiMatch(const QString& wildcard, const QString& testString, bool bCaseSensitive); static QString getArguments(QString cmd, QString& program, QStringList& args); inline static bool isEndOfLine( QChar c ) { return c=='\n' || c=='\r' || c=='\x0b'; } //Where posiable use QTextLayout in place of these functions especially when dealing with non-latin scripts. inline static int getHorizontalAdvance(const QFontMetrics &metrics, const QString& s, int len = -1) { //Warning: The Qt API used here is not accurate for some non-latin characters. #if QT_VERSION < QT_VERSION_CHECK(5,12,0) return metrics.width(s, len); #else return metrics.horizontalAdvance(s, len); #endif } inline static int getHorizontalAdvance(const QFontMetrics &metrics, const QChar& c) { //Warning: The Qt API used here is not accurate for some non-latin characters. #if QT_VERSION < QT_VERSION_CHECK(5,12,0) return metrics.width(c); #else return metrics.horizontalAdvance(c); #endif } static void calcTokenPos(const QString& s, int posOnScreen, int& pos1, int& pos2); static bool isCTokenChar(QChar c); static QString calcHistoryLead(const QString& s); }; #endif diff --git a/src/autotests/CvsIgnorelist.cpp b/src/autotests/CvsIgnorelist.cpp index 51cbc3c..27d8004 100644 --- a/src/autotests/CvsIgnorelist.cpp +++ b/src/autotests/CvsIgnorelist.cpp @@ -1,124 +1,112 @@ // SPDX-License-Identifier: GPL-2.0-or-later -/** - * Copyright (C) 2019 Michael Reeves +/* + * KDiff3 - Text Diff And Merge Tool * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2019-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include #include #include "../cvsignorelist.h" class CvsIgnoreListTest : public QObject { const QString defaultPatterns = QString::fromLatin1(". .. core RCSLOG tags TAGS RCS SCCS .make.state " ".nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj " "*.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$"); Q_OBJECT private Q_SLOTS: void init() { CvsIgnoreList test; //sanity check defaults QVERIFY(test.m_exactPatterns.isEmpty()); QVERIFY(test.m_endPatterns.isEmpty()); QVERIFY(test.m_generalPatterns.isEmpty()); QVERIFY(test.m_startPatterns.isEmpty()); } void addEntriesFromString() { CvsIgnoreList test; QString testString = ". .. core RCSLOG tags TAGS RCS SCCS .make.state"; test.addEntriesFromString(testString); QVERIFY(!test.m_exactPatterns.isEmpty()); QVERIFY(test.m_exactPatterns == testString.split(' ')); } void matches() { CvsIgnoreList test; QString testString = ". .. core RCSLOG tags TAGS RCS SCCS .make.state *.so _$*"; test.addEntriesFromString(testString); QVERIFY(test.matches(".", false)); QVERIFY(!test.matches("cores core", true)); QVERIFY(test.matches("core", true)); QVERIFY(!test.matches("Core", true)); QVERIFY(test.matches("Core", false)); QVERIFY(!test.matches("a", false)); QVERIFY(test.matches("core", false)); //ends with .so QVERIFY(test.matches("sdf3.so", true)); QVERIFY(!test.matches("sdf3.to", true)); QVERIFY(test.matches("*.so", true)); QVERIFY(test.matches("sdf4.So", false)); QVERIFY(!test.matches("sdf4.So", true)); //starts with _$ddsf QVERIFY(test.matches("_$ddsf", true)); //Should only match exact strings not partial ones QVERIFY(!test.matches("sdf4.so ", true)); QVERIFY(!test.matches(" _$ddsf", true)); testString = "*.*"; test = CvsIgnoreList(); test.addEntriesFromString("*.*"); QVERIFY(test.matches("k.K", false)); QVERIFY(test.matches("*.K", false)); QVERIFY(test.matches("*.*", false)); QVERIFY(!test.matches("*+*", false)); QVERIFY(!test.matches("asd", false)); //The fallowing are matched by the above QVERIFY(test.matches("a k.k", false)); QVERIFY(test.matches("k.k v", false)); QVERIFY(test.matches(" k.k", false)); QVERIFY(test.matches("k.k ", false)); } void testDefaults() { CvsIgnoreList test; CvsIgnoreList expected; MocIgnoreFile file; t_DirectoryList dirList; /* Verify default init. For this to work we must: 1. Unset CVSIGNORE 2. Insure no patterns are read from a .cvsignore file. MocCvsIgnore emulates a blank cvs file by default insuring the second condition. */ test = CvsIgnoreList(); // qunsetenv("CVSIGNORE"); expected.addEntriesFromString(defaultPatterns); test.init(file, &dirList); QVERIFY(test.m_endPatterns == expected.m_endPatterns); QVERIFY(test.m_exactPatterns == expected.m_exactPatterns); QVERIFY(test.m_startPatterns == expected.m_startPatterns); QVERIFY(test.m_generalPatterns == expected.m_generalPatterns); } }; QTEST_MAIN(CvsIgnoreListTest); #include "CvsIgnorelist.moc" diff --git a/src/autotests/MocIgnoreFile.h b/src/autotests/MocIgnoreFile.h index cceec29..5b28491 100644 --- a/src/autotests/MocIgnoreFile.h +++ b/src/autotests/MocIgnoreFile.h @@ -1,57 +1,44 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** - * Copyright (C) 2020 Michael Reeves +/* + * KDiff3 - Text Diff And Merge Tool * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef MOCIGNOREFILE_H #define MOCIGNOREFILE_H #include #include typedef class MocIgnoreFile FileAccess; typedef std::list t_DirectoryList; class MocIgnoreFile { public: void mocSetPath(const QString& path) { mPath = path; } void mocSetIsLocal(bool local) { mLocal = local; } void addPath(const QString& txt) { Q_UNUSED(txt); } QString absoluteFilePath() const { return mPath; } bool createLocalCopy() { return true; } bool isLocal() const { return mLocal; } bool exists() const { return mExists; } QString getTempName() const { return mPath; } QString fileName(bool needTmp = false) const { Q_UNUSED(needTmp); return ".cvsignore"; } private: QString mPath = "/test/ui/.cvsignore"; bool mLocal = true, mExists = true; }; #endif diff --git a/src/autotests/commentparser.cpp b/src/autotests/commentparser.cpp index e68e5a0..f5f5d5f 100644 --- a/src/autotests/commentparser.cpp +++ b/src/autotests/commentparser.cpp @@ -1,376 +1,364 @@ -/** - * Copyright (C) 2019 Michael Reeves +/* + * KDiff3 - Text Diff And Merge Tool * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ + * SPDX-FileCopyrightText: 2019-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include #include "../CommentParser.h" class CommentParserTest : public QObject { Q_OBJECT private Q_SLOTS: void init() { DefaultCommentParser parser; //Sanity check defaults. QVERIFY(!parser.isPureComment()); QVERIFY(!parser.isEscaped()); QVERIFY(!parser.inString()); QVERIFY(!parser.inComment()); } void singleLineComment() { DefaultCommentParser test; test.processLine("//ddj ?*8"); QVERIFY(!test.inComment()); QVERIFY(test.isPureComment()); //ignore trailing+leading whitespace test = DefaultCommentParser(); test.processLine("\t \t // comment "); QVERIFY(!test.inComment()); QVERIFY(test.isPureComment()); test = DefaultCommentParser(); test.processLine("//comment with quotes embedded \"\""); QVERIFY(!test.inComment()); QVERIFY(test.isPureComment()); test = DefaultCommentParser(); test.processLine("anythis//endof line comment"); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test = DefaultCommentParser(); test.processLine("anythis//ignore embedded multiline sequence /*"); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); } void inComment() { DefaultCommentParser test; test.mCommentType = DefaultCommentParser::multiLine; QVERIFY(test.inComment()); test.mCommentType = DefaultCommentParser::singleLine; QVERIFY(test.inComment()); test.mCommentType = DefaultCommentParser::none; QVERIFY(!test.inComment()); } void multiLineComment() { DefaultCommentParser test; //mutiline syntax on one line test.processLine("/* kjd*/"); QVERIFY(test.isPureComment()); QVERIFY(!test.inComment()); //mid line comment start. test = DefaultCommentParser(); test.processLine("fskk /* kjd */"); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); //mid line comment start. multiple lines test = DefaultCommentParser(); test.processLine("fskk /* kjd "); QVERIFY(test.inComment()); QVERIFY(!test.isPureComment()); test.processLine(" comment line "); QVERIFY(test.inComment()); QVERIFY(test.isPureComment()); test.processLine(" comment */ not comment "); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); //mid line comment start. multiple lines test = DefaultCommentParser(); test.processLine("fskk /* kjd "); QVERIFY(test.inComment()); QVERIFY(!test.isPureComment()); test.processLine(" comment line "); QVERIFY(test.inComment()); QVERIFY(test.isPureComment()); test.processLine(" comment * line / "); QVERIFY(test.inComment()); QVERIFY(test.isPureComment()); //embedded single line character sequence should not end comment test.processLine(" comment line //"); QVERIFY(test.inComment()); QVERIFY(test.isPureComment()); test.processLine(" comment */"); QVERIFY(!test.inComment()); QVERIFY(test.isPureComment()); //Escape squeances not relavate to comments test = DefaultCommentParser(); test.processLine("/* comment \\*/"); QVERIFY(!test.inComment()); QVERIFY(test.isPureComment()); test = DefaultCommentParser(); test.processLine(" int i = 8 / 8 * 3;/* comment*/"); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); //invalid in C++ should not be flagged as pure comment test.processLine("/* comment */ */"); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); //leading whitespace test = DefaultCommentParser(); test.processLine("\t \t /* comment */"); QVERIFY(!test.inComment()); QVERIFY(test.isPureComment()); //trailing whitespace test = DefaultCommentParser(); test.processLine("\t \t /* comment */ "); QVERIFY(!test.inComment()); QVERIFY(test.isPureComment()); } void stringTest() { DefaultCommentParser test; test.processLine("\"quoted string // \""); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test = DefaultCommentParser(); test.processLine("\"quoted string /* \""); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test = DefaultCommentParser(); test.processLine("\"\""); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test = DefaultCommentParser(); test.processChar("\"", '"'); QVERIFY(!test.isEscaped()); QVERIFY(test.inString()); test.processChar("\"'", '\''); QVERIFY(test.inString()); test.processChar("\"'\"", '"'); QVERIFY(!test.inString()); //test only escape sequence we care about test = DefaultCommentParser(); test.processChar("\"", '"'); test.processChar("\"", '\\'); QVERIFY(test.isEscaped()); QVERIFY(test.inString()); test.processChar("\"\\\"", '"'); QVERIFY(!test.isEscaped()); QVERIFY(test.inString()); test.processChar("\"\\\"\"", '"'); QVERIFY(!test.isEscaped()); QVERIFY(!test.inString()); } void quotedCharacter() { DefaultCommentParser test; test.processLine("'\"'"); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test.processLine("'a'"); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test.processLine("'\\\''"); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test.processLine("'*'"); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test.processLine("'/'"); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); } void nonComment() { DefaultCommentParser test; test.processLine(" int i = 8 / 8 * 3;"); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test = DefaultCommentParser(); test.processLine(" "); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test = DefaultCommentParser(); test.processLine(""); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test = DefaultCommentParser(); test.processLine(" / "); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); test = DefaultCommentParser(); test.processLine(" * "); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); //invalid in C++ should not be flagged as non-comment test.processLine(" */"); QVERIFY(!test.inString()); QVERIFY(!test.inComment()); QVERIFY(!test.isPureComment()); } void removeComment() { DefaultCommentParser test; QString line=QLatin1String(" int i = 8 / 8 * 3;"), correct=QLatin1String(" int i = 8 / 8 * 3;"); test.processLine(line); test.removeComment(line); QVERIFY(line == correct); QVERIFY(line.length() == correct.length()); test = DefaultCommentParser(); correct = line = QLatin1String(" //int i = 8 / 8 * 3;"); test.processLine(line); test.removeComment(line); QVERIFY(line == correct); test = DefaultCommentParser(); correct = line = QLatin1String("// int i = 8 / 8 * 3;"); test.processLine(line); test.removeComment(line); QVERIFY(line == correct); QVERIFY(line.length() == correct.length()); test = DefaultCommentParser(); line = QLatin1String(" int i = 8 / 8 * 3;// comment"); correct = QLatin1String(" int i = 8 / 8 * 3; "); test.processLine(line); test.removeComment(line); QVERIFY(line == correct); QVERIFY(line.length() == correct.length()); test = DefaultCommentParser(); line = QLatin1String(" int i = 8 / 8 * 3;/* comment"); correct = QLatin1String(" int i = 8 / 8 * 3; "); test.processLine(line); test.removeComment(line); QVERIFY(line == correct); QVERIFY(line.length() == correct.length()); correct = line = QLatin1String(" int i = 8 / 8 * 3;/* mot a comment"); test.processLine(line); test.removeComment(line); QVERIFY(line == correct); QVERIFY(line.length() == correct.length()); //end comment mid-line line = QLatin1String("d */ why"); correct = QLatin1String(" why"); test.processLine(line); test.removeComment(line); QVERIFY(line == correct); QVERIFY(line.length() == correct.length()); test = DefaultCommentParser(); line = QLatin1String(" int i = 8 / 8 * 3;/* comment*/"); correct = QLatin1String(" int i = 8 / 8 * 3; "); test.processLine(line); test.removeComment(line); QVERIFY(line == correct); QVERIFY(line.length() == correct.length()); test = DefaultCommentParser(); correct = line = QLatin1String(" /*int i = 8 / 8 * 3;/* comment*/"); test.processLine(line); test.removeComment(line); QVERIFY(line == correct); QVERIFY(line.length() == correct.length()); //line with multiple comments weird but legal c/c++ test = DefaultCommentParser(); line = QLatin1String(" int /*why?*/ i = 8 / 8 * 3;/* comment*/"); correct = QLatin1String(" int i = 8 / 8 * 3; "); test.processLine(line); test.removeComment(line); QVERIFY(line == correct); QVERIFY(line.length() == correct.length()); //invalid in C++ should not be flagged as non-comment test = DefaultCommentParser(); line = correct = " */"; test.processLine(" */"); QVERIFY(line == correct); QVERIFY(line.length() == correct.length()); } }; QTEST_MAIN(CommentParserTest); #include "commentparser.moc" diff --git a/src/common.cpp b/src/common.cpp index 17cfde7..121ce30 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -1,330 +1,317 @@ -/*************************************************************************** - * Copyright (C) 2004-2007 by Joachim Eibl * - * joachim.eibl at gmx.de * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "common.h" #include #include #include #include #include #include #include ValueMap::ValueMap() { } ValueMap::~ValueMap() { } void ValueMap::save(QTextStream& ts) { std::map::iterator i; for(i = m_map.begin(); i != m_map.end(); ++i) { QString key = i->first; QString val = i->second; ts << key << "=" << val << "\n"; } } QString ValueMap::getAsString() { QString result; std::map::iterator i; for(i = m_map.begin(); i != m_map.end(); ++i) { QString key = i->first; QString val = i->second; result += key + '=' + val + '\n'; } return result; } void ValueMap::load(QTextStream& ts) { while(!ts.atEnd()) { // until end of file... QString s = ts.readLine(); // line of text excluding '\n' int pos = s.indexOf('='); if(pos > 0) // seems not to have a tag { QString key = s.left(pos); QString val = s.mid(pos + 1); m_map[key] = val; } } } // safeStringJoin and safeStringSplit allow to convert a stringlist into a string and back // safely, even if the individual strings in the list contain the separator character. QString safeStringJoin(const QStringList& sl, char sepChar, char metaChar) { // Join the strings in the list, using the separator ',' // If a string contains the separator character, it will be replaced with "\,". // Any occurrences of "\" (one backslash) will be replaced with "\\" (2 backslashes) Q_ASSERT(sepChar != metaChar); QString sep; sep += sepChar; QString meta; meta += metaChar; QString safeString; QStringList::const_iterator i; for(i = sl.begin(); i != sl.end(); ++i) { QString s = *i; s.replace(meta, meta + meta); // "\" -> "\\" s.replace(sep, meta + sep); // "," -> "\," if(i == sl.begin()) safeString = s; else safeString += sep + s; } return safeString; } // Split a string that was joined with safeStringJoin QStringList safeStringSplit(const QString& s, char sepChar, char metaChar) { Q_ASSERT(sepChar != metaChar); QStringList sl; // Miniparser int i = 0; int len = s.length(); QString b; for(i = 0; i < len; ++i) { if(i + 1 < len && s[i] == metaChar && s[i + 1] == metaChar) { b += metaChar; ++i; } else if(i + 1 < len && s[i] == metaChar && s[i + 1] == sepChar) { b += sepChar; ++i; } else if(s[i] == sepChar) // real separator { sl.push_back(b); b = ""; } else { b += s[i]; } } if(!b.isEmpty()) sl.push_back(b); return sl; } void ValueMap::writeEntry(const QString& k, const QFont& v) { m_map[k] = v.family() + QLatin1String(",") + QString::number(v.pointSize()) + QLatin1String(",") + (v.bold() ? QLatin1String("bold") : QLatin1String("normal")); } void ValueMap::writeEntry(const QString& k, const QColor& v) { m_map[k].setNum(v.red()) + QLatin1String(",") + QString().setNum(v.green()) + QLatin1String(",") + QString().setNum(v.blue()); } void ValueMap::writeEntry(const QString& k, const QSize& v) { m_map[k].setNum(v.width()) + QLatin1String(",") + QString().setNum(v.height()); } void ValueMap::writeEntry(const QString& k, const QPoint& v) { m_map[k].setNum(v.x()) + QLatin1String(",") + QString().setNum(v.y()); } void ValueMap::writeEntry(const QString& k, int v) { m_map[k].setNum(v); } void ValueMap::writeEntry(const QString& k, bool v) { m_map[k].setNum(v); } void ValueMap::writeEntry(const QString& k, const QString& v) { m_map[k] = v; } void ValueMap::writeEntry(const QString& k, const char* v) { m_map[k] = QLatin1String(v); } void ValueMap::writeEntry(const QString& k, const QStringList& v) { m_map[k] = safeStringJoin(v); } QFont ValueMap::readFontEntry(const QString& k, const QFont* defaultVal) { QFont f = *defaultVal; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { f.setFamily(i->second.split(',')[0]); f.setPointSize(i->second.split(',')[1].toInt()); f.setBold(i->second.split(',')[2] == "bold"); } return f; } QColor ValueMap::readColorEntry(const QString& k, const QColor* defaultVal) { QColor c = *defaultVal; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { QString s = i->second; c = QColor(s.split(',')[0].toInt(), s.split(',')[1].toInt(), s.split(',')[2].toInt()); } return c; } QSize ValueMap::readSizeEntry(const QString& k, const QSize* defaultVal) { QSize size = defaultVal ? *defaultVal : QSize(600, 400); std::map::iterator i = m_map.find(k); if(i != m_map.end()) { QString s = i->second; size = QSize(s.split(',')[0].toInt(), s.split(',')[1].toInt()); } return size; } QPoint ValueMap::readPointEntry(const QString& k, const QPoint* defaultVal) { QPoint point = defaultVal ? *defaultVal : QPoint(0, 0); std::map::iterator i = m_map.find(k); if(i != m_map.end()) { QString s = i->second; point = QPoint(s.split(',')[0].toInt(), s.split(',')[1].toInt()); } return point; } bool ValueMap::readBoolEntry(const QString& k, bool bDefault) { bool b = bDefault; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { QString s = i->second; b = (s.split(',')[0].toInt() == 1); } return b; } int ValueMap::readNumEntry(const QString& k, int iDefault) { int ival = iDefault; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { QString s = i->second; ival = s.split(',')[0].toInt(); } return ival; } QString ValueMap::readStringEntry(const QString& k, const QString& sDefault) { QString sval = sDefault; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { sval = i->second; } return sval; } QStringList ValueMap::readListEntry(const QString& k, const QStringList& defaultVal) { QStringList strList; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { strList = safeStringSplit(i->second); return strList; } else return defaultVal; } QString ValueMap::readEntry(const QString& s, const QString& defaultVal) { return readStringEntry(s, defaultVal); } QString ValueMap::readEntry(const QString& s, const char* defaultVal) { return readStringEntry(s, QString::fromLatin1(defaultVal)); } QFont ValueMap::readEntry(const QString& s, const QFont& defaultVal) { return readFontEntry(s, &defaultVal); } QColor ValueMap::readEntry(const QString& s, const QColor defaultVal) { return readColorEntry(s, &defaultVal); } QSize ValueMap::readEntry(const QString& s, const QSize defaultVal) { return readSizeEntry(s, &defaultVal); } QPoint ValueMap::readEntry(const QString& s, const QPoint defaultVal) { return readPointEntry(s, &defaultVal); } bool ValueMap::readEntry(const QString& s, bool bDefault) { return readBoolEntry(s, bDefault); } int ValueMap::readEntry(const QString& s, int iDefault) { return readNumEntry(s, iDefault); } QStringList ValueMap::readEntry(const QString& s, const QStringList& defaultVal) { return readListEntry(s, defaultVal); } diff --git a/src/common.h b/src/common.h index 24b7d03..7df0523 100644 --- a/src/common.h +++ b/src/common.h @@ -1,103 +1,94 @@ -/*************************************************************************** - common.h - Things that are needed often - ------------------- - begin : Mon Mar 18 2002 - copyright : (C) 2002-2007 by Joachim Eibl - email : joachim.eibl at gmx.de - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef COMMON_H #define COMMON_H #include #include #include #include template T min3( T d1, T d2, T d3 ) { return std::min( std::min( d1, d2 ), d3 ); } template T max3( T d1, T d2, T d3 ) { return std::max( std::max( d1, d2 ), d3 ); } inline int getAtomic(QAtomicInt& ai) { return ai.load(); } inline qint64 getAtomic(QAtomicInteger& ai) { return ai.load(); } class QFont; class QColor; class QSize; class QPoint; class QStringList; class QTextStream; class ValueMap { private: std::map m_map; public: ValueMap(); virtual ~ValueMap(); void save(QTextStream& ts); void load(QTextStream& ts); QString getAsString(); // void load( const QString& s ); virtual void writeEntry(const QString&, const QFont&); virtual void writeEntry(const QString&, const QColor&); virtual void writeEntry(const QString&, const QSize&); virtual void writeEntry(const QString&, const QPoint&); virtual void writeEntry(const QString&, int); virtual void writeEntry(const QString&, bool); virtual void writeEntry(const QString&, const QStringList&); virtual void writeEntry(const QString&, const QString&); virtual void writeEntry(const QString&, const char*); QString readEntry(const QString& s, const QString& defaultVal); QString readEntry(const QString& s, const char* defaultVal); QFont readEntry(const QString& s, const QFont& defaultVal); QColor readEntry(const QString& s, const QColor defaultVal); QSize readEntry(const QString& s, const QSize defaultVal); QPoint readEntry(const QString& s, const QPoint defaultVal); bool readEntry(const QString& s, bool bDefault); int readEntry(const QString& s, int iDefault); QStringList readEntry(const QString& s, const QStringList& defaultVal); private: virtual QFont readFontEntry(const QString&, const QFont* defaultVal); virtual QColor readColorEntry(const QString&, const QColor* defaultVal); virtual QSize readSizeEntry(const QString&, const QSize* defaultVal); virtual QPoint readPointEntry(const QString&, const QPoint* defaultVal); virtual bool readBoolEntry(const QString&, bool bDefault); virtual int readNumEntry(const QString&, int iDefault); virtual QStringList readListEntry(const QString&, const QStringList& defaultVal); virtual QString readStringEntry(const QString&, const QString&); }; QStringList safeStringSplit(const QString& s, char sepChar=';', char metaChar='\\' ); QString safeStringJoin(const QStringList& sl, char sepChar=';', char metaChar='\\' ); #endif diff --git a/src/cvsignorelist.cpp b/src/cvsignorelist.cpp index ff46e6f..2f4ee5a 100644 --- a/src/cvsignorelist.cpp +++ b/src/cvsignorelist.cpp @@ -1,179 +1,177 @@ -/*************************************************************************** - * class CvsIgnoreList from Cervisia cvsdir.cpp * - * Copyright (C) 1999-2002 Bernd Gehrmann * - * with elements from class StringMatcher * - * Copyright (c) 2003 Andre Woebbeking * - * Modifications for KDiff3 by Joachim Eibl * - * * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - ***************************************************************************/ +/* + class CvsIgnoreList from Cervisia cvsdir.cpp + SPDX-FileCopyrightText: 1999-2002 Bernd Gehrmann + with elements from class StringMatcher + SPDX-FileCopyrightText: 2003 Andre Woebbeking + Modifications for KDiff3 by Joachim Eibl + + SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: GPL-2.0-or-later + */ #include "cvsignorelist.h" #include "TypeUtils.h" #include #include void CvsIgnoreList::init(FileAccess& dir, const t_DirectoryList* pDirList) { static const QString ignorestr = QString::fromLatin1(". .. core RCSLOG tags TAGS RCS SCCS .make.state " ".nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj " "*.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$"); constexpr char varname[] = "CVSIGNORE"; addEntriesFromString(ignorestr); addEntriesFromFile(QDir::homePath() + "/.cvsignore"); if(qEnvironmentVariableIsSet(varname) && !qEnvironmentVariableIsEmpty(varname)) { addEntriesFromString(QString::fromLocal8Bit(qgetenv(varname))); } const bool bUseLocalCvsIgnore = cvsIgnoreExists(pDirList); if(bUseLocalCvsIgnore) { FileAccess file(dir); file.addPath(".cvsignore"); if(file.exists() && file.isLocal()) addEntriesFromFile(file.absoluteFilePath()); else { file.createLocalCopy(); addEntriesFromFile(file.getTempName()); } } } void CvsIgnoreList::addEntriesFromString(const QString& str) { QStringList patternList = str.split(' '); for(const QString& pattern : patternList) { addEntry(pattern); } } /* MocIgnoreFile is incapatiable with addEntriesFromFile so do nothing in AUTORUN mode */ void CvsIgnoreList::addEntriesFromFile(const QString& name) { #ifdef AUTORUN Q_UNUSED(name) #else QFile file(name); if(file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); while(!stream.atEnd()) { addEntry(stream.readLine()); } } #endif } void CvsIgnoreList::addEntry(const QString& pattern) { if(pattern != QString("!")) { if(pattern.isEmpty()) return; // The general match is general but slow. // Special tests for '*' and '?' at the beginning or end of a pattern // allow fast checks. // Count number of '*' and '?' unsigned int nofMetaCharacters = 0; const QChar* pos; pos = pattern.unicode(); const QChar* posEnd; posEnd = pos + pattern.length(); while(pos < posEnd) { if(*pos == QChar('*') || *pos == QChar('?')) ++nofMetaCharacters; ++pos; } if(nofMetaCharacters == 0) { m_exactPatterns.append(pattern); } else if(nofMetaCharacters == 1) { if(pattern.at(0) == QChar('*')) { m_endPatterns.append(pattern.right(pattern.length() - 1)); } else if(pattern.at(pattern.length() - 1) == QChar('*')) { m_startPatterns.append(pattern.left(pattern.length() - 1)); } else { m_generalPatterns.append(pattern); } } else { m_generalPatterns.append(pattern); } } else { m_exactPatterns.clear(); m_startPatterns.clear(); m_endPatterns.clear(); m_generalPatterns.clear(); } } bool CvsIgnoreList::matches(const QString& text, bool bCaseSensitive) const { QRegExp regexp(text, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); if(m_exactPatterns.indexOf(regexp) >= 0) { return true; } for(const QString& startPattern: m_startPatterns) { if(text.startsWith(startPattern, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive)) { return true; } } for(const QString& endPattern: m_endPatterns) { if(text.endsWith(endPattern, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive)) { return true; } } for(const QString& globStr : m_generalPatterns) { QRegExp pattern(globStr, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::Wildcard); if(pattern.exactMatch(text)) return true; } return false; } bool CvsIgnoreList::cvsIgnoreExists(const t_DirectoryList* pDirList) { for(const FileAccess& dir : *pDirList) { if(dir.fileName() == ".cvsignore") return true; } return false; } diff --git a/src/cvsignorelist.h b/src/cvsignorelist.h index fe3a7e3..9bb193d 100644 --- a/src/cvsignorelist.h +++ b/src/cvsignorelist.h @@ -1,47 +1,45 @@ -/*************************************************************************** - * class CvsIgnoreList from Cervisia cvsdir.cpp * - * Copyright (C) 1999-2002 Bernd Gehrmann * - * with elements from class StringMatcher * - * Copyright (c) 2003 Andre Woebbeking * - * Modifications for KDiff3 by Joachim Eibl * - * * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - ***************************************************************************/ +/* + class CvsIgnoreList from Cervisia cvsdir.cpp + SPDX-FileCopyrightText: 1999-2002 Bernd Gehrmann + with elements from class StringMatcher + SPDX-FileCopyrightText: 2003 Andre Woebbeking + Modifications for KDiff3 by Joachim Eibl + + SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: GPL-2.0-or-later + */ #ifndef CVSIGNORELIST_H #define CVSIGNORELIST_H #ifndef AUTOTEST #include "fileaccess.h" #else #include "MocIgnoreFile.h" #endif #include #include class CvsIgnoreList { public: CvsIgnoreList() {} void init(FileAccess& dir, const t_DirectoryList* pDirList); bool matches(const QString& text, bool bCaseSensitive) const; private: friend class CvsIgnoreListTest; bool cvsIgnoreExists(const t_DirectoryList* pDirList); void addEntriesFromString(const QString& str); void addEntriesFromFile(const QString& name); void addEntry(const QString& pattern); QStringList m_exactPatterns; QStringList m_startPatterns; QStringList m_endPatterns; QStringList m_generalPatterns; }; #endif diff --git a/src/diff.cpp b/src/diff.cpp index 56bf6ec..70d5d8d 100644 --- a/src/diff.cpp +++ b/src/diff.cpp @@ -1,1593 +1,1590 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "diff.h" #include "gnudiff_diff.h" #include "merger.h" #include "options.h" #include "progress.h" #include #include #include #include #include #include constexpr bool g_bIgnoreWhiteSpace = true; QSharedPointer Diff3Line::m_pDiffBufferInfo = nullptr; int LineData::width(int tabSize) const { QString pLine = getLine(); int w = 0; int j = 0; for(int i = 0; i < size(); ++i) { if(pLine[i] == '\t') { for(j %= tabSize; j < tabSize; ++j) ++w; j = 0; } else { ++w; ++j; } } return w; } /* Implement support for g_bIgnoreWhiteSpace */ bool LineData::equal(const LineData& l1, const LineData& l2) { if(l1.getLine() == nullptr || l2.getLine() == nullptr) return false; if(g_bIgnoreWhiteSpace) { // Ignore white space diff QString::const_iterator p1 = l1.getLine().begin(); QString::const_iterator p1End = l1.getLine().end(); QString::const_iterator p2 = l2.getLine().begin(); QString::const_iterator p2End = l2.getLine().end(); for(; p1 != p1End && p2 != p2End; p1++, p2++) { while(isWhite(*p1) && p1 != p1End) ++p1; while(isWhite(*p2) && p2 != p2End) ++p2; if(*p1 != *p2) return false; } return (p1 == p1End && p2 == p2End); } else { return (l1.size() == l2.size() && QString::compare(l1.getLine(), l2.getLine()) == 0); } } // First step void Diff3LineList::calcDiff3LineListUsingAB(const DiffList* pDiffListAB) { // First make d3ll for AB (from pDiffListAB) DiffList::const_iterator i = pDiffListAB->begin(); LineRef::LineType lineA = 0; LineRef::LineType lineB = 0; Diff d; qCInfo(kdiffMain) << "Enter: calcDiff3LineListUsingAB" ; for(;;) { if(d.numberOfEquals() == 0 && d.diff1() == 0 && d.diff2() == 0) { if(i != pDiffListAB->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.numberOfEquals() > 0) { d3l.bAEqB = true; d3l.setLineA(lineA); d3l.setLineB(lineB); d.adjustNumberOfEquals(-1); ++lineA; ++lineB; } else if(d.diff1() > 0 && d.diff2() > 0) { d3l.setLineA(lineA); d3l.setLineB(lineB); d.adjustDiff1(-1); d.adjustDiff2(-1); ++lineA; ++lineB; } else if(d.diff1() > 0) { d3l.setLineA(lineA); d.adjustDiff1(-1); ++lineA; } else if(d.diff2() > 0) { d3l.setLineB(lineB); d.adjustDiff2(-1); ++lineB; } Q_ASSERT(d.numberOfEquals() >= 0); qCDebug(kdiffCore) << "lineA = " << d3l.getLineA() << ", lineB = " << d3l.getLineB() ; push_back(d3l); } qCInfo(kdiffMain) << "Leave: calcDiff3LineListUsingAB" ; } // Second step void Diff3LineList::calcDiff3LineListUsingAC(const DiffList* pDiffListAC) { //////////////// // Now insert data from C using pDiffListAC DiffList::const_iterator i = pDiffListAC->begin(); Diff3LineList::iterator i3 = begin(); LineRef::LineType lineA = 0; LineRef::LineType lineC = 0; Diff d; for(;;) { if(d.numberOfEquals() == 0 && d.diff1() == 0 && d.diff2() == 0) { if(i != pDiffListAC->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.numberOfEquals() > 0) { // Find the corresponding lineA while(i3->getLineA() != lineA) ++i3; i3->setLineC(lineC); i3->bAEqC = true; i3->bBEqC = i3->isEqualAB(); d.adjustNumberOfEquals(-1); ++lineA; ++lineC; ++i3; } else if(d.diff1() > 0 && d.diff2() > 0) { d3l.setLineC(lineC); insert(i3, d3l); d.adjustDiff1(-1); d.adjustDiff2(-1); ++lineA; ++lineC; } else if(d.diff1() > 0) { d.adjustDiff1(-1); ++lineA; } else if(d.diff2() > 0) { d3l.setLineC(lineC); insert(i3, d3l); d.adjustDiff2(-1); ++lineC; } } } // Third step void Diff3LineList::calcDiff3LineListUsingBC(const DiffList* pDiffListBC) { //////////////// // Now improve the position of data from C using pDiffListBC // If a line from C equals a line from A then it is in the // same Diff3Line already. // If a line from C equals a line from B but not A, this // information will be used here. DiffList::const_iterator i = pDiffListBC->begin(); Diff3LineList::iterator i3b = begin(); Diff3LineList::iterator i3c = begin(); LineRef::LineType lineB = 0; LineRef::LineType lineC = 0; Diff d; for(;;) { if(d.numberOfEquals() == 0 && d.diff1() == 0 && d.diff2() == 0) { if(i != pDiffListBC->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.numberOfEquals() > 0) { // Find the corresponding lineB and lineC while(i3b != end() && i3b->getLineB() != lineB) ++i3b; while(i3c != end() && i3c->getLineC() != lineC) ++i3c; Q_ASSERT(i3b != end()); Q_ASSERT(i3c != end()); if(i3b == i3c) { Q_ASSERT(i3b->getLineC() == lineC); i3b->bBEqC = true; } else { // Is it possible to move this line up? // Test if no other B's are used between i3c and i3b // First test which is before: i3c or i3b ? Diff3LineList::iterator i3c1 = i3c; Diff3LineList::iterator i3b1 = i3b; while(i3c1 != i3b && i3b1 != i3c) { Q_ASSERT(i3b1 != end() || i3c1 != end()); if(i3c1 != end()) ++i3c1; if(i3b1 != end()) ++i3b1; } if(i3c1 == i3b && !i3b->isEqualAB()) // i3c before i3b { Diff3LineList::iterator i3 = i3c; int nofDisturbingLines = 0; while(i3 != i3b && i3 != end()) { if(i3->getLineB().isValid()) ++nofDisturbingLines; ++i3; } if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 ) { Diff3LineList::iterator i3_last_equal_A = end(); i3 = i3c; while(i3 != i3b) { if(i3->isEqualAB()) { i3_last_equal_A = i3; } ++i3; } /* If i3_last_equal_A isn't still set to d3ll.end(), then * we've found a line in A that is equal to one in B * somewhere between i3c and i3b */ bool before_or_on_equal_line_in_A = (i3_last_equal_A != end()); // Move the disturbing lines up, out of sight. i3 = i3c; while(i3 != i3b) { if(i3->getLineB().isValid() || (before_or_on_equal_line_in_A && i3->getLineA().isValid())) { d3l.setLineB(i3->getLineB()); i3->getLineB().invalidate(); // Move A along if it matched B if(before_or_on_equal_line_in_A) { d3l.setLineA(i3->getLineA()); d3l.bAEqB = i3->isEqualAB(); i3->getLineA().invalidate(); i3->bAEqC = false; } i3->bAEqB = false; i3->bBEqC = false; insert(i3c, d3l); } if(i3 == i3_last_equal_A) { before_or_on_equal_line_in_A = false; } ++i3; } nofDisturbingLines = 0; } if(nofDisturbingLines == 0) { // Yes, the line from B can be moved. i3b->getLineB().invalidate(); // This might leave an empty line: removed later. i3b->bAEqB = false; i3b->bBEqC = false; i3c->setLineB(lineB); i3c->bBEqC = true; i3c->bAEqB = i3c->isEqualAC(); } } else if(i3b1 == i3c && !i3c->isEqualAC()) { Diff3LineList::iterator i3 = i3b; int nofDisturbingLines = 0; while(i3 != i3c && i3 != end()) { if(i3->getLineC().isValid()) ++nofDisturbingLines; ++i3; } if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 ) { Diff3LineList::iterator i3_last_equal_A = end(); i3 = i3b; while(i3 != i3c) { if(i3->isEqualAC()) { i3_last_equal_A = i3; } ++i3; } /* If i3_last_equal_A isn't still set to d3ll.end(), then * we've found a line in A that is equal to one in C * somewhere between i3b and i3c */ bool before_or_on_equal_line_in_A = (i3_last_equal_A != end()); // Move the disturbing lines up. i3 = i3b; while(i3 != i3c) { if(i3->getLineC().isValid() || (before_or_on_equal_line_in_A && i3->getLineA().isValid())) { d3l.setLineC(i3->getLineC()); i3->getLineC().invalidate(); // Move A along if it matched C if(before_or_on_equal_line_in_A) { d3l.setLineA(i3->getLineA()); d3l.bAEqC = i3->isEqualAC(); i3->getLineA().invalidate(); i3->bAEqB = false; } i3->bAEqC = false; i3->bBEqC = false; insert(i3b, d3l); } if(i3 == i3_last_equal_A) { before_or_on_equal_line_in_A = false; } ++i3; } nofDisturbingLines = 0; } if(nofDisturbingLines == 0) { // Yes, the line from C can be moved. i3c->getLineC().invalidate(); // This might leave an empty line: removed later. i3c->bAEqC = false; i3c->bBEqC = false; i3b->setLineC(lineC); i3b->bBEqC = true; i3b->bAEqC = i3b->isEqualAB(); } } } d.adjustNumberOfEquals(-1); ++lineB; ++lineC; ++i3b; ++i3c; } else if(d.diff1() > 0) { Diff3LineList::iterator i3 = i3b; while(i3->getLineB() != lineB) ++i3; if(i3 != i3b && !i3->isEqualAB()) { // Take B from this line and move it up as far as possible d3l.setLineB(lineB); insert(i3b, d3l); i3->getLineB().invalidate(); } else { i3b = i3; } d.adjustDiff1(-1); ++lineB; ++i3b; if(d.diff2() > 0) { d.adjustDiff2(-1); ++lineC; } } else if(d.diff2() > 0) { d.adjustDiff2(-1); ++lineC; } } /* Diff3LineList::iterator it = d3ll.begin(); int li=0; for( ; it!=d3ll.end(); ++it, ++li ) { printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", li, it->getLineA(), it->getLineB(), it->getLineC(), it->isEqualAB() ? '=' : '!', it->isEqualAC() ? '=' : '!', it->isEqualBC() ? '=' : '!' ); } printf("\n");*/ } // Test if the move would pass a barrier. Return true if not. bool ManualDiffHelpList::isValidMove(int line1, int line2, e_SrcSelector winIdx1, e_SrcSelector winIdx2) const { if(line1 >= 0 && line2 >= 0) { ManualDiffHelpList::const_iterator i; for(i = begin(); i != end(); ++i) { const ManualDiffHelpEntry& mdhe = *i; if(!mdhe.isValidMove(line1, line2, winIdx1, winIdx2)) return false; } } return true; // no barrier passed. } void ManualDiffHelpList::insertEntry(e_SrcSelector winIdx, LineRef firstLine, LineRef lastLine) { // The manual diff help list must be sorted and compact. // "Compact" means that upper items can't be empty if lower items contain data. // First insert the new item without regarding compactness. // If the new item overlaps with previous items then the previous items will be removed. ManualDiffHelpEntry mdhe; mdhe.firstLine(winIdx) = firstLine; mdhe.lastLine(winIdx) = lastLine; ManualDiffHelpList::iterator i; for(i = begin(); i != end(); ++i) { LineRef& l1 = i->firstLine(winIdx); LineRef& l2 = i->lastLine(winIdx); if(l1 >= 0 && l2 >= 0) { if((firstLine <= l1 && lastLine >= l1) || (firstLine <= l2 && lastLine >= l2)) { // overlap l1.invalidate(); l2.invalidate(); } if(firstLine < l1 && lastLine < l1) { // insert before this position insert(i, mdhe); break; } } } if(i == end()) { insert(i, mdhe); } // Now make the list compact for(int wIdx = A; wIdx <= Max; ++wIdx) { ManualDiffHelpList::iterator iEmpty = begin(); for(i = begin(); i != end(); ++i) { if(iEmpty->firstLine((e_SrcSelector)wIdx).isValid()) { ++iEmpty; continue; } if(i->firstLine((e_SrcSelector)wIdx).isValid()) // Current item is not empty -> move it to the empty place { std::swap(iEmpty, i); ++iEmpty; } } } remove(ManualDiffHelpEntry()); // Remove all completely empty items. } bool ManualDiffHelpEntry::isValidMove(int line1, int line2, e_SrcSelector winIdx1, e_SrcSelector winIdx2) const { // Barrier int l1 = winIdx1 == A ? lineA1 : winIdx1 == B ? lineB1 : lineC1; int l2 = winIdx2 == A ? lineA1 : winIdx2 == B ? lineB1 : lineC1; if(l1 >= 0 && l2 >= 0) { if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2)) return false; l1 = winIdx1 == A ? lineA2 : winIdx1 == B ? lineB2 : lineC2; l2 = winIdx2 == A ? lineA2 : winIdx2 == B ? lineB2 : lineC2; ++l1; ++l2; if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2)) return false; } return true; } int ManualDiffHelpEntry::calcManualDiffFirstDiff3LineIdx(const Diff3LineVector& d3lv) { int i; for(i = 0; i < d3lv.size(); ++i) { const Diff3Line& d3l = *d3lv[i]; if((lineA1.isValid() && lineA1 == d3l.getLineA()) || (lineB1.isValid() && lineB1 == d3l.getLineB()) || (lineC1.isValid() && lineC1 == d3l.getLineC())) return i; } return -1; } static bool runDiff(const QVector* p1, const qint32 index1, LineRef size1, const QVector* p2, const qint32 index2, LineRef size2, DiffList& diffList, const QSharedPointer &pOptions) { ProgressProxy pp; static GnuDiff gnuDiff; // All values are initialized with zeros. pp.setCurrent(0); diffList.clear(); if(p1 == nullptr || (*p1)[index1].getLine() == nullptr || p2 == nullptr || (*p2)[index2].getLine() == nullptr || size1 == 0 || size2 == 0) { if(p1 != nullptr && p2 != nullptr && (*p1)[index1].getLine() == nullptr && (*p2)[index2].getLine() == nullptr && size1 == size2) diffList.push_back(Diff(size1, 0, 0)); else { diffList.push_back(Diff(0, size1, size2)); } } else { GnuDiff::comparison comparisonInput; memset(&comparisonInput, 0, sizeof(comparisonInput)); comparisonInput.parent = nullptr; comparisonInput.file[0].buffer = (*p1)[index1].getBuffer()->unicode() + (*p1)[index1].getOffset(); //ptr to buffer comparisonInput.file[0].buffered = ((*p1)[size1].getOffset() - 1); // size of buffer comparisonInput.file[1].buffer = (*p2)[index2].getBuffer()->unicode() + (*p2)[index2].getOffset(); //ptr to buffer comparisonInput.file[1].buffered = ((*p2)[size2].getOffset() - 1); // size of buffer gnuDiff.ignore_white_space = GnuDiff::IGNORE_ALL_SPACE; // I think nobody needs anything else ... gnuDiff.bIgnoreWhiteSpace = true; gnuDiff.bIgnoreNumbers = pOptions->m_bIgnoreNumbers; gnuDiff.minimal = pOptions->m_bTryHard; gnuDiff.ignore_case = false; GnuDiff::change* script = gnuDiff.diff_2_files(&comparisonInput); LineRef equalLinesAtStart = (LineRef)comparisonInput.file[0].prefix_lines; LineRef currentLine1 = 0; LineRef currentLine2 = 0; GnuDiff::change* p = nullptr; for(GnuDiff::change* e = script; e; e = p) { Diff d((LineRef)(e->line0 - currentLine1), e->deleted, e->inserted); Q_ASSERT(d.numberOfEquals() == e->line1 - currentLine2); currentLine1 += (LineRef)(d.numberOfEquals() + d.diff1()); currentLine2 += (LineRef)(d.numberOfEquals() + d.diff2()); diffList.push_back(d); p = e->link; free(e); } if(diffList.empty()) { qint32 numofEquals = std::min(size1, size2); Diff d(numofEquals, size1 - numofEquals, size2 - numofEquals); diffList.push_back(d); } else { diffList.front().adjustNumberOfEquals(equalLinesAtStart); currentLine1 += equalLinesAtStart; currentLine2 += equalLinesAtStart; LineRef nofEquals = std::min(size1 - currentLine1, size2 - currentLine2); if(nofEquals == 0) { diffList.back().adjustDiff1(size1 - currentLine1); diffList.back().adjustDiff2(size2 - currentLine2); } else { Diff d(nofEquals, size1 - currentLine1 - nofEquals, size2 - currentLine2 - nofEquals); diffList.push_back(d); } } } // Verify difflist { LineRef::LineType l1 = 0; LineRef::LineType l2 = 0; DiffList::iterator i; for(i = diffList.begin(); i != diffList.end(); ++i) { l1 += i->numberOfEquals() + i->diff1(); l2 += i->numberOfEquals() + i->diff2(); } Q_ASSERT(l1 == size1 && l2 == size2); } pp.setCurrent(1); return true; } bool ManualDiffHelpList::runDiff(const QVector* p1, LineRef size1, const QVector* p2, LineRef size2, DiffList& diffList, e_SrcSelector winIdx1, e_SrcSelector winIdx2, const QSharedPointer &pOptions) { diffList.clear(); DiffList diffList2; int l1begin = 0; int l2begin = 0; ManualDiffHelpList::const_iterator i; for(i = begin(); i != end(); ++i) { const ManualDiffHelpEntry& mdhe = *i; LineRef l1end = mdhe.getLine1(winIdx1); LineRef l2end = mdhe.getLine1(winIdx2); if(l1end.isValid() && l2end.isValid()) { ::runDiff(p1, l1begin, l1end - l1begin, p2, l2begin, l2end - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); l1begin = l1end; l2begin = l2end; l1end = mdhe.getLine2(winIdx1); l2end = mdhe.getLine2(winIdx2); if(l1end.isValid() && l2end.isValid()) { ++l1end; // point to line after last selected line ++l2end; ::runDiff(p1, l1begin, l1end - l1begin, p2, l2begin, l2end - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); l1begin = l1end; l2begin = l2end; } } } ::runDiff(p1, l1begin, size1 - l1begin, p2, l2begin, size2 - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); return true; } void Diff3LineList::correctManualDiffAlignment(ManualDiffHelpList* pManualDiffHelpList) { if(pManualDiffHelpList->empty()) return; // If a line appears unaligned in comparison to the manual alignment, correct this. ManualDiffHelpList::iterator iMDHL; for(iMDHL = pManualDiffHelpList->begin(); iMDHL != pManualDiffHelpList->end(); ++iMDHL) { Diff3LineList::iterator i3 = begin(); e_SrcSelector missingWinIdx = None; int alignedSum = (!iMDHL->getLine1(A).isValid() ? 0 : 1) + (!iMDHL->getLine1(B).isValid() ? 0 : 1) + (!iMDHL->getLine1(C).isValid() ? 0 : 1); if(alignedSum == 2) { // If only A & B are aligned then let C rather be aligned with A // If only A & C are aligned then let B rather be aligned with A // If only B & C are aligned then let A rather be aligned with B missingWinIdx = !iMDHL->getLine1(A).isValid() ? A : (!iMDHL->getLine1(B).isValid() ? B : C); } else if(alignedSum <= 1) { return; } // At the first aligned line, move up the two other lines into new d3ls until the second input is aligned // Then move up the third input until all three lines are aligned. int wi = None; for(; i3 != end(); ++i3) { for(wi = A; wi <= Max; ++wi) { if(i3->getLineInFile((e_SrcSelector)wi).isValid() && iMDHL->firstLine((e_SrcSelector)wi) == i3->getLineInFile((e_SrcSelector)wi)) break; } if(wi <= Max) break; } if(wi >= A && wi <= Max) { // Found manual alignment for one source Diff3LineList::iterator iDest = i3; // Move lines up until the next firstLine is found. Omit wi from move and search. int wi2 = None; for(; i3 != end(); ++i3) { for(wi2 = A; wi2 <= C; ++wi2) { if(wi != wi2 && i3->getLineInFile((e_SrcSelector)wi2).isValid() && iMDHL->firstLine((e_SrcSelector)wi2) == i3->getLineInFile((e_SrcSelector)wi2)) break; } if(wi2 > C) { // Not yet found // Move both others up Diff3Line d3l; // Move both up if(wi == A) // Move B and C up { d3l.bBEqC = i3->isEqualBC(); d3l.setLineB(i3->getLineB()); d3l.setLineC(i3->getLineC()); i3->getLineB().invalidate(); i3->getLineC().invalidate(); } if(wi == B) // Move A and C up { d3l.bAEqC = i3->isEqualAC(); d3l.setLineA(i3->getLineA()); d3l.setLineC(i3->getLineC()); i3->getLineA().invalidate(); i3->getLineC().invalidate(); } if(wi == C) // Move A and B up { d3l.bAEqB = i3->isEqualAB(); d3l.setLineA(i3->getLineA()); d3l.setLineB(i3->getLineB()); i3->getLineA().invalidate(); i3->getLineB().invalidate(); } i3->bAEqB = false; i3->bAEqC = false; i3->bBEqC = false; insert(iDest, d3l); } else { // align the found line with the line we already have here if(i3 != iDest) { if(wi2 == A) { iDest->setLineA(i3->getLineA()); i3->getLineA().invalidate(); i3->bAEqB = false; i3->bAEqC = false; } else if(wi2 == B) { iDest->setLineB(i3->getLineB()); i3->getLineB().invalidate(); i3->bAEqB = false; i3->bBEqC = false; } else if(wi2 == C) { iDest->setLineC(i3->getLineC()); i3->getLineC().invalidate(); i3->bBEqC = false; i3->bAEqC = false; } } if(missingWinIdx != 0) { for(; i3 != end(); ++i3) { e_SrcSelector wi3 = missingWinIdx; if(i3->getLineInFile((e_SrcSelector)wi3).isValid()) { // not found, move the line before iDest Diff3Line d3l; if(wi3 == A) { if(i3->isEqualAB()) // Stop moving lines up if one equal is found. break; d3l.setLineA(i3->getLineA()); i3->getLineA().invalidate(); i3->bAEqB = false; i3->bAEqC = false; } if(wi3 == B) { if(i3->isEqualAB()) break; d3l.setLineB(i3->getLineB()); i3->getLineB().invalidate(); i3->bAEqB = false; i3->bBEqC = false; } if(wi3 == C) { if(i3->isEqualAC()) break; d3l.setLineC(i3->getLineC()); i3->getLineC().invalidate(); i3->bAEqC = false; i3->bBEqC = false; } insert(iDest, d3l); } } // for(), searching for wi3 } break; } } // for(), searching for wi2 } // if, wi found } // for (iMDHL) } // Fourth step void Diff3LineList::calcDiff3LineListTrim( const QVector* pldA, const QVector* pldB, const QVector* pldC, ManualDiffHelpList* pManualDiffHelpList) { const Diff3Line d3l_empty; remove(d3l_empty); Diff3LineList::iterator i3 = begin(); Diff3LineList::iterator i3A = begin(); Diff3LineList::iterator i3B = begin(); Diff3LineList::iterator i3C = begin(); int line = 0; // diff3line counters int lineA = 0; // int lineB = 0; int lineC = 0; ManualDiffHelpList::iterator iMDHL = pManualDiffHelpList->begin(); // The iterator i3 and the variable line look ahead. // The iterators i3A, i3B, i3C and corresponding lineA, lineB and lineC stop at empty lines, if found. // If possible, then the texts from the look ahead will be moved back to the empty places. for(; i3 != end(); ++i3, ++line) { if(iMDHL != pManualDiffHelpList->end()) { if((i3->getLineA().isValid() && i3->getLineA() == iMDHL->getLine1(A)) || (i3->getLineB().isValid() && i3->getLineB() == iMDHL->getLine1(B)) || (i3->getLineC().isValid() && i3->getLineC() == iMDHL->getLine1(C))) { i3A = i3; i3B = i3; i3C = i3; lineA = line; lineB = line; lineC = line; ++iMDHL; } } if(line > lineA && i3->getLineA().isValid() && i3A->getLineB().isValid() && i3A->isEqualBC() && LineData::equal((*pldA)[i3->getLineA()], (*pldB)[i3A->getLineB()]) && pManualDiffHelpList->isValidMove(i3->getLineA(), i3A->getLineB(), A, B) && pManualDiffHelpList->isValidMove(i3->getLineA(), i3A->getLineC(), A, C)) { // Empty space for A. A matches B and C in the empty line. Move it up. i3A->setLineA(i3->getLineA()); i3A->bAEqB = true; i3A->bAEqC = true; i3->getLineA().invalidate(); i3->bAEqB = false; i3->bAEqC = false; ++i3A; ++lineA; } if(line > lineB && i3->getLineB().isValid() && i3B->getLineA().isValid() && i3B->isEqualAC() && LineData::equal((*pldB)[i3->getLineB()], (*pldA)[i3B->getLineA()]) && pManualDiffHelpList->isValidMove(i3->getLineB(), i3B->getLineA(), B, A) && pManualDiffHelpList->isValidMove(i3->getLineB(), i3B->getLineC(), B, C)) { // Empty space for B. B matches A and C in the empty line. Move it up. i3B->setLineB(i3->getLineB()); i3B->bAEqB = true; i3B->bBEqC = true; i3->getLineB().invalidate(); i3->bAEqB = false; i3->bBEqC = false; ++i3B; ++lineB; } if(line > lineC && i3->getLineC().isValid() && i3C->getLineA().isValid() && i3C->isEqualAB() && LineData::equal((*pldC)[i3->getLineC()], (*pldA)[i3C->getLineA()]) && pManualDiffHelpList->isValidMove(i3->getLineC(), i3C->getLineA(), C, A) && pManualDiffHelpList->isValidMove(i3->getLineC(), i3C->getLineB(), C, B)) { // Empty space for C. C matches A and B in the empty line. Move it up. i3C->setLineC(i3->getLineC()); i3C->bAEqC = true; i3C->bBEqC = true; i3->getLineC().invalidate(); i3->bAEqC = false; i3->bBEqC = false; ++i3C; ++lineC; } if(line > lineA && i3->getLineA().isValid() && !i3->isEqualAB() && !i3->isEqualAC() && pManualDiffHelpList->isValidMove(i3->getLineA(), i3A->getLineB(), A, B) && pManualDiffHelpList->isValidMove(i3->getLineA(), i3A->getLineC(), A, C)) { // Empty space for A. A doesn't match B or C. Move it up. i3A->setLineA(i3->getLineA()); i3->getLineA().invalidate(); if(i3A->getLineB().isValid() && LineData::equal((*pldA)[i3A->getLineA()], (*pldB)[i3A->getLineB()])) { i3A->bAEqB = true; } if((i3A->isEqualAB() && i3A->isEqualBC()) || (i3A->getLineC().isValid() && LineData::equal((*pldA)[i3A->getLineA()], (*pldC)[i3A->getLineC()]))) { i3A->bAEqC = true; } ++i3A; ++lineA; } if(line > lineB && i3->getLineB().isValid() && !i3->isEqualAB() && !i3->isEqualBC() && pManualDiffHelpList->isValidMove(i3->getLineB(), i3B->getLineA(), B, A) && pManualDiffHelpList->isValidMove(i3->getLineB(), i3B->getLineC(), B, C)) { // Empty space for B. B matches neither A nor C. Move B up. i3B->setLineB(i3->getLineB()); i3->getLineB().invalidate(); if(i3B->getLineA().isValid() && LineData::equal((*pldA)[i3B->getLineA()], (*pldB)[i3B->getLineB()])) { i3B->bAEqB = true; } if((i3B->isEqualAB() && i3B->isEqualAC()) || (i3B->getLineC().isValid() && LineData::equal((*pldB)[i3B->getLineB()], (*pldC)[i3B->getLineC()]))) { i3B->bBEqC = true; } ++i3B; ++lineB; } if(line > lineC && i3->getLineC().isValid() && !i3->isEqualAC() && !i3->isEqualBC() && pManualDiffHelpList->isValidMove(i3->getLineC(), i3C->getLineA(), C, A) && pManualDiffHelpList->isValidMove(i3->getLineC(), i3C->getLineB(), C, B)) { // Empty space for C. C matches neither A nor B. Move C up. i3C->setLineC(i3->getLineC()); i3->getLineC().invalidate(); if(i3C->getLineA().isValid() && LineData::equal((*pldA)[i3C->getLineA()], (*pldC)[i3C->getLineC()])) { i3C->bAEqC = true; } if((i3C->isEqualAC() && i3C->isEqualAB()) || (i3C->getLineB().isValid() && LineData::equal((*pldB)[i3C->getLineB()], (*pldC)[i3C->getLineC()]))) { i3C->bBEqC = true; } ++i3C; ++lineC; } if(line > lineA && line > lineB && i3->getLineA().isValid() && i3->isEqualAB() && !i3->isEqualAC()) { // Empty space for A and B. A matches B, but not C. Move A & B up. Diff3LineList::iterator i = lineA > lineB ? i3A : i3B; int l = lineA > lineB ? lineA : lineB; if(pManualDiffHelpList->isValidMove(i->getLineC(), i3->getLineA(), C, A) && pManualDiffHelpList->isValidMove(i->getLineC(), i3->getLineB(), C, B)) { i->setLineA(i3->getLineA()); i->setLineB(i3->getLineB()); i->bAEqB = true; if(i->getLineC().isValid() && LineData::equal((*pldA)[i->getLineA()], (*pldC)[i->getLineC()])) { i->bAEqC = true; i->bBEqC = true; } i3->getLineA().invalidate(); i3->getLineB().invalidate(); i3->bAEqB = false; i3A = i; i3B = i; ++i3A; ++i3B; lineA = l + 1; lineB = l + 1; } } else if(line > lineA && line > lineC && i3->getLineA().isValid() && i3->isEqualAC() && !i3->isEqualAB()) { // Empty space for A and C. A matches C, but not B. Move A & C up. Diff3LineList::iterator i = lineA > lineC ? i3A : i3C; int l = lineA > lineC ? lineA : lineC; if(pManualDiffHelpList->isValidMove(i->getLineB(), i3->getLineA(), B, A) && pManualDiffHelpList->isValidMove(i->getLineB(), i3->getLineC(), B, C)) { i->setLineA(i3->getLineA()); i->setLineC(i3->getLineC()); i->bAEqC = true; if(i->getLineB().isValid() && LineData::equal((*pldA)[i->getLineA()], (*pldB)[i->getLineB()])) { i->bAEqB = true; i->bBEqC = true; } i3->getLineA().invalidate(); i3->getLineC().invalidate(); i3->bAEqC = false; i3A = i; i3C = i; ++i3A; ++i3C; lineA = l + 1; lineC = l + 1; } } else if(line > lineB && line > lineC && i3->getLineB().isValid() && i3->isEqualBC() && !i3->isEqualAC()) { // Empty space for B and C. B matches C, but not A. Move B & C up. Diff3LineList::iterator i = lineB > lineC ? i3B : i3C; int l = lineB > lineC ? lineB : lineC; if(pManualDiffHelpList->isValidMove(i->getLineA(), i3->getLineB(), A, B) && pManualDiffHelpList->isValidMove(i->getLineA(), i3->getLineC(), A, C)) { i->setLineB(i3->getLineB()); i->setLineC(i3->getLineC()); i->bBEqC = true; if(i->getLineA().isValid() && LineData::equal((*pldA)[i->getLineA()], (*pldB)[i->getLineB()])) { i->bAEqB = true; i->bAEqC = true; } i3->getLineB().invalidate(); i3->getLineC().invalidate(); i3->bBEqC = false; i3B = i; i3C = i; ++i3B; ++i3C; lineB = l + 1; lineC = l + 1; } } if(i3->getLineA().isValid()) { lineA = line + 1; i3A = i3; ++i3A; } if(i3->getLineB().isValid()) { lineB = line + 1; i3B = i3; ++i3B; } if(i3->getLineC().isValid()) { lineC = line + 1; i3C = i3; ++i3C; } } remove(d3l_empty); /* Diff3LineList::iterator it = d3ll.begin(); int li=0; for( ; it!=d3ll.end(); ++it, ++li ) { printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", li, it->getLineA(), it->getLineB(), it->getLineC(), it->isEqualAB() ? '=' : '!', it->isEqualAC() ? '=' : '!', it->isEqualBC() ? '=' : '!' ); } */ } void DiffBufferInfo::init(Diff3LineList* pD3ll, const Diff3LineVector* pD3lv, const QVector* pldA, LineCount sizeA, const QVector* pldB, LineCount sizeB, const QVector* pldC, LineCount sizeC) { m_pDiff3LineList = pD3ll; m_pDiff3LineVector = pD3lv; mLineDataA = pldA; mLineDataB = pldB; mLineDataC = pldC; m_sizeA = sizeA; m_sizeB = sizeB; m_sizeC = sizeC; } void Diff3LineList::calcWhiteDiff3Lines( const QVector* pldA, const QVector* pldB, const QVector* pldC) { Diff3LineList::iterator i3; for(i3 = begin(); i3 != end(); ++i3) { i3->bWhiteLineA = (!i3->getLineA().isValid() || pldA == nullptr || (*pldA)[i3->getLineA()].whiteLine() || (*pldA)[i3->getLineA()].isPureComment()); i3->bWhiteLineB = (!i3->getLineB().isValid() || pldB == nullptr || (*pldB)[i3->getLineB()].whiteLine() || (*pldB)[i3->getLineB()].isPureComment()); i3->bWhiteLineC = (!i3->getLineC().isValid() || pldC == nullptr || (*pldC)[i3->getLineC()].whiteLine() || (*pldC)[i3->getLineC()].isPureComment()); } } // My own diff-invention: void calcDiff(const QString& line1, const QString& line2, DiffList& diffList, int match, int maxSearchRange) { diffList.clear(); QString::const_iterator p1=line1.begin(), p2=line2.begin(); /* This loop should never reach the exit condition specified here. However it must have a hard wired stopping point to prevent runaway allocation if something unexpected happens. diffList is therefor hard capped at aprox 50 MB in size. */ for(; diffList.size() * sizeof(Diff) + sizeof(DiffList) < (50 << 20);) { int nofEquals = 0; while(p1 != line1.end() && p2 != line2.end() && *p1 == *p2) { ++p1; ++p2; ++nofEquals; } bool bBestValid = false; int bestI1 = 0; int bestI2 = 0; int i1 = 0; int i2 = 0; for(i1 = 0;; ++i1) { if(p1[i1] == *line1.end() || (bBestValid && i1 >= bestI1 + bestI2)) { break; } for(i2 = 0; i2 < maxSearchRange; ++i2) { if(p2[i2] == *line2.end() || (bBestValid && i1 + i2 >= bestI1 + bestI2)) { break; } else if(p2[i2] == p1[i1] && (match == 1 || abs(i1 - i2) < 3 || (p2[i2 + 1] == *line2.end() && p1[i1 + 1] == *line1.end()) || (p2[i2 + 1] != *line2.end() && p1[i1 + 1] != *line1.end() && p2[i2 + 1] == p1[i1 + 1]))) { if(i1 + i2 < bestI1 + bestI2 || !bBestValid) { bestI1 = i1; bestI2 = i2; bBestValid = true; break; } } } } // The match was found using the strict search. Go back if there are non-strict // matches. while(bestI1 >= 1 && bestI2 >= 1 && p1[bestI1 - 1] == p2[bestI2 - 1]) { --bestI1; --bestI2; } bool bEndReached = false; if(bBestValid) { // continue somehow Diff d(nofEquals, bestI1, bestI2); Q_ASSERT(nofEquals + bestI1 + bestI2 != 0); diffList.push_back(d); p1 += bestI1; p2 += bestI2; } else { // Nothing else to match. Diff d(nofEquals, line1.end() - p1, line2.end() - p2); diffList.push_back(d); bEndReached = true; //break; } // Sometimes the algorithm that chooses the first match unfortunately chooses // a match where later actually equal parts don't match anymore. // A different match could be achieved, if we start at the end. // Do it, if it would be a better match. int nofUnmatched = 0; QString::const_iterator pu1 = p1 - 1; QString::const_iterator pu2 = p2 - 1; while(pu1 >= line1.begin() && pu2 >= line2.begin() && *pu1 == *pu2) { ++nofUnmatched; --pu1; --pu2; } Diff d = diffList.back(); if(nofUnmatched > 0) { // We want to go backwards the nofUnmatched elements and redo // the matching d = diffList.back(); Diff origBack = d; diffList.pop_back(); while(nofUnmatched > 0) { if(d.diff1() > 0 && d.diff2() > 0) { d.adjustDiff1(-1); d.adjustDiff2(-1); --nofUnmatched; } else if(d.numberOfEquals() > 0) { d.adjustNumberOfEquals(-1); --nofUnmatched; } if(d.numberOfEquals() == 0 && (d.diff1() == 0 || d.diff2() == 0) && nofUnmatched > 0) { if(diffList.empty()) break; d.adjustNumberOfEquals(diffList.back().numberOfEquals()); d.adjustDiff1(diffList.back().diff1()); d.adjustDiff2(diffList.back().diff2()); diffList.pop_back(); bEndReached = false; } } if(bEndReached) diffList.push_back(origBack); else { p1 = pu1 + 1 + nofUnmatched; p2 = pu2 + 1 + nofUnmatched; diffList.push_back(d); } } if(bEndReached) break; } Q_ASSERT(diffList.size() * sizeof(Diff) + sizeof(DiffList) <= (50 << 20)); // Verify difflist { qint32 l1 = 0; qint32 l2 = 0; DiffList::const_iterator it; for(it = diffList.begin(); it != diffList.end(); ++it) { l1 += (it->numberOfEquals() + it->diff1()); l2 += (it->numberOfEquals() + it->diff2()); } Q_ASSERT(l1 == line1.size() && l2 == line2.size()); } } bool Diff3Line::fineDiff(bool inBTextsTotalEqual, const e_SrcSelector selector, const QVector* v1, const QVector* v2) { LineRef k1 = 0; LineRef k2 = 0; int maxSearchLength = 500; bool bTextsTotalEqual = inBTextsTotalEqual; Q_ASSERT(selector == A || selector == B || selector == C); if(selector == A) { k1 = getLineA(); k2 = getLineB(); } else if(selector == B) { k1 = getLineB(); k2 = getLineC(); } else if(selector == C) { k1 = getLineC(); k2 = getLineA(); } qDebug(kdiffCore) << "k1 = " << k1 << ", k2 = " << k2; if((!k1.isValid() && k2.isValid()) || (k1.isValid() && !k2.isValid())) bTextsTotalEqual = false; if(k1.isValid() && k2.isValid()) { if((*v1)[k1].size() != (*v2)[k2].size() || QString::compare((*v1)[k1].getLine(), (*v2)[k2].getLine()) != 0) { bTextsTotalEqual = false; DiffList* pDiffList = new DiffList; calcDiff((*v1)[k1].getLine(), (*v2)[k2].getLine(), *pDiffList, 2, maxSearchLength); // Optimize the diff list. DiffList::iterator dli; bool bUsefulFineDiff = false; for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli) { if(dli->numberOfEquals() >= 4) { bUsefulFineDiff = true; break; } } for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli) { if(dli->numberOfEquals() < 4 && (dli->diff1() > 0 || dli->diff2() > 0) && !(bUsefulFineDiff && dli == pDiffList->begin())) { dli->adjustDiff1(dli->numberOfEquals()); dli->adjustDiff2(dli->numberOfEquals()); dli->setNumberOfEquals(0); } } setFineDiff(selector, pDiffList); } if(((*v1)[k1].isPureComment() || (*v1)[k1].whiteLine()) && ((*v2)[k2].isPureComment() || (*v2)[k2].whiteLine())) { if(selector == A) { bAEqB = true; } else if(selector == B) { bBEqC = true; } else if(selector == C) { bAEqC = true; } } } return bTextsTotalEqual; } void Diff3Line::getLineInfo(const e_SrcSelector winIdx, const bool isTriple, LineRef& lineIdx, DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values ChangeFlags& changed, ChangeFlags& changed2) const { changed = NoChange; changed2 = NoChange; bool bAEqualB = this->isEqualAB() || (bWhiteLineA && bWhiteLineB); bool bAEqualC = this->isEqualAC() || (bWhiteLineA && bWhiteLineC); bool bBEqualC = this->isEqualBC() || (bWhiteLineB && bWhiteLineC); Q_ASSERT(winIdx >= A && winIdx <= C); if(winIdx == A) { lineIdx = getLineA(); pFineDiff1 = pFineAB; pFineDiff2 = pFineCA; changed = ((!getLineB().isValid()) != (!lineIdx.isValid()) ? AChanged : NoChange) | ((!getLineC().isValid()) != (!lineIdx.isValid()) && isTriple ? BChanged : NoChange); changed2 = (bAEqualB ? NoChange : AChanged) | (bAEqualC || !isTriple ? NoChange : BChanged); } else if(winIdx == B) { lineIdx = getLineB(); pFineDiff1 = pFineBC; pFineDiff2 = pFineAB; changed = ((!getLineC().isValid()) != (!lineIdx.isValid()) && isTriple ? AChanged : NoChange) | ((!getLineA().isValid()) != (!lineIdx.isValid()) ? BChanged : NoChange); changed2 = (bBEqualC || !isTriple ? NoChange : AChanged) | (bAEqualB ? NoChange : BChanged); } else if(winIdx == C) { lineIdx = getLineC(); pFineDiff1 = pFineCA; pFineDiff2 = pFineBC; changed = ((!getLineA().isValid()) != (!lineIdx.isValid()) ? AChanged : NoChange) | ((!getLineB().isValid()) != (!lineIdx.isValid()) ? BChanged : NoChange); changed2 = (bAEqualC ? NoChange : AChanged) | (bBEqualC ? NoChange : BChanged); } } bool Diff3LineList::fineDiff(const e_SrcSelector selector, const QVector* v1, const QVector* v2) { // Finetuning: Diff each line with deltas ProgressProxy pp; Diff3LineList::iterator i; bool bTextsTotalEqual = true; int listSize = size(); pp.setMaxNofSteps(listSize); int listIdx = 0; for(i = begin(); i != end(); ++i) { bTextsTotalEqual = i->fineDiff(bTextsTotalEqual, selector, v1, v2); ++listIdx; pp.step(); } return bTextsTotalEqual; } // Convert the list to a vector of pointers void Diff3LineList::calcDiff3LineVector(Diff3LineVector& d3lv) { d3lv.resize(size()); Diff3LineList::iterator i; int j = 0; for(i = begin(); i != end(); ++i, ++j) { d3lv[j] = &(*i); } Q_ASSERT(j == d3lv.size()); } // Just make sure that all input lines are in the output too, exactly once. void Diff3LineList::debugLineCheck(const LineCount size, const e_SrcSelector srcSelector) const { Diff3LineList::const_iterator it = begin(); int i = 0; for(it = begin(); it != end(); ++it) { LineRef line; Q_ASSERT(srcSelector == A || srcSelector == B || srcSelector == C); if(srcSelector == A) line = it->getLineA(); else if(srcSelector == B) line = it->getLineB(); else if(srcSelector == C) line = it->getLineC(); if(line.isValid()) { if(line != i) { KMessageBox::error(nullptr, i18n( "Data loss error:\n" "If it is reproducible please contact the author.\n"), i18n("Severe Internal Error")); qCCritical(kdiffMain) << i18n("Severe Internal Error.") << " line != i for srcSelector=" << srcSelector << "\n"; ::exit(-1); } ++i; } } if(size != i) { KMessageBox::error(nullptr, i18n( "Data loss error:\n" "If it is reproducible please contact the author.\n"), i18n("Severe Internal Error")); qCCritical(kdiffMain) << i18n("Severe Internal Error.: ") << size << " != " << i << "\n"; ::exit(-1); } } void Diff3LineList::findHistoryRange(const QRegExp& historyStart, bool bThreeFiles, Diff3LineList::const_iterator& iBegin, Diff3LineList::const_iterator& iEnd, int& idxBegin, int& idxEnd) const { QString historyLead; // Search for start of history for(iBegin = begin(), idxBegin = 0; iBegin != end(); ++iBegin, ++idxBegin) { if(historyStart.exactMatch(iBegin->getString(A)) && historyStart.exactMatch(iBegin->getString(B)) && (!bThreeFiles || historyStart.exactMatch(iBegin->getString(C)))) { historyLead = Utils::calcHistoryLead(iBegin->getString(A)); break; } } // Search for end of history for(iEnd = iBegin, idxEnd = idxBegin; iEnd != end(); ++iEnd, ++idxEnd) { QString sA = iEnd->getString(A); QString sB = iEnd->getString(B); QString sC = iEnd->getString(C); if(!((sA.isEmpty() || historyLead == Utils::calcHistoryLead(sA)) && (sB.isEmpty() || historyLead == Utils::calcHistoryLead(sB)) && (!bThreeFiles || sC.isEmpty() || historyLead == Utils::calcHistoryLead(sC)))) { break; // End of the history } } } diff --git a/src/diff.h b/src/diff.h index a44212f..1b17ba0 100644 --- a/src/diff.h +++ b/src/diff.h @@ -1,548 +1,545 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef DIFF_H #define DIFF_H #include "common.h" #include "fileaccess.h" #include "LineRef.h" #include "SourceData.h" #include "Logging.h" #include #include class Options; //These enums must be sequential with no gaps to allow loop interiation of values enum e_SrcSelector { Min = -1, Invalid=-1, None=0, A = 1, B = 2, C = 3, Max=C }; enum e_MergeDetails { eDefault, eNoChange, eBChanged, eCChanged, eBCChanged, // conflict eBCChangedAndEqual, // possible conflict eBDeleted, eCDeleted, eBCDeleted, // possible conflict eBChanged_CDeleted, // conflict eCChanged_BDeleted, // conflict eBAdded, eCAdded, eBCAdded, // conflict eBCAddedAndEqual // possible conflict }; enum ChangeFlag{ NoChange = 0, AChanged = 0x1, BChanged = 0x2, Both = AChanged | BChanged }; Q_DECLARE_FLAGS(ChangeFlags, ChangeFlag); Q_DECLARE_OPERATORS_FOR_FLAGS(ChangeFlags); // Each range with matching elements is followed by a range with differences on either side. // Then again range of matching elements should follow. class Diff { private: qint32 nofEquals = 0; qint64 mDiff1 = 0; qint64 mDiff2 = 0; public: Diff() = default;//We use defualt initialization force compiler to generate a default constructor Diff(qint32 eq, const qint64 inDiff1, const qint64 inDiff2) { nofEquals = eq; mDiff1 = inDiff1; mDiff2 = inDiff2; } inline qint32 numberOfEquals() const { return nofEquals; }; inline qint64 diff1() const { return mDiff1; }; inline qint64 diff2() const { return mDiff2; }; inline void setNumberOfEquals(const qint32 inNumOfEquals) { nofEquals = inNumOfEquals; } inline void adjustNumberOfEquals(const qint64 delta) { nofEquals += delta; } inline void adjustDiff1(const qint64 delta) { mDiff1 += delta; } inline void adjustDiff2(const qint64 delta) { mDiff2 += delta; } }; typedef std::list DiffList; class LineData { private: QSharedPointer mBuffer; //QString pLine; QtNumberType mFirstNonWhiteChar = 0; qint64 mOffset = 0; QtNumberType mSize = 0; bool bContainsPureComment = false;//TODO: Move me public: explicit LineData() = default; // needed for Qt internal reasons should not be used. inline LineData(const QSharedPointer &buffer, const qint64 inOffset, QtNumberType inSize = 0, QtNumberType inFirstNonWhiteChar=0, bool inIsPureComment=false) { mBuffer = buffer; mOffset = inOffset; mSize = inSize; bContainsPureComment = inIsPureComment; mFirstNonWhiteChar = inFirstNonWhiteChar; } Q_REQUIRED_RESULT inline int size() const { return mSize; } Q_REQUIRED_RESULT inline qint32 getFirstNonWhiteChar() const { return mFirstNonWhiteChar; } /* QString::fromRawData allows us to create a light weight QString backed by the buffer memmory. */ Q_REQUIRED_RESULT inline const QString getLine() const { return QString::fromRawData(mBuffer->data() + mOffset, mSize); } Q_REQUIRED_RESULT inline const QSharedPointer& getBuffer() const { return mBuffer; } Q_REQUIRED_RESULT inline qint64 getOffset() const { return mOffset; } Q_REQUIRED_RESULT int width(int tabSize) const; // Calcs width considering tabs. //int occurrences; inline bool whiteLine() const { return mFirstNonWhiteChar == mSize - 1; } inline bool isPureComment() const { return bContainsPureComment; } inline void setPureComment(const bool bPureComment) { bContainsPureComment = bPureComment; } static bool equal(const LineData& l1, const LineData& l2); }; class ManualDiffHelpList; // A list of corresponding ranges class Diff3LineList; class Diff3LineVector; class DiffBufferInfo { private: const QVector* mLineDataA; const QVector* mLineDataB; const QVector* mLineDataC; LineCount m_sizeA; LineCount m_sizeB; LineCount m_sizeC; const Diff3LineList* m_pDiff3LineList; const Diff3LineVector* m_pDiff3LineVector; public: void init(Diff3LineList* d3ll, const Diff3LineVector* d3lv, const QVector* pldA, LineCount sizeA, const QVector* pldB, LineCount sizeB, const QVector* pldC, LineCount sizeC); inline const QVector* getLineData(e_SrcSelector srcIndex) const { switch(srcIndex) { case A: return mLineDataA; case B: return mLineDataB; case C: return mLineDataC; default: return nullptr; } } }; class Diff3Line { private: friend class Diff3LineList; LineRef lineA; LineRef lineB; LineRef lineC; bool bAEqC = false; // These are true if equal or only white-space changes exist. bool bBEqC = false; bool bAEqB = false; bool bWhiteLineA = false; bool bWhiteLineB = false; bool bWhiteLineC = false; DiffList* pFineAB = nullptr; // These are NULL only if completely equal or if either source doesn't exist. DiffList* pFineBC = nullptr; DiffList* pFineCA = nullptr; qint32 mLinesNeededForDisplay = 1; // Due to wordwrap qint32 mSumLinesNeededForDisplay = 0; // For fast conversion to m_diff3WrapLineVector public: static QSharedPointer m_pDiffBufferInfo; // For convenience ~Diff3Line() { if(pFineAB != nullptr) delete pFineAB; if(pFineBC != nullptr) delete pFineBC; if(pFineCA != nullptr) delete pFineCA; pFineAB = nullptr; pFineBC = nullptr; pFineCA = nullptr; } LineRef getLineA() const { return lineA; } LineRef getLineB() const { return lineB; } LineRef getLineC() const { return lineC; } inline void setLineA(const LineRef& line) { lineA = line; } inline void setLineB(const LineRef& line) { lineB = line; } inline void setLineC(const LineRef& line) { lineC = line; } inline bool isEqualAB() const { return bAEqB; } inline bool isEqualAC() const { return bAEqC; } inline bool isEqualBC() const { return bBEqC; } inline bool isWhiteLine(e_SrcSelector src) const { Q_ASSERT(src == A || src == B || src == C); switch(src) { case A: return bWhiteLineA; case B: return bWhiteLineB; case C: return bWhiteLineC; default: //should never get here Q_ASSERT(false); return false; } } bool operator==(const Diff3Line& d3l) const { return lineA == d3l.lineA && lineB == d3l.lineB && lineC == d3l.lineC && bAEqB == d3l.bAEqB && bAEqC == d3l.bAEqC && bBEqC == d3l.bBEqC; } const LineData* getLineData(e_SrcSelector src) const { Q_ASSERT(m_pDiffBufferInfo != nullptr); //Use at() here not [] to avoid using really weird syntax if(src == A && lineA.isValid()) return &m_pDiffBufferInfo->getLineData(src)->at(lineA); if(src == B && lineB.isValid()) return &m_pDiffBufferInfo->getLineData(src)->at(lineB); if(src == C && lineC.isValid()) return &m_pDiffBufferInfo->getLineData(src)->at(lineC); return nullptr; } const QString getString(const e_SrcSelector src) const { const LineData* pld = getLineData(src); if(pld) return pld->getLine(); else return QString(); } LineRef getLineInFile(e_SrcSelector src) const { if(src == A) return lineA; if(src == B) return lineB; if(src == C) return lineC; return -1; } inline qint32 sumLinesNeededForDisplay() const { return mSumLinesNeededForDisplay; } inline qint32 linesNeededForDisplay() const { return mLinesNeededForDisplay; } void setLinesNeeded(const qint32 lines) { mLinesNeededForDisplay = lines; } bool fineDiff(bool bTextsTotalEqual, const e_SrcSelector selector, const QVector* v1, const QVector* v2); void mergeOneLine(e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, e_SrcSelector& src, bool bTwoInputs) const; void getLineInfo(const e_SrcSelector winIdx, const bool isTriple, LineRef& lineIdx, DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values ChangeFlags& changed, ChangeFlags& changed2) const; private: void setFineDiff(const e_SrcSelector selector, DiffList* pDiffList) { Q_ASSERT(selector == A || selector == B || selector == C); if(selector == A) { if(pFineAB != nullptr) delete pFineAB; pFineAB = pDiffList; } else if(selector == B) { if(pFineBC != nullptr) delete pFineBC; pFineBC = pDiffList; } else if(selector == C) { if(pFineCA) delete pFineCA; pFineCA = pDiffList; } } }; class Diff3LineList : public std::list { public: void findHistoryRange(const QRegExp& historyStart, bool bThreeFiles, Diff3LineList::const_iterator& iBegin, Diff3LineList::const_iterator& iEnd, int& idxBegin, int& idxEnd) const; bool fineDiff(const e_SrcSelector selector, const QVector* v1, const QVector* v2); void calcDiff3LineVector(Diff3LineVector& d3lv); void calcWhiteDiff3Lines(const QVector* pldA, const QVector* pldB, const QVector* pldC); void calcDiff3LineListUsingAB(const DiffList* pDiffListAB); void calcDiff3LineListUsingAC(const DiffList* pDiffListAC); void calcDiff3LineListUsingBC(const DiffList* pDiffListBC); void correctManualDiffAlignment(ManualDiffHelpList* pManualDiffHelpList); void calcDiff3LineListTrim(const QVector* pldA, const QVector* pldB, const QVector* pldC, ManualDiffHelpList* pManualDiffHelpList); LineCount recalcWordWrap(bool resetDisplayCount) { LineCount sumOfLines = 0; for(Diff3Line& d3l: *this) { if(resetDisplayCount) d3l.mLinesNeededForDisplay = 1; d3l.mSumLinesNeededForDisplay = sumOfLines; sumOfLines += d3l.linesNeededForDisplay(); } return sumOfLines; } //TODO: Add safety guards to prevent list from getting too large. Same problem as with QLinkedList. qint32 size() const { if(std::list::size() > (size_t)TYPE_MAX(qint32))//explicit cast to silence gcc { qCDebug(kdiffMain) << "Diff3Line: List too large. size=" << std::list::size(); Q_ASSERT(false); //Unsupported size return 0; } return (qint32)std::list::size(); } //safe for small files same limit as exited with QLinkedList. This should ultimatly be removed. void debugLineCheck(const LineCount size, const e_SrcSelector srcSelector) const; qint32 numberOfLines(bool bWordWrap) const { if(bWordWrap) { qint32 lines; lines = 0; Diff3LineList::const_iterator i; for(i = begin(); i != end(); ++i) { lines += i->linesNeededForDisplay(); } return lines; } else { return size(); } } }; class Diff3LineVector : public QVector { }; struct Diff3WrapLine { Diff3Line* pD3L; int diff3LineIndex; int wrapLineOffset; int wrapLineLength; }; typedef QVector Diff3WrapLineVector; class TotalDiffStatus { public: inline void reset() { bBinaryAEqC = false; bBinaryBEqC = false; bBinaryAEqB = false; bTextAEqC = false; bTextBEqC = false; bTextAEqB = false; nofUnsolvedConflicts = 0; nofSolvedConflicts = 0; nofWhitespaceConflicts = 0; } inline int getUnsolvedConflicts() const { return nofUnsolvedConflicts; } inline void setUnsolvedConflicts(const int unsolved) { nofUnsolvedConflicts = unsolved; } inline int getSolvedConflicts() const { return nofSolvedConflicts; } inline void setSolvedConflicts(const int solved) { nofSolvedConflicts = solved; } inline int getWhitespaceConflicts() const { return nofWhitespaceConflicts; } inline void setWhitespaceConflicts(const int wintespace) { nofWhitespaceConflicts = wintespace; } inline int getNonWhitespaceConflicts() const { return getUnsolvedConflicts() + getSolvedConflicts() - getWhitespaceConflicts(); } bool isBinaryEqualAC() const { return bBinaryAEqC; } bool isBinaryEqualBC() const { return bBinaryBEqC; } bool isBinaryEqualAB() const { return bBinaryAEqB; } void setBinaryEqualAC(const bool equal) { bBinaryAEqC = equal; } void setBinaryEqualBC(const bool equal) { bBinaryBEqC = equal; } void setBinaryEqualAB(const bool equal) { bBinaryAEqB = equal; } bool isTextEqualAC() const { return bTextAEqC; } bool isTextEqualBC() const { return bTextBEqC; } bool isTextEqualAB() const { return bTextAEqB; } void setTextEqualAC(const bool equal) { bTextAEqC = equal; } void setTextEqualBC(const bool equal) { bTextBEqC = equal; } void setTextEqualAB(const bool equal) { bTextAEqB = equal; } private: bool bBinaryAEqC = false; bool bBinaryBEqC = false; bool bBinaryAEqB = false; bool bTextAEqC = false; bool bTextBEqC = false; bool bTextAEqB = false; int nofUnsolvedConflicts = 0; int nofSolvedConflicts = 0; int nofWhitespaceConflicts = 0; }; // Three corresponding ranges. (Minimum size of a valid range is one line.) class ManualDiffHelpEntry { private: LineRef lineA1; LineRef lineA2; LineRef lineB1; LineRef lineB2; LineRef lineC1; LineRef lineC2; public: LineRef& firstLine(e_SrcSelector winIdx) { return winIdx == A ? lineA1 : (winIdx == B ? lineB1 : lineC1); } LineRef& lastLine(e_SrcSelector winIdx) { return winIdx == A ? lineA2 : (winIdx == B ? lineB2 : lineC2); } bool isLineInRange(LineRef line, e_SrcSelector winIdx) { return line.isValid() && line >= firstLine(winIdx) && line <= lastLine(winIdx); } bool operator==(const ManualDiffHelpEntry& r) const { return lineA1 == r.lineA1 && lineB1 == r.lineB1 && lineC1 == r.lineC1 && lineA2 == r.lineA2 && lineB2 == r.lineB2 && lineC2 == r.lineC2; } int calcManualDiffFirstDiff3LineIdx(const Diff3LineVector& d3lv); void getRangeForUI(const e_SrcSelector winIdx, LineRef *rangeLine1, LineRef *rangeLine2) const { if(winIdx == A) { *rangeLine1 = lineA1; *rangeLine2 = lineA2; } if(winIdx == B) { *rangeLine1 = lineB1; *rangeLine2 = lineB2; } if(winIdx == C) { *rangeLine1 = lineC1; *rangeLine2 = lineC2; } } inline const LineRef& getLine1(const e_SrcSelector winIdx) const { return winIdx == A ? lineA1 : winIdx == B ? lineB1 : lineC1;} inline const LineRef& getLine2(const e_SrcSelector winIdx) const { return winIdx == A ? lineA2 : winIdx == B ? lineB2 : lineC2;} bool isValidMove(int line1, int line2, e_SrcSelector winIdx1, e_SrcSelector winIdx2) const; }; // A list of corresponding ranges class ManualDiffHelpList: public std::list { public: bool isValidMove(int line1, int line2, e_SrcSelector winIdx1, e_SrcSelector winIdx2) const; void insertEntry(e_SrcSelector winIdx, LineRef firstLine, LineRef lastLine); bool runDiff(const QVector* p1, LineRef size1, const QVector* p2, LineRef size2, DiffList& diffList, e_SrcSelector winIdx1, e_SrcSelector winIdx2, const QSharedPointer &pOptions); }; void calcDiff(const QString &line1, const QString &line2, DiffList& diffList, int match, int maxSearchRange); inline bool isWhite(QChar c) { return c == ' ' || c == '\t' || c == '\r'; } /** Returns the number of equivalent spaces at position outPos. */ inline int tabber(int outPos, int tabSize) { return tabSize - (outPos % tabSize); } /** Returns a line number where the linerange [line, line+nofLines] can be displayed best. If it fits into the currently visible range then the returned value is the current firstLine. */ int getBestFirstLine(int line, int nofLines, int firstLine, int visibleLines); extern bool g_bAutoSolve; enum e_CoordType { eFileCoords, eD3LLineCoords, eWrapCoords }; QString calcHistorySortKey(const QString& keyOrder, QRegExp& matchedRegExpr, const QStringList& parenthesesGroupList); bool findParenthesesGroups(const QString& s, QStringList& sl); #endif diff --git a/src/difftextwindow.cpp b/src/difftextwindow.cpp index 7ac2de9..6a9901f 100644 --- a/src/difftextwindow.cpp +++ b/src/difftextwindow.cpp @@ -1,2094 +1,2090 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * joachim.eibl at gmx.de * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "difftextwindow.h" #include "FileNameLineEdit.h" #include "RLPainter.h" #include "SourceData.h" // for SourceData #include "Utils.h" // for Utils #include "common.h" // for getAtomic, max3, min3 #include "kdiff3.h" #include "merger.h" #include "options.h" #include "progress.h" #include "selection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QList DiffTextWindow::s_runnables; //Used in startRunables and recalWordWrap class RecalcWordWrapRunnable : public QRunnable { private: static QAtomicInt s_runnableCount; DiffTextWindow* m_pDTW; int m_visibleTextWidth; int m_cacheIdx; public: static QAtomicInt s_maxNofRunnables; RecalcWordWrapRunnable(DiffTextWindow* p, int visibleTextWidth, int cacheIdx) : m_pDTW(p), m_visibleTextWidth(visibleTextWidth), m_cacheIdx(cacheIdx) { setAutoDelete(true); s_runnableCount.fetchAndAddOrdered(1); } void run() override { m_pDTW->recalcWordWrapHelper(0, m_visibleTextWidth, m_cacheIdx); int newValue = s_runnableCount.fetchAndAddOrdered(-1) - 1; g_pProgressDialog->setCurrent(s_maxNofRunnables - getAtomic(s_runnableCount)); if(newValue == 0) { Q_EMIT m_pDTW->finishRecalcWordWrap(m_visibleTextWidth); } } }; QAtomicInt RecalcWordWrapRunnable::s_runnableCount = 0; QAtomicInt RecalcWordWrapRunnable::s_maxNofRunnables = 0; class WrapLineCacheData { public: WrapLineCacheData() {} WrapLineCacheData(int d3LineIdx, int textStart, int textLength) : m_d3LineIdx(d3LineIdx), m_textStart(textStart), m_textLength(textLength) {} qint32 d3LineIdx() const { return m_d3LineIdx; } qint32 textStart() const { return m_textStart; } qint32 textLength() const { return m_textLength; } private: qint32 m_d3LineIdx = 0; qint32 m_textStart = 0; qint32 m_textLength = 0; }; class DiffTextWindowData { public: explicit DiffTextWindowData(DiffTextWindow* p) { m_pDiffTextWindow = p; #if defined(Q_OS_WIN) m_eLineEndStyle = eLineEndStyleDos; #else m_eLineEndStyle = eLineEndStyleUnix; #endif } QString getString(int d3lIdx); QString getLineString(int line); void writeLine( RLPainter& p, const LineData* pld, const DiffList* pLineDiff1, const DiffList* pLineDiff2, const LineRef& line, const ChangeFlags whatChanged, const ChangeFlags whatChanged2, const LineRef& srcLineIdx, int wrapLineOffset, int wrapLineLength, bool bWrapLine, const QRect& invalidRect, int deviceWidth); void draw(RLPainter& p, const QRect& invalidRect, int deviceWidth, int beginLine, int endLine); void myUpdate(int afterMilliSecs); int leftInfoWidth() const { return 4 + m_lineNumberWidth; } // Nr of information columns on left side int convertLineOnScreenToLineInSource(int lineOnScreen, e_CoordType coordType, bool bFirstLine); void prepareTextLayout(QTextLayout& textLayout, bool bFirstLine, int visibleTextWidth = -1); bool isThreeWay() const { return m_bTriple; }; const QString& getFileName() { return m_filename; } const Diff3LineVector* getDiff3LineVector() { return m_pDiff3LineVector; } private: //TODO: Remove friend classes after creating accessors. Please don't add new classes here friend DiffTextWindow; DiffTextWindow* m_pDiffTextWindow; DiffTextWindowFrame* m_pDiffTextWindowFrame = nullptr; QTextCodec* m_pTextCodec = nullptr; e_LineEndStyle m_eLineEndStyle; const QVector* m_pLineData = nullptr; int m_size = 0; QString m_filename; bool m_bWordWrap = false; int m_delayedDrawTimer = 0; const Diff3LineVector* m_pDiff3LineVector = nullptr; Diff3WrapLineVector m_diff3WrapLineVector; const ManualDiffHelpList* m_pManualDiffHelpList = nullptr; QList> m_wrapLineCacheList; QSharedPointer m_pOptions; QColor m_cThis; QColor m_cDiff1; QColor m_cDiff2; QColor m_cDiffBoth; int m_fastSelectorLine1 = 0; int m_fastSelectorNofLines = 0; bool m_bTriple = false; e_SrcSelector m_winIdx = None; int m_firstLine = 0; int m_oldFirstLine = 0; int m_horizScrollOffset = 0; int m_lineNumberWidth = 0; QAtomicInt m_maxTextWidth = -1; QStatusBar* m_pStatusBar = nullptr; Selection m_selection; int m_scrollDeltaX = 0; int m_scrollDeltaY = 0; bool m_bMyUpdate = false; bool m_bSelectionInProgress = false; QPoint m_lastKnownMousePos; QSharedPointer sourceData; }; void DiffTextWindow::setSourceData(const QSharedPointer& inData) { d->sourceData = inData; } bool DiffTextWindow::isThreeWay() const { return d->isThreeWay(); }; const QString& DiffTextWindow::getFileName() const { return d->getFileName(); } e_SrcSelector DiffTextWindow::getWindowIndex() const { return d->m_winIdx; }; const QString DiffTextWindow::getEncodingDisplayString() const { return d->m_pTextCodec != nullptr ? QLatin1String(d->m_pTextCodec->name()) : QString(); } e_LineEndStyle DiffTextWindow::getLineEndStyle() const { return d->m_eLineEndStyle; } const Diff3LineVector* DiffTextWindow::getDiff3LineVector() const { return d->getDiff3LineVector(); } qint32 DiffTextWindow::getLineNumberWidth() const { return (int)log10((double)std::max(d->m_size, 1)) + 1; } DiffTextWindow::DiffTextWindow( DiffTextWindowFrame* pParent, QStatusBar* pStatusBar, const QSharedPointer& pOptions, e_SrcSelector winIdx) : QWidget(pParent) { setObjectName(QString("DiffTextWindow%1").arg(winIdx)); setAttribute(Qt::WA_OpaquePaintEvent); //setAttribute( Qt::WA_PaintOnScreen ); setUpdatesEnabled(false); d = new DiffTextWindowData(this); d->m_pDiffTextWindowFrame = pParent; setFocusPolicy(Qt::ClickFocus); setAcceptDrops(true); d->m_pOptions = pOptions; init(QString(""), nullptr, d->m_eLineEndStyle, nullptr, 0, nullptr, nullptr, false); setMinimumSize(QSize(20, 20)); d->m_pStatusBar = pStatusBar; setUpdatesEnabled(true); d->m_bWordWrap = false; d->m_winIdx = winIdx; setFont(d->m_pOptions->m_font); } DiffTextWindow::~DiffTextWindow() { delete d; } void DiffTextWindow::init( const QString& filename, QTextCodec* pTextCodec, e_LineEndStyle eLineEndStyle, const QVector* pLineData, int size, const Diff3LineVector* pDiff3LineVector, const ManualDiffHelpList* pManualDiffHelpList, bool bTriple) { d->m_filename = filename; d->m_pLineData = pLineData; d->m_size = size; d->m_pDiff3LineVector = pDiff3LineVector; d->m_diff3WrapLineVector.clear(); d->m_pManualDiffHelpList = pManualDiffHelpList; d->m_firstLine = 0; d->m_oldFirstLine = -1; d->m_horizScrollOffset = 0; d->m_bTriple = bTriple; d->m_scrollDeltaX = 0; d->m_scrollDeltaY = 0; d->m_bMyUpdate = false; d->m_fastSelectorLine1 = 0; d->m_fastSelectorNofLines = 0; d->m_lineNumberWidth = 0; d->m_maxTextWidth = -1; d->m_pTextCodec = pTextCodec; d->m_eLineEndStyle = eLineEndStyle; update(); } void DiffTextWindow::setupConnections(const KDiff3App *app) const { connect(this, &DiffTextWindow::newSelection, app, &KDiff3App::slotSelectionStart); connect(this, &DiffTextWindow::selectionEnd, app, &KDiff3App::slotSelectionEnd); connect(this, &DiffTextWindow::scrollDiffTextWindow, app, &KDiff3App::scrollDiffTextWindow); connect(this, &DiffTextWindow::finishRecalcWordWrap, app, &KDiff3App::slotFinishRecalcWordWrap, Qt::QueuedConnection); connect(this, &DiffTextWindow::checkIfCanContinue, app, &KDiff3App::slotCheckIfCanContinue); connect(this, &DiffTextWindow::finishDrop, app, &KDiff3App::slotFinishDrop); connect(this, static_cast(&DiffTextWindow::update), app, &KDiff3App::showWhiteSpaceToggled); connect(this, static_cast(&DiffTextWindow::update), app, &KDiff3App::showLineNumbersToggled); connect(app, &KDiff3App::doRefresh, this, &DiffTextWindow::slotRefresh); } void DiffTextWindow::reset() { d->m_pLineData = nullptr; d->m_size = 0; d->m_pDiff3LineVector = nullptr; d->m_filename = ""; d->m_diff3WrapLineVector.clear(); } void DiffTextWindow::slotRefresh() { setFont(d->m_pOptions->m_font); update(); } void DiffTextWindow::setPaintingAllowed(bool bAllowPainting) { if(updatesEnabled() != bAllowPainting) { setUpdatesEnabled(bAllowPainting); if(bAllowPainting) update(); else reset(); } } void DiffTextWindow::dragEnterEvent(QDragEnterEvent* e) { e->setAccepted(e->mimeData()->hasUrls() || e->mimeData()->hasText()); } void DiffTextWindow::dropEvent(QDropEvent* dropEvent) { dropEvent->accept(); if(dropEvent->mimeData()->hasUrls()) { QList urlList = dropEvent->mimeData()->urls(); bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(bShouldConintue && !urlList.isEmpty()) { QString filename = urlList.first().toLocalFile(); d->sourceData->setFilename(filename); Q_EMIT finishDrop(); } } else if(dropEvent->mimeData()->hasText()) { QString text = dropEvent->mimeData()->text(); bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(bShouldConintue) { QString error; error = d->sourceData->setData(text); if(!error.isEmpty()) { KMessageBox::error(this, error); } Q_EMIT finishDrop(); } } } void DiffTextWindow::printWindow(RLPainter& painter, const QRect& view, const QString& headerText, int line, int linesPerPage, const QColor& fgColor) { QRect clipRect = view; clipRect.setTop(0); painter.setClipRect(clipRect); painter.translate(view.left(), 0); QFontMetrics fm = painter.fontMetrics(); //if ( fm.width(headerText) > view.width() ) { // A simple wrapline algorithm int l = 0; for(int p = 0; p < headerText.length();) { QString s = headerText.mid(p); int i; for(i = 2; i < s.length(); ++i) if(Utils::getHorizontalAdvance(fm, s, i) > view.width()) { --i; break; } //QString s2 = s.left(i); painter.drawText(0, l * fm.height() + fm.ascent(), s.left(i)); p += i; ++l; } painter.setPen(fgColor); painter.drawLine(0, view.top() - 2, view.width(), view.top() - 2); } painter.translate(0, view.top()); print(painter, view, line, linesPerPage); painter.resetTransform(); } void DiffTextWindow::setFirstLine(QtNumberType firstLine) { int fontHeight = fontMetrics().lineSpacing(); LineRef newFirstLine = std::max(0, firstLine); int deltaY = fontHeight * (d->m_firstLine - newFirstLine); d->m_firstLine = newFirstLine; if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine()) { LineRef line; int pos; convertToLinePos(d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos); d->m_selection.end(line, pos); update(); } else { scroll(0, deltaY); } d->m_pDiffTextWindowFrame->setFirstLine(d->m_firstLine); } int DiffTextWindow::getFirstLine() { return d->m_firstLine; } void DiffTextWindow::setHorizScrollOffset(int horizScrollOffset) { int fontWidth = Utils::getHorizontalAdvance(fontMetrics(), '0'); int xOffset = d->leftInfoWidth() * fontWidth; int deltaX = d->m_horizScrollOffset - std::max(0, horizScrollOffset); d->m_horizScrollOffset = std::max(0, horizScrollOffset); QRect r(xOffset, 0, width() - xOffset, height()); if(d->m_pOptions->m_bRightToLeftLanguage) { deltaX = -deltaX; r = QRect(width() - xOffset - 2, 0, -(width() - xOffset), height()).normalized(); } if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine()) { LineRef line; int pos; convertToLinePos(d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos); d->m_selection.end(line, pos); update(); } else { scroll(deltaX, 0, r); } } int DiffTextWindow::getMaxTextWidth() { if(d->m_bWordWrap) { return getVisibleTextAreaWidth(); } else if(getAtomic(d->m_maxTextWidth) < 0) { d->m_maxTextWidth = 0; QTextLayout textLayout(QString(), font(), this); for(int i = 0; i < d->m_size; ++i) { textLayout.clearLayout(); textLayout.setText(d->getString(i)); d->prepareTextLayout(textLayout, true); if(textLayout.maximumWidth() > getAtomic(d->m_maxTextWidth)) d->m_maxTextWidth = qCeil(textLayout.maximumWidth()); } } return getAtomic(d->m_maxTextWidth); } LineCount DiffTextWindow::getNofLines() { return d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->m_pDiff3LineVector->size(); } int DiffTextWindow::convertLineToDiff3LineIdx(LineRef line) { if(line.isValid() && d->m_bWordWrap && d->m_diff3WrapLineVector.size() > 0) return d->m_diff3WrapLineVector[std::min((LineRef::LineType)line, d->m_diff3WrapLineVector.size() - 1)].diff3LineIndex; else return line; } LineRef DiffTextWindow::convertDiff3LineIdxToLine(int d3lIdx) { if(d->m_bWordWrap && d->m_pDiff3LineVector != nullptr && d->m_pDiff3LineVector->size() > 0) return (*d->m_pDiff3LineVector)[std::min(d3lIdx, (int)d->m_pDiff3LineVector->size() - 1)]->sumLinesNeededForDisplay(); else return d3lIdx; } /** Returns a line number where the linerange [line, line+nofLines] can be displayed best. If it fits into the currently visible range then the returned value is the current firstLine. */ int getBestFirstLine(int line, int nofLines, int firstLine, int visibleLines) { int newFirstLine = firstLine; if(line < firstLine || line + nofLines + 2 > firstLine + visibleLines) { if(nofLines > visibleLines || nofLines <= (2 * visibleLines / 3 - 1)) newFirstLine = line - visibleLines / 3; else newFirstLine = line - (visibleLines - nofLines); } return newFirstLine; } void DiffTextWindow::setFastSelectorRange(int line1, int nofLines) { d->m_fastSelectorLine1 = line1; d->m_fastSelectorNofLines = nofLines; if(isVisible()) { int newFirstLine = getBestFirstLine( convertDiff3LineIdxToLine(d->m_fastSelectorLine1), convertDiff3LineIdxToLine(d->m_fastSelectorLine1 + d->m_fastSelectorNofLines) - convertDiff3LineIdxToLine(d->m_fastSelectorLine1), d->m_firstLine, getNofVisibleLines()); if(newFirstLine != d->m_firstLine) { Q_EMIT scrollDiffTextWindow(0, newFirstLine - d->m_firstLine); } update(); } } /* Takes the line number estimated from mouse position and converts it to the actual line in the file. Then sets the status message accordingly. emits lineClicked signal. */ void DiffTextWindow::showStatusLine(const LineRef aproxLine) { int d3lIdx = convertLineToDiff3LineIdx(aproxLine); if(d->m_pDiff3LineVector != nullptr && d3lIdx >= 0 && d3lIdx < (int)d->m_pDiff3LineVector->size()) { const Diff3Line* pD3l = (*d->m_pDiff3LineVector)[d3lIdx]; if(pD3l != nullptr) { LineRef actualLine = pD3l->getLineInFile(d->m_winIdx); QString message; if(actualLine.isValid()) message = i18n("File %1: Line %2", d->m_filename, actualLine + 1); else message = i18n("File %1: Line not available", d->m_filename); if(d->m_pStatusBar != nullptr) d->m_pStatusBar->showMessage(message); Q_EMIT lineClicked(d->m_winIdx, actualLine); } } } void DiffTextWindow::focusInEvent(QFocusEvent* e) { Q_EMIT gotFocus(); QWidget::focusInEvent(e); } void DiffTextWindow::mousePressEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { LineRef line; int pos; convertToLinePos(e->x(), e->y(), line, pos); int fontWidth = Utils::getHorizontalAdvance(fontMetrics(), '0'); int xOffset = d->leftInfoWidth() * fontWidth; if((!d->m_pOptions->m_bRightToLeftLanguage && e->x() < xOffset) || (d->m_pOptions->m_bRightToLeftLanguage && e->x() > width() - xOffset)) { Q_EMIT setFastSelectorLine(convertLineToDiff3LineIdx(line)); d->m_selection.reset(); // Disable current d->m_selection } else { // Selection resetSelection(); d->m_selection.start(line, pos); d->m_selection.end(line, pos); d->m_bSelectionInProgress = true; d->m_lastKnownMousePos = e->pos(); showStatusLine(line); } } } void DiffTextWindow::mouseDoubleClickEvent(QMouseEvent* e) { d->m_bSelectionInProgress = false; d->m_lastKnownMousePos = e->pos(); if(e->button() == Qt::LeftButton) { LineRef line; int pos; convertToLinePos(e->x(), e->y(), line, pos); // Get the string data of the current line QString s; if(d->m_bWordWrap) { if(!line.isValid() || line >= (int)d->m_diff3WrapLineVector.size()) return; const Diff3WrapLine& d3wl = d->m_diff3WrapLineVector[line]; s = d->getString(d3wl.diff3LineIndex).mid(d3wl.wrapLineOffset, d3wl.wrapLineLength); } else { if(!line.isValid() || line >= (int)d->m_pDiff3LineVector->size()) return; s = d->getString(line); } if(!s.isEmpty()) { int pos1, pos2; Utils::calcTokenPos(s, pos, pos1, pos2); resetSelection(); d->m_selection.start(line, pos1); d->m_selection.end(line, pos2); update(); // Q_EMIT d->m_selectionEnd() happens in the mouseReleaseEvent. showStatusLine(line); } } } void DiffTextWindow::mouseReleaseEvent(QMouseEvent* e) { d->m_bSelectionInProgress = false; d->m_lastKnownMousePos = e->pos(); //if ( e->button() == LeftButton ) { if(d->m_delayedDrawTimer) killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = 0; if(d->m_selection.isValidFirstLine()) { Q_EMIT selectionEnd(); } } d->m_scrollDeltaX = 0; d->m_scrollDeltaY = 0; } void DiffTextWindow::mouseMoveEvent(QMouseEvent* e) { LineRef line; int pos; convertToLinePos(e->x(), e->y(), line, pos); d->m_lastKnownMousePos = e->pos(); if(d->m_selection.isValidFirstLine()) { d->m_selection.end(line, pos); showStatusLine(line); // Scroll because mouse moved out of the window const QFontMetrics& fm = fontMetrics(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); int deltaX = 0; int deltaY = 0; if(!d->m_pOptions->m_bRightToLeftLanguage) { if(e->x() < d->leftInfoWidth() * fontWidth) deltaX = -1 - abs(e->x() - d->leftInfoWidth() * fontWidth) / fontWidth; if(e->x() > width()) deltaX = +1 + abs(e->x() - width()) / fontWidth; } else { if(e->x() > width() - 1 - d->leftInfoWidth() * fontWidth) deltaX = +1 + abs(e->x() - (width() - 1 - d->leftInfoWidth() * fontWidth)) / fontWidth; if(e->x() < fontWidth) deltaX = -1 - abs(e->x() - fontWidth) / fontWidth; } if(e->y() < 0) deltaY = -1 - (int)std::pow(e->y(), 2) / (int)std::pow(fm.lineSpacing(), 2); if(e->y() > height()) deltaY = 1 + (int)std::pow(e->y() - height(), 2) / (int)std::pow(fm.lineSpacing(), 2); if((deltaX != 0 && d->m_scrollDeltaX != deltaX) || (deltaY != 0 && d->m_scrollDeltaY != deltaY)) { d->m_scrollDeltaX = deltaX; d->m_scrollDeltaY = deltaY; Q_EMIT scrollDiffTextWindow(deltaX, deltaY); if(d->m_delayedDrawTimer) killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = startTimer(50); } else { d->m_scrollDeltaX = deltaX; d->m_scrollDeltaY = deltaY; d->myUpdate(0); } } } void DiffTextWindowData::myUpdate(int afterMilliSecs) { if(m_delayedDrawTimer) m_pDiffTextWindow->killTimer(m_delayedDrawTimer); m_bMyUpdate = true; m_delayedDrawTimer = m_pDiffTextWindow->startTimer(afterMilliSecs); } void DiffTextWindow::timerEvent(QTimerEvent*) { killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = 0; if(d->m_bMyUpdate) { int fontHeight = fontMetrics().lineSpacing(); if(d->m_selection.getOldLastLine().isValid()) { int lastLine; int firstLine; if(d->m_selection.getOldFirstLine().isValid()) { firstLine = min3(d->m_selection.getOldFirstLine(), d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); lastLine = max3(d->m_selection.getOldFirstLine(), d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); } else { firstLine = std::min(d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); lastLine = std::max(d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); } int y1 = (firstLine - d->m_firstLine) * fontHeight; int y2 = std::min(height(), (lastLine - d->m_firstLine + 1) * fontHeight); if(y1 < height() && y2 > 0) { QRect invalidRect = QRect(0, y1 - 1, width(), y2 - y1 + fontHeight); // Some characters in exotic exceed the regular bottom. update(invalidRect); } } d->m_bMyUpdate = false; } if(d->m_scrollDeltaX != 0 || d->m_scrollDeltaY != 0) { d->m_selection.end(d->m_selection.getLastLine() + d->m_scrollDeltaY, d->m_selection.getLastPos() + d->m_scrollDeltaX); Q_EMIT scrollDiffTextWindow(d->m_scrollDeltaX, d->m_scrollDeltaY); killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = startTimer(50); } } void DiffTextWindow::resetSelection() { d->m_selection.reset(); update(); } void DiffTextWindow::convertToLinePos(int x, int y, LineRef& line, int& pos) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int yOffset = -d->m_firstLine * fontHeight; line = (y - yOffset) / fontHeight; if(line.isValid() && (!d->m_pOptions->m_bWordWrap || line < d->m_diff3WrapLineVector.count())) { QString s = d->getLineString(line); QTextLayout textLayout(s, font(), this); d->prepareTextLayout(textLayout, !d->m_pOptions->m_bWordWrap || d->m_diff3WrapLineVector[line].wrapLineOffset == 0); pos = textLayout.lineAt(0).xToCursor(x - textLayout.position().x()); } else pos = -1; } class FormatRangeHelper { private: QFont m_font; QPen m_pen; QColor m_background; int m_currentPos; QVector m_formatRanges; public: inline operator QVector() { return m_formatRanges; } FormatRangeHelper() { m_pen = QColor(Qt::black); m_background = QColor(Qt::white); m_currentPos = 0; } void setFont(const QFont& f) { m_font = f; } void setPen(const QPen& pen) { m_pen = pen; } void setBackground(const QColor& background) { m_background = background; } void next() { if(m_formatRanges.isEmpty() || m_formatRanges.back().format.foreground().color() != m_pen.color() || m_formatRanges.back().format.background().color() != m_background) { QTextLayout::FormatRange fr; fr.length = 1; fr.start = m_currentPos; fr.format.setForeground(m_pen.color()); fr.format.setBackground(m_background); m_formatRanges.append(fr); } else { ++m_formatRanges.back().length; } ++m_currentPos; } }; void DiffTextWindowData::prepareTextLayout(QTextLayout& textLayout, bool /*bFirstLine*/, int visibleTextWidth) { QTextOption textOption; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) textOption.setTabStop(QFontMetricsF(m_pDiffTextWindow->font()).width(' ') * m_pOptions->m_tabSize); #else textOption.setTabStopDistance(QFontMetricsF(m_pDiffTextWindow->font()).width(' ') * m_pOptions->m_tabSize); #endif if(m_pOptions->m_bShowWhiteSpaceCharacters) textOption.setFlags(QTextOption::ShowTabsAndSpaces); if(m_pOptions->m_bRightToLeftLanguage) textOption.setAlignment(Qt::AlignRight); // only relevant for multi line text layout if(visibleTextWidth >= 0) textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); textLayout.setTextOption(textOption); if(m_pOptions->m_bShowWhiteSpaceCharacters) { // This additional format is only necessary for the tab arrow QVector formats; QTextLayout::FormatRange formatRange; formatRange.start = 0; formatRange.length = textLayout.text().length(); formatRange.format.setFont(m_pDiffTextWindow->font()); formats.append(formatRange); textLayout.setFormats(formats); } textLayout.beginLayout(); int leading = m_pDiffTextWindow->fontMetrics().leading(); int height = 0; int fontWidth = Utils::getHorizontalAdvance(m_pDiffTextWindow->fontMetrics(), '0'); int xOffset = leftInfoWidth() * fontWidth - m_horizScrollOffset; int textWidth = visibleTextWidth; if(textWidth < 0) textWidth = m_pDiffTextWindow->width() - xOffset; int indentation = 0; while(true) { QTextLine line = textLayout.createLine(); if(!line.isValid()) break; height += leading; //if ( !bFirstLine ) // indentation = m_pDiffTextWindow->fontMetrics().width(' ') * m_pOptions->m_tabSize; if(visibleTextWidth >= 0) { line.setLineWidth(visibleTextWidth - indentation); line.setPosition(QPointF(indentation, height)); height += qCeil(line.height()); //bFirstLine = false; } else // only one line { line.setPosition(QPointF(indentation, height)); break; } } textLayout.endLayout(); if(m_pOptions->m_bRightToLeftLanguage) textLayout.setPosition(QPointF(textWidth - textLayout.maximumWidth(), 0)); else textLayout.setPosition(QPointF(xOffset, 0)); } void DiffTextWindowData::writeLine( RLPainter& p, const LineData* pld, const DiffList* pLineDiff1, const DiffList* pLineDiff2, const LineRef& line, const ChangeFlags whatChanged, const ChangeFlags whatChanged2, const LineRef& srcLineIdx, int wrapLineOffset, int wrapLineLength, bool bWrapLine, const QRect& invalidRect, int deviceWidth) { QFont normalFont = p.font(); const QFontMetrics& fm = p.fontMetrics(); int fontHeight = fm.lineSpacing(); int fontAscent = fm.ascent(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); int xOffset = leftInfoWidth() * fontWidth - m_horizScrollOffset; int yOffset = (line - m_firstLine) * fontHeight; QRect lineRect(xOffset, yOffset, deviceWidth, fontHeight); if(!invalidRect.intersects(lineRect)) { return; } int fastSelectorLine1 = m_pDiffTextWindow->convertDiff3LineIdxToLine(m_fastSelectorLine1); int fastSelectorLine2 = m_pDiffTextWindow->convertDiff3LineIdxToLine(m_fastSelectorLine1 + m_fastSelectorNofLines) - 1; bool bFastSelectionRange = (line >= fastSelectorLine1 && line <= fastSelectorLine2); QColor bgColor = m_pOptions->m_bgColor; QColor diffBgColor = m_pOptions->m_diffBgColor; if(bFastSelectionRange) { bgColor = m_pOptions->m_currentRangeBgColor; diffBgColor = m_pOptions->m_currentRangeDiffBgColor; } if(yOffset + fontHeight < invalidRect.top() || invalidRect.bottom() < yOffset - fontHeight) return; ChangeFlags changed = whatChanged; if(pLineDiff1 != nullptr) changed |= AChanged; if(pLineDiff2 != nullptr) changed |= BChanged; QColor c = m_pOptions->m_fgColor; p.setPen(c); if(changed == BChanged) { c = m_cDiff2; } else if(changed == AChanged) { c = m_cDiff1; } else if(changed == Both) { c = m_cDiffBoth; } if(pld != nullptr) { // First calculate the "changed" information for each character. int i = 0; QString lineString = pld->getLine(); if(!lineString.isEmpty()) { switch(lineString[lineString.length() - 1].unicode()) { case '\n': lineString[lineString.length() - 1] = 0x00B6; break; // "Pilcrow", "paragraph mark" case '\r': lineString[lineString.length() - 1] = 0x00A4; break; // Currency sign ;0x2761 "curved stem paragraph sign ornament" //case '\0b' : lineString[lineString.length()-1] = 0x2756; break; // some other nice looking character } } QVector charChanged(pld->size()); Merger merger(pLineDiff1, pLineDiff2); while(!merger.isEndReached() && i < pld->size()) { if(i < pld->size()) { charChanged[i] = merger.whatChanged(); ++i; } merger.next(); } int outPos = 0; int lineLength = m_bWordWrap ? wrapLineOffset + wrapLineLength : lineString.length(); FormatRangeHelper frh; for(i = wrapLineOffset; i < lineLength; ++i) { c = m_pOptions->m_fgColor; ChangeFlags cchanged = charChanged[i] | whatChanged; if(cchanged == BChanged) { c = m_cDiff2; } else if(cchanged == AChanged) { c = m_cDiff1; } else if(cchanged == Both) { c = m_cDiffBoth; } if(c != m_pOptions->m_fgColor && whatChanged2 == 0 && !m_pOptions->m_bShowWhiteSpace) { // The user doesn't want to see highlighted white space. c = m_pOptions->m_fgColor; } { frh.setBackground(bgColor); if(!m_selection.within(line, outPos)) { if(c != m_pOptions->m_fgColor) { QColor lightc = diffBgColor; frh.setBackground(lightc); // Setting italic font here doesn't work: Changing the font only when drawing is too late } frh.setPen(c); frh.next(); frh.setFont(normalFont); } else { frh.setBackground(m_pDiffTextWindow->palette().highlight().color()); frh.setPen(m_pDiffTextWindow->palette().highlightedText().color()); frh.next(); m_selection.bSelectionContainsData = true; } } ++outPos; } // end for QTextLayout textLayout(lineString.mid(wrapLineOffset, lineLength - wrapLineOffset), m_pDiffTextWindow->font(), m_pDiffTextWindow); prepareTextLayout(textLayout, !m_bWordWrap || wrapLineOffset == 0); textLayout.draw(&p, QPoint(0, yOffset), frh /*, const QRectF & clip = QRectF() */); } p.fillRect(0, yOffset, leftInfoWidth() * fontWidth, fontHeight, m_pOptions->m_bgColor); xOffset = (m_lineNumberWidth + 2) * fontWidth; int xLeft = m_lineNumberWidth * fontWidth; p.setPen(m_pOptions->m_fgColor); if(pld != nullptr) { if(m_pOptions->m_bShowLineNumbers && !bWrapLine) { QString num; num.asprintf("%0*d", m_lineNumberWidth, srcLineIdx + 1); p.drawText(0, yOffset + fontAscent, num); //p.drawLine( xLeft -1, yOffset, xLeft -1, yOffset+fontHeight-1 ); } if(!bWrapLine || wrapLineLength > 0) { Qt::PenStyle wrapLinePenStyle = Qt::DotLine; p.setPen(QPen(m_pOptions->m_fgColor, 0, bWrapLine ? wrapLinePenStyle : Qt::SolidLine)); p.drawLine(xOffset + 1, yOffset, xOffset + 1, yOffset + fontHeight - 1); p.setPen(QPen(m_pOptions->m_fgColor, 0, Qt::SolidLine)); } } if(c != m_pOptions->m_fgColor && whatChanged2 == 0) //&& whatChanged==0 ) { if(m_pOptions->m_bShowWhiteSpace) { p.setBrushOrigin(0, 0); p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, QBrush(c, Qt::Dense5Pattern)); } } else { p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, c == m_pOptions->m_fgColor ? bgColor : c); } if(bFastSelectionRange) { p.fillRect(xOffset + fontWidth - 1, yOffset, 3, fontHeight, m_pOptions->m_fgColor); } // Check if line needs a manual diff help mark ManualDiffHelpList::const_iterator ci; for(ci = m_pManualDiffHelpList->begin(); ci != m_pManualDiffHelpList->end(); ++ci) { const ManualDiffHelpEntry& mdhe = *ci; LineRef rangeLine1; LineRef rangeLine2; mdhe.getRangeForUI(m_winIdx, &rangeLine1, &rangeLine2); if(rangeLine1.isValid() && rangeLine2.isValid() && srcLineIdx >= rangeLine1 && srcLineIdx <= rangeLine2) { p.fillRect(xOffset - fontWidth, yOffset, fontWidth - 1, fontHeight, m_pOptions->m_manualHelpRangeColor); break; } } } void DiffTextWindow::paintEvent(QPaintEvent* e) { QRect invalidRect = e->rect(); if(invalidRect.isEmpty()) return; if(d->m_pDiff3LineVector == nullptr || (d->m_diff3WrapLineVector.empty() && d->m_bWordWrap)) { QPainter p(this); p.fillRect(invalidRect, d->m_pOptions->m_bgColor); return; } bool bOldSelectionContainsData = d->m_selection.bSelectionContainsData; d->m_selection.bSelectionContainsData = false; int endLine = std::min(d->m_firstLine + getNofVisibleLines() + 2, getNofLines()); RLPainter p(this, d->m_pOptions->m_bRightToLeftLanguage, width(), Utils::getHorizontalAdvance(fontMetrics(), '0')); p.setFont(font()); p.QPainter::fillRect(invalidRect, d->m_pOptions->m_bgColor); d->draw(p, invalidRect, width(), d->m_firstLine, endLine); p.end(); d->m_oldFirstLine = d->m_firstLine; d->m_selection.clearOldSelection(); if(!bOldSelectionContainsData && d->m_selection.selectionContainsData()) Q_EMIT newSelection(); } void DiffTextWindow::print(RLPainter& p, const QRect&, int firstLine, int nofLinesPerPage) { if(d->m_pDiff3LineVector == nullptr || !updatesEnabled() || (d->m_diff3WrapLineVector.empty() && d->m_bWordWrap)) return; resetSelection(); int oldFirstLine = d->m_firstLine; d->m_firstLine = firstLine; QRect invalidRect = QRect(0, 0, 1000000000, 1000000000); QColor bgColor = d->m_pOptions->m_bgColor; d->m_pOptions->m_bgColor = Qt::white; d->draw(p, invalidRect, p.window().width(), firstLine, std::min(firstLine + nofLinesPerPage, getNofLines())); d->m_pOptions->m_bgColor = bgColor; d->m_firstLine = oldFirstLine; } void DiffTextWindowData::draw(RLPainter& p, const QRect& invalidRect, int deviceWidth, int beginLine, int endLine) { m_lineNumberWidth = m_pOptions->m_bShowLineNumbers ? (int)log10((double)std::max(m_size, 1)) + 1 : 0; if(m_winIdx == A) { m_cThis = m_pOptions->m_colorA; m_cDiff1 = m_pOptions->m_colorB; m_cDiff2 = m_pOptions->m_colorC; } if(m_winIdx == B) { m_cThis = m_pOptions->m_colorB; m_cDiff1 = m_pOptions->m_colorC; m_cDiff2 = m_pOptions->m_colorA; } if(m_winIdx == C) { m_cThis = m_pOptions->m_colorC; m_cDiff1 = m_pOptions->m_colorA; m_cDiff2 = m_pOptions->m_colorB; } m_cDiffBoth = m_pOptions->m_colorForConflict; // Conflict color p.setPen(m_cThis); for(int line = beginLine; line < endLine; ++line) { int wrapLineOffset = 0; int wrapLineLength = 0; const Diff3Line* d3l = nullptr; bool bWrapLine = false; if(m_bWordWrap) { Diff3WrapLine& d3wl = m_diff3WrapLineVector[line]; wrapLineOffset = d3wl.wrapLineOffset; wrapLineLength = d3wl.wrapLineLength; d3l = d3wl.pD3L; bWrapLine = line > 0 && m_diff3WrapLineVector[line - 1].pD3L == d3l; } else { d3l = (*m_pDiff3LineVector)[line]; } DiffList* pFineDiff1; DiffList* pFineDiff2; ChangeFlags changed = NoChange; ChangeFlags changed2 = NoChange; LineRef srcLineIdx; d3l->getLineInfo(m_winIdx, m_bTriple, srcLineIdx, pFineDiff1, pFineDiff2, changed, changed2); writeLine( p, // QPainter !srcLineIdx.isValid() ? nullptr : &(*m_pLineData)[srcLineIdx], // Text in this line pFineDiff1, pFineDiff2, line, // Line on the screen changed, changed2, srcLineIdx, wrapLineOffset, wrapLineLength, bWrapLine, invalidRect, deviceWidth); } } QString DiffTextWindowData::getString(int d3lIdx) { if(d3lIdx < 0 || d3lIdx >= m_pDiff3LineVector->size()) return QString(); const Diff3Line* d3l = (*m_pDiff3LineVector)[d3lIdx]; DiffList* pFineDiff1; DiffList* pFineDiff2; ChangeFlags changed = NoChange; ChangeFlags changed2 = NoChange; LineRef lineIdx; d3l->getLineInfo(m_winIdx, m_bTriple, lineIdx, pFineDiff1, pFineDiff2, changed, changed2); if(!lineIdx.isValid()) return QString(); return (*m_pLineData)[lineIdx].getLine(); } QString DiffTextWindowData::getLineString(int line) { if(m_bWordWrap) { if(line < m_diff3WrapLineVector.count()) { int d3LIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(line); return getString(d3LIdx).mid(m_diff3WrapLineVector[line].wrapLineOffset, m_diff3WrapLineVector[line].wrapLineLength); } else return QString(); } else { return getString(line); } } void DiffTextWindow::resizeEvent(QResizeEvent* e) { QSize s = e->size(); QFontMetrics fm = fontMetrics(); int visibleLines = s.height() / fm.lineSpacing() - 2; int visibleColumns = s.width() / Utils::getHorizontalAdvance(fm, '0') - d->leftInfoWidth(); if(e->size().height() != e->oldSize().height()) Q_EMIT resizeHeightChangedSignal(visibleLines); if(e->size().width() != e->oldSize().width()) Q_EMIT resizeWidthChangedSignal(visibleColumns); QWidget::resizeEvent(e); } int DiffTextWindow::getNofVisibleLines() { QFontMetrics fm = fontMetrics(); int fmh = fm.lineSpacing(); int h = height(); return h / fmh - 1; } int DiffTextWindow::getVisibleTextAreaWidth() { QFontMetrics fm = fontMetrics(); return width() - d->leftInfoWidth() * Utils::getHorizontalAdvance(fm, '0'); } QString DiffTextWindow::getSelection() { if(d->m_pLineData == nullptr) return QString(); QString selectionString; int line = 0; int lineIdx = 0; int it; int vectorSize = d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->m_pDiff3LineVector->size(); for(it = 0; it < vectorSize; ++it) { const Diff3Line* d3l = d->m_bWordWrap ? d->m_diff3WrapLineVector[it].pD3L : (*d->m_pDiff3LineVector)[it]; Q_ASSERT(d->m_winIdx >= 1 && d->m_winIdx <= 3); if(d->m_winIdx == A) { lineIdx = d3l->getLineA(); } else if(d->m_winIdx == B) { lineIdx = d3l->getLineB(); } else if(d->m_winIdx == C) { lineIdx = d3l->getLineC(); } if(lineIdx != -1) { int size = (*d->m_pLineData)[lineIdx].size(); QString lineString = (*d->m_pLineData)[lineIdx].getLine(); if(d->m_bWordWrap) { size = d->m_diff3WrapLineVector[it].wrapLineLength; lineString = lineString.mid(d->m_diff3WrapLineVector[it].wrapLineOffset, size); } for(int i = 0; i < size; ++i) { if(d->m_selection.within(line, i)) { selectionString += lineString[i]; } } if(d->m_selection.within(line, size) && !(d->m_bWordWrap && it + 1 < vectorSize && d3l == d->m_diff3WrapLineVector[it + 1].pD3L)) { #if defined(Q_OS_WIN) selectionString += '\r'; #endif selectionString += '\n'; } } ++line; } return selectionString; } bool DiffTextWindow::findString(const QString& s, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive) { int it = d3vLine; int endIt = bDirDown ? d->m_pDiff3LineVector->size() : -1; int step = bDirDown ? 1 : -1; int startPos = posInLine; for(; it != endIt; it += step) { QString line = d->getString(it); if(!line.isEmpty()) { int pos = line.indexOf(s, startPos, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); if(pos != -1) { d3vLine = it; posInLine = pos; return true; } startPos = 0; } } return false; } void DiffTextWindow::convertD3LCoordsToLineCoords(int d3LIdx, int d3LPos, int& line, int& pos) { if(d->m_bWordWrap) { int wrapPos = d3LPos; int wrapLine = convertDiff3LineIdxToLine(d3LIdx); while(wrapPos > d->m_diff3WrapLineVector[wrapLine].wrapLineLength) { wrapPos -= d->m_diff3WrapLineVector[wrapLine].wrapLineLength; ++wrapLine; } pos = wrapPos; line = wrapLine; } else { pos = d3LPos; line = d3LIdx; } } void DiffTextWindow::convertLineCoordsToD3LCoords(int line, int pos, int& d3LIdx, int& d3LPos) { if(d->m_bWordWrap) { d3LPos = pos; d3LIdx = convertLineToDiff3LineIdx(line); int wrapLine = convertDiff3LineIdxToLine(d3LIdx); // First wrap line belonging to this d3LIdx while(wrapLine < line) { d3LPos += d->m_diff3WrapLineVector[wrapLine].wrapLineLength; ++wrapLine; } } else { d3LPos = pos; d3LIdx = line; } } void DiffTextWindow::setSelection(LineRef firstLine, int startPos, LineRef lastLine, int endPos, LineRef& l, int& p) { d->m_selection.reset(); if(lastLine >= getNofLines()) { lastLine = getNofLines() - 1; const Diff3Line* d3l = (*d->m_pDiff3LineVector)[convertLineToDiff3LineIdx(lastLine)]; LineRef line; if(d->m_winIdx == A) line = d3l->getLineA(); if(d->m_winIdx == B) line = d3l->getLineB(); if(d->m_winIdx == C) line = d3l->getLineC(); if(line.isValid()) endPos = (*d->m_pLineData)[line].width(d->m_pOptions->m_tabSize); } if(d->m_bWordWrap && d->m_pDiff3LineVector != nullptr) { QString s1 = d->getString(firstLine); int firstWrapLine = convertDiff3LineIdxToLine(firstLine); int wrapStartPos = startPos; while(wrapStartPos > d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength) { wrapStartPos -= d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength; s1 = s1.mid(d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength); ++firstWrapLine; } QString s2 = d->getString(lastLine); int lastWrapLine = convertDiff3LineIdxToLine(lastLine); int wrapEndPos = endPos; while(wrapEndPos > d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength) { wrapEndPos -= d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength; s2 = s2.mid(d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength); ++lastWrapLine; } d->m_selection.start(firstWrapLine, wrapStartPos); d->m_selection.end(lastWrapLine, wrapEndPos); l = firstWrapLine; p = wrapStartPos; } else { if(d->m_pDiff3LineVector != nullptr) { d->m_selection.start(firstLine, startPos); d->m_selection.end(lastLine, endPos); l = firstLine; p = startPos; } } update(); } int DiffTextWindowData::convertLineOnScreenToLineInSource(int lineOnScreen, e_CoordType coordType, bool bFirstLine) { LineRef line; if(lineOnScreen >= 0) { if(coordType == eWrapCoords) return lineOnScreen; int d3lIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(lineOnScreen); if(!bFirstLine && d3lIdx >= m_pDiff3LineVector->size()) d3lIdx = m_pDiff3LineVector->size() - 1; if(coordType == eD3LLineCoords) return d3lIdx; while(!line.isValid() && d3lIdx < m_pDiff3LineVector->size() && d3lIdx >= 0) { const Diff3Line* d3l = (*m_pDiff3LineVector)[d3lIdx]; if(m_winIdx == A) line = d3l->getLineA(); if(m_winIdx == B) line = d3l->getLineB(); if(m_winIdx == C) line = d3l->getLineC(); if(bFirstLine) ++d3lIdx; else --d3lIdx; } if(coordType == eFileCoords) return line; } return line; } void DiffTextWindow::getSelectionRange(LineRef* pFirstLine, LineRef* pLastLine, e_CoordType coordType) { if(pFirstLine) *pFirstLine = d->convertLineOnScreenToLineInSource(d->m_selection.beginLine(), coordType, true); if(pLastLine) *pLastLine = d->convertLineOnScreenToLineInSource(d->m_selection.endLine(), coordType, false); } void DiffTextWindow::convertSelectionToD3LCoords() { if(d->m_pDiff3LineVector == nullptr || !updatesEnabled() || !isVisible() || d->m_selection.isEmpty()) { return; } // convert the d->m_selection to unwrapped coordinates: Later restore to new coords int firstD3LIdx, firstD3LPos; QString s = d->getLineString(d->m_selection.beginLine()); int firstPosInText = d->m_selection.beginPos(); convertLineCoordsToD3LCoords(d->m_selection.beginLine(), firstPosInText, firstD3LIdx, firstD3LPos); int lastD3LIdx, lastD3LPos; s = d->getLineString(d->m_selection.endLine()); int lastPosInText = d->m_selection.endPos(); convertLineCoordsToD3LCoords(d->m_selection.endLine(), lastPosInText, lastD3LIdx, lastD3LPos); d->m_selection.start(firstD3LIdx, firstD3LPos); d->m_selection.end(lastD3LIdx, lastD3LPos); } bool DiffTextWindow::startRunnables() { if(s_runnables.count() == 0) { return false; } else { g_pProgressDialog->setStayHidden(true); g_pProgressDialog->push(); g_pProgressDialog->setMaxNofSteps(s_runnables.count()); RecalcWordWrapRunnable::s_maxNofRunnables = s_runnables.count(); g_pProgressDialog->setCurrent(0); for(int i = 0; i < s_runnables.count(); ++i) { QThreadPool::globalInstance()->start(s_runnables[i]); } s_runnables.clear(); return true; } } void DiffTextWindow::recalcWordWrap(bool bWordWrap, int wrapLineVectorSize, int visibleTextWidth) { if(d->m_pDiff3LineVector == nullptr || !isVisible()) { d->m_bWordWrap = bWordWrap; if(!bWordWrap) d->m_diff3WrapLineVector.resize(0); return; } d->m_bWordWrap = bWordWrap; if(bWordWrap) { d->m_lineNumberWidth = d->m_pOptions->m_bShowLineNumbers ? (int)log10((double)std::max(d->m_size, 1)) + 1 : 0; d->m_diff3WrapLineVector.resize(wrapLineVectorSize); if(wrapLineVectorSize == 0) { d->m_wrapLineCacheList.clear(); setUpdatesEnabled(false); for(int i = 0, j = 0; i < d->m_pDiff3LineVector->size(); i += s_linesPerRunnable, ++j) { d->m_wrapLineCacheList.append(QVector()); s_runnables.push_back(new RecalcWordWrapRunnable(this, visibleTextWidth, j)); } } else { recalcWordWrapHelper(wrapLineVectorSize, visibleTextWidth, 0); setUpdatesEnabled(true); } } else { if(wrapLineVectorSize == 0 && getAtomic(d->m_maxTextWidth) < 0) { d->m_diff3WrapLineVector.resize(0); d->m_wrapLineCacheList.clear(); setUpdatesEnabled(false); for(int i = 0, j = 0; i < d->m_pDiff3LineVector->size(); i += s_linesPerRunnable, ++j) { s_runnables.push_back(new RecalcWordWrapRunnable(this, visibleTextWidth, j)); } } else { setUpdatesEnabled(true); } } } void DiffTextWindow::recalcWordWrapHelper(int wrapLineVectorSize, int visibleTextWidth, int cacheListIdx) { if(d->m_bWordWrap) { if(g_pProgressDialog->wasCancelled()) return; if(visibleTextWidth < 0) visibleTextWidth = getVisibleTextAreaWidth(); else visibleTextWidth -= d->leftInfoWidth() * Utils::getHorizontalAdvance(fontMetrics(), '0'); int i; int wrapLineIdx = 0; int size = d->m_pDiff3LineVector->size(); int firstD3LineIdx = wrapLineVectorSize > 0 ? 0 : cacheListIdx * s_linesPerRunnable; int endIdx = wrapLineVectorSize > 0 ? size : std::min(firstD3LineIdx + s_linesPerRunnable, size); QVector& wrapLineCache = d->m_wrapLineCacheList[cacheListIdx]; int cacheListIdx2 = 0; QTextLayout textLayout(QString(), font(), this); for(i = firstD3LineIdx; i < endIdx; ++i) { if(g_pProgressDialog->wasCancelled()) return; int linesNeeded = 0; if(wrapLineVectorSize == 0) { QString s = d->getString(i); textLayout.clearLayout(); textLayout.setText(s); d->prepareTextLayout(textLayout, true, visibleTextWidth); linesNeeded = textLayout.lineCount(); for(int l = 0; l < linesNeeded; ++l) { QTextLine line = textLayout.lineAt(l); wrapLineCache.push_back(WrapLineCacheData(i, line.textStart(), line.textLength())); } } else if(wrapLineVectorSize > 0 && cacheListIdx2 < d->m_wrapLineCacheList.count()) { WrapLineCacheData* pWrapLineCache = d->m_wrapLineCacheList[cacheListIdx2].data(); int cacheIdx = 0; int clc = d->m_wrapLineCacheList.count() - 1; int cllc = d->m_wrapLineCacheList.last().count(); int curCount = d->m_wrapLineCacheList[cacheListIdx2].count() - 1; int l = 0; while((cacheListIdx2 < clc || (cacheListIdx2 == clc && cacheIdx < cllc)) && pWrapLineCache->d3LineIdx() <= i) { if(pWrapLineCache->d3LineIdx() == i) { Diff3WrapLine* pDiff3WrapLine = &d->m_diff3WrapLineVector[wrapLineIdx + l]; pDiff3WrapLine->wrapLineOffset = pWrapLineCache->textStart(); pDiff3WrapLine->wrapLineLength = pWrapLineCache->textLength(); ++l; } if(cacheIdx < curCount) { ++cacheIdx; ++pWrapLineCache; } else { ++cacheListIdx2; if(cacheListIdx2 >= d->m_wrapLineCacheList.count()) break; pWrapLineCache = d->m_wrapLineCacheList[cacheListIdx2].data(); curCount = d->m_wrapLineCacheList[cacheListIdx2].count(); cacheIdx = 0; } } linesNeeded = l; } Diff3Line& d3l = *(*d->m_pDiff3LineVector)[i]; if(d3l.linesNeededForDisplay() < linesNeeded) { Q_ASSERT(wrapLineVectorSize == 0); d3l.setLinesNeeded(linesNeeded); } if(wrapLineVectorSize > 0) { int j; for(j = 0; j < d3l.linesNeededForDisplay(); ++j, ++wrapLineIdx) { Diff3WrapLine& d3wl = d->m_diff3WrapLineVector[wrapLineIdx]; d3wl.diff3LineIndex = i; d3wl.pD3L = (*d->m_pDiff3LineVector)[i]; if(j >= linesNeeded) { d3wl.wrapLineOffset = 0; d3wl.wrapLineLength = 0; } } } } if(wrapLineVectorSize > 0) { d->m_firstLine = std::min(d->m_firstLine, wrapLineVectorSize - 1); d->m_horizScrollOffset = 0; d->m_pDiffTextWindowFrame->setFirstLine(d->m_firstLine); } } else // no word wrap, just calc the maximum text width { if(g_pProgressDialog->wasCancelled()) return; int size = d->m_pDiff3LineVector->size(); int firstD3LineIdx = cacheListIdx * s_linesPerRunnable; int endIdx = std::min(firstD3LineIdx + s_linesPerRunnable, size); int maxTextWidth = getAtomic(d->m_maxTextWidth); // current value QTextLayout textLayout(QString(), font(), this); for(int i = firstD3LineIdx; i < endIdx; ++i) { if(g_pProgressDialog->wasCancelled()) return; textLayout.clearLayout(); textLayout.setText(d->getString(i)); d->prepareTextLayout(textLayout, true); if(textLayout.maximumWidth() > maxTextWidth) maxTextWidth = qCeil(textLayout.maximumWidth()); } for(;;) { int prevMaxTextWidth = d->m_maxTextWidth.fetchAndStoreOrdered(maxTextWidth); if(prevMaxTextWidth <= maxTextWidth) break; maxTextWidth = prevMaxTextWidth; } } if(!d->m_selection.isEmpty() && (!d->m_bWordWrap || wrapLineVectorSize > 0)) { // Assume unwrapped coordinates //( Why? ->Conversion to unwrapped coords happened a few lines above in this method. // Also see KDiff3App::recalcWordWrap() on the role of wrapLineVectorSize) // Wrap them now. // convert the d->m_selection to unwrapped coordinates. int firstLine, firstPos; convertD3LCoordsToLineCoords(d->m_selection.beginLine(), d->m_selection.beginPos(), firstLine, firstPos); int lastLine, lastPos; convertD3LCoordsToLineCoords(d->m_selection.endLine(), d->m_selection.endPos(), lastLine, lastPos); d->m_selection.start(firstLine, firstPos); d->m_selection.end(lastLine, lastPos); } } class DiffTextWindowFrameData { public: DiffTextWindowFrameData(DiffTextWindowFrame* frame, const QSharedPointer& pOptions, const e_SrcSelector winIdx) { m_winIdx = winIdx; m_pOptions = pOptions; m_pTopLineWidget = new QWidget(frame); m_pFileSelection = new FileNameLineEdit(m_pTopLineWidget); m_pBrowseButton = new QPushButton("...", m_pTopLineWidget); m_pBrowseButton->setFixedWidth(30); m_pFileSelection->setAcceptDrops(true); m_pLabel = new QLabel("A:", m_pTopLineWidget); m_pTopLine = new QLabel(m_pTopLineWidget); } const QPushButton* getBrowseButton() const { return m_pBrowseButton; } const FileNameLineEdit* getFileSelectionField() const { return m_pFileSelection; } const QWidget* getTopLineWidget() const { return m_pTopLineWidget; } const QLabel* getLabel() const { return m_pLabel; } private: friend DiffTextWindowFrame; DiffTextWindow* m_pDiffTextWindow; FileNameLineEdit* m_pFileSelection; QPushButton* m_pBrowseButton; QSharedPointer m_pOptions; QLabel* m_pLabel; QLabel* m_pTopLine; QLabel* m_pEncoding; QLabel* m_pLineEndStyle; QWidget* m_pTopLineWidget; e_SrcSelector m_winIdx; QSharedPointer mSourceData; }; DiffTextWindowFrame::DiffTextWindowFrame(QWidget* pParent, QStatusBar* pStatusBar, const QSharedPointer& pOptions, e_SrcSelector winIdx, QSharedPointer psd) : QWidget(pParent) { d = new DiffTextWindowFrameData(this, pOptions, winIdx); d->mSourceData = psd; setAutoFillBackground(true); connect(d->getBrowseButton(), &QPushButton::clicked, this, &DiffTextWindowFrame::slotBrowseButtonClicked); connect(d->getFileSelectionField(), &QLineEdit::returnPressed, this, &DiffTextWindowFrame::slotReturnPressed); d->m_pDiffTextWindow = new DiffTextWindow(this, pStatusBar, pOptions, winIdx); d->m_pDiffTextWindow->setSourceData(psd); QVBoxLayout* pVTopLayout = new QVBoxLayout(const_cast(d->getTopLineWidget())); pVTopLayout->setMargin(2); pVTopLayout->setSpacing(0); QHBoxLayout* pHL = new QHBoxLayout(); QHBoxLayout* pHL2 = new QHBoxLayout(); pVTopLayout->addLayout(pHL); pVTopLayout->addLayout(pHL2); // Upper line: pHL->setMargin(0); pHL->setSpacing(2); pHL->addWidget(const_cast(d->getLabel()), 0); pHL->addWidget(const_cast(d->getFileSelectionField()), 1); pHL->addWidget(const_cast(d->getBrowseButton()), 0); pHL->addWidget(d->m_pTopLine, 0); // Lower line pHL2->setMargin(0); pHL2->setSpacing(2); pHL2->addWidget(d->m_pTopLine, 0); d->m_pEncoding = new EncodingLabel(i18n("Encoding:"), psd, pOptions); //EncodeLabel::EncodingChanged should be handled asyncroniously. connect((EncodingLabel*)d->m_pEncoding, &EncodingLabel::encodingChanged, this, &DiffTextWindowFrame::slotEncodingChanged, Qt::QueuedConnection); d->m_pLineEndStyle = new QLabel(i18n("Line end style:")); pHL2->addWidget(d->m_pEncoding); pHL2->addWidget(d->m_pLineEndStyle); QVBoxLayout* pVL = new QVBoxLayout(this); pVL->setMargin(0); pVL->setSpacing(0); pVL->addWidget(const_cast(d->getTopLineWidget()), 0); pVL->addWidget(d->m_pDiffTextWindow, 1); d->m_pDiffTextWindow->installEventFilter(this); d->m_pFileSelection->installEventFilter(this); d->m_pBrowseButton->installEventFilter(this); init(); } DiffTextWindowFrame::~DiffTextWindowFrame() { delete d; } void DiffTextWindowFrame::init() { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW) { QString s = QDir::toNativeSeparators(pDTW->getFileName()); d->m_pFileSelection->setText(s); QString winId = pDTW->getWindowIndex() == A ? (pDTW->isThreeWay() ? i18n("A (Base)") : i18n("A")) : (pDTW->getWindowIndex() == B ? i18n("B") : i18n("C")); d->m_pLabel->setText(winId + ':'); d->m_pEncoding->setText(i18n("Encoding: %1", pDTW->getEncodingDisplayString())); d->m_pLineEndStyle->setText(i18n("Line end style: %1", pDTW->getLineEndStyle() == eLineEndStyleDos ? i18n("DOS") : i18n("Unix"))); } } void DiffTextWindowFrame::setupConnections(const KDiff3App *app) { connect(this, &DiffTextWindowFrame::fileNameChanged, app, &KDiff3App::slotFileNameChanged); connect(this, &DiffTextWindowFrame::encodingChanged, app, &KDiff3App::slotEncodingChanged); connect(this, &DiffTextWindowFrame::encodingChanged, &(*d->mSourceData), &SourceData::setEncoding); } // Search for the first visible line (search loop needed when no line exists for this file.) LineRef DiffTextWindow::calcTopLineInFile(const LineRef firstLine) { LineRef currentLine; for(int i = convertLineToDiff3LineIdx(firstLine); i < (int)d->m_pDiff3LineVector->size(); ++i) { const Diff3Line* d3l = (*d->m_pDiff3LineVector)[i]; currentLine = d3l->getLineInFile(d->m_winIdx); if(currentLine.isValid()) break; } return currentLine; } void DiffTextWindowFrame::setFirstLine(QtNumberType firstLine) { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW && pDTW->getDiff3LineVector()) { QString s = i18n("Top line"); int lineNumberWidth = pDTW->getLineNumberWidth(); LineRef topVisiableLine = pDTW->calcTopLineInFile(firstLine); int w = Utils::getHorizontalAdvance(d->m_pTopLine->fontMetrics(), s + ' ' + QString().fill('0', lineNumberWidth)); d->m_pTopLine->setMinimumWidth(w); if(!topVisiableLine.isValid()) s = i18n("End"); else s += ' ' + QString::number(topVisiableLine + 1); d->m_pTopLine->setText(s); d->m_pTopLine->repaint(); } } DiffTextWindow* DiffTextWindowFrame::getDiffTextWindow() { return d->m_pDiffTextWindow; } bool DiffTextWindowFrame::eventFilter(QObject* o, QEvent* e) { Q_UNUSED(o); if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { QColor c1 = d->m_pOptions->m_bgColor; QColor c2; if(d->m_winIdx == A) c2 = d->m_pOptions->m_colorA; else if(d->m_winIdx == B) c2 = d->m_pOptions->m_colorB; else if(d->m_winIdx == C) c2 = d->m_pOptions->m_colorC; QPalette p = d->m_pTopLineWidget->palette(); if(e->type() == QEvent::FocusOut) std::swap(c1, c2); p.setColor(QPalette::Window, c2); setPalette(p); p.setColor(QPalette::WindowText, c1); d->m_pLabel->setPalette(p); d->m_pTopLine->setPalette(p); d->m_pEncoding->setPalette(p); d->m_pLineEndStyle->setPalette(p); } return false; } void DiffTextWindowFrame::slotReturnPressed() { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW->getFileName() != d->m_pFileSelection->text()) { Q_EMIT fileNameChanged(d->m_pFileSelection->text(), pDTW->getWindowIndex()); } } void DiffTextWindowFrame::slotBrowseButtonClicked() { QString current = d->m_pFileSelection->text(); QUrl newURL = QFileDialog::getOpenFileUrl(this, i18n("Open File"), QUrl::fromUserInput(current, QString(), QUrl::AssumeLocalFile)); if(!newURL.isEmpty()) { DiffTextWindow* pDTW = d->m_pDiffTextWindow; Q_EMIT fileNameChanged(newURL.url(), pDTW->getWindowIndex()); } } EncodingLabel::EncodingLabel(const QString& text, QSharedPointer pSD, const QSharedPointer& pOptions) : QLabel(text) { m_pOptions = pOptions; m_pSourceData = pSD; m_pContextEncodingMenu = nullptr; setMouseTracking(true); } void EncodingLabel::mouseMoveEvent(QMouseEvent*) { // When there is no data to display or it came from clipboard, // we will be use UTF-8 only, // in that case there is no possibility to change the encoding in the SourceData // so, we should hide the HandCursor and display usual ArrowCursor if(m_pSourceData->isFromBuffer() || m_pSourceData->isEmpty()) setCursor(QCursor(Qt::ArrowCursor)); else setCursor(QCursor(Qt::PointingHandCursor)); } void EncodingLabel::mousePressEvent(QMouseEvent*) { if(!(m_pSourceData->isFromBuffer() || m_pSourceData->isEmpty())) { delete m_pContextEncodingMenu; m_pContextEncodingMenu = new QMenu(this); QMenu* pContextEncodingSubMenu = new QMenu(m_pContextEncodingMenu); int currentTextCodecEnum = m_pSourceData->getEncoding()->mibEnum(); // the codec that will be checked in the context menu QList mibs = QTextCodec::availableMibs(); QList codecEnumList; // Adding "main" encodings insertCodec(i18n("Unicode, 8 bit"), QTextCodec::codecForName("UTF-8"), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); if(QTextCodec::codecForName("System")) { insertCodec(QString(), QTextCodec::codecForName("System"), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); } // Adding recent encodings if(m_pOptions != nullptr) { QStringList& recentEncodings = m_pOptions->m_recentEncodings; for(const QString& s : recentEncodings) { insertCodec("", QTextCodec::codecForName(s.toLatin1()), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); } } // Submenu to add the rest of available encodings pContextEncodingSubMenu->setTitle(i18n("Other")); for(int i : mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) insertCodec("", c, codecEnumList, pContextEncodingSubMenu, currentTextCodecEnum); } m_pContextEncodingMenu->addMenu(pContextEncodingSubMenu); m_pContextEncodingMenu->exec(QCursor::pos()); } } void EncodingLabel::insertCodec(const QString& visibleCodecName, QTextCodec* pCodec, QList& codecEnumList, QMenu* pMenu, int currentTextCodecEnum) const { int CodecMIBEnum = pCodec->mibEnum(); if(pCodec != nullptr && !codecEnumList.contains(CodecMIBEnum)) { QAction* pAction = new QAction(pMenu); // menu takes ownership, so deleting the menu deletes the action too. QByteArray nameArray = pCodec->name(); QLatin1String codecName = QLatin1String(nameArray); pAction->setText(visibleCodecName.isEmpty() ? codecName : visibleCodecName + QLatin1String(" (") + codecName + QLatin1String(")")); pAction->setData(CodecMIBEnum); pAction->setCheckable(true); if(currentTextCodecEnum == CodecMIBEnum) pAction->setChecked(true); pMenu->addAction(pAction); connect(pAction, &QAction::triggered, this, &EncodingLabel::slotSelectEncoding); codecEnumList.append(CodecMIBEnum); } } void EncodingLabel::slotSelectEncoding() { QAction* pAction = qobject_cast(sender()); if(pAction) { QTextCodec* pCodec = QTextCodec::codecForMib(pAction->data().toInt()); if(pCodec != nullptr) { QString s(QLatin1String(pCodec->name())); QStringList& recentEncodings = m_pOptions->m_recentEncodings; if(!recentEncodings.contains(s) && s != "UTF-8" && s != "System") { int itemsToRemove = recentEncodings.size() - m_maxRecentEncodings + 1; for(int i = 0; i < itemsToRemove; ++i) { recentEncodings.removeFirst(); } recentEncodings.append(s); } } Q_EMIT encodingChanged(pCodec); } } diff --git a/src/difftextwindow.h b/src/difftextwindow.h index 70d4d04..327cb40 100644 --- a/src/difftextwindow.h +++ b/src/difftextwindow.h @@ -1,198 +1,194 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * joachim.eibl at gmx.de * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef DIFFTEXTWINDOW_H #define DIFFTEXTWINDOW_H #include "diff.h" #include #include // for QSharedPointer #include // for QString class QMenu; class RecalcWordWrapRunnable; class QStatusBar; class Options; class DiffTextWindowData; class DiffTextWindowFrame; class EncodingLabel; class RLPainter; class KDiff3App; class DiffTextWindow : public QWidget { Q_OBJECT public: DiffTextWindow(DiffTextWindowFrame* pParent, QStatusBar* pStatusBar, const QSharedPointer &pOptions, e_SrcSelector winIdx); ~DiffTextWindow() override; void init( const QString& fileName, QTextCodec* pTextCodec, e_LineEndStyle eLineEndStyle, const QVector* pLineData, int size, const Diff3LineVector* pDiff3LineVector, const ManualDiffHelpList* pManualDiffHelpList, bool bTriple ); void setupConnections(const KDiff3App *app) const; void reset(); void convertToLinePos(int x, int y, LineRef& line, int& pos); QString getSelection(); int getFirstLine(); LineRef calcTopLineInFile(const LineRef firstLine); int getMaxTextWidth(); LineCount getNofLines(); int getNofVisibleLines(); int getVisibleTextAreaWidth(); int convertLineToDiff3LineIdx(LineRef line); LineRef convertDiff3LineIdxToLine(int d3lIdx); void convertD3LCoordsToLineCoords(int d3LIdx, int d3LPos, int& line, int& pos); void convertLineCoordsToD3LCoords(int line, int pos, int& d3LIdx, int& d3LPos); void convertSelectionToD3LCoords(); bool findString(const QString& s, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive); void setSelection(LineRef firstLine, int startPos, LineRef lastLine, int endPos, LineRef& l, int& p); void getSelectionRange(LineRef* firstLine, LineRef* lastLine, e_CoordType coordType); void setPaintingAllowed(bool bAllowPainting); void recalcWordWrap(bool bWordWrap, int wrapLineVectorSize, int visibleTextWidth); void recalcWordWrapHelper(int wrapLineVectorSize, int visibleTextWidth, int cacheListIdx); void printWindow(RLPainter& painter, const QRect& view, const QString& headerText, int line, int linesPerPage, const QColor& fgColor); void print(RLPainter& painter, const QRect& r, int firstLine, int nofLinesPerPage); static bool startRunnables(); bool isThreeWay() const; const QString& getFileName() const; e_SrcSelector getWindowIndex() const; const QString getEncodingDisplayString() const; e_LineEndStyle getLineEndStyle() const; const Diff3LineVector* getDiff3LineVector() const; qint32 getLineNumberWidth() const; void setSourceData(const QSharedPointer& inData); Q_SIGNALS: void resizeHeightChangedSignal(int nofVisibleLines); void resizeWidthChangedSignal(int nofVisibleColumns); void scrollDiffTextWindow(int deltaX, int deltaY); void newSelection(); void selectionEnd(); void setFastSelectorLine(LineIndex line); void gotFocus(); void lineClicked(e_SrcSelector winIdx, LineRef line); void finishRecalcWordWrap(int visibleTextWidthForPrinting); void checkIfCanContinue(bool& pbContinue); void finishDrop(); public Q_SLOTS: void setFirstLine(QtNumberType line); void setHorizScrollOffset(int horizScrollOffset); void resetSelection(); void setFastSelectorRange(int line1, int nofLines); void slotRefresh(); protected: void mousePressEvent(QMouseEvent*) override; void mouseReleaseEvent(QMouseEvent*) override; void mouseMoveEvent(QMouseEvent*) override; void mouseDoubleClickEvent(QMouseEvent* e) override; void paintEvent(QPaintEvent*) override; void dragEnterEvent(QDragEnterEvent* e) override; void dropEvent(QDropEvent* dropEvent) override; void focusInEvent(QFocusEvent* e) override; void resizeEvent(QResizeEvent*) override; void timerEvent(QTimerEvent*) override; private: static QList s_runnables; static constexpr int s_linesPerRunnable = 2000; DiffTextWindowData* d; void showStatusLine(const LineRef lineFromPos); }; class DiffTextWindowFrameData; class DiffTextWindowFrame : public QWidget { Q_OBJECT public: DiffTextWindowFrame(QWidget* pParent, QStatusBar* pStatusBar, const QSharedPointer &pOptions, e_SrcSelector winIdx, QSharedPointer psd); ~DiffTextWindowFrame() override; DiffTextWindow* getDiffTextWindow(); void init(); void setFirstLine(QtNumberType firstLine); void setupConnections(const KDiff3App *app); Q_SIGNALS: void fileNameChanged(const QString&, e_SrcSelector); void encodingChanged(QTextCodec*); protected: bool eventFilter(QObject*, QEvent*) override; //void paintEvent(QPaintEvent*); private Q_SLOTS: void slotReturnPressed(); void slotBrowseButtonClicked(); void slotEncodingChanged(QTextCodec* c) { Q_EMIT encodingChanged(c); };//relay signal from encoding label private: DiffTextWindowFrameData* d; }; class EncodingLabel : public QLabel { Q_OBJECT public: EncodingLabel(const QString& text, QSharedPointer psd, const QSharedPointer &pOptions); protected: void mouseMoveEvent(QMouseEvent* ev) override; void mousePressEvent(QMouseEvent* ev) override; Q_SIGNALS: void encodingChanged(QTextCodec*); private Q_SLOTS: void slotSelectEncoding(); private: QMenu* m_pContextEncodingMenu; QSharedPointer m_pSourceData; //SourceData to get access to "isEmpty()" and "isFromBuffer()" functions static const int m_maxRecentEncodings = 5; QSharedPointer m_pOptions; void insertCodec(const QString& visibleCodecName, QTextCodec* pCodec, QList& CodecEnumList, QMenu* pMenu, int currentTextCodecEnum) const; }; #endif diff --git a/src/directorymergewindow.cpp b/src/directorymergewindow.cpp index 33123a5..f88abad 100644 --- a/src/directorymergewindow.cpp +++ b/src/directorymergewindow.cpp @@ -1,3065 +1,3061 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "directorymergewindow.h" #include "DirectoryInfo.h" #include "MergeFileInfos.h" #include "PixMapUtils.h" #include "Utils.h" #include "guiutils.h" #include "kdiff3.h" #include "options.h" #include "progress.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct DirectoryMergeWindow::t_ItemInfo { bool bExpanded; bool bOperationComplete; QString status; e_MergeOperation eMergeOperation; }; class StatusInfo : public QDialog { private: KTextEdit* m_pTextEdit; public: explicit StatusInfo(QWidget* pParent) : QDialog(pParent) { QVBoxLayout* pVLayout = new QVBoxLayout(this); m_pTextEdit = new KTextEdit(this); pVLayout->addWidget(m_pTextEdit); setObjectName("StatusInfo"); setWindowFlags(Qt::Dialog); m_pTextEdit->setWordWrapMode(QTextOption::NoWrap); m_pTextEdit->setReadOnly(true); QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Close, this); connect(box, &QDialogButtonBox::rejected, this, &QDialog::accept); pVLayout->addWidget(box); } bool isEmpty() { return m_pTextEdit->toPlainText().isEmpty(); } void addText(const QString& s) { m_pTextEdit->append(s); } void clear() { m_pTextEdit->clear(); } void setVisible(bool bVisible) override { if(bVisible) { m_pTextEdit->moveCursor(QTextCursor::End); m_pTextEdit->moveCursor(QTextCursor::StartOfLine); m_pTextEdit->ensureCursorVisible(); } QDialog::setVisible(bVisible); if(bVisible) setWindowState(windowState() | Qt::WindowMaximized); } }; enum Columns { s_NameCol = 0, s_ACol = 1, s_BCol = 2, s_CCol = 3, s_OpCol = 4, s_OpStatusCol = 5, s_UnsolvedCol = 6, // Nr of unsolved conflicts (for 3 input files) s_SolvedCol = 7, // Nr of auto-solvable conflicts (for 3 input files) s_NonWhiteCol = 8, // Nr of nonwhite deltas (for 2 input files) s_WhiteCol = 9 // Nr of white deltas (for 2 input files) }; static Qt::CaseSensitivity s_eCaseSensitivity = Qt::CaseSensitive; //TODO: clean up this mess. class DirectoryMergeWindow::DirectoryMergeWindowPrivate : public QAbstractItemModel { friend class DirMergeItem; public: DirectoryMergeWindow* mWindow; explicit DirectoryMergeWindowPrivate(DirectoryMergeWindow* pDMW) { mWindow = pDMW; m_pStatusInfo = new StatusInfo(mWindow); m_pStatusInfo->hide(); } ~DirectoryMergeWindowPrivate() override { delete m_pRoot; } // Implement QAbstractItemModel QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; //Qt::ItemFlags flags ( const QModelIndex & index ) const QModelIndex parent(const QModelIndex& index) const override { MergeFileInfos* pMFI = getMFI(index); if(pMFI == nullptr || pMFI == m_pRoot || pMFI->parent() == m_pRoot) return QModelIndex(); MergeFileInfos* pParentsParent = pMFI->parent()->parent(); return createIndex(pParentsParent->children().indexOf(pMFI->parent()), 0, pMFI->parent()); } int rowCount(const QModelIndex& parent = QModelIndex()) const override { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI != nullptr) return pParentMFI->children().count(); else return m_pRoot->children().count(); } int columnCount(const QModelIndex& /*parent*/) const override { return 10; } QModelIndex index(int row, int column, const QModelIndex& parent) const override { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI == nullptr && row < m_pRoot->children().count()) return createIndex(row, column, m_pRoot->children()[row]); else if(pParentMFI != nullptr && row < pParentMFI->children().count()) return createIndex(row, column, pParentMFI->children()[row]); else return QModelIndex(); } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void sort(int column, Qt::SortOrder order) override; // private data and helper methods MergeFileInfos* getMFI(const QModelIndex& mi) const { if(mi.isValid()) return (MergeFileInfos*)mi.internalPointer(); else return nullptr; } bool isThreeWay() const { if(rootMFI() == nullptr) return false; return rootMFI()->isThreeWay(); } MergeFileInfos* rootMFI() const { return m_pRoot; } QSharedPointer m_pOptions = nullptr; void calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges); void mergeContinue(bool bStart, bool bVerbose); void prepareListView(ProgressProxy& pp); void calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp); void setAllMergeOperations(e_MergeOperation eDefaultOperation); bool canContinue(); QModelIndex treeIterator(QModelIndex mi, bool bVisitChildren = true, bool bFindInvisible = false); void prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose); bool executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge); void scanDirectory(const QString& dirName, t_DirectoryList& dirList); void scanLocalDirectory(const QString& dirName, t_DirectoryList& dirList); void setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive = true); bool isDir(const QModelIndex& mi) const; QString getFileName(const QModelIndex& mi) const; bool copyFLD(const QString& srcName, const QString& destName); bool deleteFLD(const QString& name, bool bCreateBackup); bool makeDir(const QString& name, bool bQuiet = false); bool renameFLD(const QString& srcName, const QString& destName); bool mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge); void buildMergeMap(const QSharedPointer& dirInfo); private: class FileKey { private: const FileAccess* m_pFA; public: explicit FileKey(const FileAccess& fa) : m_pFA(&fa) {} quint32 getParents(const FileAccess* pFA, const FileAccess* v[], quint32 maxSize) const { quint32 s = 0; for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s) { if(s == maxSize) break; v[s] = pFA; } return s; } // This is essentially the same as // int r = filePath().compare( fa.filePath() ) // if ( r<0 ) return true; // if ( r==0 ) return m_col < fa.m_col; // return false; bool operator<(const FileKey& fk) const { const FileAccess* v1[100]; const FileAccess* v2[100]; quint32 v1Size = getParents(m_pFA, v1, 100); quint32 v2Size = getParents(fk.m_pFA, v2, 100); for(quint32 i = 0; i < v1Size && i < v2Size; ++i) { int r = v1[v1Size - i - 1]->fileName().compare(v2[v2Size - i - 1]->fileName(), s_eCaseSensitivity); if(r < 0) return true; else if(r > 0) return false; } return v1Size < v2Size; } }; typedef QMap t_fileMergeMap; MergeFileInfos* m_pRoot = new MergeFileInfos(); t_fileMergeMap m_fileMergeMap; public: bool m_bFollowDirLinks = false; bool m_bFollowFileLinks = false; bool m_bSimulatedMergeStarted = false; bool m_bRealMergeStarted = false; bool m_bError = false; bool m_bSyncMode = false; bool m_bDirectoryMerge = false; // if true, then merge is the default operation, otherwise it's diff. bool m_bCaseSensitive = true; bool m_bUnfoldSubdirs = false; bool m_bSkipDirStatus = false; bool m_bScanning = false; // true while in init() DirectoryMergeInfo* m_pDirectoryMergeInfo = nullptr; StatusInfo* m_pStatusInfo = nullptr; typedef std::list MergeItemList; // linked list MergeItemList m_mergeItemList; MergeItemList::iterator m_currentIndexForOperation; QModelIndex m_selection1Index; QModelIndex m_selection2Index; QModelIndex m_selection3Index; void selectItemAndColumn(const QModelIndex& mi, bool bContextMenu); QAction* m_pDirStartOperation; QAction* m_pDirRunOperationForCurrentItem; QAction* m_pDirCompareCurrent; QAction* m_pDirMergeCurrent; QAction* m_pDirRescan; QAction* m_pDirChooseAEverywhere; QAction* m_pDirChooseBEverywhere; QAction* m_pDirChooseCEverywhere; QAction* m_pDirAutoChoiceEverywhere; QAction* m_pDirDoNothingEverywhere; QAction* m_pDirFoldAll; QAction* m_pDirUnfoldAll; KToggleAction* m_pDirShowIdenticalFiles; KToggleAction* m_pDirShowDifferentFiles; KToggleAction* m_pDirShowFilesOnlyInA; KToggleAction* m_pDirShowFilesOnlyInB; KToggleAction* m_pDirShowFilesOnlyInC; KToggleAction* m_pDirSynchronizeDirectories; KToggleAction* m_pDirChooseNewerFiles; QAction* m_pDirCompareExplicit; QAction* m_pDirMergeExplicit; QAction* m_pDirCurrentDoNothing; QAction* m_pDirCurrentChooseA; QAction* m_pDirCurrentChooseB; QAction* m_pDirCurrentChooseC; QAction* m_pDirCurrentMerge; QAction* m_pDirCurrentDelete; QAction* m_pDirCurrentSyncDoNothing; QAction* m_pDirCurrentSyncCopyAToB; QAction* m_pDirCurrentSyncCopyBToA; QAction* m_pDirCurrentSyncDeleteA; QAction* m_pDirCurrentSyncDeleteB; QAction* m_pDirCurrentSyncDeleteAAndB; QAction* m_pDirCurrentSyncMergeToA; QAction* m_pDirCurrentSyncMergeToB; QAction* m_pDirCurrentSyncMergeToAAndB; QAction* m_pDirSaveMergeState; QAction* m_pDirLoadMergeState; bool init(const QSharedPointer& dirInfo, bool bDirectoryMerge, bool bReload); void setOpStatus(const QModelIndex& mi, e_OperationStatus eOpStatus) { if(MergeFileInfos* pMFI = getMFI(mi)) { pMFI->setOpStatus(eOpStatus); Q_EMIT dataChanged(mi, mi); } } QModelIndex nextSibling(const QModelIndex& mi); }; QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::data(const QModelIndex& index, int role) const { MergeFileInfos* pMFI = getMFI(index); if(pMFI) { if(role == Qt::DisplayRole) { switch(index.column()) { case s_NameCol: return QFileInfo(pMFI->subPath()).fileName(); case s_ACol: return i18n("A"); case s_BCol: return i18n("B"); case s_CCol: return i18n("C"); //case s_OpCol: return i18n("Operation"); //case s_OpStatusCol: return i18n("Status"); case s_UnsolvedCol: return pMFI->diffStatus().getUnsolvedConflicts(); case s_SolvedCol: return pMFI->diffStatus().getSolvedConflicts(); case s_NonWhiteCol: return pMFI->diffStatus().getNonWhitespaceConflicts(); case s_WhiteCol: return pMFI->diffStatus().getWhitespaceConflicts(); //default : return QVariant(); } if(s_OpCol == index.column()) { bool bDir = pMFI->hasDir(); switch(pMFI->getOperation()) { case eNoOperation: return ""; break; case eCopyAToB: return i18n("Copy A to B"); break; case eCopyBToA: return i18n("Copy B to A"); break; case eDeleteA: return i18n("Delete A"); break; case eDeleteB: return i18n("Delete B"); break; case eDeleteAB: return i18n("Delete A & B"); break; case eMergeToA: return i18n("Merge to A"); break; case eMergeToB: return i18n("Merge to B"); break; case eMergeToAB: return i18n("Merge to A & B"); break; case eCopyAToDest: return i18n("A"); break; case eCopyBToDest: return i18n("B"); break; case eCopyCToDest: return i18n("C"); break; case eDeleteFromDest: return i18n("Delete (if exists)"); break; case eMergeABCToDest: case eMergeABToDest: return bDir ? i18n("Merge") : i18n("Merge (manual)"); break; case eConflictingFileTypes: return i18n("Error: Conflicting File Types"); break; case eChangedAndDeleted: return i18n("Error: Changed and Deleted"); break; case eConflictingAges: return i18n("Error: Dates are equal but files are not."); break; default: Q_ASSERT(true); break; } } if(s_OpStatusCol == index.column()) { switch(pMFI->getOpStatus()) { case eOpStatusNone: return ""; case eOpStatusDone: return i18n("Done"); case eOpStatusError: return i18n("Error"); case eOpStatusSkipped: return i18n("Skipped."); case eOpStatusNotSaved: return i18n("Not saved."); case eOpStatusInProgress: return i18n("In progress..."); case eOpStatusToDo: return i18n("To do."); } } } else if(role == Qt::DecorationRole) { if(s_NameCol == index.column()) { return PixMapUtils::getOnePixmap(eAgeEnd, pMFI->hasLink(), pMFI->hasDir()); } if(s_ACol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->getAgeA(), pMFI->isLinkA(), pMFI->isDirA()); } if(s_BCol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->getAgeB(), pMFI->isLinkB(), pMFI->isDirB()); } if(s_CCol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->getAgeC(), pMFI->isLinkC(), pMFI->isDirC()); } } else if(role == Qt::TextAlignmentRole) { if(s_UnsolvedCol == index.column() || s_SolvedCol == index.column() || s_NonWhiteCol == index.column() || s_WhiteCol == index.column()) return Qt::AlignRight; } } return QVariant(); } QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation == Qt::Horizontal && section >= 0 && section < columnCount(QModelIndex()) && role == Qt::DisplayRole) { switch(section) { case s_NameCol: return i18n("Name"); case s_ACol: return i18n("A"); case s_BCol: return i18n("B"); case s_CCol: return i18n("C"); case s_OpCol: return i18n("Operation"); case s_OpStatusCol: return i18n("Status"); case s_UnsolvedCol: return i18n("Unsolved"); case s_SolvedCol: return i18n("Solved"); case s_NonWhiteCol: return i18n("Nonwhite"); case s_WhiteCol: return i18n("White"); default: return QVariant(); } } return QVariant(); } int DirectoryMergeWindow::getIntFromIndex(const QModelIndex& index) const { return index == d->m_selection1Index ? 1 : index == d->m_selection2Index ? 2 : index == d->m_selection3Index ? 3 : 0; } const QSharedPointer& DirectoryMergeWindow::getOptions() const { return d->m_pOptions; } // Previously Q3ListViewItem::paintCell(p,cg,column,width,align); class DirectoryMergeWindow::DirMergeItemDelegate : public QStyledItemDelegate { private: DirectoryMergeWindow* m_pDMW; const QSharedPointer& getOptions() const { return m_pDMW->getOptions(); } public: explicit DirMergeItemDelegate(DirectoryMergeWindow* pParent) : QStyledItemDelegate(pParent), m_pDMW(pParent) { } void paint(QPainter* thePainter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { QtNumberType column = index.column(); if(column == s_ACol || column == s_BCol || column == s_CCol) { QVariant value = index.data(Qt::DecorationRole); QPixmap icon; if(value.isValid()) { if(value.type() == QVariant::Icon) { icon = qvariant_cast(value).pixmap(16, 16); //icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), icon.actualSize(option.decorationSize, iconMode, iconState)); } else { icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect()); } } int x = option.rect.left(); int y = option.rect.top(); //QPixmap icon = value.value(); //pixmap(column); if(!icon.isNull()) { int yOffset = (sizeHint(option, index).height() - icon.height()) / 2; thePainter->drawPixmap(x + 2, y + yOffset, icon); int i = m_pDMW->getIntFromIndex(index); if(i != 0) { QColor c(i == 1 ? getOptions()->m_colorA : i == 2 ? getOptions()->m_colorB : getOptions()->m_colorC); thePainter->setPen(c); // highlight() ); thePainter->drawRect(x + 2, y + yOffset, icon.width(), icon.height()); thePainter->setPen(QPen(c, 0, Qt::DotLine)); thePainter->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); thePainter->setPen(Qt::white); QString s(QChar('A' + i - 1)); thePainter->drawText(x + 2 + (icon.width() - Utils::getHorizontalAdvance(thePainter->fontMetrics(), s)) / 2, y + yOffset + (icon.height() + thePainter->fontMetrics().ascent()) / 2 - 1, s); } else { thePainter->setPen(m_pDMW->palette().window().color()); thePainter->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); } return; } } QStyleOptionViewItem option2 = option; if(column >= s_UnsolvedCol) { option2.displayAlignment = Qt::AlignRight; } QStyledItemDelegate::paint(thePainter, option2, index); } QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override { QSize sz = QStyledItemDelegate::sizeHint(option, index); return sz.expandedTo(QSize(0, 18)); } }; DirectoryMergeWindow::DirectoryMergeWindow(QWidget* pParent, const QSharedPointer& pOptions) : QTreeView(pParent) { d = new DirectoryMergeWindowPrivate(this); setModel(d); setItemDelegate(new DirMergeItemDelegate(this)); connect(this, &DirectoryMergeWindow::doubleClicked, this, &DirectoryMergeWindow::onDoubleClick); connect(this, &DirectoryMergeWindow::expanded, this, &DirectoryMergeWindow::onExpanded); d->m_pOptions = pOptions; setSortingEnabled(true); } DirectoryMergeWindow::~DirectoryMergeWindow() { delete d; } void DirectoryMergeWindow::setDirectoryMergeInfo(DirectoryMergeInfo* p) { d->m_pDirectoryMergeInfo = p; } bool DirectoryMergeWindow::isDirectoryMergeInProgress() { return d->m_bRealMergeStarted; } bool DirectoryMergeWindow::isSyncMode() { return d->m_bSyncMode; } bool DirectoryMergeWindow::isScanning() { return d->m_bScanning; } int DirectoryMergeWindow::totalColumnWidth() { int w = 0; for(int i = 0; i < s_OpStatusCol; ++i) { w += columnWidth(i); } return w; } void DirectoryMergeWindow::reload() { if(isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a folder merge. Are you sure, you want to abort the merge and rescan the folder?"), i18n("Warning"), KGuiItem(i18n("Rescan")), KGuiItem(i18n("Continue Merging"))); if(result != KMessageBox::Yes) return; } init(d->rootMFI()->getDirectoryInfo(), true); //fix file visibilities after reload or menu will be out of sync with display if changed from defaults. updateFileVisibilities(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI->hasDir()) { ++nofDirs; } else { ++nofFiles; if(pMFI->isEqualAB() && (!bThreeDirs || pMFI->isEqualAC())) { ++nofEqualFiles; } else { if(pMFI->getOperation() == eMergeABCToDest || pMFI->getOperation() == eMergeABToDest) ++nofManualMerges; } } for(int childIdx = 0; childIdx < rowCount(mi); ++childIdx) calcDirStatus(bThreeDirs, index(childIdx, 0, mi), nofFiles, nofDirs, nofEqualFiles, nofManualMerges); } bool DirectoryMergeWindow::init( const QSharedPointer& dirInfo, bool bDirectoryMerge, bool bReload) { return d->init(dirInfo, bDirectoryMerge, bReload); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::buildMergeMap(const QSharedPointer& dirInfo) { t_DirectoryList::iterator dirIterator; if(dirInfo->dirA().isValid()) { for(dirIterator = dirInfo->getDirListA().begin(); dirIterator != dirInfo->getDirListA().end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoA(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } if(dirInfo->dirB().isValid()) { for(dirIterator = dirInfo->getDirListB().begin(); dirIterator != dirInfo->getDirListB().end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoB(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } if(dirInfo->dirC().isValid()) { for(dirIterator = dirInfo->getDirListC().begin(); dirIterator != dirInfo->getDirListC().end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoC(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::init( const QSharedPointer& dirInfo, bool bDirectoryMerge, bool bReload) { //set root data now that we have the directory info. rootMFI()->setDirectoryInfo(dirInfo); if(m_pOptions->m_bDmFullAnalysis) { // A full analysis uses the same resources that a normal text-diff/merge uses. // So make sure that the user saves his data first. bool bCanContinue = false; Q_EMIT mWindow->checkIfCanContinue(bCanContinue); if(!bCanContinue) return false; Q_EMIT mWindow->startDiffMerge("", "", "", "", "", "", "", nullptr); // hide main window } mWindow->show(); mWindow->setUpdatesEnabled(true); std::map expandedDirsMap; if(bReload) { // Remember expanded items TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // t_ItemInfo& ii = expandedDirsMap[ pDMI->m_pMFI->subPath() ]; // ii.bExpanded = pDMI->isExpanded(); // ii.bOperationComplete = pDMI->m_pMFI->m_bOperationComplete; // ii.status = pDMI->text( s_OpStatusCol ); // ii.eMergeOperation = pDMI->m_pMFI->getOperation(); // ++it; //} } ProgressProxy pp; m_bFollowDirLinks = m_pOptions->m_bDmFollowDirLinks; m_bFollowFileLinks = m_pOptions->m_bDmFollowFileLinks; m_bSimulatedMergeStarted = false; m_bRealMergeStarted = false; m_bError = false; m_bDirectoryMerge = bDirectoryMerge; m_selection1Index = QModelIndex(); m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); m_bCaseSensitive = m_pOptions->m_bDmCaseSensitiveFilenameComparison; m_bUnfoldSubdirs = m_pOptions->m_bDmUnfoldSubdirs; m_bSkipDirStatus = m_pOptions->m_bDmSkipDirStatus; beginResetModel(); m_pRoot->clear(); m_mergeItemList.clear(); endResetModel(); m_currentIndexForOperation = m_mergeItemList.end(); if(!bReload) { m_pDirShowIdenticalFiles->setChecked(true); m_pDirShowDifferentFiles->setChecked(true); m_pDirShowFilesOnlyInA->setChecked(true); m_pDirShowFilesOnlyInB->setChecked(true); m_pDirShowFilesOnlyInC->setChecked(true); } Q_ASSERT(dirInfo != nullptr); FileAccess dirA = dirInfo->dirA(); FileAccess dirB = dirInfo->dirB(); FileAccess dirC = dirInfo->dirC(); const FileAccess dirDest = dirInfo->destDir(); // Check if all input directories exist and are valid. The dest dir is not tested now. // The test will happen only when we are going to write to it. if(!dirA.isDir() || !dirB.isDir() || (dirC.isValid() && !dirC.isDir())) { QString text(i18n("Opening of folders failed:")); text += "\n\n"; if(!dirA.isDir()) { text += i18n("Folder A \"%1\" does not exist or is not a folder.\n", dirA.prettyAbsPath()); } if(!dirB.isDir()) { text += i18n("Folder B \"%1\" does not exist or is not a folder.\n", dirB.prettyAbsPath()); } if(dirC.isValid() && !dirC.isDir()) { text += i18n("Folder C \"%1\" does not exist or is not a folder.\n", dirC.prettyAbsPath()); } KMessageBox::sorry(mWindow, text, i18n("Folder Opening Error")); return false; } if(dirC.isValid() && (dirDest.prettyAbsPath() == dirA.prettyAbsPath() || dirDest.prettyAbsPath() == dirB.prettyAbsPath())) { KMessageBox::error(mWindow, i18n("The destination folder must not be the same as A or B when " "three folders are merged.\nCheck again before continuing."), i18n("Parameter Warning")); return false; } m_bScanning = true; Q_EMIT mWindow->statusBarMessage(i18n("Scanning folders...")); m_bSyncMode = m_pOptions->m_bDmSyncMode && dirInfo->allowSyncMode(); m_fileMergeMap.clear(); s_eCaseSensitivity = m_bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; // calc how many directories will be read: double nofScans = (dirA.isValid() ? 1 : 0) + (dirB.isValid() ? 1 : 0) + (dirC.isValid() ? 1 : 0); int currentScan = 0; //TODO setColumnWidthMode(s_UnsolvedCol, Q3ListView::Manual); // setColumnWidthMode(s_SolvedCol, Q3ListView::Manual); // setColumnWidthMode(s_WhiteCol, Q3ListView::Manual); // setColumnWidthMode(s_NonWhiteCol, Q3ListView::Manual); mWindow->setColumnHidden(s_CCol, !dirC.isValid()); mWindow->setColumnHidden(s_WhiteCol, !m_pOptions->m_bDmFullAnalysis); mWindow->setColumnHidden(s_NonWhiteCol, !m_pOptions->m_bDmFullAnalysis); mWindow->setColumnHidden(s_UnsolvedCol, !m_pOptions->m_bDmFullAnalysis); mWindow->setColumnHidden(s_SolvedCol, !(m_pOptions->m_bDmFullAnalysis && dirC.isValid())); bool bListDirSuccessA = true; bool bListDirSuccessB = true; bool bListDirSuccessC = true; if(dirA.isValid()) { pp.setInformation(i18n("Reading Folder A")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessA = dirInfo->listDirA(*m_pOptions); } if(dirB.isValid()) { pp.setInformation(i18n("Reading Folder B")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessB = dirInfo->listDirB(*m_pOptions); } e_MergeOperation eDefaultMergeOp; if(dirC.isValid()) { pp.setInformation(i18n("Reading Folder C")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessC = dirInfo->listDirC(*m_pOptions); eDefaultMergeOp = eMergeABCToDest; } else eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest; buildMergeMap(dirInfo); bool bContinue = true; if(!bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC) { QString s = i18n("Some subfolders were not readable in"); if(!bListDirSuccessA) s += "\nA: " + dirA.prettyAbsPath(); if(!bListDirSuccessB) s += "\nB: " + dirB.prettyAbsPath(); if(!bListDirSuccessC) s += "\nC: " + dirC.prettyAbsPath(); s += '\n'; s += i18n("Check the permissions of the subfolders."); bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel(mWindow, s); } if(bContinue) { prepareListView(pp); mWindow->updateFileVisibilities(); for(int childIdx = 0; childIdx < rowCount(); ++childIdx) { QModelIndex mi = index(childIdx, 0, QModelIndex()); calcSuggestedOperation(mi, eDefaultMergeOp); } } mWindow->sortByColumn(0, Qt::AscendingOrder); for(int column = 0; column < columnCount(QModelIndex()); ++column) mWindow->resizeColumnToContents(column); // Try to improve the view a little bit. QWidget* pParent = mWindow->parentWidget(); QSplitter* pSplitter = static_cast(pParent); if(pSplitter != nullptr) { QList sizes = pSplitter->sizes(); int total = sizes[0] + sizes[1]; if(total < 10) total = 100; sizes[0] = total * 6 / 10; sizes[1] = total - sizes[0]; pSplitter->setSizes(sizes); } m_bScanning = false; Q_EMIT mWindow->statusBarMessage(i18n("Ready.")); if(bContinue && !m_bSkipDirStatus) { // Generate a status report int nofFiles = 0; int nofDirs = 0; int nofEqualFiles = 0; int nofManualMerges = 0; //TODO for(int childIdx = 0; childIdx < rowCount(); ++childIdx) calcDirStatus(dirC.isValid(), index(childIdx, 0, QModelIndex()), nofFiles, nofDirs, nofEqualFiles, nofManualMerges); QString s; s = i18n("Folder Comparison Status\n\n" "Number of subfolders: %1\n" "Number of equal files: %2\n" "Number of different files: %3", nofDirs, nofEqualFiles, nofFiles - nofEqualFiles); if(dirC.isValid()) s += '\n' + i18n("Number of manual merges: %1", nofManualMerges); KMessageBox::information(mWindow, s); // //TODO //if ( topLevelItemCount()>0 ) //{ // topLevelItem(0)->setSelected(true); // setCurrentItem( topLevelItem(0) ); //} } if(bReload) { // Remember expanded items //TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // std::map::iterator i = expandedDirsMap.find( pDMI->m_pMFI->subPath() ); // if ( i!=expandedDirsMap.end() ) // { // t_ItemInfo& ii = i->second; // pDMI->setExpanded( ii.bExpanded ); // //pDMI->m_pMFI->setMergeOperation( ii.eMergeOperation, false ); unsafe, might have changed // pDMI->m_pMFI->m_bOperationComplete = ii.bOperationComplete; // pDMI->setText( s_OpStatusCol, ii.status ); // } // ++it; //} } else if(m_bUnfoldSubdirs) { m_pDirUnfoldAll->trigger(); } return true; } inline QString DirectoryMergeWindow::getDirNameA() const { return d->rootMFI()->getDirectoryInfo()->dirA().prettyAbsPath(); } inline QString DirectoryMergeWindow::getDirNameB() const { return d->rootMFI()->getDirectoryInfo()->dirB().prettyAbsPath(); } inline QString DirectoryMergeWindow::getDirNameC() const { return d->rootMFI()->getDirectoryInfo()->dirC().prettyAbsPath(); } inline QString DirectoryMergeWindow::getDirNameDest() const { return d->rootMFI()->getDirectoryInfo()->destDir().prettyAbsPath(); } void DirectoryMergeWindow::onExpanded() { resizeColumnToContents(s_NameCol); } void DirectoryMergeWindow::slotChooseAEverywhere() { d->setAllMergeOperations(eCopyAToDest); } void DirectoryMergeWindow::slotChooseBEverywhere() { d->setAllMergeOperations(eCopyBToDest); } void DirectoryMergeWindow::slotChooseCEverywhere() { d->setAllMergeOperations(eCopyCToDest); } void DirectoryMergeWindow::slotAutoChooseEverywhere() { e_MergeOperation eDefaultMergeOp = d->isThreeWay() ? eMergeABCToDest : d->m_bSyncMode ? eMergeToAB : eMergeABToDest; d->setAllMergeOperations(eDefaultMergeOp); } void DirectoryMergeWindow::slotNoOpEverywhere() { d->setAllMergeOperations(eNoOperation); } void DirectoryMergeWindow::slotFoldAllSubdirs() { collapseAll(); } void DirectoryMergeWindow::slotUnfoldAllSubdirs() { expandAll(); } // Merge current item (merge mode) void DirectoryMergeWindow::slotCurrentDoNothing() { d->setMergeOperation(currentIndex(), eNoOperation); } void DirectoryMergeWindow::slotCurrentChooseA() { d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyAToB : eCopyAToDest); } void DirectoryMergeWindow::slotCurrentChooseB() { d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyBToA : eCopyBToDest); } void DirectoryMergeWindow::slotCurrentChooseC() { d->setMergeOperation(currentIndex(), eCopyCToDest); } void DirectoryMergeWindow::slotCurrentMerge() { bool bThreeDirs = d->isThreeWay(); d->setMergeOperation(currentIndex(), bThreeDirs ? eMergeABCToDest : eMergeABToDest); } void DirectoryMergeWindow::slotCurrentDelete() { d->setMergeOperation(currentIndex(), eDeleteFromDest); } // Sync current item void DirectoryMergeWindow::slotCurrentCopyAToB() { d->setMergeOperation(currentIndex(), eCopyAToB); } void DirectoryMergeWindow::slotCurrentCopyBToA() { d->setMergeOperation(currentIndex(), eCopyBToA); } void DirectoryMergeWindow::slotCurrentDeleteA() { d->setMergeOperation(currentIndex(), eDeleteA); } void DirectoryMergeWindow::slotCurrentDeleteB() { d->setMergeOperation(currentIndex(), eDeleteB); } void DirectoryMergeWindow::slotCurrentDeleteAAndB() { d->setMergeOperation(currentIndex(), eDeleteAB); } void DirectoryMergeWindow::slotCurrentMergeToA() { d->setMergeOperation(currentIndex(), eMergeToA); } void DirectoryMergeWindow::slotCurrentMergeToB() { d->setMergeOperation(currentIndex(), eMergeToB); } void DirectoryMergeWindow::slotCurrentMergeToAAndB() { d->setMergeOperation(currentIndex(), eMergeToAB); } void DirectoryMergeWindow::keyPressEvent(QKeyEvent* e) { if((e->QInputEvent::modifiers() & Qt::ControlModifier) != 0) { MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI == nullptr) return; bool bThreeDirs = pMFI->isThreeWay(); bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : pMFI->conflictingFileTypes(); if(bMergeMode) { switch(e->key()) { case Qt::Key_1: if(pMFI->existsInA()) { slotCurrentChooseA(); } return; case Qt::Key_2: if(pMFI->existsInB()) { slotCurrentChooseB(); } return; case Qt::Key_3: if(pMFI->existsInC()) { slotCurrentChooseC(); } return; case Qt::Key_Space: slotCurrentDoNothing(); return; case Qt::Key_4: if(!bFTConflict) { slotCurrentMerge(); } return; case Qt::Key_Delete: slotCurrentDelete(); return; default: break; } } else { switch(e->key()) { case Qt::Key_1: if(pMFI->existsInA()) { slotCurrentCopyAToB(); } return; case Qt::Key_2: if(pMFI->existsInB()) { slotCurrentCopyBToA(); } return; case Qt::Key_Space: slotCurrentDoNothing(); return; case Qt::Key_4: if(!bFTConflict) { slotCurrentMergeToAAndB(); } return; case Qt::Key_Delete: if(pMFI->existsInA() && pMFI->existsInB()) slotCurrentDeleteAAndB(); else if(pMFI->existsInA()) slotCurrentDeleteA(); else if(pMFI->existsInB()) slotCurrentDeleteB(); return; default: break; } } } else if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { onDoubleClick(currentIndex()); return; } QTreeView::keyPressEvent(e); } void DirectoryMergeWindow::focusInEvent(QFocusEvent*) { Q_EMIT updateAvailabilities(); } void DirectoryMergeWindow::focusOutEvent(QFocusEvent*) { Q_EMIT updateAvailabilities(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setAllMergeOperations(e_MergeOperation eDefaultOperation) { if(KMessageBox::Yes == KMessageBox::warningYesNo(mWindow, i18n("This affects all merge operations."), i18n("Changing All Merge Operations"), KStandardGuiItem::cont(), KStandardGuiItem::cancel())) { for(int i = 0; i < rowCount(); ++i) { calcSuggestedOperation(index(i, 0, QModelIndex()), eDefaultOperation); } } } QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::nextSibling(const QModelIndex& mi) { QModelIndex miParent = mi.parent(); int currentIdx = mi.row(); if(currentIdx + 1 < mi.model()->rowCount(miParent)) return mi.model()->index(mi.row() + 1, 0, miParent); // next child of parent return QModelIndex(); } // Iterate through the complete tree. Start by specifying QListView::firstChild(). QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::treeIterator(QModelIndex mi, bool bVisitChildren, bool bFindInvisible) { if(mi.isValid()) { do { if(bVisitChildren && mi.model()->rowCount(mi) != 0) mi = mi.model()->index(0, 0, mi); else { QModelIndex miNextSibling = nextSibling(mi); if(miNextSibling.isValid()) mi = miNextSibling; else { mi = mi.parent(); while(mi.isValid()) { miNextSibling = nextSibling(mi); if(miNextSibling.isValid()) { mi = miNextSibling; break; } else { mi = mi.parent(); } } } } } while(mi.isValid() && mWindow->isRowHidden(mi.row(), mi.parent()) && !bFindInvisible); } return mi; } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareListView(ProgressProxy& pp) { QStringList errors; //TODO clear(); PixMapUtils::initPixmaps(m_pOptions->m_newestFileColor, m_pOptions->m_oldestFileColor, m_pOptions->m_midAgeFileColor, m_pOptions->m_missingFileColor); mWindow->setRootIsDecorated(true); t_fileMergeMap::iterator j; int nrOfFiles = m_fileMergeMap.size(); int currentIdx = 1; QElapsedTimer t; t.start(); pp.setMaxNofSteps(nrOfFiles); for(j = m_fileMergeMap.begin(); j != m_fileMergeMap.end(); ++j) { MergeFileInfos& mfi = j.value(); // const QString& fileName = j->first; const QString& fileName = mfi.subPath(); pp.setInformation( i18n("Processing %1 / %2\n%3", currentIdx, nrOfFiles, fileName), currentIdx, false); if(pp.wasCancelled()) break; ++currentIdx; // The comparisons and calculations for each file take place here. mfi.compareFilesAndCalcAges(errors, m_pOptions, mWindow); // Get dirname from fileName: Search for "/" from end: int pos = fileName.lastIndexOf('/'); QString dirPart; QString filePart; if(pos == -1) { // Top dir filePart = fileName; } else { dirPart = fileName.left(pos); filePart = fileName.mid(pos + 1); } if(dirPart.isEmpty()) // Top level { m_pRoot->addChild(&mfi); //new DirMergeItem( this, filePart, &mfi ); mfi.setParent(m_pRoot); } else { FileAccess* pFA = mfi.getFileInfoA() ? mfi.getFileInfoA() : mfi.getFileInfoB() ? mfi.getFileInfoB() : mfi.getFileInfoC(); MergeFileInfos& dirMfi = pFA->parent() ? m_fileMergeMap[FileKey(*pFA->parent())] : *m_pRoot; // parent dirMfi.addChild(&mfi); //new DirMergeItem( dirMfi.m_pDMI, filePart, &mfi ); mfi.setParent(&dirMfi); // // Equality for parent dirs is set in updateFileVisibilities() } mfi.updateAge(); } if(errors.size() > 0) { if(errors.size() < 15) { KMessageBox::errorList(mWindow, i18n("Some files could not be processed."), errors); } else { KMessageBox::error(mWindow, i18n("Some files could not be processed.")); } } beginResetModel(); endResetModel(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; bool bCheckC = pMFI->isThreeWay(); bool bCopyNewer = m_pOptions->m_bDmCopyNewer; bool bOtherDest = !((pMFI->getDirectoryInfo()->destDir().absoluteFilePath() == pMFI->getDirectoryInfo()->dirA().absoluteFilePath()) || (pMFI->getDirectoryInfo()->destDir().absoluteFilePath() == pMFI->getDirectoryInfo()->dirB().absoluteFilePath()) || (bCheckC && pMFI->getDirectoryInfo()->destDir().absoluteFilePath() == pMFI->getDirectoryInfo()->dirC().absoluteFilePath())); if(eDefaultMergeOp == eMergeABCToDest && !bCheckC) { eDefaultMergeOp = eMergeABToDest; } if(eDefaultMergeOp == eMergeToAB && bCheckC) { Q_ASSERT(true); } if(eDefaultMergeOp == eMergeToA || eDefaultMergeOp == eMergeToB || eDefaultMergeOp == eMergeABCToDest || eDefaultMergeOp == eMergeABToDest || eDefaultMergeOp == eMergeToAB) { if(!bCheckC) { if(pMFI->isEqualAB()) { setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation); } else if(pMFI->existsInA() && pMFI->existsInB()) { if(!bCopyNewer || pMFI->isDirA()) setMergeOperation(mi, eDefaultMergeOp); else if(bCopyNewer && pMFI->conflictingAges()) { setMergeOperation(mi, eConflictingAges); } else { if(pMFI->getAgeA() == eNew) setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyAToB : eCopyAToDest); else setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyBToA : eCopyBToDest); } } else if(!pMFI->existsInA() && pMFI->existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyBToDest); else if(eDefaultMergeOp == eMergeToB) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyBToA); } else if(pMFI->existsInA() && !pMFI->existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyAToDest); else if(eDefaultMergeOp == eMergeToA) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyAToB); } else //if ( !pMFI->existsInA() && !pMFI->existsInB() ) { setMergeOperation(mi, eNoOperation); } } else { if(pMFI->isEqualAB() && pMFI->isEqualAC()) { setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation); } else if(pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->isEqualAB() || pMFI->isEqualBC()) setMergeOperation(mi, eCopyCToDest); else if(pMFI->isEqualAC()) setMergeOperation(mi, eCopyBToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) { if(pMFI->isEqualAB()) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->isEqualAC()) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(!pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->isEqualBC()) setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(!pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()) { setMergeOperation(mi, eCopyCToDest); } else if(!pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) { setMergeOperation(mi, eCopyBToDest); } else if(pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC()) { setMergeOperation(mi, eDeleteFromDest); } else //if ( !pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC() ) { setMergeOperation(mi, eNoOperation); } } // Now check if file/dir-types fit. if(pMFI->conflictingFileTypes()) { setMergeOperation(mi, eConflictingFileTypes); } } else { e_MergeOperation eMO = eDefaultMergeOp; switch(eDefaultMergeOp) { case eConflictingFileTypes: case eChangedAndDeleted: case eConflictingAges: case eDeleteA: case eDeleteB: case eDeleteAB: case eDeleteFromDest: case eNoOperation: break; case eCopyAToB: if(!pMFI->existsInA()) { eMO = eDeleteB; } break; case eCopyBToA: if(!pMFI->existsInB()) { eMO = eDeleteA; } break; case eCopyAToDest: if(!pMFI->existsInA()) { eMO = eDeleteFromDest; } break; case eCopyBToDest: if(!pMFI->existsInB()) { eMO = eDeleteFromDest; } break; case eCopyCToDest: if(!pMFI->existsInC()) { eMO = eDeleteFromDest; } break; case eMergeToA: case eMergeToB: case eMergeToAB: case eMergeABCToDest: case eMergeABToDest: break; default: Q_ASSERT(true); break; } setMergeOperation(mi, eMO); } } void DirectoryMergeWindow::onDoubleClick(const QModelIndex& mi) { if(!mi.isValid()) return; d->m_bSimulatedMergeStarted = false; if(d->m_bDirectoryMerge) mergeCurrentFile(); else compareCurrentFile(); } void DirectoryMergeWindow::currentChanged(const QModelIndex& current, const QModelIndex& previous) { QTreeView::currentChanged(current, previous); MergeFileInfos* pMFI = d->getMFI(current); if(pMFI == nullptr) return; d->m_pDirectoryMergeInfo->setInfo(pMFI->getDirectoryInfo()->dirA(), pMFI->getDirectoryInfo()->dirB(), pMFI->getDirectoryInfo()->dirC(), pMFI->getDirectoryInfo()->destDir(), *pMFI); } void DirectoryMergeWindow::mousePressEvent(QMouseEvent* e) { QTreeView::mousePressEvent(e); QModelIndex mi = indexAt(e->pos()); int c = mi.column(); QPoint p = e->globalPos(); MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) return; if(c == s_OpCol) { bool bThreeDirs = d->isThreeWay(); QMenu m(this); if(bThreeDirs) { m.addAction(d->m_pDirCurrentDoNothing); int count = 0; if(pMFI->existsInA()) { m.addAction(d->m_pDirCurrentChooseA); ++count; } if(pMFI->existsInB()) { m.addAction(d->m_pDirCurrentChooseB); ++count; } if(pMFI->existsInC()) { m.addAction(d->m_pDirCurrentChooseC); ++count; } if(!pMFI->conflictingFileTypes() && count > 1) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } else if(d->m_bSyncMode) { m.addAction(d->m_pDirCurrentSyncDoNothing); if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncCopyAToB); if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncCopyBToA); if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncDeleteA); if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncDeleteB); if(pMFI->existsInA() && pMFI->existsInB()) { m.addAction(d->m_pDirCurrentSyncDeleteAAndB); if(!pMFI->conflictingFileTypes()) { m.addAction(d->m_pDirCurrentSyncMergeToA); m.addAction(d->m_pDirCurrentSyncMergeToB); m.addAction(d->m_pDirCurrentSyncMergeToAAndB); } } } else { m.addAction(d->m_pDirCurrentDoNothing); if(pMFI->existsInA()) { m.addAction(d->m_pDirCurrentChooseA); } if(pMFI->existsInB()) { m.addAction(d->m_pDirCurrentChooseB); } if(!pMFI->conflictingFileTypes() && pMFI->existsInA() && pMFI->existsInB()) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } m.exec(p); } else if(c == s_ACol || c == s_BCol || c == s_CCol) { QString itemPath; if(c == s_ACol && pMFI->existsInA()) { itemPath = pMFI->fullNameA(); } else if(c == s_BCol && pMFI->existsInB()) { itemPath = pMFI->fullNameB(); } else if(c == s_CCol && pMFI->existsInC()) { itemPath = pMFI->fullNameC(); } if(!itemPath.isEmpty()) { d->selectItemAndColumn(mi, e->button() == Qt::RightButton); } } } #ifndef QT_NO_CONTEXTMENU void DirectoryMergeWindow::contextMenuEvent(QContextMenuEvent* e) { QModelIndex mi = indexAt(e->pos()); int c = mi.column(); MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) return; if(c == s_ACol || c == s_BCol || c == s_CCol) { QString itemPath; if(c == s_ACol && pMFI->existsInA()) { itemPath = pMFI->fullNameA(); } else if(c == s_BCol && pMFI->existsInB()) { itemPath = pMFI->fullNameB(); } else if(c == s_CCol && pMFI->existsInC()) { itemPath = pMFI->fullNameC(); } if(!itemPath.isEmpty()) { d->selectItemAndColumn(mi, true); QMenu m(this); m.addAction(d->m_pDirCompareExplicit); m.addAction(d->m_pDirMergeExplicit); m.popup(e->globalPos()); } } } #endif QString DirectoryMergeWindow::DirectoryMergeWindowPrivate::getFileName(const QModelIndex& mi) const { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->getFileInfoA()->absoluteFilePath() : mi.column() == s_BCol ? pMFI->getFileInfoB()->absoluteFilePath() : mi.column() == s_CCol ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""); } return QString(); } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::isDir(const QModelIndex& mi) const { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->isDirA() : mi.column() == s_BCol ? pMFI->isDirB() : pMFI->isDirC(); } return false; } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::selectItemAndColumn(const QModelIndex& mi, bool bContextMenu) { if(bContextMenu && (mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index)) return; QModelIndex old1 = m_selection1Index; QModelIndex old2 = m_selection2Index; QModelIndex old3 = m_selection3Index; bool bReset = false; if(m_selection1Index.isValid()) { if(isDir(m_selection1Index) != isDir(mi)) bReset = true; } if(bReset || m_selection3Index.isValid() || mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index) { // restart m_selection1Index = QModelIndex(); m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); } else if(!m_selection1Index.isValid()) { m_selection1Index = mi; m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); } else if(!m_selection2Index.isValid()) { m_selection2Index = mi; m_selection3Index = QModelIndex(); } else if(!m_selection3Index.isValid()) { m_selection3Index = mi; } if(old1.isValid()) Q_EMIT dataChanged(old1, old1); if(old2.isValid()) Q_EMIT dataChanged(old2, old2); if(old3.isValid()) Q_EMIT dataChanged(old3, old3); if(m_selection1Index.isValid()) Q_EMIT dataChanged(m_selection1Index, m_selection1Index); if(m_selection2Index.isValid()) Q_EMIT dataChanged(m_selection2Index, m_selection2Index); if(m_selection3Index.isValid()) Q_EMIT dataChanged(m_selection3Index, m_selection3Index); Q_EMIT mWindow->updateAvailabilities(); } //TODO //void DirMergeItem::init(MergeFileInfos* pMFI) //{ // pMFI->m_pDMI = this; //no not here // m_pMFI = pMFI; // TotalDiffStatus& tds = pMFI->m_totalDiffStatus; // if ( m_pMFI->dirA() || m_pMFI->dirB() || m_pMFI->isDirC() ) // { // } // else // { // setText( s_UnsolvedCol, QString::number( tds.getUnsolvedConflicts() ) ); // setText( s_SolvedCol, QString::number( tds.getSolvedConflicts() ) ); // setText( s_NonWhiteCol, QString::number( tds.getUnsolvedConflicts() + tds.getSolvedConflicts() - tds.getWhitespaceConflicts() ) ); // setText( s_WhiteCol, QString::number( tds.getWhitespaceConflicts() ) ); // } // setSizeHint( s_ACol, QSize(17,17) ); // Iconsize // setSizeHint( s_BCol, QSize(17,17) ); // Iconsize // setSizeHint( s_CCol, QSize(17,17) ); // Iconsize //} void DirectoryMergeWindow::DirectoryMergeWindowPrivate::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); beginResetModel(); m_pRoot->sort(order); endResetModel(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; if(eMergeOp != pMFI->getOperation()) { pMFI->startOperation(); setOpStatus(mi, eOpStatusNone); } pMFI->setOperation(eMergeOp); if(bRecursive) { e_MergeOperation eChildrenMergeOp = pMFI->getOperation(); if(eChildrenMergeOp == eConflictingFileTypes) eChildrenMergeOp = eMergeABCToDest; for(int childIdx = 0; childIdx < pMFI->children().count(); ++childIdx) { calcSuggestedOperation(index(childIdx, 0, mi), eChildrenMergeOp); } } } void DirectoryMergeWindow::compareCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } if(MergeFileInfos* pMFI = d->getMFI(currentIndex())) { if(!(pMFI->hasDir())) { Q_EMIT startDiffMerge( pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""), pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""), pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""), "", "", "", "", nullptr); } } Q_EMIT updateAvailabilities(); } void DirectoryMergeWindow::slotCompareExplicitlySelectedFiles() { if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } Q_EMIT startDiffMerge( d->getFileName(d->m_selection1Index), d->getFileName(d->m_selection2Index), d->getFileName(d->m_selection3Index), "", "", "", "", nullptr); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); Q_EMIT updateAvailabilities(); update(); } void DirectoryMergeWindow::slotMergeExplicitlySelectedFiles() { if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } QString fn1 = d->getFileName(d->m_selection1Index); QString fn2 = d->getFileName(d->m_selection2Index); QString fn3 = d->getFileName(d->m_selection3Index); Q_EMIT startDiffMerge(fn1, fn2, fn3, fn3.isEmpty() ? fn2 : fn3, "", "", "", nullptr); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); Q_EMIT updateAvailabilities(); update(); } bool DirectoryMergeWindow::isFileSelected() { if(MergeFileInfos* pMFI = d->getMFI(currentIndex())) { return !(pMFI->hasDir() || pMFI->conflictingFileTypes()); } return false; } void DirectoryMergeWindow::mergeResultSaved(const QString& fileName) { QModelIndex mi = (d->m_mergeItemList.empty() || d->m_currentIndexForOperation == d->m_mergeItemList.end()) ? QModelIndex() : *d->m_currentIndexForOperation; MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) { // This can happen if the same file is saved and modified and saved again. Nothing to do then. return; } if(fileName == pMFI->fullNameDest()) { if(pMFI->getOperation() == eMergeToAB) { bool bSuccess = d->copyFLD(pMFI->fullNameB(), pMFI->fullNameA()); if(!bSuccess) { KMessageBox::error(this, i18n("An error occurred while copying.")); d->m_pStatusInfo->setWindowTitle(i18n("Merge Error")); d->m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); d->m_bError = true; d->setOpStatus(mi, eOpStatusError); pMFI->setOperation(eCopyBToA); return; } } d->setOpStatus(mi, eOpStatusDone); pMFI->endOperation(); if(d->m_mergeItemList.size() == 1) { d->m_mergeItemList.clear(); d->m_bRealMergeStarted = false; } } Q_EMIT updateAvailabilities(); } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::canContinue() { bool bCanContinue = false; Q_EMIT mWindow->checkIfCanContinue(bCanContinue); if(bCanContinue && !m_bError) { QModelIndex mi = (m_mergeItemList.empty() || m_currentIndexForOperation == m_mergeItemList.end()) ? QModelIndex() : *m_currentIndexForOperation; MergeFileInfos* pMFI = getMFI(mi); if(pMFI && pMFI->isOperationRunning()) { setOpStatus(mi, eOpStatusNotSaved); pMFI->endOperation(); if(m_mergeItemList.size() == 1) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } } return bCanContinue; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge) { bool bCreateBackups = m_pOptions->m_bDmCreateBakFiles; // First decide destname QString destName; switch(mfi.getOperation()) { case eNoOperation: case eDeleteAB: break; case eMergeToAB: // let the user save in B. In mergeResultSaved() the file will be copied to A. case eMergeToB: case eDeleteB: case eCopyAToB: destName = mfi.fullNameB(); break; case eMergeToA: case eDeleteA: case eCopyBToA: destName = mfi.fullNameA(); break; case eMergeABToDest: case eMergeABCToDest: case eCopyAToDest: case eCopyBToDest: case eCopyCToDest: case eDeleteFromDest: destName = mfi.fullNameDest(); break; default: KMessageBox::error(mWindow, i18n("Unknown merge operation. (This must never happen!)")); } bool bSuccess = false; bSingleFileMerge = false; switch(mfi.getOperation()) { case eNoOperation: bSuccess = true; break; case eCopyAToDest: case eCopyAToB: bSuccess = copyFLD(mfi.fullNameA(), destName); break; case eCopyBToDest: case eCopyBToA: bSuccess = copyFLD(mfi.fullNameB(), destName); break; case eCopyCToDest: bSuccess = copyFLD(mfi.fullNameC(), destName); break; case eDeleteFromDest: case eDeleteA: case eDeleteB: bSuccess = deleteFLD(destName, bCreateBackups); break; case eDeleteAB: bSuccess = deleteFLD(mfi.fullNameA(), bCreateBackups) && deleteFLD(mfi.fullNameB(), bCreateBackups); break; case eMergeABToDest: case eMergeToA: case eMergeToAB: case eMergeToB: bSuccess = mergeFLD(mfi.fullNameA(), mfi.fullNameB(), "", destName, bSingleFileMerge); break; case eMergeABCToDest: bSuccess = mergeFLD( mfi.existsInA() ? mfi.fullNameA() : QString(""), mfi.existsInB() ? mfi.fullNameB() : QString(""), mfi.existsInC() ? mfi.fullNameC() : QString(""), destName, bSingleFileMerge); break; default: KMessageBox::error(mWindow, i18n("Unknown merge operation.")); } return bSuccess; } // Check if the merge can start, and prepare the m_mergeItemList which then contains all // items that must be merged. void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose) { if(bVerbose) { int status = KMessageBox::warningYesNoCancel(mWindow, i18n("The merge is about to begin.\n\n" "Choose \"Do it\" if you have read the instructions and know what you are doing.\n" "Choosing \"Simulate it\" will tell you what would happen.\n\n" "Be aware that this program still has beta status " "and there is NO WARRANTY whatsoever! Make backups of your vital data!"), i18n("Starting Merge"), KGuiItem(i18n("Do It")), KGuiItem(i18n("Simulate It"))); if(status == KMessageBox::Yes) m_bRealMergeStarted = true; else if(status == KMessageBox::No) m_bSimulatedMergeStarted = true; else return; } else { m_bRealMergeStarted = true; } m_mergeItemList.clear(); if(!miBegin.isValid()) return; for(QModelIndex mi = miBegin; mi != miEnd; mi = treeIterator(mi)) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI && pMFI->isOperationRunning()) { m_mergeItemList.push_back(mi); QString errorText; if(pMFI->getOperation() == eConflictingFileTypes) { errorText = i18n("The highlighted item has a different type in the different folders. Select what to do."); } if(pMFI->getOperation() == eConflictingAges) { errorText = i18n("The modification dates of the file are equal but the files are not. Select what to do."); } if(pMFI->getOperation() == eChangedAndDeleted) { errorText = i18n("The highlighted item was changed in one folder and deleted in the other. Select what to do."); } if(!errorText.isEmpty()) { mWindow->scrollTo(mi, QAbstractItemView::EnsureVisible); mWindow->setCurrentIndex(mi); KMessageBox::error(mWindow, errorText); m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } } } m_currentIndexForOperation = m_mergeItemList.begin(); } void DirectoryMergeWindow::slotRunOperationForCurrentItem() { if(!d->canContinue()) return; bool bVerbose = false; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = currentIndex(); QModelIndex miEnd = d->treeIterator(miBegin, false, false); // find next visible sibling (no children) d->prepareMergeStart(miBegin, miEnd, bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::slotRunOperationForAllItems() { if(!d->canContinue()) return; bool bVerbose = true; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); d->prepareMergeStart(miBegin, QModelIndex(), bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::mergeCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible because folder merge is currently running."), i18n("Operation Not Possible")); return; } if(isFileSelected()) { MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI != nullptr) { d->m_mergeItemList.clear(); d->m_mergeItemList.push_back(currentIndex()); d->m_currentIndexForOperation = d->m_mergeItemList.begin(); bool bDummy = false; d->mergeFLD( pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""), pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""), pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""), pMFI->fullNameDest(), bDummy); } } Q_EMIT updateAvailabilities(); } // When bStart is true then m_currentIndexForOperation must still be processed. // When bVerbose is true then a messagebox will tell when the merge is complete. void DirectoryMergeWindow::DirectoryMergeWindowPrivate::mergeContinue(bool bStart, bool bVerbose) { ProgressProxy pp; if(m_mergeItemList.empty()) return; int nrOfItems = 0; int nrOfCompletedItems = 0; int nrOfCompletedSimItems = 0; // Count the number of completed items (for the progress bar). for(const QModelIndex& i : m_mergeItemList) { MergeFileInfos* pMFI = getMFI(i); ++nrOfItems; if(!pMFI->isOperationRunning()) ++nrOfCompletedItems; if(!pMFI->isSimOpRunning()) ++nrOfCompletedSimItems; } m_pStatusInfo->hide(); m_pStatusInfo->clear(); QModelIndex miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; bool bContinueWithCurrentItem = bStart; // true for first item, else false bool bSkipItem = false; if(!bStart && m_bError && miCurrent.isValid()) { int status = KMessageBox::warningYesNoCancel(mWindow, i18n("There was an error in the last step.\n" "Do you want to continue with the item that caused the error or do you want to skip this item?"), i18n("Continue merge after an error"), KGuiItem(i18n("Continue With Last Item")), KGuiItem(i18n("Skip Item"))); if(status == KMessageBox::Yes) bContinueWithCurrentItem = true; else if(status == KMessageBox::No) bSkipItem = true; else return; m_bError = false; } pp.setMaxNofSteps(nrOfItems); bool bSuccess = true; bool bSingleFileMerge = false; bool bSim = m_bSimulatedMergeStarted; while(bSuccess) { MergeFileInfos* pMFI = getMFI(miCurrent); if(pMFI == nullptr) { m_mergeItemList.clear(); m_bRealMergeStarted = false; break; } if(pMFI != nullptr && !bContinueWithCurrentItem) { if(bSim) { if(rowCount(miCurrent) == 0) { pMFI->endSimOp(); } } else { if(rowCount(miCurrent) == 0) { if(pMFI->isOperationRunning()) { setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone); pMFI->endOperation(); bSkipItem = false; } } else { setOpStatus(miCurrent, eOpStatusInProgress); } } } if(!bContinueWithCurrentItem) { // Depth first QModelIndex miPrev = miCurrent; ++m_currentIndexForOperation; miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; if((!miCurrent.isValid() || miCurrent.parent() != miPrev.parent()) && miPrev.parent().isValid()) { // Check if the parent may be set to "Done" QModelIndex miParent = miPrev.parent(); bool bDone = true; while(bDone && miParent.isValid()) { for(int childIdx = 0; childIdx < rowCount(miParent); ++childIdx) { pMFI = getMFI(index(childIdx, 0, miParent)); if((!bSim && pMFI->isOperationRunning()) || (bSim && !pMFI->isSimOpRunning())) { bDone = false; break; } } if(bDone) { pMFI = getMFI(miParent); if(bSim) pMFI->endSimOp(); else { setOpStatus(miParent, eOpStatusDone); pMFI->endOperation(); } } miParent = miParent.parent(); } } } if(!miCurrent.isValid()) // end? { if(m_bRealMergeStarted) { if(bVerbose) { KMessageBox::information(mWindow, i18n("Merge operation complete."), i18n("Merge Complete")); } m_bRealMergeStarted = false; m_pStatusInfo->setWindowTitle(i18n("Merge Complete")); } if(m_bSimulatedMergeStarted) { m_bSimulatedMergeStarted = false; QModelIndex mi = rowCount() > 0 ? index(0, 0, QModelIndex()) : QModelIndex(); for(; mi.isValid(); mi = treeIterator(mi)) { getMFI(mi)->startSimOp(); } m_pStatusInfo->setWindowTitle(i18n("Simulated merge complete: Check if you agree with the proposed operations.")); m_pStatusInfo->exec(); } m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } pMFI = getMFI(miCurrent); pp.setInformation(pMFI->subPath(), bSim ? nrOfCompletedSimItems : nrOfCompletedItems, false // bRedrawUpdate ); bSuccess = executeMergeOperation(*pMFI, bSingleFileMerge); // Here the real operation happens. if(bSuccess) { if(bSim) ++nrOfCompletedSimItems; else ++nrOfCompletedItems; bContinueWithCurrentItem = false; } if(pp.wasCancelled()) break; } // end while //g_pProgressDialog->hide(); mWindow->setCurrentIndex(miCurrent); mWindow->scrollTo(miCurrent, EnsureVisible); if(!bSuccess && !bSingleFileMerge) { KMessageBox::error(mWindow, i18n("An error occurred. Press OK to see detailed information.")); m_pStatusInfo->setWindowTitle(i18n("Merge Error")); m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); m_bError = true; setOpStatus(miCurrent, eOpStatusError); } else { m_bError = false; } Q_EMIT mWindow->updateAvailabilities(); if(m_currentIndexForOperation == m_mergeItemList.end()) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::deleteFLD(const QString& name, bool bCreateBackup) { FileAccess fi(name, true); if(!fi.exists()) return true; if(bCreateBackup) { bool bSuccess = renameFLD(name, name + ".orig"); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: While deleting %1: Creating backup failed.", name)); return false; } } else { if(fi.isDir() && !fi.isSymLink()) m_pStatusInfo->addText(i18n("delete folder recursively( %1 )", name)); else m_pStatusInfo->addText(i18n("delete( %1 )", name)); if(m_bSimulatedMergeStarted) { return true; } if(fi.isDir() && !fi.isSymLink()) // recursive directory delete only for real dirs, not symlinks { t_DirectoryList dirList; bool bSuccess = fi.listDir(&dirList, false, true, "*", "", "", false, false); // not recursive, find hidden files if(!bSuccess) { // No Permission to read directory or other error. m_pStatusInfo->addText(i18n("Error: delete folder operation failed while trying to read the folder.")); return false; } t_DirectoryList::iterator it; // create list iterator for(it = dirList.begin(); it != dirList.end(); ++it) // for each file... { FileAccess& fi2 = *it; Q_ASSERT(fi2.fileName() != "." && fi2.fileName() != ".."); bSuccess = deleteFLD(fi2.absoluteFilePath(), false); if(!bSuccess) break; } if(bSuccess) { bSuccess = FileAccess::removeDir(name); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: rmdir( %1 ) operation failed.", name)); // krazy:exclude=syscalls return false; } } } else { bool bSuccess = fi.removeFile(); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: delete operation failed.")); return false; } } } return true; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge) { FileAccess fi(nameA); if(fi.isDir()) { return makeDir(nameDest); } // Make sure that the dir exists, into which we will save the file later. int pos = nameDest.lastIndexOf('/'); if(pos > 0) { QString parentName = nameDest.left(pos); bool bSuccess = makeDir(parentName, true /*quiet*/); if(!bSuccess) return false; } m_pStatusInfo->addText(i18n("manual merge( %1, %2, %3 -> %4)", nameA, nameB, nameC, nameDest)); if(m_bSimulatedMergeStarted) { m_pStatusInfo->addText(i18n(" Note: After a manual merge the user should continue by pressing F7.")); return true; } bSingleFileMerge = true; setOpStatus(*m_currentIndexForOperation, eOpStatusInProgress); mWindow->scrollTo(*m_currentIndexForOperation, EnsureVisible); Q_EMIT mWindow->startDiffMerge(nameA, nameB, nameC, nameDest, "", "", "", nullptr); return false; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::copyFLD(const QString& srcName, const QString& destName) { bool bSuccess = false; if(srcName == destName) return true; FileAccess fi(srcName); FileAccess faDest(destName, true); if(faDest.exists() && !(fi.isDir() && faDest.isDir() && (fi.isSymLink() == faDest.isSymLink()))) { bSuccess = deleteFLD(destName, m_pOptions->m_bDmCreateBakFiles); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: copy( %1 -> %2 ) failed." "Deleting existing destination failed.", srcName, destName)); return bSuccess; } } if(fi.isSymLink() && ((fi.isDir() && !m_bFollowDirLinks) || (!fi.isDir() && !m_bFollowFileLinks))) { m_pStatusInfo->addText(i18n("copyLink( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } FileAccess destFi(destName); if(!destFi.isLocal() || !fi.isLocal()) { m_pStatusInfo->addText(i18n("Error: copyLink failed: Remote links are not yet supported.")); return false; } bSuccess = false; QString linkTarget = fi.readLink(); if(!linkTarget.isEmpty()) { bSuccess = FileAccess::symLink(linkTarget, destName); if(!bSuccess) m_pStatusInfo->addText(i18n("Error: copyLink failed.")); } return bSuccess; } if(fi.isDir()) { if(faDest.exists()) return true; bSuccess = makeDir(destName); return bSuccess; } int pos = destName.lastIndexOf('/'); if(pos > 0) { QString parentName = destName.left(pos); bSuccess = makeDir(parentName, true /*quiet*/); if(!bSuccess) return false; } m_pStatusInfo->addText(i18n("copy( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } FileAccess faSrc(srcName); bSuccess = faSrc.copyFile(destName); if(!bSuccess) m_pStatusInfo->addText(faSrc.getStatusText()); return bSuccess; } // Rename is not an operation that can be selected by the user. // It will only be used to create backups. // Hence it will delete an existing destination without making a backup (of the old backup.) bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::renameFLD(const QString& srcName, const QString& destName) { if(srcName == destName) return true; FileAccess destFile = FileAccess(destName, true); if(destFile.exists()) { bool bSuccess = deleteFLD(destName, false /*no backup*/); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during rename( %1 -> %2 ): " "Cannot delete existing destination.", srcName, destName)); return false; } } m_pStatusInfo->addText(i18n("rename( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess(srcName).rename(destFile); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: Rename failed.")); return false; } return true; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::makeDir(const QString& name, bool bQuiet) { FileAccess fi(name, true); if(fi.exists() && fi.isDir()) return true; if(fi.exists() && !fi.isDir()) { bool bSuccess = deleteFLD(name, true); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during makeDir of %1. " "Cannot delete existing file.", name)); return false; } } int pos = name.lastIndexOf('/'); if(pos > 0) { QString parentName = name.left(pos); bool bSuccess = makeDir(parentName, true); if(!bSuccess) return false; } if(!bQuiet) m_pStatusInfo->addText(i18n("makeDir( %1 )", name)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess::makeDir(name); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error while creating folder.")); return false; } return true; } DirectoryMergeInfo::DirectoryMergeInfo(QWidget* pParent) : QFrame(pParent) { QVBoxLayout* topLayout = new QVBoxLayout(this); topLayout->setMargin(0); QGridLayout* grid = new QGridLayout(); topLayout->addLayout(grid); grid->setColumnStretch(1, 10); int line = 0; m_pA = new QLabel(i18n("A"), this); grid->addWidget(m_pA, line, 0); m_pInfoA = new QLabel(this); grid->addWidget(m_pInfoA, line, 1); ++line; m_pB = new QLabel(i18n("B"), this); grid->addWidget(m_pB, line, 0); m_pInfoB = new QLabel(this); grid->addWidget(m_pInfoB, line, 1); ++line; m_pC = new QLabel(i18n("C"), this); grid->addWidget(m_pC, line, 0); m_pInfoC = new QLabel(this); grid->addWidget(m_pInfoC, line, 1); ++line; m_pDest = new QLabel(i18n("Dest"), this); grid->addWidget(m_pDest, line, 0); m_pInfoDest = new QLabel(this); grid->addWidget(m_pInfoDest, line, 1); ++line; m_pInfoList = new QTreeWidget(this); topLayout->addWidget(m_pInfoList); m_pInfoList->setHeaderLabels({i18n("Folder"), i18n("Type"), i18n("Size"), i18n("Attr"), i18n("Last Modification"), i18n("Link-Destination")}); setMinimumSize(100, 100); m_pInfoList->installEventFilter(this); m_pInfoList->setRootIsDecorated(false); } bool DirectoryMergeInfo::eventFilter(QObject* o, QEvent* e) { if(e->type() == QEvent::FocusIn && o == m_pInfoList) Q_EMIT gotFocus(); return false; } void DirectoryMergeInfo::addListViewItem(const QString& dir, const QString& basePath, FileAccess* fi) { if(basePath.isEmpty()) { return; } if(fi != nullptr && fi->exists()) { QString dateString = fi->lastModified().toString(QLocale::system().dateTimeFormat()); m_pInfoList->addTopLevelItem(new QTreeWidgetItem( m_pInfoList, {dir, QString(fi->isDir() ? i18n("Folder") : i18n("File")) + (fi->isSymLink() ? i18n("-Link") : ""), QString::number(fi->size()), QLatin1String(fi->isReadable() ? "r" : " ") + QLatin1String(fi->isWritable() ? "w" : " ") + QLatin1String((fi->isExecutable() ? "x" : " ")), dateString, QString(fi->isSymLink() ? (" -> " + fi->readLink()) : QString(""))})); } else { m_pInfoList->addTopLevelItem(new QTreeWidgetItem( m_pInfoList, {dir, i18n("not available"), "", "", "", ""})); } } void DirectoryMergeInfo::setInfo( const FileAccess& dirA, const FileAccess& dirB, const FileAccess& dirC, const FileAccess& dirDest, MergeFileInfos& mfi) { bool bHideDest = false; if(dirA.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pA->setText(i18n("A (Dest): ")); bHideDest = true; } else m_pA->setText(!dirC.isValid() ? i18n("A: ") : i18n("A (Base): ")); m_pInfoA->setText(dirA.prettyAbsPath()); if(dirB.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pB->setText(i18n("B (Dest): ")); bHideDest = true; } else m_pB->setText(i18n("B: ")); m_pInfoB->setText(dirB.prettyAbsPath()); if(dirC.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pC->setText(i18n("C (Dest): ")); bHideDest = true; } else m_pC->setText(i18n("C: ")); m_pInfoC->setText(dirC.prettyAbsPath()); m_pDest->setText(i18n("Dest: ")); m_pInfoDest->setText(dirDest.prettyAbsPath()); if(!dirC.isValid()) { m_pC->hide(); m_pInfoC->hide(); } else { m_pC->show(); m_pInfoC->show(); } if(!dirDest.isValid() || bHideDest) { m_pDest->hide(); m_pInfoDest->hide(); } else { m_pDest->show(); m_pInfoDest->show(); } m_pInfoList->clear(); addListViewItem(i18n("A"), dirA.prettyAbsPath(), mfi.getFileInfoA()); addListViewItem(i18n("B"), dirB.prettyAbsPath(), mfi.getFileInfoB()); addListViewItem(i18n("C"), dirC.prettyAbsPath(), mfi.getFileInfoC()); if(!bHideDest) { FileAccess fiDest(dirDest.prettyAbsPath() + '/' + mfi.subPath(), true); addListViewItem(i18n("Dest"), dirDest.prettyAbsPath(), &fiDest); } for(int i = 0; i < m_pInfoList->columnCount(); ++i) m_pInfoList->resizeColumnToContents(i); } void DirectoryMergeWindow::slotSaveMergeState() { //slotStatusMsg(i18n("Saving Directory Merge State ...")); QString dirMergeStateFilename = QFileDialog::getSaveFileName(this, i18n("Save Folder Merge State As..."), QDir::currentPath()); if(!dirMergeStateFilename.isEmpty()) { QFile file(dirMergeStateFilename); bool bSuccess = file.open(QIODevice::WriteOnly); if(bSuccess) { QTextStream ts(&file); QModelIndex mi(d->index(0, 0, QModelIndex())); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); ts << *pMFI; mi = d->treeIterator(mi, true, true); } } } //slotStatusMsg(i18n("Ready.")); } void DirectoryMergeWindow::slotLoadMergeState() { } void DirectoryMergeWindow::updateFileVisibilities() { bool bShowIdentical = d->m_pDirShowIdenticalFiles->isChecked(); bool bShowDifferent = d->m_pDirShowDifferentFiles->isChecked(); bool bShowOnlyInA = d->m_pDirShowFilesOnlyInA->isChecked(); bool bShowOnlyInB = d->m_pDirShowFilesOnlyInB->isChecked(); bool bShowOnlyInC = d->m_pDirShowFilesOnlyInC->isChecked(); bool bThreeDirs = d->isThreeWay(); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); // in first run set all dirs to equal and determine if they are not equal. // on second run don't change the equal-status anymore; it is needed to // set the visibility (when bShowIdentical is false). for(int loop = 0; loop < 2; ++loop) { QModelIndex mi = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); bool bDir = pMFI->hasDir(); if(loop == 0 && bDir) { //Treat all links and directories to equal by default. pMFI->updateDirectoryOrLink(); } bool bVisible = (bShowIdentical && pMFI->existsEveryWhere() && pMFI->isEqualAB() && (pMFI->isEqualAC() || !bThreeDirs)) || ((bShowDifferent || bDir) && pMFI->existsCount() >= 2 && (!pMFI->isEqualAB() || !(pMFI->isEqualAC() || !bThreeDirs))) || (bShowOnlyInA && pMFI->onlyInA()) || (bShowOnlyInB && pMFI->onlyInB()) || (bShowOnlyInC && pMFI->onlyInC()); QString fileName = pMFI->fileName(); bVisible = bVisible && ((bDir && !Utils::wildcardMultiMatch(d->m_pOptions->m_DmDirAntiPattern, fileName, d->m_bCaseSensitive)) || (Utils::wildcardMultiMatch(d->m_pOptions->m_DmFilePattern, fileName, d->m_bCaseSensitive) && !Utils::wildcardMultiMatch(d->m_pOptions->m_DmFileAntiPattern, fileName, d->m_bCaseSensitive))); if(loop != 0) setRowHidden(mi.row(), mi.parent(), !bVisible); bool bEqual = bThreeDirs ? pMFI->isEqualAB() && pMFI->isEqualAC() : pMFI->isEqualAB(); if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal" { pMFI->updateParents(); } mi = d->treeIterator(mi, true, true); } } } void DirectoryMergeWindow::slotShowIdenticalFiles() { d->m_pOptions->m_bDmShowIdenticalFiles = d->m_pDirShowIdenticalFiles->isChecked(); updateFileVisibilities(); } void DirectoryMergeWindow::slotShowDifferentFiles() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInA() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInB() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInC() { updateFileVisibilities(); } void DirectoryMergeWindow::slotSynchronizeDirectories() {} void DirectoryMergeWindow::slotChooseNewerFiles() {} void DirectoryMergeWindow::initDirectoryMergeActions(KDiff3App* pKDiff3App, KActionCollection* ac) { #include "xpm/showequalfiles.xpm" #include "xpm/showfilesonlyina.xpm" #include "xpm/showfilesonlyinb.xpm" #include "xpm/showfilesonlyinc.xpm" #include "xpm/startmerge.xpm" d->m_pDirStartOperation = GuiUtils::createAction(i18n("Start/Continue Folder Merge"), QKeySequence(Qt::Key_F7), this, &DirectoryMergeWindow::slotRunOperationForAllItems, ac, "dir_start_operation"); d->m_pDirRunOperationForCurrentItem = GuiUtils::createAction(i18n("Run Operation for Current Item"), QKeySequence(Qt::Key_F6), this, &DirectoryMergeWindow::slotRunOperationForCurrentItem, ac, "dir_run_operation_for_current_item"); d->m_pDirCompareCurrent = GuiUtils::createAction(i18n("Compare Selected File"), this, &DirectoryMergeWindow::compareCurrentFile, ac, "dir_compare_current"); d->m_pDirMergeCurrent = GuiUtils::createAction(i18n("Merge Current File"), QIcon(QPixmap(startmerge)), i18n("Merge\nFile"), pKDiff3App, &KDiff3App::slotMergeCurrentFile, ac, "merge_current"); d->m_pDirFoldAll = GuiUtils::createAction(i18n("Fold All Subfolders"), this, &DirectoryMergeWindow::collapseAll, ac, "dir_fold_all"); d->m_pDirUnfoldAll = GuiUtils::createAction(i18n("Unfold All Subfolders"), this, &DirectoryMergeWindow::expandAll, ac, "dir_unfold_all"); d->m_pDirRescan = GuiUtils::createAction(i18n("Rescan"), QKeySequence(Qt::SHIFT + Qt::Key_F5), this, &DirectoryMergeWindow::reload, ac, "dir_rescan"); d->m_pDirSaveMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Save Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotSaveMergeState, ac, "dir_save_merge_state"); d->m_pDirLoadMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Load Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotLoadMergeState, ac, "dir_load_merge_state"); d->m_pDirChooseAEverywhere = GuiUtils::createAction(i18n("Choose A for All Items"), this, &DirectoryMergeWindow::slotChooseAEverywhere, ac, "dir_choose_a_everywhere"); d->m_pDirChooseBEverywhere = GuiUtils::createAction(i18n("Choose B for All Items"), this, &DirectoryMergeWindow::slotChooseBEverywhere, ac, "dir_choose_b_everywhere"); d->m_pDirChooseCEverywhere = GuiUtils::createAction(i18n("Choose C for All Items"), this, &DirectoryMergeWindow::slotChooseCEverywhere, ac, "dir_choose_c_everywhere"); d->m_pDirAutoChoiceEverywhere = GuiUtils::createAction(i18n("Auto-Choose Operation for All Items"), this, &DirectoryMergeWindow::slotAutoChooseEverywhere, ac, "dir_autochoose_everywhere"); d->m_pDirDoNothingEverywhere = GuiUtils::createAction(i18n("No Operation for All Items"), this, &DirectoryMergeWindow::slotNoOpEverywhere, ac, "dir_nothing_everywhere"); // d->m_pDirSynchronizeDirectories = GuiUtils::createAction< KToggleAction >(i18n("Synchronize Directories"), 0, this, &DirectoryMergeWindow::slotSynchronizeDirectories, ac, "dir_synchronize_directories"); // d->m_pDirChooseNewerFiles = GuiUtils::createAction< KToggleAction >(i18n("Copy Newer Files Instead of Merging"), 0, this, &DirectoryMergeWindow::slotChooseNewerFiles, ac, "dir_choose_newer_files"); d->m_pDirShowIdenticalFiles = GuiUtils::createAction(i18n("Show Identical Files"), QIcon(QPixmap(showequalfiles)), i18n("Identical\nFiles"), this, &DirectoryMergeWindow::slotShowIdenticalFiles, ac, "dir_show_identical_files"); d->m_pDirShowDifferentFiles = GuiUtils::createAction(i18n("Show Different Files"), this, &DirectoryMergeWindow::slotShowDifferentFiles, ac, "dir_show_different_files"); d->m_pDirShowFilesOnlyInA = GuiUtils::createAction(i18n("Show Files only in A"), QIcon(QPixmap(showfilesonlyina)), i18n("Files\nonly in A"), this, &DirectoryMergeWindow::slotShowFilesOnlyInA, ac, "dir_show_files_only_in_a"); d->m_pDirShowFilesOnlyInB = GuiUtils::createAction(i18n("Show Files only in B"), QIcon(QPixmap(showfilesonlyinb)), i18n("Files\nonly in B"), this, &DirectoryMergeWindow::slotShowFilesOnlyInB, ac, "dir_show_files_only_in_b"); d->m_pDirShowFilesOnlyInC = GuiUtils::createAction(i18n("Show Files only in C"), QIcon(QPixmap(showfilesonlyinc)), i18n("Files\nonly in C"), this, &DirectoryMergeWindow::slotShowFilesOnlyInC, ac, "dir_show_files_only_in_c"); d->m_pDirShowIdenticalFiles->setChecked(d->m_pOptions->m_bDmShowIdenticalFiles); d->m_pDirCompareExplicit = GuiUtils::createAction(i18n("Compare Explicitly Selected Files"), this, &DirectoryMergeWindow::slotCompareExplicitlySelectedFiles, ac, "dir_compare_explicitly_selected_files"); d->m_pDirMergeExplicit = GuiUtils::createAction(i18n("Merge Explicitly Selected Files"), this, &DirectoryMergeWindow::slotMergeExplicitlySelectedFiles, ac, "dir_merge_explicitly_selected_files"); d->m_pDirCurrentDoNothing = GuiUtils::createAction(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_do_nothing"); d->m_pDirCurrentChooseA = GuiUtils::createAction(i18n("A"), this, &DirectoryMergeWindow::slotCurrentChooseA, ac, "dir_current_choose_a"); d->m_pDirCurrentChooseB = GuiUtils::createAction(i18n("B"), this, &DirectoryMergeWindow::slotCurrentChooseB, ac, "dir_current_choose_b"); d->m_pDirCurrentChooseC = GuiUtils::createAction(i18n("C"), this, &DirectoryMergeWindow::slotCurrentChooseC, ac, "dir_current_choose_c"); d->m_pDirCurrentMerge = GuiUtils::createAction(i18n("Merge"), this, &DirectoryMergeWindow::slotCurrentMerge, ac, "dir_current_merge"); d->m_pDirCurrentDelete = GuiUtils::createAction(i18n("Delete (if exists)"), this, &DirectoryMergeWindow::slotCurrentDelete, ac, "dir_current_delete"); d->m_pDirCurrentSyncDoNothing = GuiUtils::createAction(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_sync_do_nothing"); d->m_pDirCurrentSyncCopyAToB = GuiUtils::createAction(i18n("Copy A to B"), this, &DirectoryMergeWindow::slotCurrentCopyAToB, ac, "dir_current_sync_copy_a_to_b"); d->m_pDirCurrentSyncCopyBToA = GuiUtils::createAction(i18n("Copy B to A"), this, &DirectoryMergeWindow::slotCurrentCopyBToA, ac, "dir_current_sync_copy_b_to_a"); d->m_pDirCurrentSyncDeleteA = GuiUtils::createAction(i18n("Delete A"), this, &DirectoryMergeWindow::slotCurrentDeleteA, ac, "dir_current_sync_delete_a"); d->m_pDirCurrentSyncDeleteB = GuiUtils::createAction(i18n("Delete B"), this, &DirectoryMergeWindow::slotCurrentDeleteB, ac, "dir_current_sync_delete_b"); d->m_pDirCurrentSyncDeleteAAndB = GuiUtils::createAction(i18n("Delete A && B"), this, &DirectoryMergeWindow::slotCurrentDeleteAAndB, ac, "dir_current_sync_delete_a_and_b"); d->m_pDirCurrentSyncMergeToA = GuiUtils::createAction(i18n("Merge to A"), this, &DirectoryMergeWindow::slotCurrentMergeToA, ac, "dir_current_sync_merge_to_a"); d->m_pDirCurrentSyncMergeToB = GuiUtils::createAction(i18n("Merge to B"), this, &DirectoryMergeWindow::slotCurrentMergeToB, ac, "dir_current_sync_merge_to_b"); d->m_pDirCurrentSyncMergeToAAndB = GuiUtils::createAction(i18n("Merge to A && B"), this, &DirectoryMergeWindow::slotCurrentMergeToAAndB, ac, "dir_current_sync_merge_to_a_and_b"); } void DirectoryMergeWindow::updateAvailabilities(bool bDirCompare, bool bDiffWindowVisible, KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC) { d->m_pDirStartOperation->setEnabled(bDirCompare); d->m_pDirRunOperationForCurrentItem->setEnabled(bDirCompare); d->m_pDirFoldAll->setEnabled(bDirCompare); d->m_pDirUnfoldAll->setEnabled(bDirCompare); d->m_pDirCompareCurrent->setEnabled(bDirCompare && isVisible() && isFileSelected()); d->m_pDirMergeCurrent->setEnabled((bDirCompare && isVisible() && isFileSelected()) || bDiffWindowVisible); d->m_pDirRescan->setEnabled(bDirCompare); bool bThreeDirs = d->isThreeWay(); d->m_pDirAutoChoiceEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirDoNothingEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseAEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseBEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseCEverywhere->setEnabled(bDirCompare && isVisible() && bThreeDirs); MergeFileInfos* pMFI = d->getMFI(currentIndex()); bool bItemActive = bDirCompare && isVisible() && pMFI != nullptr; // && hasFocus(); bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : pMFI->conflictingFileTypes(); bool bDirWindowHasFocus = isVisible() && hasFocus(); d->m_pDirShowIdenticalFiles->setEnabled(bDirCompare && isVisible()); d->m_pDirShowDifferentFiles->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInA->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInB->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInC->setEnabled(bDirCompare && isVisible() && bThreeDirs); d->m_pDirCompareExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid()); d->m_pDirMergeExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid()); d->m_pDirCurrentDoNothing->setEnabled(bItemActive && bMergeMode); d->m_pDirCurrentChooseA->setEnabled(bItemActive && bMergeMode && pMFI->existsInA()); d->m_pDirCurrentChooseB->setEnabled(bItemActive && bMergeMode && pMFI->existsInB()); d->m_pDirCurrentChooseC->setEnabled(bItemActive && bMergeMode && pMFI->existsInC()); d->m_pDirCurrentMerge->setEnabled(bItemActive && bMergeMode && !bFTConflict); d->m_pDirCurrentDelete->setEnabled(bItemActive && bMergeMode); if(bDirWindowHasFocus) { chooseA->setEnabled(bItemActive && pMFI->existsInA()); chooseB->setEnabled(bItemActive && pMFI->existsInB()); chooseC->setEnabled(bItemActive && pMFI->existsInC()); chooseA->setChecked(false); chooseB->setChecked(false); chooseC->setChecked(false); } d->m_pDirCurrentSyncDoNothing->setEnabled(bItemActive && !bMergeMode); d->m_pDirCurrentSyncCopyAToB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA()); d->m_pDirCurrentSyncCopyBToA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB()); d->m_pDirCurrentSyncDeleteA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA()); d->m_pDirCurrentSyncDeleteB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB()); d->m_pDirCurrentSyncDeleteAAndB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA() && pMFI->existsInB()); d->m_pDirCurrentSyncMergeToA->setEnabled(bItemActive && !bMergeMode && !bFTConflict); d->m_pDirCurrentSyncMergeToB->setEnabled(bItemActive && !bMergeMode && !bFTConflict); d->m_pDirCurrentSyncMergeToAAndB->setEnabled(bItemActive && !bMergeMode && !bFTConflict); } //#include "directorymergewindow.moc" diff --git a/src/directorymergewindow.h b/src/directorymergewindow.h index 9f79024..3120a7a 100644 --- a/src/directorymergewindow.h +++ b/src/directorymergewindow.h @@ -1,169 +1,166 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef DIRECTORY_MERGE_WINDOW_H #define DIRECTORY_MERGE_WINDOW_H #include #include #include #include #include "common.h" #include "fileaccess.h" #include "diff.h" //TotalDiffStatus class Options; class KIconLoader; class StatusInfo; class DirectoryMergeInfo; class OneDirectoryInfo; class QLabel; class QAction; class KToggleAction; class KActionCollection; class TotalDiffStatus; class DirectoryInfo; class MergeFileInfos; class KDiff3App; class DirectoryMergeWindow : public QTreeView { Q_OBJECT public: struct t_ItemInfo; DirectoryMergeWindow( QWidget* pParent, const QSharedPointer &pOptions); ~DirectoryMergeWindow() override; void setDirectoryMergeInfo(DirectoryMergeInfo* p); bool init( const QSharedPointer &dirInfo, bool bDirectoryMerge, bool bReload = false ); bool isFileSelected(); bool isDirectoryMergeInProgress(); int totalColumnWidth(); bool isSyncMode(); bool isScanning(); void initDirectoryMergeActions( KDiff3App* pKDiff3App, KActionCollection* ac ); void updateAvailabilities( bool bDirCompare, bool bDiffWindowVisible, KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC ); void updateFileVisibilities(); void mousePressEvent( QMouseEvent* e ) override; void keyPressEvent( QKeyEvent* e ) override; void focusInEvent( QFocusEvent* e ) override; void focusOutEvent( QFocusEvent* e ) override; void contextMenuEvent( QContextMenuEvent* e ) override; QString getDirNameA() const; QString getDirNameB() const; QString getDirNameC() const; QString getDirNameDest() const; public Q_SLOTS: void reload(); void mergeCurrentFile(); void compareCurrentFile(); void slotRunOperationForAllItems(); void slotRunOperationForCurrentItem(); void mergeResultSaved(const QString& fileName); void slotChooseAEverywhere(); void slotChooseBEverywhere(); void slotChooseCEverywhere(); void slotAutoChooseEverywhere(); void slotNoOpEverywhere(); void slotFoldAllSubdirs(); void slotUnfoldAllSubdirs(); void slotShowIdenticalFiles(); void slotShowDifferentFiles(); void slotShowFilesOnlyInA(); void slotShowFilesOnlyInB(); void slotShowFilesOnlyInC(); void slotSynchronizeDirectories(); void slotChooseNewerFiles(); void slotCompareExplicitlySelectedFiles(); void slotMergeExplicitlySelectedFiles(); // Merge current item (merge mode) void slotCurrentDoNothing(); void slotCurrentChooseA(); void slotCurrentChooseB(); void slotCurrentChooseC(); void slotCurrentMerge(); void slotCurrentDelete(); // Sync current item void slotCurrentCopyAToB(); void slotCurrentCopyBToA(); void slotCurrentDeleteA(); void slotCurrentDeleteB(); void slotCurrentDeleteAAndB(); void slotCurrentMergeToA(); void slotCurrentMergeToB(); void slotCurrentMergeToAAndB(); void slotSaveMergeState(); void slotLoadMergeState(); Q_SIGNALS: void startDiffMerge(const QString &fn1,const QString &fn2, const QString &fn3, const QString &ofn, const QString&, const QString&, const QString&,TotalDiffStatus*); void checkIfCanContinue( bool& pbContinue ); void updateAvailabilities(); void statusBarMessage( const QString& msg ); protected Q_SLOTS: void onDoubleClick( const QModelIndex& ); void onExpanded(); void currentChanged( const QModelIndex & current, const QModelIndex & previous ) override; // override private: int getIntFromIndex(const QModelIndex& index) const; const QSharedPointer& getOptions() const; class DirectoryMergeWindowPrivate; DirectoryMergeWindowPrivate* d; class DirMergeItemDelegate; }; class DirectoryMergeInfo : public QFrame { Q_OBJECT public: explicit DirectoryMergeInfo( QWidget* pParent ); void setInfo( const FileAccess& dirA, const FileAccess& dirB, const FileAccess& dirC, const FileAccess& dirDest, MergeFileInfos& mfi ); QTreeWidget* getInfoList() {return m_pInfoList;} bool eventFilter( QObject* o, QEvent* e ) override; Q_SIGNALS: void gotFocus(); private: void addListViewItem(const QString& dir, const QString& basePath, FileAccess* fi); QLabel* m_pInfoA; QLabel* m_pInfoB; QLabel* m_pInfoC; QLabel* m_pInfoDest; QLabel* m_pA; QLabel* m_pB; QLabel* m_pC; QLabel* m_pDest; QTreeWidget* m_pInfoList; }; #endif diff --git a/src/fileaccess.cpp b/src/fileaccess.cpp index bffeb67..e177a5e 100644 --- a/src/fileaccess.cpp +++ b/src/fileaccess.cpp @@ -1,1249 +1,1247 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "fileaccess.h" #include "cvsignorelist.h" #include "common.h" #include "Logging.h" #include "progress.h" #include "ProgressProxyExtender.h" #include "Utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include FileAccess::FileAccess(const QString& name, bool bWantToWrite) { reset(); setFile(name, bWantToWrite); } FileAccess::FileAccess() { reset(); } void FileAccess::reset() { m_fileInfo = QFileInfo(); m_bExists = false; m_bFile = false; m_bDir = false; m_bSymLink = false; m_bWritable = false; m_bHidden = false; m_size = 0; m_modificationTime = QDateTime::fromMSecsSinceEpoch(0); m_url = QUrl(); m_bValidData = false; m_name = QString(); m_linkTarget = ""; //m_fileType = -1; tmpFile.clear(); tmpFile = QSharedPointer::create(); realFile = nullptr; } FileAccess::~FileAccess() { tmpFile.clear(); } /* Needed only during directory listing right now. */ void FileAccess::setFile(FileAccess* pParent, const QFileInfo& fi) { reset(); m_fileInfo = fi; m_url = QUrl::fromLocalFile(m_fileInfo.filePath()); if(!m_url.scheme().isEmpty()) m_url.setScheme(QLatin1Literal("file")); m_pParent = pParent; loadData(); } void FileAccess::setFile(const QString& name, bool bWantToWrite) { if(name.isEmpty()) return; QUrl url = QUrl::fromUserInput(name, QString(), QUrl::AssumeLocalFile); setFile(url, bWantToWrite); } void FileAccess::setFile(const QUrl& url, bool bWantToWrite) { reset(); Q_ASSERT(parent() == nullptr || url != parent()->url()); m_url = url; //QUrl::isLocalFile assumes the scheme is set. if(m_url.scheme().isEmpty()) m_url.setScheme(QLatin1Literal("file")); if(m_url.isLocalFile() || !m_url.isValid()) // Treat invalid urls as local files. { m_fileInfo.setFile(m_url.toLocalFile()); m_pParent = nullptr; loadData(); } else { m_name = m_url.fileName(); FileAccessJobHandler jh(this); // A friend, which writes to the parameters of this class! jh.stat(2 /*all details*/, bWantToWrite); // returns bSuccess, ignored m_bValidData = true; // After running stat() the variables are initialised // and valid even if the file doesn't exist and the stat // query failed. } } void FileAccess::loadData() { m_fileInfo.setCaching(true); if(parent() == nullptr) m_baseDir.setPath(m_fileInfo.absoluteFilePath()); else m_baseDir = m_pParent->m_baseDir; //convert to absolute path that doesn't depend on the current directory. m_fileInfo.makeAbsolute(); m_bSymLink = m_fileInfo.isSymLink(); m_bFile = m_fileInfo.isFile(); m_bDir = m_fileInfo.isDir(); m_bExists = m_fileInfo.exists(); m_size = m_fileInfo.size(); m_modificationTime = m_fileInfo.lastModified(); m_bHidden = m_fileInfo.isHidden(); m_bWritable = m_fileInfo.isWritable(); m_bReadable = m_fileInfo.isReadable(); m_bExecutable = m_fileInfo.isExecutable(); m_name = m_fileInfo.fileName(); if(isLocal() && m_name.isEmpty()) { m_name = m_fileInfo.absoluteDir().dirName(); } if(isLocal() && m_bSymLink) { m_linkTarget = m_fileInfo.symLinkTarget(); #ifndef Q_OS_WIN // Unfortunately Qt5 symLinkTarget/readLink always returns an absolute path, even if the link is relative char* s = (char*)malloc(PATH_MAX + 1); ssize_t len = readlink(QFile::encodeName(absoluteFilePath()).constData(), s, PATH_MAX); if(len > 0) { s[len] = '\0'; m_linkTarget = QFile::decodeName(s); } free(s); #endif } realFile = QSharedPointer::create(absoluteFilePath()); m_bValidData = true; } void FileAccess::addPath(const QString& txt) { if(!isLocal() && m_url.isValid()) { QUrl url = m_url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + txt); setFile(url); // reinitialise } else { QString slash = (txt.isEmpty() || txt[0] == '/') ? QLatin1String("") : QLatin1String("/"); setFile(absoluteFilePath() + slash + txt); } } /* Filetype: S_IFMT 0170000 bitmask for the file type bitfields S_IFSOCK 0140000 socket S_IFLNK 0120000 symbolic link S_IFREG 0100000 regular file S_IFBLK 0060000 block device S_IFDIR 0040000 directory S_IFCHR 0020000 character device S_IFIFO 0010000 fifo S_ISUID 0004000 set UID bit S_ISGID 0002000 set GID bit (see below) S_ISVTX 0001000 sticky bit (see below) Access: S_IRWXU 00700 mask for file owner permissions S_IRUSR 00400 owner has read permission S_IWUSR 00200 owner has write permission S_IXUSR 00100 owner has execute permission S_IRWXG 00070 mask for group permissions S_IRGRP 00040 group has read permission S_IWGRP 00020 group has write permission S_IXGRP 00010 group has execute permission S_IRWXO 00007 mask for permissions for others (not in group) S_IROTH 00004 others have read permission S_IWOTH 00002 others have write permission S_IXOTH 00001 others have execute permission */ void FileAccess::setFromUdsEntry(const KIO::UDSEntry& e, FileAccess *parent) { long acc = 0; long fileType = 0; QVector fields = e.fields(); QString filePath; m_pParent = parent; for(QVector::ConstIterator ei = fields.constBegin(); ei != fields.constEnd(); ++ei) { uint f = *ei; switch(f) { case KIO::UDSEntry::UDS_SIZE: m_size = e.numberValue(f); break; case KIO::UDSEntry::UDS_NAME: filePath = e.stringValue(f); break; // During listDir the relative path is given here. case KIO::UDSEntry::UDS_MODIFICATION_TIME: m_modificationTime = QDateTime::fromMSecsSinceEpoch(e.numberValue(f)); break; case KIO::UDSEntry::UDS_LINK_DEST: m_linkTarget = e.stringValue(f); break; case KIO::UDSEntry::UDS_ACCESS: { #ifndef Q_OS_WIN acc = e.numberValue(f); m_bReadable = (acc & S_IRUSR) != 0; m_bWritable = (acc & S_IWUSR) != 0; m_bExecutable = (acc & S_IXUSR) != 0; #endif break; } case KIO::UDSEntry::UDS_FILE_TYPE: { fileType = e.numberValue(f); m_bDir = (fileType & QT_STAT_MASK) == QT_STAT_DIR; m_bFile = (fileType & QT_STAT_MASK) == QT_STAT_REG; m_bSymLink = (fileType & QT_STAT_MASK) == QT_STAT_LNK; m_bExists = fileType != 0; //m_fileType = fileType; break; } case KIO::UDSEntry::UDS_URL: m_url = QUrl(e.stringValue(f)); break; case KIO::UDSEntry::UDS_MIME_TYPE: case KIO::UDSEntry::UDS_GUESSED_MIME_TYPE: case KIO::UDSEntry::UDS_XML_PROPERTIES: default: break; } } m_fileInfo = QFileInfo(filePath); m_fileInfo.setCaching(true); if(m_url.isEmpty()) { if(parent != nullptr) { m_url = parent->url().resolved(QUrl(filePath)); //Verify that the scheme doesn't change. Q_ASSERT(m_url.scheme() == parent->url().scheme()); } else { /* Invalid entry we don't know the full url because KIO didn't tell us and there is no parent node supplied. This is a bug if it happens and should be logged . However it is a recoverable error. */ qCWarning(kdiffFileAccess) << i18n("Unable to determine full url. No parent specified."); return; } } m_name = m_fileInfo.fileName(); if(isLocal() && m_name.isEmpty()) { m_name = m_fileInfo.absoluteDir().dirName(); } m_bExists = m_fileInfo.exists(); //insure modification time is initialized if it wasn't already. if(m_modificationTime == QDateTime::fromMSecsSinceEpoch(0)) m_modificationTime = m_fileInfo.lastModified(); m_bValidData = true; m_bSymLink = !m_linkTarget.isEmpty(); #ifndef Q_OS_WIN m_bHidden = m_name[0] == '.'; #endif } bool FileAccess::isValid() const { return m_bValidData; } bool FileAccess::isNormal() const { return !exists() || isFile() || isDir() || isSymLink(); } bool FileAccess::isFile() const { if(!isLocal()) return m_bFile; else return m_fileInfo.isFile(); } bool FileAccess::isDir() const { if(!isLocal()) return m_bDir; else return m_fileInfo.isDir(); } bool FileAccess::isSymLink() const { if(!isLocal()) return m_bSymLink; else return m_fileInfo.isSymLink(); } bool FileAccess::exists() const { if(!isLocal()) return m_bExists; else return m_fileInfo.exists(); } qint64 FileAccess::size() const { if(!isLocal()) return m_size; else return m_fileInfo.size(); } QUrl FileAccess::url() const { QUrl url = m_url; if(url.isLocalFile()) { url = QUrl::fromLocalFile(absoluteFilePath()); } return url; } bool FileAccess::isLocal() const { return m_url.isLocalFile() || !m_url.isValid(); } bool FileAccess::isReadable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bReadable; else return m_fileInfo.isReadable(); } bool FileAccess::isWritable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bWritable; else return m_fileInfo.isWritable(); } bool FileAccess::isExecutable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bExecutable; else return m_fileInfo.isExecutable(); } bool FileAccess::isHidden() const { if(!(isLocal())) return m_bHidden; else return m_fileInfo.isHidden(); } QString FileAccess::readLink() const { return m_linkTarget; } QString FileAccess::absoluteFilePath() const { if(!isLocal()) return m_url.url(); // return complete url return m_fileInfo.absoluteFilePath(); } // Full abs path // Just the name-part of the path, without parent directories QString FileAccess::fileName(bool needTmp) const { if(!isLocal()) return (needTmp) ? m_localCopy : m_name; else return m_name; } QString FileAccess::fileRelPath() const { QString path = m_baseDir.relativeFilePath(m_fileInfo.absoluteFilePath()); return path; } FileAccess* FileAccess::parent() const { return m_pParent; } QString FileAccess::prettyAbsPath() const { return isLocal() ? absoluteFilePath() : m_url.toDisplayString(); } QDateTime FileAccess::lastModified() const { Q_ASSERT(!m_modificationTime.isNull()); return m_modificationTime; } bool FileAccess::interruptableReadFile(void* pDestBuffer, qint64 maxLength) { ProgressProxy pp; const qint64 maxChunkSize = 100000; qint64 i = 0; pp.setMaxNofSteps(maxLength / maxChunkSize + 1); while(i < maxLength) { qint64 nextLength = std::min(maxLength - i, maxChunkSize); qint64 reallyRead = read((char*)pDestBuffer + i, nextLength); if(reallyRead != nextLength) { setStatusText(i18n("Failed to read file: %1", absoluteFilePath())); return false; } i += reallyRead; pp.setCurrent(qFloor(double(i) / maxLength * 100)); if(pp.wasCancelled()) return false; } return true; } bool FileAccess::readFile(void* pDestBuffer, qint64 maxLength) { bool success = false; //Avoid hang on linux for special files. if(!isNormal()) return true; if(isLocal() || !m_localCopy.isEmpty()) { if(open(QIODevice::ReadOnly))//krazy:exclude=syscalls { success = interruptableReadFile(pDestBuffer, maxLength); // maxLength == f.read( (char*)pDestBuffer, maxLength ) close(); } } else { FileAccessJobHandler jh(this); success = jh.get(pDestBuffer, maxLength); } close(); Q_ASSERT(!realFile->isOpen() && !tmpFile->isOpen()); return success; } bool FileAccess::writeFile(const void* pSrcBuffer, qint64 length) { ProgressProxy pp; if(isLocal()) { if(realFile->open(QIODevice::WriteOnly)) { const qint64 maxChunkSize = 100000; pp.setMaxNofSteps(length / maxChunkSize + 1); qint64 i = 0; while(i < length) { qint64 nextLength = std::min(length - i, maxChunkSize); qint64 reallyWritten = realFile->write((char*)pSrcBuffer + i, nextLength); if(reallyWritten != nextLength) { realFile->close(); return false; } i += reallyWritten; pp.step(); if(pp.wasCancelled()) { realFile->close(); return false; } } if(isExecutable()) // value is true if the old file was executable { // Preserve attributes realFile->setPermissions(realFile->permissions() | QFile::ExeUser); } realFile->close(); return true; } } else { FileAccessJobHandler jh(this); bool success = jh.put(pSrcBuffer, length, true /*overwrite*/); close(); Q_ASSERT(!realFile->isOpen() && !tmpFile->isOpen()); return success; } close(); Q_ASSERT(!realFile->isOpen() && !tmpFile->isOpen()); return false; } bool FileAccess::copyFile(const QString& dest) { FileAccessJobHandler jh(this); return jh.copyFile(dest); // Handles local and remote copying. } bool FileAccess::rename(const FileAccess& dest) { FileAccessJobHandler jh(this); return jh.rename(dest); } bool FileAccess::removeFile() { if(isLocal()) { return QDir().remove(absoluteFilePath()); } else { FileAccessJobHandler jh(this); return jh.removeFile(url()); } } bool FileAccess::listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore) { FileAccessJobHandler jh(this); return jh.listDir(pDirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, dirAntiPattern, bFollowDirLinks, bUseCvsIgnore); } QString FileAccess::getTempName() const { return m_localCopy; } const QString FileAccess::errorString() const { return getStatusText(); } bool FileAccess::open(const QFile::OpenMode flags) { bool result; result = createLocalCopy(); if(!result) { setStatusText(i18n("Creating temp copy of %1 failed.", absoluteFilePath())); return result; } if(m_localCopy.isEmpty() && realFile != nullptr) { bool r = realFile->open(flags); setStatusText(i18n("Opening %1 failed. %2", absoluteFilePath(), realFile->errorString())); return r; } bool r = tmpFile->open(); setStatusText(i18n("Opening %1 failed. %2", tmpFile->fileName(), tmpFile->errorString())); return r; } qint64 FileAccess::read(char* data, const qint64 maxlen) { if(!isNormal()) { //This is not an error special files should be skipped setStatusText(QString()); return 0; } qint64 len = 0; if(m_localCopy.isEmpty() && realFile != nullptr) { len = realFile->read(data, maxlen); if(len != maxlen) { setStatusText(i18n("Error reading from %1. %2", absoluteFilePath(), realFile->errorString())); } } else { len = tmpFile->read(data, maxlen); if(len != maxlen) { setStatusText(i18n("Error reading from %1. %2", absoluteFilePath(), tmpFile->errorString())); } } return len; } void FileAccess::close() { if(m_localCopy.isEmpty() && realFile != nullptr) { realFile->close(); } tmpFile->close(); } bool FileAccess::createLocalCopy() { if(isLocal() || !m_localCopy.isEmpty()) return true; tmpFile->setAutoRemove(true); tmpFile->open(); tmpFile->close(); m_localCopy = tmpFile->fileName(); return copyFile(tmpFile->fileName()); } //static tempfile Generator void FileAccess::createTempFile(QTemporaryFile& tmpFile) { tmpFile.setAutoRemove(true); tmpFile.open(); tmpFile.close(); } bool FileAccess::makeDir(const QString& dirName) { FileAccessJobHandler fh(nullptr); return fh.mkDir(dirName); } bool FileAccess::removeDir(const QString& dirName) { FileAccessJobHandler fh(nullptr); return fh.rmDir(dirName); } bool FileAccess::symLink(const QString& linkTarget, const QString& linkLocation) { if(linkTarget.isEmpty() || linkLocation.isEmpty()) return false; return QFile::link(linkTarget, linkLocation); //FileAccessJobHandler fh(0); //return fh.symLink( linkTarget, linkLocation ); } bool FileAccess::exists(const QString& name) { FileAccess fa(name); return fa.exists(); } // If the size couldn't be determined by stat() then the file is copied to a local temp file. qint64 FileAccess::sizeForReading() { if(!isLocal() && m_size == 0) { // Size couldn't be determined. Copy the file to a local temp place. createLocalCopy(); QString localCopy = tmpFile->fileName(); bool bSuccess = copyFile(localCopy); if(bSuccess) { QFileInfo fi(localCopy); m_size = fi.size(); m_localCopy = localCopy; return m_size; } else { return 0; } } else return size(); } QString FileAccess::getStatusText() const { return m_statusText; } void FileAccess::setStatusText(const QString& s) { m_statusText = s; } QString FileAccess::cleanPath(const QString& path) // static { FileAccess fa(path); if(fa.isLocal()) { return QDir::cleanPath(path); } else { return path; } } bool FileAccess::createBackup(const QString& bakExtension) { if(exists()) { // First rename the existing file to the bak-file. If a bak-file file exists, delete that. QString bakName = absoluteFilePath() + bakExtension; FileAccess bakFile(bakName, true /*bWantToWrite*/); if(bakFile.exists()) { bool bSuccess = bakFile.removeFile(); if(!bSuccess) { setStatusText(i18n("While trying to make a backup, deleting an older backup failed.\nFilename: %1", bakName)); return false; } } bool bSuccess = rename(bakFile); // krazy:exclude=syscalls if(!bSuccess) { setStatusText(i18n("While trying to make a backup, renaming failed.\nFilenames: %1 -> %2", absoluteFilePath(), bakName)); return false; } } return true; } void FileAccess::doError() { m_bExists = false; } void FileAccess::filterList(t_DirectoryList* pDirList, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, const bool bUseCvsIgnore) { CvsIgnoreList cvsIgnoreList; if(bUseCvsIgnore) { cvsIgnoreList.init(*this, pDirList); } //TODO: Ask os for this information don't hard code it. #if defined(Q_OS_WIN) bool bCaseSensitive = false; #else bool bCaseSensitive = true; #endif // Now remove all entries that should be ignored: t_DirectoryList::iterator i; for(i = pDirList->begin(); i != pDirList->end();) { t_DirectoryList::iterator i2 = i; ++i2; QString fileName = i->fileName(); if((i->isFile() && (!Utils::wildcardMultiMatch(filePattern, fileName, bCaseSensitive) || Utils::wildcardMultiMatch(fileAntiPattern, fileName, bCaseSensitive))) || (i->isDir() && Utils::wildcardMultiMatch(dirAntiPattern, fileName, bCaseSensitive)) || (bUseCvsIgnore && cvsIgnoreList.matches(fileName, bCaseSensitive))) { // Remove it pDirList->erase(i); i = i2; } else { ++i; } } } FileAccessJobHandler::FileAccessJobHandler(FileAccess* pFileAccess) { m_pFileAccess = pFileAccess; } bool FileAccessJobHandler::stat(short detail, bool bWantToWrite) { m_bSuccess = false; m_pFileAccess->setStatusText(QString()); KIO::StatJob* pStatJob = KIO::stat(m_pFileAccess->url(), bWantToWrite ? KIO::StatJob::DestinationSide : KIO::StatJob::SourceSide, detail, KIO::HideProgressInfo); connect(pStatJob, &KIO::StatJob::result, this, &FileAccessJobHandler::slotStatResult); ProgressProxy::enterEventLoop(pStatJob, i18n("Getting file status: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } void FileAccessJobHandler::slotStatResult(KJob* pJob) { if(pJob->error() != KJob::NoError) { //pJob->uiDelegate()->showErrorMessage(); m_pFileAccess->doError(); m_bSuccess = true; } else { m_bSuccess = true; const KIO::UDSEntry e = static_cast(pJob)->statResult(); m_pFileAccess->setFromUdsEntry(e, m_pFileAccess); } ProgressProxy::exitEventLoop(); } bool FileAccessJobHandler::get(void* pDestBuffer, long maxLength) { ProgressProxyExtender pp; // Implicitly used in slotPercent() if(maxLength > 0 && !pp.wasCancelled()) { KIO::TransferJob* pJob = KIO::get(m_pFileAccess->url(), KIO::NoReload); m_transferredBytes = 0; m_pTransferBuffer = (char*)pDestBuffer; m_maxLength = maxLength; m_bSuccess = false; m_pFileAccess->setStatusText(QString()); connect(pJob, &KIO::TransferJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, &KIO::TransferJob::data, this, &FileAccessJobHandler::slotGetData); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Reading file: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } else return true; } void FileAccessJobHandler::slotGetData(KJob* pJob, const QByteArray& newData) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { qint64 length = std::min(qint64(newData.size()), m_maxLength - m_transferredBytes); ::memcpy(m_pTransferBuffer + m_transferredBytes, newData.data(), newData.size()); m_transferredBytes += length; } } bool FileAccessJobHandler::put(const void* pSrcBuffer, long maxLength, bool bOverwrite, bool bResume, int permissions) { ProgressProxyExtender pp; // Implicitly used in slotPercent() if(maxLength > 0) { KIO::TransferJob* pJob = KIO::put(m_pFileAccess->url(), permissions, KIO::HideProgressInfo | (bOverwrite ? KIO::Overwrite : KIO::DefaultFlags) | (bResume ? KIO::Resume : KIO::DefaultFlags)); m_transferredBytes = 0; m_pTransferBuffer = (char*)pSrcBuffer; m_maxLength = maxLength; m_bSuccess = false; m_pFileAccess->setStatusText(QString()); connect(pJob, &KIO::TransferJob::result, this, &FileAccessJobHandler::slotPutJobResult); connect(pJob, &KIO::TransferJob::dataReq, this, &FileAccessJobHandler::slotPutData); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Writing file: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } else return true; } void FileAccessJobHandler::slotPutData(KIO::Job* pJob, QByteArray& data) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { /* Think twice before doing this in new code. The maxChunkSize must be able to fit a 32-bit int. Given that the fallowing is safe. */ qint64 maxChunkSize = 100000; qint64 length = std::min(maxChunkSize, m_maxLength - m_transferredBytes); data.resize((int)length); if(data.size() == (int)length) { if(length > 0) { ::memcpy(data.data(), m_pTransferBuffer + m_transferredBytes, data.size()); m_transferredBytes += length; } } else { KMessageBox::error(ProgressProxy::getDialog(), i18n("Out of memory")); data.resize(0); m_bSuccess = false; } } } void FileAccessJobHandler::slotPutJobResult(KJob* pJob) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { m_bSuccess = (m_transferredBytes == m_maxLength); // Special success condition } ProgressProxy::exitEventLoop(); // Close the dialog, return from exec() } bool FileAccessJobHandler::mkDir(const QString& dirName) { if(dirName.isEmpty()) return false; FileAccess dir(dirName); if(dir.isLocal()) { return QDir().mkdir(dir.absoluteFilePath()); } else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::mkdir(dir.url()); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Making folder: %1", dirName)); return m_bSuccess; } } bool FileAccessJobHandler::rmDir(const QString& dirName) { if(dirName.isEmpty()) return false; FileAccess fa(dirName); if(fa.isLocal()) { return QDir().rmdir(fa.absoluteFilePath()); } else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::rmdir(fa.url()); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Removing folder: %1", dirName)); return m_bSuccess; } } bool FileAccessJobHandler::removeFile(const QUrl& fileName) { if(fileName.isEmpty()) return false; else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::file_delete(fileName, KIO::HideProgressInfo); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Removing file: %1", fileName.toDisplayString())); return m_bSuccess; } } bool FileAccessJobHandler::symLink(const QUrl& linkTarget, const QUrl& linkLocation) { if(linkTarget.isEmpty() || linkLocation.isEmpty()) return false; else { m_bSuccess = false; KIO::CopyJob* pJob = KIO::link(linkTarget, linkLocation, KIO::HideProgressInfo); connect(pJob, &KIO::CopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Creating symbolic link: %1 -> %2", linkLocation.toDisplayString(), linkTarget.toDisplayString())); return m_bSuccess; } } bool FileAccessJobHandler::rename(const FileAccess& destFile) { if(destFile.fileName().isEmpty()) return false; if(m_pFileAccess->isLocal() && destFile.isLocal()) { return QDir().rename(m_pFileAccess->absoluteFilePath(), destFile.absoluteFilePath()); } else { ProgressProxyExtender pp; int permissions = -1; m_bSuccess = false; KIO::FileCopyJob* pJob = KIO::file_move(m_pFileAccess->url(), destFile.url(), permissions, KIO::HideProgressInfo); connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Renaming file: %1 -> %2", m_pFileAccess->prettyAbsPath(), destFile.prettyAbsPath())); return m_bSuccess; } } void FileAccessJobHandler::slotSimpleJobResult(KJob* pJob) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { m_bSuccess = true; } ProgressProxy::exitEventLoop(); // Close the dialog, return from exec() } // Copy local or remote files. bool FileAccessJobHandler::copyFile(const QString& inDest) { ProgressProxyExtender pp; FileAccess dest; dest.setFile(inDest); m_pFileAccess->setStatusText(QString()); if(!m_pFileAccess->isNormal() || !dest.isNormal()) return false; int permissions = (m_pFileAccess->isExecutable() ? 0111 : 0) + (m_pFileAccess->isWritable() ? 0222 : 0) + (m_pFileAccess->isReadable() ? 0444 : 0); m_bSuccess = false; KIO::FileCopyJob* pJob = KIO::file_copy(m_pFileAccess->url(), dest.url(), permissions, KIO::HideProgressInfo|KIO::Overwrite); connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Copying file: %1 -> %2", m_pFileAccess->prettyAbsPath(), dest.prettyAbsPath())); return m_bSuccess; // Note that the KIO-slave preserves the original date, if this is supported. } bool FileAccessJobHandler::listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, const bool bUseCvsIgnore) { ProgressProxyExtender pp; m_pDirList = pDirList; m_pDirList->clear(); m_bFindHidden = bFindHidden; m_bRecursive = bRecursive; m_bFollowDirLinks = bFollowDirLinks; // Only relevant if bRecursive==true. m_fileAntiPattern = fileAntiPattern; m_filePattern = filePattern; m_dirAntiPattern = dirAntiPattern; if(pp.wasCancelled()) return true; // Cancelled is not an error. pp.setInformation(i18n("Reading folder: %1", m_pFileAccess->absoluteFilePath()), 0, false); if(m_pFileAccess->isLocal()) { m_bSuccess = true; QDir dir(m_pFileAccess->absoluteFilePath()); dir.setSorting(QDir::Name | QDir::DirsFirst); if(bFindHidden) dir.setFilter(QDir::Files | QDir::Dirs | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); else dir.setFilter(QDir::Files | QDir::Dirs | QDir::System | QDir::NoDotAndDotDot); QFileInfoList fiList = dir.entryInfoList(); if(fiList.isEmpty()) { /* Sadly Qt provides no error information making this case ambigious. A readablity check is the best we can do. */ m_bSuccess = dir.isReadable(); } else { for(const QFileInfo& fi: fiList) // for each file... { if(pp.wasCancelled()) break; Q_ASSERT(fi.fileName() != "." && fi.fileName() != ".."); FileAccess fa; fa.setFile(m_pFileAccess, fi); pDirList->push_back(fa); } } } else { KIO::ListJob* pListJob = nullptr; pListJob = KIO::listDir(m_pFileAccess->url(), KIO::HideProgressInfo, true /*bFindHidden*/); m_bSuccess = false; if(pListJob != nullptr) { connect(pListJob, &KIO::ListJob::entries, this, &FileAccessJobHandler::slotListDirProcessNewEntries); connect(pListJob, &KIO::ListJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pListJob, &KIO::ListJob::infoMessage, &pp, &ProgressProxyExtender::slotListDirInfoMessage); // This line makes the transfer via fish unreliable.:-( /*if(m_pFileAccess->url().scheme() != QLatin1Literal("fish")){ connect( pListJob, static_cast(&KIO::ListJob::percent), &pp, &ProgressProxyExtender::slotPercent); }*/ ProgressProxy::enterEventLoop(pListJob, i18n("Listing directory: %1", m_pFileAccess->prettyAbsPath())); } } m_pFileAccess->filterList(pDirList, filePattern, fileAntiPattern, dirAntiPattern, bUseCvsIgnore); if(bRecursive) { t_DirectoryList::iterator i; t_DirectoryList subDirsList; for(i = m_pDirList->begin(); i != m_pDirList->end(); ++i) { if(i->isDir() && (!i->isSymLink() || m_bFollowDirLinks)) { t_DirectoryList dirList; i->listDir(&dirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, dirAntiPattern, bFollowDirLinks, bUseCvsIgnore); // append data onto the main list subDirsList.splice(subDirsList.end(), dirList); } } m_pDirList->splice(m_pDirList->end(), subDirsList); } return m_bSuccess; } void FileAccessJobHandler::slotListDirProcessNewEntries(KIO::Job*, const KIO::UDSEntryList& l) { //This function is called for non-local urls. Don't use QUrl::fromLocalFile here as it does not handle these. KIO::UDSEntryList::ConstIterator i; for(i = l.begin(); i != l.end(); ++i) { const KIO::UDSEntry& e = *i; FileAccess fa; fa.setFromUdsEntry(e, m_pFileAccess); //must be manually filtered KDE does not supply API for ignoring these. if(fa.fileName() != "." && fa.fileName() != "..") { //quick fix to preserve behavoir without creating invalid urls. TODO: look for altertive machanism for use with next major release. fa.setFile(fa.url()); m_pDirList->push_back(fa); } } } //#include "fileaccess.moc" diff --git a/src/fileaccess.h b/src/fileaccess.h index 61a0e55..23ae7db 100644 --- a/src/fileaccess.h +++ b/src/fileaccess.h @@ -1,198 +1,196 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef FILEACCESS_H #define FILEACCESS_H #include #include #include #include #include #include #include #include #include namespace KIO { class Job; } class t_DirectoryList; class FileAccess { public: FileAccess(); ~FileAccess(); explicit FileAccess(const QString& name, bool bWantToWrite = false); // name: local file or dirname or url (when supported) void setFile(const QString& name, bool bWantToWrite = false); void setFile(const QUrl& url, bool bWantToWrite = false); void setFile(FileAccess* pParent, const QFileInfo& fi); void loadData(); bool isNormal() const; bool isValid() const; bool isFile() const; bool isDir() const; bool isSymLink() const; bool exists() const; qint64 size() const; // Size as returned by stat(). qint64 sizeForReading(); // If the size can't be determined by stat() then the file is copied to a local temp file. bool isReadable() const; bool isWritable() const; bool isExecutable() const; bool isHidden() const; QString readLink() const; QDateTime lastModified() const; QString fileName(bool needTmp = false) const; // Just the name-part of the path, without parent directories QString fileRelPath() const; // The path relative to base comparison directory QString prettyAbsPath() const; QUrl url() const; void setUrl(const QUrl& inUrl) { m_url = inUrl; } QString absoluteFilePath() const; bool isLocal() const; bool readFile(void* pDestBuffer, qint64 maxLength); bool writeFile(const void* pSrcBuffer, qint64 length); bool listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore); bool copyFile(const QString& destUrl); bool createBackup(const QString& bakExtension); QString getTempName() const; bool createLocalCopy(); static void createTempFile(QTemporaryFile&); bool removeFile(); static bool makeDir(const QString&); static bool removeDir(const QString&); static bool exists(const QString&); static QString cleanPath(const QString&); //bool chmod( const QString& ); bool rename(const FileAccess&); static bool symLink(const QString& linkTarget, const QString& linkLocation); void addPath(const QString& txt); QString getStatusText() const; FileAccess* parent() const; // !=0 for listDir-results, but only valid if the parent was not yet destroyed. void doError(); void filterList(t_DirectoryList* pDirList, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, const bool bUseCvsIgnore); QDir getBaseDirectory() const { return m_baseDir; } bool open(const QFile::OpenMode flags); qint64 read(char* data, const qint64 maxlen); void close(); const QString errorString() const; private: friend class FileAccessJobHandler; void setFromUdsEntry(const KIO::UDSEntry& e, FileAccess* parent); void setStatusText(const QString& s); void reset(); bool interruptableReadFile(void* pDestBuffer, qint64 maxLength); QUrl m_url; bool m_bValidData = false; //long m_fileType; // for testing only FileAccess* m_pParent = nullptr; QDir m_baseDir; QFileInfo m_fileInfo; QString m_linkTarget; QString m_name; QString m_localCopy; QSharedPointer tmpFile = nullptr; QSharedPointer realFile = nullptr; qint64 m_size = 0; QDateTime m_modificationTime = QDateTime::fromMSecsSinceEpoch(0); bool m_bSymLink = false; bool m_bFile = false; bool m_bDir = false; bool m_bExists = false; bool m_bWritable = false; bool m_bReadable = false; bool m_bExecutable = false; bool m_bHidden = false; QString m_statusText; // Might contain an error string, when the last operation didn't succeed. }; class t_DirectoryList : public std::list { }; class FileAccessJobHandler : public QObject { Q_OBJECT public: explicit FileAccessJobHandler(FileAccess* pFileAccess); bool get(void* pDestBuffer, long maxLength); bool put(const void* pSrcBuffer, long maxLength, bool bOverwrite, bool bResume = false, int permissions = -1); bool stat(short detailLevel = 2, bool bWantToWrite = false); bool copyFile(const QString& dest); bool rename(const FileAccess& dest); bool listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore); bool mkDir(const QString& dirName); bool rmDir(const QString& dirName); bool removeFile(const QUrl& fileName); bool symLink(const QUrl& linkTarget, const QUrl& linkLocation); private: FileAccess* m_pFileAccess = nullptr; bool m_bSuccess = false; // Data needed during Job qint64 m_transferredBytes = 0; char* m_pTransferBuffer = nullptr; // Needed during get or put qint64 m_maxLength = 0; QString m_filePattern; QString m_fileAntiPattern; QString m_dirAntiPattern; t_DirectoryList* m_pDirList = nullptr; bool m_bFindHidden = false; bool m_bRecursive = false; bool m_bFollowDirLinks = false; bool scanLocalDirectory(const QString& dirName, t_DirectoryList* dirList); private Q_SLOTS: void slotStatResult(KJob*); void slotSimpleJobResult(KJob* pJob); void slotPutJobResult(KJob* pJob); void slotGetData(KJob*, const QByteArray&); void slotPutData(KIO::Job*, QByteArray&); void slotListDirProcessNewEntries(KIO::Job*, const KIO::UDSEntryList& l); }; #endif diff --git a/src/gnudiff_analyze.cpp b/src/gnudiff_analyze.cpp index 9c4fea5..1931cfb 100644 --- a/src/gnudiff_analyze.cpp +++ b/src/gnudiff_analyze.cpp @@ -1,847 +1,838 @@ /* Analyze file differences for GNU DIFF. Modified for KDiff3 by Joachim Eibl 2003. The original file was part of GNU DIFF. - Copyright (C) 1988, 1989, 1992, 1993, 1994, 1995, 1998, 2001, 2002 - Free Software Foundation, Inc. - - GNU DIFF is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. - - GNU DIFF 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; see the file COPYING. - If not, write to the Free Software Foundation, - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + + Part of KDiff3 - Text Diff And Merge Tool + + SPDX-FileCopyrightText: 1988-2002 Free Software Foundation, Inc. + SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: GPL-2.0-or-later +*/ /* The basic algorithm is described in: "An O(ND) Difference Algorithm and its Variations", Eugene Myers, Algorithmica Vol. 1 No. 2, 1986, pp. 251-266; see especially section 4.2, which describes the variation used below. Unless the --minimal option is specified, this code uses the TOO_EXPENSIVE heuristic, by Paul Eggert, to limit the cost to O(N**1.5 log N) at the price of producing suboptimal output for large inputs with many differences. The basic algorithm was independently discovered as described in: "Algorithms for Approximate String Matching", E. Ukkonen, Information and Control Vol. 64, 1985, pp. 100-118. */ #define GDIFF_MAIN #include "gnudiff_diff.h" #include // for max, min #include static GNULineRef *xvec, *yvec; /* Vectors being compared. */ static GNULineRef *fdiag; /* Vector, indexed by diagonal, containing 1 + the X coordinate of the point furthest along the given diagonal in the forward search of the edit matrix. */ static GNULineRef *bdiag; /* Vector, indexed by diagonal, containing the X coordinate of the point furthest along the given diagonal in the backward search of the edit matrix. */ static GNULineRef too_expensive; /* Edit scripts longer than this are too expensive to compute. */ #define SNAKE_LIMIT 20 /* Snakes bigger than this are considered `big'. */ struct partition { GNULineRef xmid, ymid; /* Midpoints of this partition. */ bool lo_minimal; /* Nonzero if low half will be analyzed minimally. */ bool hi_minimal; /* Likewise for high half. */ }; /* Find the midpoint of the shortest edit script for a specified portion of the two files. Scan from the beginnings of the files, and simultaneously from the ends, doing a breadth-first search through the space of edit-sequence. When the two searches meet, we have found the midpoint of the shortest edit sequence. If FIND_MINIMAL is nonzero, find the minimal edit script regardless of expense. Otherwise, if the search is too expensive, use heuristics to stop the search and report a suboptimal answer. Set PART->(xmid,ymid) to the midpoint (XMID,YMID). The diagonal number XMID - YMID equals the number of inserted lines minus the number of deleted lines (counting only lines before the midpoint). Return the approximate edit cost; this is the total number of lines inserted or deleted (counting only lines before the midpoint), unless a heuristic is used to terminate the search prematurely. Set PART->lo_minimal to true iff the minimal edit script for the left half of the partition is known; similarly for PART->hi_minimal. This function assumes that the first lines of the specified portions of the two files do not match, and likewise that the last lines do not match. The caller must trim matching lines from the beginning and end of the portions it is going to specify. If we return the "wrong" partitions, the worst this can do is cause suboptimal diff output. It cannot cause incorrect diff output. */ GNULineRef GnuDiff::diag(GNULineRef xoff, GNULineRef xlim, GNULineRef yoff, GNULineRef ylim, bool find_minimal, partition *part) const { GNULineRef *const fd = fdiag; /* Give the compiler a chance. */ GNULineRef *const bd = bdiag; /* Additional help for the compiler. */ GNULineRef const *const xv = xvec; /* Still more help for the compiler. */ GNULineRef const *const yv = yvec; /* And more and more . . . */ GNULineRef const dmin = xoff - ylim; /* Minimum valid diagonal. */ GNULineRef const dmax = xlim - yoff; /* Maximum valid diagonal. */ GNULineRef const fmid = xoff - yoff; /* Center diagonal of top-down search. */ GNULineRef const bmid = xlim - ylim; /* Center diagonal of bottom-up search. */ GNULineRef fmin = fmid, fmax = fmid; /* Limits of top-down search. */ GNULineRef bmin = bmid, bmax = bmid; /* Limits of bottom-up search. */ GNULineRef c; /* Cost. */ bool odd = (fmid - bmid) & 1; /* True if southeast corner is on an odd diagonal with respect to the northwest. */ fd[fmid] = xoff; bd[bmid] = xlim; for(c = 1;; ++c) { GNULineRef d; /* Active diagonal. */ bool big_snake = false; /* Extend the top-down search by an edit step in each diagonal. */ fmin > dmin ? fd[--fmin - 1] = -1 : ++fmin; fmax < dmax ? fd[++fmax + 1] = -1 : --fmax; for(d = fmax; d >= fmin; d -= 2) { GNULineRef x, y, oldx, tlo = fd[d - 1], thi = fd[d + 1]; if(tlo >= thi) x = tlo + 1; else x = thi; oldx = x; y = x - d; while(x < xlim && y < ylim && xv[x] == yv[y]) ++x, ++y; if(x - oldx > SNAKE_LIMIT) big_snake = true; fd[d] = x; if(odd && bmin <= d && d <= bmax && bd[d] <= x) { part->xmid = x; part->ymid = y; part->lo_minimal = part->hi_minimal = true; return 2 * c - 1; } } /* Similarly extend the bottom-up search. */ bmin > dmin ? bd[--bmin - 1] = GNULINEREF_MAX : ++bmin; bmax < dmax ? bd[++bmax + 1] = GNULINEREF_MAX : --bmax; for(d = bmax; d >= bmin; d -= 2) { GNULineRef x, y, oldx, tlo = bd[d - 1], thi = bd[d + 1]; if(tlo < thi) x = tlo; else x = thi - 1; oldx = x; y = x - d; while(x > xoff && y > yoff && xv[x - 1] == yv[y - 1]) --x, --y; if(oldx - x > SNAKE_LIMIT) big_snake = true; bd[d] = x; if(!odd && fmin <= d && d <= fmax && x <= fd[d]) { part->xmid = x; part->ymid = y; part->lo_minimal = part->hi_minimal = true; return 2 * c; } } if(find_minimal) continue; /* Heuristic: check occasionally for a diagonal that has made lots of progress compared with the edit distance. If we have any such, find the one that has made the most progress and return it as if it had succeeded. With this heuristic, for files with a constant small density of changes, the algorithm is linear in the file size. */ if(200 < c && big_snake && speed_large_files) { GNULineRef best; best = 0; for(d = fmax; d >= fmin; d -= 2) { GNULineRef dd = d - fmid; GNULineRef x = fd[d]; GNULineRef y = x - d; GNULineRef v = (x - xoff) * 2 - dd; if(v > 12 * (c + (dd < 0 ? -dd : dd))) { if(v > best && xoff + SNAKE_LIMIT <= x && x < xlim && yoff + SNAKE_LIMIT <= y && y < ylim) { /* We have a good enough best diagonal; now insist that it end with a significant snake. */ int k; for(k = 1; xv[x - k] == yv[y - k]; k++) if(k == SNAKE_LIMIT) { best = v; part->xmid = x; part->ymid = y; break; } } } } if(best > 0) { part->lo_minimal = true; part->hi_minimal = false; return 2 * c - 1; } best = 0; for(d = bmax; d >= bmin; d -= 2) { GNULineRef dd = d - bmid; GNULineRef x = bd[d]; GNULineRef y = x - d; GNULineRef v = (xlim - x) * 2 + dd; if(v > 12 * (c + (dd < 0 ? -dd : dd))) { if(v > best && xoff < x && x <= xlim - SNAKE_LIMIT && yoff < y && y <= ylim - SNAKE_LIMIT) { /* We have a good enough best diagonal; now insist that it end with a significant snake. */ int k; for(k = 0; xv[x + k] == yv[y + k]; k++) if(k == SNAKE_LIMIT - 1) { best = v; part->xmid = x; part->ymid = y; break; } } } } if(best > 0) { part->lo_minimal = false; part->hi_minimal = true; return 2 * c - 1; } } /* Heuristic: if we've gone well beyond the call of duty, give up and report halfway between our best results so far. */ if(c >= too_expensive) { GNULineRef fxybest, fxbest; GNULineRef bxybest, bxbest; fxbest = bxbest = 0; /* Pacify `gcc -Wall'. */ /* Find forward diagonal that maximizes X + Y. */ fxybest = -1; for(d = fmax; d >= fmin; d -= 2) { GNULineRef x = std::min(fd[d], xlim); GNULineRef y = x - d; if(ylim < y) x = ylim + d, y = ylim; if(fxybest < x + y) { fxybest = x + y; fxbest = x; } } /* Find backward diagonal that minimizes X + Y. */ bxybest = GNULINEREF_MAX; for(d = bmax; d >= bmin; d -= 2) { GNULineRef x = std::max(xoff, bd[d]); GNULineRef y = x - d; if(y < yoff) x = yoff + d, y = yoff; if(x + y < bxybest) { bxybest = x + y; bxbest = x; } } /* Use the better of the two diagonals. */ if((xlim + ylim) - bxybest < fxybest - (xoff + yoff)) { part->xmid = fxbest; part->ymid = fxybest - fxbest; part->lo_minimal = true; part->hi_minimal = false; } else { part->xmid = bxbest; part->ymid = bxybest - bxbest; part->lo_minimal = false; part->hi_minimal = true; } return 2 * c - 1; } } } /* Compare in detail contiguous subsequences of the two files which are known, as a whole, to match each other. The results are recorded in the vectors files[N].changed, by storing 1 in the element for each line that is an insertion or deletion. The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. Note that XLIM, YLIM are exclusive bounds. All line numbers are origin-0 and discarded lines are not counted. If FIND_MINIMAL, find a minimal difference no matter how expensive it is. */ void GnuDiff::compareseq(GNULineRef xoff, GNULineRef xlim, GNULineRef yoff, GNULineRef ylim, bool find_minimal) { GNULineRef *const xv = xvec; /* Help the compiler. */ GNULineRef *const yv = yvec; /* Slide down the bottom initial diagonal. */ while(xoff < xlim && yoff < ylim && xv[xoff] == yv[yoff]) ++xoff, ++yoff; /* Slide up the top initial diagonal. */ while(xlim > xoff && ylim > yoff && xv[xlim - 1] == yv[ylim - 1]) --xlim, --ylim; /* Handle simple cases. */ if(xoff == xlim) while(yoff < ylim) files[1].changed[files[1].realindexes[yoff++]] = true; else if(yoff == ylim) while(xoff < xlim) files[0].changed[files[0].realindexes[xoff++]] = true; else { GNULineRef c; partition part; /* Find a point of correspondence in the middle of the files. */ c = diag(xoff, xlim, yoff, ylim, find_minimal, &part); /* This should be impossible, because it implies that one of the two subsequences is empty, and that case was handled above without calling `diag'. */ Q_ASSERT(c != 1); /* Use the partitions to split this problem into subproblems. */ compareseq(xoff, part.xmid, yoff, part.ymid, part.lo_minimal); compareseq(part.xmid, xlim, part.ymid, ylim, part.hi_minimal); } } /* Discard lines from one file that have no matches in the other file. A line which is discarded will not be considered by the actual comparison algorithm; it will be as if that line were not in the file. The file's `realindexes' table maps virtual line numbers (which don't count the discarded lines) into real line numbers; this is how the actual comparison algorithm produces results that are comprehensible when the discarded lines are counted. When we discard a line, we also mark it as a deletion or insertion so that it will be printed in the output. */ void GnuDiff::discard_confusing_lines(file_data filevec[]) { int f; GNULineRef i; char *discarded[2]; GNULineRef *equiv_count[2]; GNULineRef *p; /* Allocate our results. */ p = (GNULineRef *)xmalloc((filevec[0].buffered_lines + filevec[1].buffered_lines) * (2 * sizeof(*p))); for(f = 0; f < 2; ++f) { filevec[f].undiscarded = p; p += filevec[f].buffered_lines; filevec[f].realindexes = p; p += filevec[f].buffered_lines; } /* Set up equiv_count[F][I] as the number of lines in file F that fall in equivalence class I. */ p = (GNULineRef *)zalloc(filevec[0].equiv_max * (2 * sizeof(*p))); equiv_count[0] = p; equiv_count[1] = p + filevec[0].equiv_max; for(i = 0; i < filevec[0].buffered_lines; ++i) ++equiv_count[0][filevec[0].equivs[i]]; for(i = 0; i < filevec[1].buffered_lines; ++i) ++equiv_count[1][filevec[1].equivs[i]]; /* Set up tables of which lines are going to be discarded. */ discarded[0] = (char *)zalloc(filevec[0].buffered_lines + filevec[1].buffered_lines); discarded[1] = discarded[0] + filevec[0].buffered_lines; /* Mark to be discarded each line that matches no line of the other file. If a line matches many lines, mark it as provisionally discardable. */ for(f = 0; f < 2; ++f) { size_t end = filevec[f].buffered_lines; char *discards = discarded[f]; GNULineRef *counts = equiv_count[1 - f]; GNULineRef *equivs = filevec[f].equivs; size_t many = 5; size_t tem = end / 64; /* Multiply MANY by approximate square root of number of lines. That is the threshold for provisionally discardable lines. */ while((tem = tem >> 2) > 0) many *= 2; for(i = 0; i < (GNULineRef)end; ++i) { GNULineRef nmatch; if(equivs[i] == 0) continue; nmatch = counts[equivs[i]]; if(nmatch == 0) discards[i] = 1; else if(nmatch > (GNULineRef)many) discards[i] = 2; } } /* Don't really discard the provisional lines except when they occur in a run of discardables, with nonprovisionals at the beginning and end. */ for(f = 0; f < 2; ++f) { GNULineRef end = filevec[f].buffered_lines; char *discards = discarded[f]; for(i = 0; i < end; ++i) { /* Cancel provisional discards not in middle of run of discards. */ if(discards[i] == 2) discards[i] = 0; else if(discards[i] != 0) { /* We have found a nonprovisional discard. */ GNULineRef j; GNULineRef length; GNULineRef provisional = 0; /* Find end of this run of discardable lines. Count how many are provisionally discardable. */ for(j = i; j < end; ++j) { if(discards[j] == 0) break; if(discards[j] == 2) ++provisional; } /* Cancel provisional discards at end, and shrink the run. */ while(j > i && discards[j - 1] == 2) discards[--j] = 0, --provisional; /* Now we have the length of a run of discardable lines whose first and last are not provisional. */ length = j - i; /* If 1/4 of the lines in the run are provisional, cancel discarding of all provisional lines in the run. */ if(provisional * 4 > length) { while(j > i) if(discards[--j] == 2) discards[j] = 0; } else { GNULineRef consec; GNULineRef minimum = 1; GNULineRef tem = length >> 2; /* MINIMUM is approximate square root of LENGTH/4. A subrun of two or more provisionals can stand when LENGTH is at least 16. A subrun of 4 or more can stand when LENGTH >= 64. */ while(0 < (tem >>= 2)) minimum <<= 1; minimum++; /* Cancel any subrun of MINIMUM or more provisionals within the larger run. */ for(j = 0, consec = 0; j < length; ++j) if(discards[i + j] != 2) consec = 0; else if(minimum == ++consec) /* Back up to start of subrun, to cancel it all. */ j -= consec; else if(minimum < consec) discards[i + j] = 0; /* Scan from beginning of run until we find 3 or more nonprovisionals in a row or until the first nonprovisional at least 8 lines in. Until that point, cancel any provisionals. */ for(j = 0, consec = 0; j < length; ++j) { if(j >= 8 && discards[i + j] == 1) break; if(discards[i + j] == 2) consec = 0, discards[i + j] = 0; else if(discards[i + j] == 0) consec = 0; else consec++; if(consec == 3) break; } /* I advances to the last line of the run. */ i += length - 1; /* Same thing, from end. */ for(j = 0, consec = 0; j < length; ++j) { if(j >= 8 && discards[i - j] == 1) break; if(discards[i - j] == 2) consec = 0, discards[i - j] = 0; else if(discards[i - j] == 0) consec = 0; else consec++; if(consec == 3) break; } } } } } /* Actually discard the lines. */ for(f = 0; f < 2; ++f) { char *discards = discarded[f]; GNULineRef end = filevec[f].buffered_lines; GNULineRef j = 0; for(i = 0; i < end; ++i) if(minimal || discards[i] == 0) { filevec[f].undiscarded[j] = filevec[f].equivs[i]; filevec[f].realindexes[j++] = i; } else filevec[f].changed[i] = true; filevec[f].nondiscarded_lines = j; } free(discarded[0]); free(equiv_count[0]); } /* Adjust inserts/deletes of identical lines to join changes as much as possible. We do something when a run of changed lines include a line at one end and have an excluded, identical line at the other. We are free to choose which identical line is included. `compareseq' usually chooses the one at the beginning, but usually it is cleaner to consider the following identical line to be the "change". */ void GnuDiff::shift_boundaries(file_data filevec[]) { int f; for(f = 0; f < 2; ++f) { bool *changed = filevec[f].changed; bool const *other_changed = filevec[1 - f].changed; GNULineRef const *equivs = filevec[f].equivs; GNULineRef i = 0; GNULineRef j = 0; GNULineRef i_end = filevec[f].buffered_lines; while(true) { GNULineRef runlength, start, corresponding; /* Scan forwards to find beginning of another run of changes. Also keep track of the corresponding point in the other file. */ while(i < i_end && !changed[i]) { while(other_changed[j++]) continue; i++; } if(i == i_end) break; start = i; /* Find the end of this run of changes. */ while(changed[++i]) continue; while(other_changed[j]) j++; do { /* Record the length of this run of changes, so that we can later determine whether the run has grown. */ runlength = i - start; /* Move the changed region back, so long as the previous unchanged line matches the last changed one. This merges with previous changed regions. */ while(start && equivs[start - 1] == equivs[i - 1]) { changed[--start] = true; changed[--i] = false; while(changed[start - 1]) start--; while(other_changed[--j]) continue; } /* Set CORRESPONDING to the end of the changed run, at the last point where it corresponds to a changed run in the other file. CORRESPONDING == I_END means no such point has been found. */ corresponding = other_changed[j - 1] ? i : i_end; /* Move the changed region forward, so long as the first changed line matches the following unchanged one. This merges with following changed regions. Do this second, so that if there are no merges, the changed region is moved forward as far as possible. */ while(i != i_end && equivs[start] == equivs[i]) { changed[start++] = false; changed[i++] = true; while(changed[i]) i++; while(other_changed[++j]) corresponding = i; } } while(runlength != i - start); /* If possible, move the fully-merged run of changes back to a corresponding run in the other file. */ while(corresponding < i) { changed[--start] = true; changed[--i] = false; while(other_changed[--j]) continue; } } } } /* Cons an additional entry onto the front of an edit script OLD. LINE0 and LINE1 are the first affected lines in the two files (origin 0). DELETED is the number of lines deleted here from file 0. INSERTED is the number of lines inserted here in file 1. If DELETED is 0 then LINE0 is the number of the line before which the insertion was done; vice versa for INSERTED and LINE1. */ GnuDiff::change *GnuDiff::add_change(GNULineRef line0, GNULineRef line1, GNULineRef deleted, GNULineRef inserted, change *old) { change *newChange = (change *)xmalloc(sizeof(*newChange)); newChange->line0 = line0; newChange->line1 = line1; newChange->inserted = inserted; newChange->deleted = deleted; newChange->link = old; return newChange; } /* Scan the tables of which lines are inserted and deleted, producing an edit script in reverse order. */ GnuDiff::change *GnuDiff::build_reverse_script(file_data const filevec[]) { change *script = nullptr; bool *changed0 = filevec[0].changed; bool *changed1 = filevec[1].changed; GNULineRef len0 = filevec[0].buffered_lines; GNULineRef len1 = filevec[1].buffered_lines; /* Note that changedN[len0] does exist, and is 0. */ GNULineRef i0 = 0, i1 = 0; while(i0 < len0 || i1 < len1) { if(changed0[i0] | changed1[i1]) { GNULineRef line0 = i0, line1 = i1; /* Find # lines changed here in each file. */ while(changed0[i0]) ++i0; while(changed1[i1]) ++i1; /* Record this change. */ script = add_change(line0, line1, i0 - line0, i1 - line1, script); } /* We have reached lines in the two files that match each other. */ i0++, i1++; } return script; } /* Scan the tables of which lines are inserted and deleted, producing an edit script in forward order. */ GnuDiff::change *GnuDiff::build_script(file_data const filevec[]) { change *script = nullptr; bool *changed0 = filevec[0].changed; bool *changed1 = filevec[1].changed; GNULineRef i0 = filevec[0].buffered_lines, i1 = filevec[1].buffered_lines; /* Note that changedN[-1] does exist, and is 0. */ while(i0 >= 0 || i1 >= 0) { if(changed0[i0 - 1] | changed1[i1 - 1]) { GNULineRef line0 = i0, line1 = i1; /* Find # lines changed here in each file. */ while(changed0[i0 - 1]) --i0; while(changed1[i1 - 1]) --i1; /* Record this change. */ script = add_change(i0, i1, line0 - i0, line1 - i1, script); } /* We have reached lines in the two files that match each other. */ i0--, i1--; } return script; } /* Report the differences of two files. */ GnuDiff::change *GnuDiff::diff_2_files(comparison *cmp) { GNULineRef diags; int f; change *script; read_files(cmp->file, files_can_be_treated_as_binary); { /* Allocate vectors for the results of comparison: a flag for each line of each file, saying whether that line is an insertion or deletion. Allocate an extra element, always 0, at each end of each vector. */ size_t s = cmp->file[0].buffered_lines + cmp->file[1].buffered_lines + 4; bool *flag_space = (bool *)zalloc(s * sizeof(*flag_space)); cmp->file[0].changed = flag_space + 1; cmp->file[1].changed = flag_space + cmp->file[0].buffered_lines + 3; /* Some lines are obviously insertions or deletions because they don't match anything. Detect them now, and avoid even thinking about them in the main comparison algorithm. */ discard_confusing_lines(cmp->file); /* Now do the main comparison algorithm, considering just the undiscarded lines. */ xvec = cmp->file[0].undiscarded; yvec = cmp->file[1].undiscarded; diags = (cmp->file[0].nondiscarded_lines + cmp->file[1].nondiscarded_lines + 3); fdiag = (GNULineRef *)xmalloc(diags * (2 * sizeof(*fdiag))); bdiag = fdiag + diags; fdiag += cmp->file[1].nondiscarded_lines + 1; bdiag += cmp->file[1].nondiscarded_lines + 1; /* Set TOO_EXPENSIVE to be approximate square root of input size, bounded below by 256. */ too_expensive = 1; for(; diags != 0; diags >>= 2) too_expensive <<= 1; too_expensive = std::max((GNULineRef)256, too_expensive); files[0] = cmp->file[0]; files[1] = cmp->file[1]; compareseq(0, cmp->file[0].nondiscarded_lines, 0, cmp->file[1].nondiscarded_lines, minimal); free(fdiag - (cmp->file[1].nondiscarded_lines + 1)); /* Modify the results slightly to make them prettier in cases where that can validly be done. */ shift_boundaries(cmp->file); /* Get the results of comparison in the form of a chain of `change's -- an edit script. */ script = build_script(cmp->file); free(cmp->file[0].undiscarded); free(flag_space); for(f = 0; f < 2; ++f) { free(cmp->file[f].equivs); free(cmp->file[f].linbuf + cmp->file[f].linbuf_base); } } return script; } diff --git a/src/gnudiff_diff.h b/src/gnudiff_diff.h index 4b13ff9..1e9429c 100644 --- a/src/gnudiff_diff.h +++ b/src/gnudiff_diff.h @@ -1,229 +1,219 @@ -/* Shared definitions for GNU DIFF - - Modified for KDiff3 by Joachim Eibl 2003, 2004, 2005. - The original file was part of GNU DIFF. - - Copyright (C) 1988, 1989, 1991, 1992, 1993, 1994, 1995, 1998, 2001, - 2002 Free Software Foundation, Inc. - - GNU DIFF is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. - - GNU DIFF 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; see the file COPYING. - If not, write to the Free Software Foundation, - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* + Shared definitions for GNU DIFF + Modified for KDiff3 by Joachim Eibl 2003, 2004, 2005. + The original file was part of GNU DIFF. + + Part of KDiff3 - Text Diff And Merge Tool + + SPDX-FileCopyrightText: 1988-2002 Free Software Foundation, Inc. + SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef GNUDIFF_DIFF_H #define GNUDIFF_DIFF_H #include "LineRef.h" #include "Utils.h" #include #include #include #include #include #include #include #include #include #include #include /* The integer type of a line number. */ typedef qint64 GNULineRef; #define GNULINEREF_MAX std::numeric_limits::max() static_assert(std::is_signed::value, "GNULineRef must be signed."); static_assert(sizeof(GNULineRef) >= sizeof(size_t), "GNULineRef must be able to receive size_t values."); class GnuDiff { public: /* Variables for command line options */ /* Nonzero if output cannot be generated for identical files. */ bool no_diff_means_no_output; /* Number of lines of context to show in each set of diffs. This is zero when context is not to be shown. */ GNULineRef context; /* The significance of white space during comparisons. */ enum { /* All white space is significant (the default). */ IGNORE_NO_WHITE_SPACE, /* Ignore changes due to tab expansion (-E). */ IGNORE_TAB_EXPANSION, /* Ignore changes in horizontal white space (-b). */ IGNORE_SPACE_CHANGE, /* Ignore all horizontal white space (-w). */ IGNORE_ALL_SPACE } ignore_white_space; /* Ignore changes that affect only numbers. (J. Eibl) */ bool bIgnoreNumbers; bool bIgnoreWhiteSpace; /* Files can be compared byte-by-byte, as if they were binary. This depends on various options. */ bool files_can_be_treated_as_binary; /* Ignore differences in case of letters (-i). */ bool ignore_case; /* Use heuristics for better speed with large files with a small density of changes. */ bool speed_large_files; /* Don't discard lines. This makes things slower (sometimes much slower) but will find a guaranteed minimal set of changes. */ bool minimal; /* The result of comparison is an "edit script": a chain of `struct change'. Each `struct change' represents one place where some lines are deleted and some are inserted. LINE0 and LINE1 are the first affected lines in the two files (origin 0). DELETED is the number of lines deleted here from file 0. INSERTED is the number of lines inserted here in file 1. If DELETED is 0 then LINE0 is the number of the line before which the insertion was done; vice versa for INSERTED and LINE1. */ struct change { change *link; /* Previous or next edit command */ GNULineRef inserted; /* # lines of file 1 changed here. */ GNULineRef deleted; /* # lines of file 0 changed here. */ GNULineRef line0; /* Line number of 1st deleted line. */ GNULineRef line1; /* Line number of 1st inserted line. */ bool ignore; /* Flag used in context.c. */ }; /* Structures that describe the input files. */ /* Data on one input file being compared. */ struct file_data { /* Buffer in which text of file is read. */ const QChar *buffer; /* Allocated size of buffer, in QChars. Always a multiple of sizeof(*buffer). */ size_t bufsize; /* Number of valid bytes now in the buffer. */ size_t buffered; /* Array of pointers to lines in the file. */ const QChar **linbuf; /* linbuf_base <= buffered_lines <= valid_lines <= alloc_lines. linebuf[linbuf_base ... buffered_lines - 1] are possibly differing. linebuf[linbuf_base ... valid_lines - 1] contain valid data. linebuf[linbuf_base ... alloc_lines - 1] are allocated. */ GNULineRef linbuf_base, buffered_lines, valid_lines, alloc_lines; /* Pointer to end of prefix of this file to ignore when hashing. */ const QChar *prefix_end; /* Count of lines in the prefix. There are this many lines in the file before linbuf[0]. */ GNULineRef prefix_lines; /* Pointer to start of suffix of this file to ignore when hashing. */ const QChar *suffix_begin; /* Vector, indexed by line number, containing an equivalence code for each line. It is this vector that is actually compared with that of another file to generate differences. */ GNULineRef *equivs; /* Vector, like the previous one except that the elements for discarded lines have been squeezed out. */ GNULineRef *undiscarded; /* Vector mapping virtual line numbers (not counting discarded lines) to real ones (counting those lines). Both are origin-0. */ GNULineRef *realindexes; /* Total number of nondiscarded lines. */ GNULineRef nondiscarded_lines; /* Vector, indexed by real origin-0 line number, containing TRUE for a line that is an insertion or a deletion. The results of comparison are stored here. */ bool *changed; /* 1 if at end of file. */ bool eof; /* 1 more than the maximum equivalence value used for this or its sibling file. */ GNULineRef equiv_max; }; /* Data on two input files being compared. */ struct comparison { file_data file[2]; comparison const *parent; /* parent, if a recursive comparison */ }; /* Describe the two files currently being compared. */ file_data files[2]; /* Declare various functions. */ /* analyze.c */ change *diff_2_files(comparison *); /* io.c */ bool read_files(file_data[], bool); /* util.c */ bool lines_differ(const QChar *, size_t, const QChar *, size_t); void *zalloc(size_t); private: // gnudiff_analyze.cpp GNULineRef diag(GNULineRef xoff, GNULineRef xlim, GNULineRef yoff, GNULineRef ylim, bool find_minimal, struct partition *part) const; void compareseq(GNULineRef xoff, GNULineRef xlim, GNULineRef yoff, GNULineRef ylim, bool find_minimal); void discard_confusing_lines(file_data filevec[]); void shift_boundaries(file_data filevec[]); change *add_change(GNULineRef line0, GNULineRef line1, GNULineRef deleted, GNULineRef inserted, change *old); change *build_reverse_script(file_data const filevec[]); change *build_script(file_data const filevec[]); // gnudiff_io.cpp GNULineRef guess_lines(GNULineRef n, size_t s, size_t t); void find_and_hash_each_line(file_data *current); void find_identical_ends(file_data filevec[]); // gnudiff_xmalloc.cpp void *xmalloc(size_t n); void *xrealloc(void *p, size_t n); void xalloc_die(); inline bool isWhite(QChar c) { return c == ' ' || c == '\t' || c == '\r'; } }; // class GnuDiff #endif diff --git a/src/gnudiff_io.cpp b/src/gnudiff_io.cpp index f19e3ad..cd26156 100644 --- a/src/gnudiff_io.cpp +++ b/src/gnudiff_io.cpp @@ -1,545 +1,535 @@ /* File I/O for GNU DIFF. - Modified for KDiff3 by Joachim Eibl 2003, 2004, 2005. - The original file was part of GNU DIFF. - - Copyright (C) 1988, 1989, 1992, 1993, 1994, 1995, 1998, 2001, 2002 - Free Software Foundation, Inc. - - GNU DIFF is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. - - GNU DIFF 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; see the file COPYING. - If not, write to the Free Software Foundation, - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + Modified for KDiff3 by Joachim Eibl 2003, 2004, 2005. + The original file was part of GNU DIFF. + + Part of KDiff3 - Text Diff And Merge Tool + + SPDX-FileCopyrightText: 1988-2002 Free Software Foundation, Inc. + SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "gnudiff_diff.h" #include #include /* Rotate an unsigned value to the left. */ #define ROL(v, n) ((v) << (n) | (v) >> (sizeof(v) * CHAR_BIT - (n))) /* Given a hash value and a new character, return a new hash value. */ #define HASH(h, c) ((c) + ROL(h, 7)) /* The type of a hash value. */ typedef size_t hash_value; static_assert(std::is_unsigned::value, "hash_value must be signed."); /* Lines are put into equivalence classes of lines that match in lines_differ. Each equivalence class is represented by one of these structures, but only while the classes are being computed. Afterward, each class is represented by a number. */ struct equivclass { GNULineRef next; /* Next item in this bucket. */ hash_value hash; /* Hash of lines in this class. */ const QChar *line; /* A line that fits this class. */ size_t length; /* That line's length, not counting its newline. */ }; /* Hash-table: array of buckets, each being a chain of equivalence classes. buckets[-1] is reserved for incomplete lines. */ static GNULineRef *buckets; /* Number of buckets in the hash table array, not counting buckets[-1]. */ static size_t nbuckets; /* Array in which the equivalence classes are allocated. The bucket-chains go through the elements in this array. The number of an equivalence class is its index in this array. */ static equivclass *equivs; /* Index of first free element in the array `equivs'. */ static GNULineRef equivs_index; /* Number of elements allocated in the array `equivs'. */ static GNULineRef equivs_alloc; /* Check for binary files and compare them for exact identity. */ /* Return 1 if BUF contains a non text character. SIZE is the number of characters in BUF. */ #define binary_file_p(buf, size) (memchr(buf, 0, size) != 0) /* Compare two lines (typically one from each input file) according to the command line options. For efficiency, this is invoked only when the lines do not match exactly but an option like -i might cause us to ignore the difference. Return nonzero if the lines differ. */ bool GnuDiff::lines_differ(const QChar *s1, size_t len1, const QChar *s2, size_t len2) { const QChar *t1 = s1; const QChar *t2 = s2; const QChar *s1end = s1 + len1; const QChar *s2end = s2 + len2; for(;; ++t1, ++t2) { /* Test for exact char equality first, since it's a common case. */ if(t1 != s1end && t2 != s2end && *t1 == *t2) continue; else { while(t1 != s1end && ((bIgnoreWhiteSpace && isWhite(*t1)) || (bIgnoreNumbers && (t1->isDigit() || *t1 == '-' || *t1 == '.')))) { ++t1; } while(t2 != s2end && ((bIgnoreWhiteSpace && isWhite(*t2)) || (bIgnoreNumbers && (t2->isDigit() || *t2 == '-' || *t2 == '.')))) { ++t2; } if(t1 != s1end && t2 != s2end) { if(ignore_case) { /* Lowercase comparison. */ if(t1->toLower() == t2->toLower()) continue; } else if(*t1 == *t2) continue; else return true; } else if(t1 == s1end && t2 == s2end) return false; else return true; } } return false; } /* Split the file into lines, simultaneously computing the equivalence class for each line. */ void GnuDiff::find_and_hash_each_line(file_data *current) { hash_value h; const QChar *p = current->prefix_end; QChar c; GNULineRef i, *bucket; size_t length; /* Cache often-used quantities in local variables to help the compiler. */ const QChar **linbuf = current->linbuf; GNULineRef alloc_lines = current->alloc_lines; GNULineRef line = 0; GNULineRef linbuf_base = current->linbuf_base; GNULineRef *cureqs = (GNULineRef *)xmalloc(alloc_lines * sizeof(*cureqs)); equivclass *eqs = equivs; GNULineRef eqs_index = equivs_index; GNULineRef eqs_alloc = equivs_alloc; const QChar *suffix_begin = current->suffix_begin; const QChar *bufend = current->buffer + current->buffered; bool diff_length_compare_anyway = ignore_white_space != IGNORE_NO_WHITE_SPACE || bIgnoreNumbers; bool same_length_diff_contents_compare_anyway = diff_length_compare_anyway | ignore_case; while(p < suffix_begin) { const QChar *ip = p; h = 0; /* Hash this line until we find a newline or bufend is reached. */ if(ignore_case) switch(ignore_white_space) { case IGNORE_ALL_SPACE: while(p < bufend && !Utils::isEndOfLine(c = *p)) { if(!(isWhite(c) || (bIgnoreNumbers && (c.isDigit() || c == '-' || c == '.')))) h = HASH(h, c.toLower().unicode()); ++p; } break; default: while(p < bufend && !Utils::isEndOfLine(c = *p)) { h = HASH(h, c.toLower().unicode()); ++p; } break; } else switch(ignore_white_space) { case IGNORE_ALL_SPACE: while(p < bufend && !Utils::isEndOfLine(c = *p)) { if(!(isWhite(c) || (bIgnoreNumbers && (c.isDigit() || c == '-' || c == '.')))) h = HASH(h, c.unicode()); ++p; } break; default: while(p < bufend && !Utils::isEndOfLine(c = *p)) { h = HASH(h, c.unicode()); ++p; } break; } bucket = &buckets[h % nbuckets]; length = p - ip; ++p; for(i = *bucket;; i = eqs[i].next) if(!i) { /* Create a new equivalence class in this bucket. */ i = eqs_index++; if(i == eqs_alloc) { if((GNULineRef)(GNULINEREF_MAX / (2 * sizeof(*eqs))) <= eqs_alloc) xalloc_die(); eqs_alloc *= 2; eqs = (equivclass *)xrealloc(eqs, eqs_alloc * sizeof(*eqs)); } eqs[i].next = *bucket; eqs[i].hash = h; eqs[i].line = ip; eqs[i].length = length; *bucket = i; break; } else if(eqs[i].hash == h) { const QChar *eqline = eqs[i].line; /* Reuse existing class if lines_differ reports the lines equal. */ if(eqs[i].length == length) { /* Reuse existing equivalence class if the lines are identical. This detects the common case of exact identity faster than lines_differ would. */ if(memcmp(eqline, ip, length * sizeof(QChar)) == 0) break; if(!same_length_diff_contents_compare_anyway) continue; } else if(!diff_length_compare_anyway) continue; if(!lines_differ(eqline, eqs[i].length, ip, length)) break; } /* Maybe increase the size of the line table. */ if(line == alloc_lines) { /* Double (alloc_lines - linbuf_base) by adding to alloc_lines. */ if((GNULineRef)(GNULINEREF_MAX / 3) <= alloc_lines || (GNULineRef)(GNULINEREF_MAX / sizeof(*cureqs)) <= 2 * alloc_lines - linbuf_base || (GNULineRef)(GNULINEREF_MAX / sizeof(ptrdiff_t)) <= alloc_lines - linbuf_base) xalloc_die(); alloc_lines = 2 * alloc_lines - linbuf_base; cureqs = (GNULineRef *)xrealloc(cureqs, alloc_lines * sizeof(*cureqs)); linbuf += linbuf_base; linbuf = (const QChar **)xrealloc(linbuf, (alloc_lines - linbuf_base) * sizeof(ptrdiff_t)); linbuf -= linbuf_base; } linbuf[line] = ip; cureqs[line] = i; ++line; } current->buffered_lines = line; for(i = 0;; ++i) { /* Record the line start for lines in the suffix that we care about. Record one more line start than lines, so that we can compute the length of any buffered line. */ if(line == alloc_lines) { /* Double (alloc_lines - linbuf_base) by adding to alloc_lines. */ if((GNULineRef)(GNULINEREF_MAX / 3) <= alloc_lines || (GNULineRef)(GNULINEREF_MAX / sizeof(*cureqs)) <= 2 * alloc_lines - linbuf_base || (GNULineRef)(GNULINEREF_MAX / sizeof(ptrdiff_t)) <= alloc_lines - linbuf_base) xalloc_die(); alloc_lines = 2 * alloc_lines - linbuf_base; linbuf += linbuf_base; linbuf = (const QChar **)xrealloc(linbuf, (alloc_lines - linbuf_base) * sizeof(ptrdiff_t)); linbuf -= linbuf_base; } linbuf[line] = p; if(p >= bufend) break; if(context <= i && no_diff_means_no_output) break; line++; while(p < bufend && !Utils::isEndOfLine(*p++)) continue; } /* Done with cache in local variables. */ current->linbuf = linbuf; current->valid_lines = line; current->alloc_lines = alloc_lines; current->equivs = cureqs; equivs = eqs; equivs_alloc = eqs_alloc; equivs_index = eqs_index; } /* We have found N lines in a buffer of size S; guess the proportionate number of lines that will be found in a buffer of size T. However, do not guess a number of lines so large that the resulting line table might cause overflow in size calculations. */ GNULineRef GnuDiff::guess_lines(GNULineRef n, size_t s, size_t t) { size_t guessed_bytes_per_line = n < 10 ? 32 : s / (n - 1); size_t guessed_lines = std::max((size_t)1, t / guessed_bytes_per_line); return (GNULineRef)std::min((GNULineRef)guessed_lines, (GNULineRef)(GNULINEREF_MAX / (2 * sizeof(ptrdiff_t) + 1) - 5)) + 5; } /* Given a vector of two file_data objects, find the identical prefixes and suffixes of each object. */ void GnuDiff::find_identical_ends(file_data filevec[]) { /* Find identical prefix. */ const QChar *p0, *p1, *buffer0, *buffer1; p0 = buffer0 = filevec[0].buffer; p1 = buffer1 = filevec[1].buffer; size_t n0, n1; n0 = filevec[0].buffered; n1 = filevec[1].buffered; const QChar *const pEnd0 = p0 + n0; const QChar *const pEnd1 = p1 + n1; if(p0 == p1) /* The buffers are the same; sentinels won't work. */ p0 = p1 += n1; else { /* Loop until first mismatch, or end. */ while(p0 != pEnd0 && p1 != pEnd1 && *p0 == *p1) { p0++; p1++; } } /* Now P0 and P1 point at the first nonmatching characters. */ /* Skip back to last line-beginning in the prefix. */ while(p0 != buffer0 && !Utils::isEndOfLine(p0[-1])) p0--, p1--; /* Record the prefix. */ filevec[0].prefix_end = p0; filevec[1].prefix_end = p1; /* Find identical suffix. */ /* P0 and P1 point beyond the last chars not yet compared. */ p0 = buffer0 + n0; p1 = buffer1 + n1; const QChar *end0, *beg0; end0 = p0; /* Addr of last char in file 0. */ /* Get value of P0 at which we should stop scanning backward: this is when either P0 or P1 points just past the last char of the identical prefix. */ beg0 = filevec[0].prefix_end + (n0 < n1 ? 0 : n0 - n1); /* Scan back until chars don't match or we reach that point. */ for(; p0 != beg0; p0--, p1--) { if(*p0 != *p1) { /* Point at the first char of the matching suffix. */ beg0 = p0; break; } } // Go to the next line (skip last line with a difference) if(p0 != end0) { if(*p0 != *p1) ++p0; while(p0 < pEnd0 && !Utils::isEndOfLine(*p0++)) continue; } p1 += p0 - beg0; /* Record the suffix. */ filevec[0].suffix_begin = p0; filevec[1].suffix_begin = p1; /* Calculate number of lines of prefix to save. prefix_count == 0 means save the whole prefix; we need this for options like -D that output the whole file, or for enormous contexts (to avoid worrying about arithmetic overflow). We also need it for options like -F that output some preceding line; at least we will need to find the last few lines, but since we don't know how many, it's easiest to find them all. Otherwise, prefix_count != 0. Save just prefix_count lines at start of the line buffer; they'll be moved to the proper location later. Handle 1 more line than the context says (because we count 1 too many), rounded up to the next power of 2 to speed index computation. */ const QChar **linbuf0, **linbuf1; GNULineRef alloc_lines0, alloc_lines1; GNULineRef buffered_prefix, prefix_count, prefix_mask; GNULineRef middle_guess, suffix_guess; if(no_diff_means_no_output && context < (GNULineRef)(GNULINEREF_MAX / 4) && context < (GNULineRef)(n0)) { middle_guess = guess_lines(0, 0, p0 - filevec[0].prefix_end); suffix_guess = guess_lines(0, 0, buffer0 + n0 - p0); for(prefix_count = 1; prefix_count <= context; prefix_count *= 2) continue; alloc_lines0 = (prefix_count + middle_guess + std::min(context, suffix_guess)); } else { prefix_count = 0; alloc_lines0 = guess_lines(0, 0, n0); } prefix_mask = prefix_count - 1; GNULineRef lines = 0; linbuf0 = (const QChar **)xmalloc(alloc_lines0 * sizeof(ptrdiff_t)); p0 = buffer0; /* If the prefix is needed, find the prefix lines. */ if(!(no_diff_means_no_output && filevec[0].prefix_end == p0 && filevec[1].prefix_end == p1)) { end0 = filevec[0].prefix_end; while(p0 != end0) { GNULineRef l = lines++ & prefix_mask; if(l == alloc_lines0) { if((GNULineRef)(GNULINEREF_MAX / (2 * sizeof(ptrdiff_t))) <= alloc_lines0) xalloc_die(); alloc_lines0 *= 2; linbuf0 = (const QChar **)xrealloc(linbuf0, alloc_lines0 * sizeof(ptrdiff_t)); } linbuf0[l] = p0; while(p0 < pEnd0 && !Utils::isEndOfLine(*p0++)) continue; } } buffered_prefix = prefix_count && context < lines ? context : lines; /* Allocate line buffer 1. */ middle_guess = guess_lines(lines, p0 - buffer0, p1 - filevec[1].prefix_end); suffix_guess = guess_lines(lines, p0 - buffer0, buffer1 + n1 - p1); alloc_lines1 = buffered_prefix + middle_guess + std::min(context, suffix_guess); if(alloc_lines1 < buffered_prefix || (GNULineRef)(GNULINEREF_MAX / sizeof(ptrdiff_t)) <= alloc_lines1) xalloc_die(); linbuf1 = (const QChar **)xmalloc(alloc_lines1 * sizeof(ptrdiff_t)); GNULineRef i; if(buffered_prefix != lines) { /* Rotate prefix lines to proper location. */ for(i = 0; i < buffered_prefix; ++i) linbuf1[i] = linbuf0[(lines - context + i) & prefix_mask]; for(i = 0; i < buffered_prefix; ++i) linbuf0[i] = linbuf1[i]; } /* Initialize line buffer 1 from line buffer 0. */ for(i = 0; i < buffered_prefix; ++i) linbuf1[i] = linbuf0[i] - buffer0 + buffer1; /* Record the line buffer, adjusted so that linbuf[0] points at the first differing line. */ filevec[0].linbuf = linbuf0 + buffered_prefix; filevec[1].linbuf = linbuf1 + buffered_prefix; filevec[0].linbuf_base = filevec[1].linbuf_base = -buffered_prefix; filevec[0].alloc_lines = alloc_lines0 - buffered_prefix; filevec[1].alloc_lines = alloc_lines1 - buffered_prefix; filevec[0].prefix_lines = filevec[1].prefix_lines = lines; } /* If 1 < k, then (2**k - prime_offset[k]) is the largest prime less than 2**k. This table is derived from Chris K. Caldwell's list . */ static unsigned char const prime_offset[] = { 0, 0, 1, 1, 3, 1, 3, 1, 5, 3, 3, 9, 3, 1, 3, 19, 15, 1, 5, 1, 3, 9, 3, 15, 3, 39, 5, 39, 57, 3, 35, 1, 5, 9, 41, 31, 5, 25, 45, 7, 87, 21, 11, 57, 17, 55, 21, 115, 59, 81, 27, 129, 47, 111, 33, 55, 5, 13, 27, 55, 93, 1, 57, 25}; /* Verify that this host's size_t is not too wide for the above table. */ static_assert(sizeof(size_t) * CHAR_BIT <= sizeof prime_offset, "Not enough primes in table"); /* Given a vector of two file_data objects, read the file associated with each one, and build the table of equivalence classes. Return nonzero if either file appears to be a binary file. If PRETEND_BINARY is nonzero, pretend they are binary regardless. */ bool GnuDiff::read_files(file_data filevec[], bool /*pretend_binary*/) { GNULineRef i; find_identical_ends(filevec); equivs_alloc = filevec[0].alloc_lines + filevec[1].alloc_lines + 1; if((GNULineRef)(GNULINEREF_MAX / sizeof(*equivs)) <= equivs_alloc) xalloc_die(); equivs = (equivclass *)xmalloc(equivs_alloc * sizeof(*equivs)); /* Equivalence class 0 is permanently safe for lines that were not hashed. Real equivalence classes start at 1. */ equivs_index = 1; /* Allocate (one plus) a prime number of hash buckets. Use a prime number between 1/3 and 2/3 of the value of equiv_allocs, approximately. */ for(i = 9; ((GNULineRef)1 << i) < equivs_alloc / 3; ++i) continue; nbuckets = ((GNULineRef)1 << i) - prime_offset[i]; if(GNULINEREF_MAX / sizeof(*buckets) <= nbuckets) xalloc_die(); buckets = (GNULineRef *)zalloc((nbuckets + 1) * sizeof(*buckets)); buckets++; for(i = 0; i < 2; ++i) find_and_hash_each_line(&filevec[i]); filevec[0].equiv_max = filevec[1].equiv_max = equivs_index; free(equivs); free(buckets - 1); return false; } diff --git a/src/gnudiff_xmalloc.cpp b/src/gnudiff_xmalloc.cpp index cfc0a8e..e437273 100644 --- a/src/gnudiff_xmalloc.cpp +++ b/src/gnudiff_xmalloc.cpp @@ -1,77 +1,70 @@ /* xmalloc.c -- malloc with out of memory checking Modified for KDiff3 by Joachim Eibl 2003. The original file was part of GNU DIFF. - Copyright (C) 1990-1999, 2000, 2002 Free Software Foundation, Inc. - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + Part of KDiff3 - Text Diff And Merge Tool + + SPDX-FileCopyrightText: 1988-2002 Free Software Foundation, Inc. + SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + SPDX-License-Identifier: GPL-2.0-or-later +*/ #include #include #ifndef EXIT_FAILURE #define EXIT_FAILURE 1 #endif #include "gnudiff_diff.h" /* If non NULL, call this function when memory is exhausted. */ void (*xalloc_fail_func)() = nullptr; void GnuDiff::xalloc_die() { if(xalloc_fail_func) (*xalloc_fail_func)(); //error (exit_failure, 0, "%s", _(xalloc_msg_memory_exhausted)); /* The `noreturn' cannot be given to error, since it may return if its first argument is 0. To help compilers understand the xalloc_die does terminate, call exit. */ exit(EXIT_FAILURE); } /* Allocate N bytes of memory dynamically, with error checking. */ void * GnuDiff::xmalloc(size_t n) { void *p; p = malloc(n == 0 ? 1 : n); // There are systems where malloc returns 0 for n==0. if(p == nullptr) xalloc_die(); return p; } /* Change the size of an allocated block of memory P to N bytes, with error checking. */ void * GnuDiff::xrealloc(void *p, size_t n) { p = realloc(p, n == 0 ? 1 : n); if(p == nullptr) xalloc_die(); return p; } /* Yield a new block of SIZE bytes, initialized to zero. */ void * GnuDiff::zalloc(size_t size) { void *p = xmalloc(size); memset(p, 0, size); return p; } diff --git a/src/guiutils.h b/src/guiutils.h index 51e3a10..7a454a7 100644 --- a/src/guiutils.h +++ b/src/guiutils.h @@ -1,226 +1,214 @@ -/** - * Copyright (c) 2008 by Valentin Rusu kde at rusu.info - * Copyright (C) 2018 Michael Reeves - * - * This file is part of KDiff3. - * - * KDiff3 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * KDiff3 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 KDiff3. If not, see . - */ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef GUIUTILS_H #define GUIUTILS_H #include #include namespace GuiUtils { //Use std::enable_if since complires don't disabiguate overloads based on return type alone template inline typename std::enable_if::value, QAction>::type* createAction( const QString& text, const Receiver receiver, const Func slot, KActionCollection* ac, const QString& actionName) { Q_ASSERT(ac != nullptr); QAction* theAction; theAction = ac->addAction(actionName); theAction->setText(text); QObject::connect(theAction, &QAction::triggered, receiver, slot); return theAction; } template inline typename std::enable_if::value, KToggleAction>::type* createAction( const QString& text, const Receiver receiver, const Func slot, KActionCollection* ac, const QString &actionName) { Q_ASSERT( ac != nullptr ); KToggleAction* theAction = new KToggleAction(ac); ac->addAction( actionName, theAction ); theAction->setText( text ); QObject::connect( theAction, &KToggleAction::triggered, receiver, slot ); return theAction; } template T* createAction( const QString& text, const QKeySequence& shortcut, Receiver receiver, Func slot, KActionCollection* ac, const QString &actionName) { T* theAction = createAction( text, receiver, slot, ac, actionName ); ac->setDefaultShortcut(theAction, shortcut); return theAction; } template T* createAction( const QString& text, const QIcon& icon, Receiver receiver, Func slot, KActionCollection* ac, const QString &actionName) { T* theAction = createAction( text, receiver, slot, ac, actionName ); theAction->setIcon( icon ); return theAction; } template T* createAction( const QString& text, const QIcon& icon, const QString& iconText, Receiver receiver, Func slot, KActionCollection* ac, const QString &actionName) { T* theAction = createAction( text, receiver, slot, ac, actionName ); theAction->setIcon( icon ); theAction->setIconText( iconText ); return theAction; } template T* createAction( const QString& text, const QIcon& icon, const QKeySequence& shortcut, Receiver receiver, Func slot, KActionCollection* ac, const QString &actionName) { T* theAction = createAction( text, shortcut, receiver, slot, ac, actionName ); theAction->setIcon( icon ); return theAction; } template T* createAction( const QString& text, const QIcon& icon, const QString& iconText, const QKeySequence& shortcut, Receiver receiver, Func slot, KActionCollection* ac, const QString &actionName) { T* theAction = createAction( text, shortcut, receiver, slot, ac, actionName ); theAction->setIcon( icon ); theAction->setIconText( iconText ); return theAction; } //Allow actions to be created without connecting them immediately. template inline typename std::enable_if::value, QAction>::type* createAction( const QString& text, KActionCollection* ac, const QString& actionName) { Q_ASSERT(ac != nullptr); QAction* theAction; theAction = ac->addAction(actionName); theAction->setText(text); return theAction; } template inline typename std::enable_if::value, KToggleAction>::type* createAction( const QString& text, KActionCollection* ac, const QString &actionName) { Q_ASSERT( ac != nullptr ); KToggleAction* theAction = new KToggleAction(ac); ac->addAction( actionName, theAction ); theAction->setText( text ); return theAction; } template T* createAction( const QString& text, const QKeySequence& shortcut, KActionCollection* ac, const QString &actionName) { T* theAction = createAction( text, ac, actionName ); ac->setDefaultShortcut(theAction, shortcut); return theAction; } template T* createAction( const QString& text, const QIcon& icon, KActionCollection* ac, const QString &actionName) { T* theAction = createAction( text, ac, actionName ); theAction->setIcon( icon ); return theAction; } template T* createAction( const QString& text, const QIcon& icon, const QString& iconText, KActionCollection* ac, const QString &actionName) { T* theAction = createAction( text, ac, actionName ); theAction->setIcon( icon ); theAction->setIconText( iconText ); return theAction; } template T* createAction( const QString& text, const QIcon& icon, const QKeySequence& shortcut, KActionCollection* ac, const QString &actionName) { T* theAction = createAction( text, shortcut, ac, actionName ); theAction->setIcon( icon ); return theAction; } template T* createAction( const QString& text, const QIcon& icon, const QString& iconText, const QKeySequence& shortcut, KActionCollection* ac, const QString &actionName) { T* theAction = createAction( text, shortcut, ac, actionName ); theAction->setIcon( icon ); theAction->setIconText( iconText ); return theAction; } } #endif diff --git a/src/kdiff3.cpp b/src/kdiff3.cpp index 8a3ae3e..83f5b0f 100644 --- a/src/kdiff3.cpp +++ b/src/kdiff3.cpp @@ -1,1048 +1,1045 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ // application specific includes #include "kdiff3.h" #include "directorymergewindow.h" #include "fileaccess.h" #include "guiutils.h" #include "kdiff3_part.h" #include "kdiff3_shell.h" #include "optiondialog.h" #include "progress.h" #include "smalldialogs.h" #include "difftextwindow.h" #include "mergeresultwindow.h" #include "RLPainter.h" #include "Utils.h" #ifndef Q_OS_WIN #include #endif // include files for QT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // include files for KDE #include #include #include #include #include #include #include #include #include #include #define ID_STATUS_MSG 1 #define MAIN_TOOLBAR_NAME QLatin1String("mainToolBar") KActionCollection* KDiff3App::actionCollection() { if(m_pKDiff3Shell == nullptr) return m_pKDiff3Part->actionCollection(); else return m_pKDiff3Shell->actionCollection(); } QStatusBar* KDiff3App::statusBar() { if(m_pKDiff3Shell == nullptr) return nullptr; else return m_pKDiff3Shell->statusBar(); } KToolBar* KDiff3App::toolBar(const QLatin1String toolBarId) { if(m_pKDiff3Shell == nullptr) return nullptr; else return m_pKDiff3Shell->toolBar(toolBarId); } bool KDiff3App::isPart() { return m_pKDiff3Shell == nullptr; } bool KDiff3App::isFileSaved() { return m_bFileSaved; } bool KDiff3App::isDirComparison() { return m_bDirCompare; } KDiff3App::KDiff3App(QWidget* pParent, const QString& name, KDiff3Part* pKDiff3Part) : QSplitter(pParent) //previously KMainWindow { setObjectName(name); m_pKDiff3Part = pKDiff3Part; m_pKDiff3Shell = qobject_cast(pParent); setWindowTitle("KDiff3"); setOpaqueResize(false); // faster resizing setUpdatesEnabled(false); KCrash::initialize(); // set Disabled to same color as enabled to prevent flicker in DirectoryMergeWindow QPalette pal; pal.setBrush(QPalette::Base, pal.brush(QPalette::Active, QPalette::Base)); pal.setColor(QPalette::Text, pal.color(QPalette::Active, QPalette::Text)); setPalette(pal); // Needed before any file operations via FileAccess happen. if(g_pProgressDialog == nullptr) { g_pProgressDialog = new ProgressDialog(this, statusBar()); g_pProgressDialog->setStayHidden(true); } // All default values must be set before calling readOptions(). m_pOptionDialog = new OptionDialog(m_pKDiff3Shell != nullptr, this); connect(m_pOptionDialog, &OptionDialog::applyDone, this, &KDiff3App::slotRefresh); // This is just a convenience variable to make code that accesses options more readable m_pOptions = m_pOptionDialog->getOptions(); m_pOptionDialog->readOptions(KSharedConfig::openConfig()); // Option handling: Only when pParent==0 (no parent) int argCount = KDiff3Shell::getParser()->optionNames().count() + KDiff3Shell::getParser()->positionalArguments().count(); bool hasArgs = !isPart() && argCount > 0; if(hasArgs) { QString s; QString title; if(KDiff3Shell::getParser()->isSet("confighelp")) { s = m_pOptionDialog->calcOptionHelp(); title = i18n("Current Configuration:"); } else { s = m_pOptionDialog->parseOptions(KDiff3Shell::getParser()->values("cs")); title = i18n("Config Option Error:"); } if(!s.isEmpty()) { //KMessageBox::information(0, s,i18n("KDiff3-Usage")); #ifndef Q_OS_WIN if(isatty(fileno(stderr)) != 1) #endif { QPointer pDialog = QPointer(new QDialog(this)); pDialog->setAttribute(Qt::WA_DeleteOnClose); pDialog->setModal(true); pDialog->setWindowTitle(title); QVBoxLayout* pVBoxLayout = new QVBoxLayout(pDialog); QPointer pTextEdit = QPointer(new KTextEdit(pDialog)); pTextEdit->setText(s); pTextEdit->setReadOnly(true); pTextEdit->setWordWrapMode(QTextOption::NoWrap); pVBoxLayout->addWidget(pTextEdit); pDialog->resize(600, 400); pDialog->exec(); } #if !defined(Q_OS_WIN) else { // Launched from a console QTextStream outStream(stdout); outStream << title << "\n"; outStream << s;//newline already appended by parseOptions } #endif exit(1); } } m_sd1->setOptions(m_pOptions); m_sd2->setOptions(m_pOptions); m_sd3->setOptions(m_pOptions); #ifdef ENABLE_AUTO m_bAutoFlag = hasArgs && KDiff3Shell::getParser()->isSet("auto"); #else m_bAutoFlag = false; #endif m_bAutoMode = m_bAutoFlag || m_pOptions->m_bAutoSaveAndQuitOnMergeWithoutConflicts; if(hasArgs) { m_outputFilename = KDiff3Shell::getParser()->value("output"); if(m_outputFilename.isEmpty()) m_outputFilename = KDiff3Shell::getParser()->value("out"); if(!m_outputFilename.isEmpty()) m_outputFilename = FileAccess(m_outputFilename, true).absoluteFilePath(); if(m_bAutoMode && m_outputFilename.isEmpty()) { if(m_bAutoFlag) { QTextStream(stderr) << i18n("Option --auto used, but no output file specified.") << "\n"; } m_bAutoMode = false; } if(m_outputFilename.isEmpty() && KDiff3Shell::getParser()->isSet("merge")) { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } else { m_bDefaultFilename = false; } g_bAutoSolve = !KDiff3Shell::getParser()->isSet("qall"); // Note that this is effective only once. QStringList args = KDiff3Shell::getParser()->positionalArguments(); m_sd1->setFilename(KDiff3Shell::getParser()->value("base")); if(m_sd1->isEmpty()) { if(args.count() > 0) m_sd1->setFilename(args[0]); // args->arg(0) if(args.count() > 1) m_sd2->setFilename(args[1]); if(args.count() > 2) m_sd3->setFilename(args[2]); } else { if(args.count() > 0) m_sd2->setFilename(args[0]); if(args.count() > 1) m_sd3->setFilename(args[1]); } //Set m_bDirCompare flag m_bDirCompare = FileAccess(m_sd1->getFilename()).isDir(); QStringList aliasList = KDiff3Shell::getParser()->values( "fname" ); QStringList::Iterator ali = aliasList.begin(); QString an1 = KDiff3Shell::getParser()->value("L1"); if(!an1.isEmpty()) { m_sd1->setAliasName(an1); } else if(ali != aliasList.end()) { m_sd1->setAliasName(*ali); ++ali; } QString an2 = KDiff3Shell::getParser()->value("L2"); if(!an2.isEmpty()) { m_sd2->setAliasName(an2); } else if(ali != aliasList.end()) { m_sd2->setAliasName(*ali); ++ali; } QString an3 = KDiff3Shell::getParser()->value("L3"); if(!an3.isEmpty()) { m_sd3->setAliasName(an3); } else if(ali != aliasList.end()) { m_sd3->setAliasName(*ali); ++ali; } } else { m_bDefaultFilename = false; g_bAutoSolve = false; } g_pProgressDialog->setStayHidden(m_bAutoMode); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts initActions(actionCollection()); //Warning: Call this before connecting KDiff3App::slotUpdateAvailabilities or calling KXMLGUIClient::setXMLFile MergeResultWindow::initActions(actionCollection()); initStatusBar(); m_pFindDialog = new FindDialog(this); connect(m_pFindDialog, &FindDialog::findNext, this, &KDiff3App::slotEditFindNext); autoAdvance->setChecked(m_pOptions->m_bAutoAdvance); showWhiteSpaceCharacters->setChecked(m_pOptions->m_bShowWhiteSpaceCharacters); showWhiteSpace->setChecked(m_pOptions->m_bShowWhiteSpace); showWhiteSpaceCharacters->setEnabled(m_pOptions->m_bShowWhiteSpace); showLineNumbers->setChecked(m_pOptions->m_bShowLineNumbers); wordWrap->setChecked(m_pOptions->m_bWordWrap); if(!isPart()) { viewStatusBar->setChecked(m_pOptions->isStatusBarVisable()); slotViewStatusBar(); KToolBar *mainToolBar = toolBar(MAIN_TOOLBAR_NAME); if(mainToolBar != nullptr){ mainToolBar->mainWindow()->addToolBar(m_pOptions->getToolbarPos(), mainToolBar); } // TODO restore window size/pos? /* QSize size = m_pOptions->m_geometry; QPoint pos = m_pOptions->m_position; if(!size.isEmpty()) { m_pKDiff3Shell->resize( size ); QRect visibleRect = QRect( pos, size ) & QApplication::desktop()->rect(); if ( visibleRect.width()>100 && visibleRect.height()>100 ) m_pKDiff3Shell->move( pos ); }*/ } slotRefresh(); m_pMainSplitter = this; m_pMainSplitter->setOrientation(Qt::Vertical); // setCentralWidget( m_pMainSplitter ); m_pDirectoryMergeSplitter = new QSplitter(m_pMainSplitter); m_pDirectoryMergeSplitter->setObjectName("DirectoryMergeSplitter"); m_pMainSplitter->addWidget(m_pDirectoryMergeSplitter); m_pDirectoryMergeSplitter->setOrientation(Qt::Horizontal); m_pDirectoryMergeWindow = new DirectoryMergeWindow(m_pDirectoryMergeSplitter, m_pOptions); m_pDirectoryMergeSplitter->addWidget(m_pDirectoryMergeWindow); m_pDirectoryMergeInfo = new DirectoryMergeInfo(m_pDirectoryMergeSplitter); m_pDirectoryMergeWindow->setDirectoryMergeInfo(m_pDirectoryMergeInfo); m_pDirectoryMergeSplitter->addWidget(m_pDirectoryMergeInfo); //Warning: Make sure DirectoryMergeWindow::initActions is called before this point or we can crash when selectionChanged is sent. connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::startDiffMerge, this, &KDiff3App::slotFileOpen2); connect(m_pDirectoryMergeWindow->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow->selectionModel(), &QItemSelectionModel::currentChanged, this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::checkIfCanContinue, this, &KDiff3App::slotCheckIfCanContinue); connect(m_pDirectoryMergeWindow, static_cast(&DirectoryMergeWindow::updateAvailabilities), this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::statusBarMessage, this, &KDiff3App::slotStatusMsg); connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &KDiff3App::slotClipboardChanged); connect(this, &KDiff3App::sigRecalcWordWrap, this, &KDiff3App::slotRecalcWordWrap, Qt::QueuedConnection); connect(this, &KDiff3App::checkIfCanContinue, this, &KDiff3App::slotCheckIfCanContinue); connect(this, &KDiff3App::finishDrop, this, &KDiff3App::slotFinishDrop); m_pDirectoryMergeWindow->initDirectoryMergeActions(this, actionCollection()); delete KDiff3Shell::getParser(); if(m_pKDiff3Shell == nullptr) { completeInit(QString()); } } void KDiff3App::completeInit(const QString& fn1, const QString& fn2, const QString& fn3) { if(m_pKDiff3Shell != nullptr) { QSize size = m_pOptions->getGeometry(); QPoint pos = m_pOptions->getPosition(); if(!size.isEmpty()) { m_pKDiff3Shell->resize(size); QRect visibleRect = QRect(pos, size) & QApplication::desktop()->rect(); if(visibleRect.width() > 100 && visibleRect.height() > 100) m_pKDiff3Shell->move(pos); if(!m_bAutoMode) { //Here we want the extra setup showMaximized does since the window has not be shown before if(m_pOptions->isMaximised()) m_pKDiff3Shell->showMaximized();// krazy:exclude=qmethods else m_pKDiff3Shell->show(); } } } if(!fn1.isEmpty()) { m_sd1->setFilename(fn1); } if(!fn2.isEmpty()) { m_sd2->setFilename(fn2); } if(!fn3.isEmpty()) { m_sd3->setFilename(fn3); } //should not happen now. Q_ASSERT(m_bDirCompare == FileAccess(m_sd1->getFilename()).isDir()); bool bSuccess = improveFilenames(false); if(m_bAutoFlag && m_bAutoMode && m_bDirCompare) { QTextStream(stderr) << i18n("Option --auto ignored for folder comparison.") << "\n"; m_bAutoMode = false; } if(!m_bDirCompare) { m_pDirectoryMergeSplitter->hide(); mainInit(); if(m_bAutoMode) { QSharedPointer pSD = nullptr; if(m_sd3->isEmpty()) { if(m_totalDiffStatus.isBinaryEqualAB()) { pSD = m_sd1; } } else { if(m_totalDiffStatus.isBinaryEqualBC() || m_totalDiffStatus.isBinaryEqualAB()) { //if B==C (assume A is old), if A==B then C has changed pSD = m_sd3; } else if(m_totalDiffStatus.isBinaryEqualAC()) { pSD = m_sd2; // assuming B has changed } } if(pSD != nullptr) { // Save this file directly, not via the merge result window. FileAccess fa(m_outputFilename); if(m_pOptions->m_bDmCreateBakFiles && fa.exists()) { fa.createBackup(".orig"); } bSuccess = pSD->saveNormalDataAs(m_outputFilename); if(bSuccess) ::exit(0); else KMessageBox::error(this, i18n("Saving failed.")); } else if(m_pMergeResultWindow->getNrOfUnsolvedConflicts() == 0) { bSuccess = m_pMergeResultWindow->saveDocument(m_pMergeResultWindowTitle->getFileName(), m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) ::exit(0); } } } m_bAutoMode = false; if(m_pKDiff3Shell != nullptr) { if(m_pOptions->isMaximised()) //We want showMaximized here as the window has never been shown. m_pKDiff3Shell->showMaximized();// krazy:exclude=qmethods else m_pKDiff3Shell->show(); } g_pProgressDialog->setStayHidden(false); if(statusBar() != nullptr) statusBar()->setSizeGripEnabled(true); slotClipboardChanged(); // For initialisation. slotUpdateAvailabilities(); if(!m_bDirCompare && m_pKDiff3Shell != nullptr) { bool bFileOpenError = false; if((!m_sd1->isEmpty() && !m_sd1->hasData()) || (!m_sd2->isEmpty() && !m_sd2->hasData()) || (!m_sd3->isEmpty() && !m_sd3->hasData())) { QString text(i18n("Opening of these files failed:")); text += "\n\n"; if(!m_sd1->isEmpty() && !m_sd1->hasData()) text += " - " + m_sd1->getAliasName() + '\n'; if(!m_sd2->isEmpty() && !m_sd2->hasData()) text += " - " + m_sd2->getAliasName() + '\n'; if(!m_sd3->isEmpty() && !m_sd3->hasData()) text += " - " + m_sd3->getAliasName() + '\n'; KMessageBox::sorry(this, text, i18n("File Open Error")); bFileOpenError = true; } if(m_sd1->isEmpty() || m_sd2->isEmpty() || bFileOpenError) slotFileOpen(); } else if(!bSuccess) // Directory open failed { slotFileOpen(); } } KDiff3App::~KDiff3App() { } /** * Helper function used to create actions into the ac collection */ void KDiff3App::initActions(KActionCollection* ac) { if(ac == nullptr){ KMessageBox::error(nullptr, "actionCollection==0"); exit(-1);//we cannot recover from this. } fileOpen = KStandardAction::open(this, &KDiff3App::slotFileOpen, ac); fileOpen->setStatusTip(i18n("Opens documents for comparison...")); fileReload = GuiUtils::createAction(i18n("Reload"), QKeySequence(QKeySequence::Refresh), this, &KDiff3App::slotReload, ac, QLatin1String("file_reload")); fileSave = KStandardAction::save(this, &KDiff3App::slotFileSave, ac); fileSave->setStatusTip(i18n("Saves the merge result. All conflicts must be solved!")); fileSaveAs = KStandardAction::saveAs(this, &KDiff3App::slotFileSaveAs, ac); fileSaveAs->setStatusTip(i18n("Saves the current document as...")); #ifndef QT_NO_PRINTER filePrint = KStandardAction::print(this, &KDiff3App::slotFilePrint, ac); filePrint->setStatusTip(i18n("Print the differences")); #endif fileQuit = KStandardAction::quit(this, &KDiff3App::slotFileQuit, ac); fileQuit->setStatusTip(i18n("Quits the application")); editCut = KStandardAction::cut(this, &KDiff3App::slotEditCut, ac); editCut->setShortcuts(QKeySequence::Cut); editCut->setStatusTip(i18n("Cuts the selected section and puts it to the clipboard")); editCopy = KStandardAction::copy(this, &KDiff3App::slotEditCopy, ac); editCopy->setShortcut(QKeySequence::Copy); editCopy->setStatusTip(i18n("Copies the selected section to the clipboard")); editPaste = KStandardAction::paste(this, &KDiff3App::slotEditPaste, ac); editPaste->setStatusTip(i18n("Pastes the clipboard contents to current position")); editCut->setShortcut(QKeySequence::Paste); editSelectAll = KStandardAction::selectAll(this, &KDiff3App::slotEditSelectAll, ac); editSelectAll->setStatusTip(i18n("Select everything in current window")); editFind = KStandardAction::find(this, &KDiff3App::slotEditFind, ac); editFind->setShortcut(QKeySequence::Find); editFind->setStatusTip(i18n("Search for a string")); editFindNext = KStandardAction::findNext(this, &KDiff3App::slotEditFindNext, ac); editFindNext->setStatusTip(i18n("Search again for the string")); /* FIXME figure out how to implement this action viewToolBar = KStandardAction::showToolbar(this, &KDiff3App::slotViewToolBar, ac); viewToolBar->setStatusTip(i18n("Enables/disables the toolbar")); */ viewStatusBar = KStandardAction::showStatusbar(this, &KDiff3App::slotViewStatusBar, ac); viewStatusBar->setStatusTip(i18n("Enables/disables the statusbar")); KStandardAction::keyBindings(this, &KDiff3App::slotConfigureKeys, ac); QAction* pAction = KStandardAction::preferences(this, &KDiff3App::slotConfigure, ac); if(isPart()) pAction->setText(i18n("Configure KDiff3...")); #include "xpm/autoadvance.xpm" #include "xpm/currentpos.xpm" #include "xpm/down1arrow.xpm" #include "xpm/down2arrow.xpm" #include "xpm/downend.xpm" #include "xpm/iconA.xpm" #include "xpm/iconB.xpm" #include "xpm/iconC.xpm" #include "xpm/nextunsolved.xpm" #include "xpm/prevunsolved.xpm" #include "xpm/showlinenumbers.xpm" #include "xpm/showwhitespace.xpm" #include "xpm/showwhitespacechars.xpm" #include "xpm/up1arrow.xpm" #include "xpm/up2arrow.xpm" #include "xpm/upend.xpm" mGoCurrent = GuiUtils::createAction(i18n("Go to Current Delta"), QIcon(QPixmap(currentpos)), i18n("Current\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Space), this, &KDiff3App::slotGoCurrent, ac, "go_current"); mGoTop = GuiUtils::createAction(i18n("Go to First Delta"), QIcon(QPixmap(upend)), i18n("First\nDelta"), this, &KDiff3App::slotGoTop, ac, "go_top"); mGoBottom = GuiUtils::createAction(i18n("Go to Last Delta"), QIcon(QPixmap(downend)), i18n("Last\nDelta"), this, &KDiff3App::slotGoBottom, ac, "go_bottom"); QString omitsWhitespace = ".\n" + i18n("(Skips white space differences when \"Show White Space\" is disabled.)"); QString includeWhitespace = ".\n" + i18n("(Does not skip white space differences even when \"Show White Space\" is disabled.)"); mGoPrevDelta = GuiUtils::createAction(i18n("Go to Previous Delta"), QIcon(QPixmap(up1arrow)), i18n("Prev\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Up), this, &KDiff3App::slotGoPrevDelta, ac, "go_prev_delta"); mGoPrevDelta->setToolTip(mGoPrevDelta->text() + omitsWhitespace); mGoNextDelta = GuiUtils::createAction(i18n("Go to Next Delta"), QIcon(QPixmap(down1arrow)), i18n("Next\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Down), this, &KDiff3App::slotGoNextDelta, ac, "go_next_delta"); mGoNextDelta->setToolTip(mGoNextDelta->text() + omitsWhitespace); mGoPrevConflict = GuiUtils::createAction(i18n("Go to Previous Conflict"), QIcon(QPixmap(up2arrow)), i18n("Prev\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageUp), this, &KDiff3App::slotGoPrevConflict, ac, "go_prev_conflict"); mGoPrevConflict->setToolTip(mGoPrevConflict->text() + omitsWhitespace); mGoNextConflict = GuiUtils::createAction(i18n("Go to Next Conflict"), QIcon(QPixmap(down2arrow)), i18n("Next\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageDown), this, &KDiff3App::slotGoNextConflict, ac, "go_next_conflict"); mGoNextConflict->setToolTip(mGoNextConflict->text() + omitsWhitespace); mGoPrevUnsolvedConflict = GuiUtils::createAction(i18n("Go to Previous Unsolved Conflict"), QIcon(QPixmap(prevunsolved)), i18n("Prev\nUnsolved"), this, &KDiff3App::slotGoPrevUnsolvedConflict, ac, "go_prev_unsolved_conflict"); mGoPrevUnsolvedConflict->setToolTip(mGoPrevUnsolvedConflict->text() + includeWhitespace); mGoNextUnsolvedConflict = GuiUtils::createAction(i18n("Go to Next Unsolved Conflict"), QIcon(QPixmap(nextunsolved)), i18n("Next\nUnsolved"), this, &KDiff3App::slotGoNextUnsolvedConflict, ac, "go_next_unsolved_conflict"); mGoNextUnsolvedConflict->setToolTip(mGoNextUnsolvedConflict->text() + includeWhitespace); chooseA = GuiUtils::createAction(i18n("Select Line(s) From A"), QIcon(QPixmap(iconA)), i18n("Choose\nA"), QKeySequence(Qt::CTRL + Qt::Key_1), this, &KDiff3App::slotChooseA, ac, "merge_choose_a"); chooseB = GuiUtils::createAction(i18n("Select Line(s) From B"), QIcon(QPixmap(iconB)), i18n("Choose\nB"), QKeySequence(Qt::CTRL + Qt::Key_2), this, &KDiff3App::slotChooseB, ac, "merge_choose_b"); chooseC = GuiUtils::createAction(i18n("Select Line(s) From C"), QIcon(QPixmap(iconC)), i18n("Choose\nC"), QKeySequence(Qt::CTRL + Qt::Key_3), this, &KDiff3App::slotChooseC, ac, "merge_choose_c"); autoAdvance = GuiUtils::createAction(i18n("Automatically Go to Next Unsolved Conflict After Source Selection"), QIcon(QPixmap(autoadvance)), i18n("Auto\nNext"), this, &KDiff3App::slotAutoAdvanceToggled, ac, "merge_autoadvance"); showWhiteSpaceCharacters = GuiUtils::createAction(i18n("Show Space && Tabulator Characters"), QIcon(QPixmap(showwhitespacechars)), i18n("White\nCharacters"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace_characters"); showWhiteSpace = GuiUtils::createAction(i18n("Show White Space"), QIcon(QPixmap(showwhitespace)), i18n("White\nDeltas"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace"); showLineNumbers = GuiUtils::createAction(i18n("Show Line Numbers"), QIcon(QPixmap(showlinenumbers)), i18n("Line\nNumbers"), this, &KDiff3App::slotShowLineNumbersToggled, ac, "diff_showlinenumbers"); mAutoSolve = GuiUtils::createAction(i18n("Automatically Solve Simple Conflicts"), this, &KDiff3App::slotAutoSolve, ac, "merge_autosolve"); mUnsolve = GuiUtils::createAction(i18n("Set Deltas to Conflicts"), this, &KDiff3App::slotUnsolve, ac, "merge_autounsolve"); mergeRegExp = GuiUtils::createAction(i18n("Run Regular Expression Auto Merge"), this, &KDiff3App::slotRegExpAutoMerge, ac, "merge_regexp_automerge"); mMergeHistory = GuiUtils::createAction(i18n("Automatically Solve History Conflicts"), this, &KDiff3App::slotMergeHistory, ac, "merge_versioncontrol_history"); splitDiff = GuiUtils::createAction(i18n("Split Diff At Selection"), this, &KDiff3App::slotSplitDiff, ac, "merge_splitdiff"); joinDiffs = GuiUtils::createAction(i18n("Join Selected Diffs"), this, &KDiff3App::slotJoinDiffs, ac, "merge_joindiffs"); showWindowA = GuiUtils::createAction(i18n("Show Window A"), this, &KDiff3App::slotShowWindowAToggled, ac, "win_show_a"); showWindowB = GuiUtils::createAction(i18n("Show Window B"), this, &KDiff3App::slotShowWindowBToggled, ac, "win_show_b"); showWindowC = GuiUtils::createAction(i18n("Show Window C"), this, &KDiff3App::slotShowWindowCToggled, ac, "win_show_c"); overviewModeNormal = GuiUtils::createAction(i18n("Normal Overview"), this, &KDiff3App::slotOverviewNormal, ac, "diff_overview_normal"); overviewModeAB = GuiUtils::createAction(i18n("A vs. B Overview"), this, &KDiff3App::slotOverviewAB, ac, "diff_overview_ab"); overviewModeAC = GuiUtils::createAction(i18n("A vs. C Overview"), this, &KDiff3App::slotOverviewAC, ac, "diff_overview_ac"); overviewModeBC = GuiUtils::createAction(i18n("B vs. C Overview"), this, &KDiff3App::slotOverviewBC, ac, "diff_overview_bc"); wordWrap = GuiUtils::createAction(i18n("Word Wrap Diff Windows"), this, &KDiff3App::slotWordWrapToggled, ac, "diff_wordwrap"); addManualDiffHelp = GuiUtils::createAction(i18n("Add Manual Diff Alignment"), QKeySequence(Qt::CTRL + Qt::Key_Y), this, &KDiff3App::slotAddManualDiffHelp, ac, "diff_add_manual_diff_help"); clearManualDiffHelpList = GuiUtils::createAction(i18n("Clear All Manual Diff Alignments"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Y), this, &KDiff3App::slotClearManualDiffHelpList, ac, "diff_clear_manual_diff_help_list"); winFocusNext = GuiUtils::createAction(i18n("Focus Next Window"), QKeySequence(Qt::ALT + Qt::Key_Right), this, &KDiff3App::slotWinFocusNext, ac, "win_focus_next"); winFocusPrev = GuiUtils::createAction(i18n("Focus Prev Window"), QKeySequence(Qt::ALT + Qt::Key_Left), this, &KDiff3App::slotWinFocusPrev, ac, "win_focus_prev"); winToggleSplitOrientation = GuiUtils::createAction(i18n("Toggle Split Orientation"), this, &KDiff3App::slotWinToggleSplitterOrientation, ac, "win_toggle_split_orientation"); dirShowBoth = GuiUtils::createAction(i18n("Folder && Text Split Screen View"), this, &KDiff3App::slotDirShowBoth, ac, "win_dir_show_both"); dirShowBoth->setChecked(true); dirViewToggle = GuiUtils::createAction(i18n("Toggle Between Folder && Text View"), this, &KDiff3App::slotDirViewToggle, ac, "win_dir_view_toggle"); m_pMergeEditorPopupMenu = new QMenu(this); /* chooseA->plug( m_pMergeEditorPopupMenu ); chooseB->plug( m_pMergeEditorPopupMenu ); chooseC->plug( m_pMergeEditorPopupMenu );*/ m_pMergeEditorPopupMenu->addAction(chooseA); m_pMergeEditorPopupMenu->addAction(chooseB); m_pMergeEditorPopupMenu->addAction(chooseC); } void KDiff3App::showPopupMenu(const QPoint& point) { m_pMergeEditorPopupMenu->popup(point); } void KDiff3App::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR if(statusBar() != nullptr) statusBar()->showMessage(i18n("Ready.")); } void KDiff3App::saveOptions(KSharedConfigPtr config) { if(!m_bAutoMode) { if(!isPart()) { m_pOptions->setMaximised(m_pKDiff3Shell->isMaximized()); if(!m_pKDiff3Shell->isMaximized() && m_pKDiff3Shell->isVisible()) { m_pOptions->setGeometry(m_pKDiff3Shell->size()); m_pOptions->setPosition(m_pKDiff3Shell->pos()); } /* TODO change this option as now KToolbar uses QToolbar positioning style if ( toolBar(MAIN_TOOLBAR_NAME)!=0 ) m_pOptionDialog->m_toolBarPos = (int) toolBar(MAIN_TOOLBAR_NAME)->allowedAreas();*/ } m_pOptionDialog->saveOptions(std::move(config)); } } bool KDiff3App::queryClose() { saveOptions(KSharedConfig::openConfig()); if(m_bOutputModified) { int result = KMessageBox::warningYesNoCancel(this, i18n("The merge result has not been saved."), i18n("Warning"), KGuiItem(i18n("Save && Quit")), KGuiItem(i18n("Quit Without Saving"))); if(result == KMessageBox::Cancel) return false; else if(result == KMessageBox::Yes) { slotFileSave(); if(m_bOutputModified) { KMessageBox::sorry(this, i18n("Saving the merge result failed."), i18n("Warning")); return false; } } } m_bOutputModified = false; if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a folder merge. Are you sure, you want to abort?"), i18n("Warning"), KStandardGuiItem::quit(), KStandardGuiItem::cont() /* i18n("Continue Merging") */); if(result != KMessageBox::Yes) return false; } return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KDiff3App::slotFileSave() { if(m_bDefaultFilename) { slotFileSaveAs(); } else { slotStatusMsg(i18n("Saving file...")); bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) { m_bFileSaved = true; m_bOutputModified = false; if(m_bDirCompare) m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); } slotStatusMsg(i18n("Ready.")); } } void KDiff3App::slotFileSaveAs() { slotStatusMsg(i18n("Saving file with a new filename...")); QString s = QFileDialog::getSaveFileUrl(this, i18n("Save As..."), QUrl::fromLocalFile(QDir::currentPath())).url(QUrl::PreferLocalFile); if(!s.isEmpty()) { m_outputFilename = s; m_pMergeResultWindowTitle->setFileName(m_outputFilename); bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) { m_bOutputModified = false; if(m_bDirCompare) m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); } //setWindowTitle(url.fileName(),doc->isModified()); m_bDefaultFilename = false; } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFilePrint() { if(m_pDiffTextWindow1 == nullptr || m_pDiffTextWindow2 == nullptr) return; #ifdef QT_NO_PRINTER slotStatusMsg(i18n("Printing not implemented.")); #else QPrinter printer; QPointer printDialog=QPointer(new QPrintDialog(&printer, this)); LineRef firstSelectionD3LIdx; LineRef lastSelectionD3LIdx; m_pDiffTextWindow1->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); if(firstSelectionD3LIdx < 0) { m_pDiffTextWindow2->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindow3->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx >= 0) { printDialog->addEnabledOption(QPrintDialog::PrintSelection); //printer.setOptionEnabled(QPrinter::PrintSelection,true); printDialog->setPrintRange(QAbstractPrintDialog::Selection); } if(firstSelectionD3LIdx == -1) printDialog->setPrintRange(QAbstractPrintDialog::AllPages); //printDialog.setMinMax(0,0); printDialog->setFromTo(0, 0); int currentFirstLine = m_pDiffTextWindow1->getFirstLine(); int currentFirstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(currentFirstLine); // do some printer initialization printer.setFullPage(false); // initialize the printer using the print dialog if(printDialog->exec() == QDialog::Accepted) { slotStatusMsg(i18n("Printing...")); // create a painter to paint on the printer object RLPainter painter(&printer, m_pOptions->m_bRightToLeftLanguage, width(), Utils::getHorizontalAdvance(fontMetrics(),'W')); QPaintDevice* pPaintDevice = painter.device(); int dpiy = pPaintDevice->logicalDpiY(); int columnDistance = qRound((0.5 / 2.54) * dpiy); // 0.5 cm between the columns int columns = m_bTripleDiff ? 3 : 2; int columnWidth = (pPaintDevice->width() - (columns - 1) * columnDistance) / columns; QFont f = m_pOptions->m_font; f.setPointSizeF(f.pointSizeF() - 1); // Print with slightly smaller font. painter.setFont(f); QFontMetrics fm = painter.fontMetrics(); QString topLineText = i18n("Top line"); //int headerWidth = fm.width( m_sd1->getAliasName() + ", "+topLineText+": 01234567" ); int headerLines = Utils::getHorizontalAdvance(fm, m_sd1->getAliasName() + ", " + topLineText + ": 01234567") / columnWidth + 1; int headerMargin = headerLines * fm.height() + 3; // Text + one horizontal line int footerMargin = fm.height() + 3; QRect view(0, headerMargin, pPaintDevice->width(), pPaintDevice->height() - (headerMargin + footerMargin)); QRect view1(0 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); QRect view2(1 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); QRect view3(2 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); int linesPerPage = view.height() / fm.lineSpacing(); m_pEventLoopForPrinting = QPointer(new QEventLoop()); if(m_pOptions->m_bWordWrap) { // For printing the lines are wrapped differently (this invalidates the first line) recalcWordWrap(columnWidth); m_pEventLoopForPrinting->exec(); } LineCount totalNofLines = std::max(m_pDiffTextWindow1->getNofLines(), m_pDiffTextWindow2->getNofLines()); if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr) totalNofLines = std::max(totalNofLines, m_pDiffTextWindow3->getNofLines()); QList pageList; // = printer.pageList(); bool bPrintCurrentPage = false; bool bFirstPrintedPage = false; bool bPrintSelection = false; int totalNofPages = (totalNofLines + linesPerPage - 1) / linesPerPage; LineRef line; LineRef selectionEndLine; if(printer.printRange() == QPrinter::AllPages) { pageList.clear(); for(int i = 0; i < totalNofPages; ++i) { pageList.push_back(i + 1); } } else if(printer.printRange() == QPrinter::PageRange) { pageList.clear(); for(int i = printer.fromPage(); i <= printer.toPage(); ++i) { pageList.push_back(i); } } if(printer.printRange() == QPrinter::Selection) { bPrintSelection = true; if(firstSelectionD3LIdx >= 0) { line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(firstSelectionD3LIdx); selectionEndLine = m_pDiffTextWindow1->convertDiff3LineIdxToLine(lastSelectionD3LIdx + 1); totalNofPages = (selectionEndLine - line + linesPerPage - 1) / linesPerPage; } } int page = 1; ProgressProxy pp; pp.setMaxNofSteps(totalNofPages); QList::iterator pageListIt = pageList.begin(); for(;;) { pp.setInformation(i18n("Printing page %1 of %2", page, totalNofPages), false); pp.setCurrent(page - 1); if(pp.wasCancelled()) { printer.abort(); break; } if(!bPrintSelection) { if(pageListIt == pageList.end()) break; page = *pageListIt; line = (page - 1) * linesPerPage; if(page == 10000) { // This means "Print the current page" bPrintCurrentPage = true; // Detect the first visible line in the window. line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx); } } else { if(line >= selectionEndLine) { break; } else { if(selectionEndLine - line < linesPerPage) linesPerPage = selectionEndLine - line; } } if(line.isValid() && line < totalNofLines) { if(bFirstPrintedPage) printer.newPage(); painter.setClipping(true); painter.setPen(m_pOptions->m_colorA); QString headerText1 = m_sd1->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow1->calcTopLineInFile(line) + 1); m_pDiffTextWindow1->printWindow(painter, view1, headerText1, line, linesPerPage, m_pOptions->m_fgColor); painter.setPen(m_pOptions->m_colorB); QString headerText2 = m_sd2->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow2->calcTopLineInFile(line) + 1); m_pDiffTextWindow2->printWindow(painter, view2, headerText2, line, linesPerPage, m_pOptions->m_fgColor); if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr) { painter.setPen(m_pOptions->m_colorC); QString headerText3 = m_sd3->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow3->calcTopLineInFile(line) + 1); m_pDiffTextWindow3->printWindow(painter, view3, headerText3, line, linesPerPage, m_pOptions->m_fgColor); } painter.setClipping(false); painter.setPen(m_pOptions->m_fgColor); painter.drawLine(0, view.bottom() + 3, view.width(), view.bottom() + 3); QString s = bPrintCurrentPage ? QString("") : QString::number(page) + '/' + QString::number(totalNofPages); if(bPrintSelection) s += i18n(" (Selection)"); painter.drawText((view.right() - Utils::getHorizontalAdvance(painter.fontMetrics(), s)) / 2, view.bottom() + painter.fontMetrics().ascent() + 5, s); bFirstPrintedPage = true; } if(bPrintSelection) { line += linesPerPage; ++page; } else { ++pageListIt; } } painter.end(); if(m_pOptions->m_bWordWrap) { recalcWordWrap(); m_pEventLoopForPrinting->exec(); m_pDiffVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx)); } m_pEventLoopForPrinting.clear(); slotStatusMsg(i18n("Printing completed.")); } else { slotStatusMsg(i18n("Printing aborted.")); } #endif } void KDiff3App::slotFileQuit() { slotStatusMsg(i18n("Exiting...")); if(!queryClose()) return; // Don't quit QApplication::exit(isFileSaved() || isDirComparison() ? 0 : 1); } void KDiff3App::slotViewToolBar() { Q_ASSERT(viewToolBar != nullptr); slotStatusMsg(i18n("Toggling toolbar...")); m_pOptions->setToolbarState(viewToolBar->isChecked()); /////////////////////////////////////////////////////////////////// // turn Toolbar on or off if(toolBar(MAIN_TOOLBAR_NAME) != nullptr) { if(!m_pOptions->isToolBarVisable()) { toolBar(MAIN_TOOLBAR_NAME)->hide(); } else { toolBar(MAIN_TOOLBAR_NAME)->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotViewStatusBar() { slotStatusMsg(i18n("Toggle the statusbar...")); m_pOptions->setStatusBarState(viewStatusBar->isChecked()); /////////////////////////////////////////////////////////////////// //turn Statusbar on or off if(statusBar() != nullptr) { if(!viewStatusBar->isChecked()) { statusBar()->hide(); } else { statusBar()->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotStatusMsg(const QString& text) { /////////////////////////////////////////////////////////////////// // change status message permanently if(statusBar() != nullptr) { statusBar()->clearMessage(); statusBar()->showMessage(text); } } //#include "kdiff3.moc" diff --git a/src/kdiff3.h b/src/kdiff3.h index 10d8da6..940db81 100644 --- a/src/kdiff3.h +++ b/src/kdiff3.h @@ -1,435 +1,432 @@ -/*************************************************************************** - * Copyright (C) 2002-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef KDIFF3_H #define KDIFF3_H #include "diff.h" // include files for Qt #include #include #include #include #include #include // include files for KDE #include #include #include #include #include // forward declaration of the KDiff3 classes class OptionDialog; class FindDialog; //class ManualDiffHelpDialog; class DiffTextWindow; class DiffTextWindowFrame; class MergeResultWindow; class WindowTitleWidget; class Overview; class QStatusBar; class QMenu; class KToggleAction; class KToolBar; class KActionCollection; namespace KParts { class MainWindow; } class KDiff3Part; class DirectoryMergeWindow; class DirectoryMergeInfo; class ReversibleScrollBar : public QScrollBar { Q_OBJECT bool* m_pbRightToLeftLanguage; int m_realVal; public: ReversibleScrollBar(Qt::Orientation o, bool* pbRightToLeftLanguage) : QScrollBar(o) { m_pbRightToLeftLanguage = pbRightToLeftLanguage; m_realVal = 0; connect(this, &ReversibleScrollBar::valueChanged, this, &ReversibleScrollBar::slotValueChanged); } void setAgain() { setValue(m_realVal); } void setValue(int i) { if(m_pbRightToLeftLanguage != nullptr && *m_pbRightToLeftLanguage) QScrollBar::setValue(maximum() - (i - minimum())); else QScrollBar::setValue(i); } int value() const { return m_realVal; } public Q_SLOTS: void slotValueChanged(int i) { m_realVal = i; if(m_pbRightToLeftLanguage != nullptr && *m_pbRightToLeftLanguage) m_realVal = maximum() - (i - minimum()); Q_EMIT valueChanged2(m_realVal); } Q_SIGNALS: void valueChanged2(int); }; class KDiff3App : public QSplitter { Q_OBJECT public: /** constructor of KDiff3App, calls all init functions to create the application. */ KDiff3App(QWidget* parent, const QString& name, KDiff3Part* pKDiff3Part); ~KDiff3App() override; bool isPart(); /** initializes the KActions of the application */ void initActions(KActionCollection*); /** save general Options like all bar positions and status as well as the geometry and the recent file list to the configuration file */ void saveOptions(KSharedConfigPtr); /** read general Options again and initialize all variables like the recent file list */ void readOptions(KSharedConfigPtr); // Finish initialisation (virtual, so that it can be called from the shell too.) virtual void completeInit(const QString& fn1 = QString(), const QString& fn2 = QString(), const QString& fn3 = QString()); /** queryClose is called by KMainWindow on each closeEvent of a window. Against the * default implementation (only returns true), this calles saveModified() on the document object to ask if the document shall * be saved if Modified; on cancel the closeEvent is rejected. * @see KMainWindow#queryClose * @see KMainWindow#closeEvent */ virtual bool queryClose(); virtual bool isFileSaved(); virtual bool isDirComparison(); Q_SIGNALS: void createNewInstance(const QString& fn1, const QString& fn2, const QString& fn3); void sigRecalcWordWrap(); void checkIfCanContinue(bool& pbContinue); void finishDrop(); void showWhiteSpaceToggled(); void showLineNumbersToggled(); void doRefresh(); void autoSolve(); void unsolve(); void mergeHistory(); void regExpAutoMerge(); void goCurrent(); void goTop(); void goBottom(); void goPrevUnsolvedConflict(); void goNextUnsolvedConflict(); void goPrevConflict(); void goNextConflict(); void goPrevDelta(); void goNextDelta(); protected: void setLockPainting(bool bLock); void createCaption(); void initDirectoryMergeActions(); /** sets up the statusbar for the main window by initialzing a statuslabel. */ void initStatusBar(); /** creates the centerwidget of the KMainWindow instance and sets it as the view */ void initView(); public Q_SLOTS: /** open a file and load it into the document*/ void slotFileOpen(); void slotFileOpen2(const QString& fn1, const QString& fn2, const QString& fn3, const QString& ofn, const QString& an1, const QString& an2, const QString& an3, TotalDiffStatus* pTotalDiffStatus); void slotFileNameChanged(const QString& fileName, e_SrcSelector winIdx); /** save a document */ void slotFileSave(); /** save a document by a new filename*/ void slotFileSaveAs(); void slotFilePrint(); /** closes all open windows by calling close() on each memberList item until the list is empty, then quits the application. * If queryClose() returns false because the user canceled the saveModified() dialog, the closing breaks. */ void slotFileQuit(); /** put the marked text/object into the clipboard and remove * it from the document */ void slotEditCut(); /** put the marked text/object into the clipboard */ void slotEditCopy(); /** paste the clipboard into the document */ void slotEditPaste(); /** toggles the toolbar */ void slotViewToolBar(); /** toggles the statusbar */ void slotViewStatusBar(); /** changes the statusbar contents for the standard label permanently, used to indicate current actions. * @param text the text that is displayed in the statusbar */ void slotStatusMsg(const QString& text); void resizeDiffTextWindowHeight(int newHeight); void resizeMergeResultWindow(); void slotRecalcWordWrap(); void postRecalcWordWrap(); void slotFinishRecalcWordWrap(int visibleTextWidth); void showPopupMenu(const QPoint& point); void scrollDiffTextWindow(int deltaX, int deltaY); void scrollMergeResultWindow(int deltaX, int deltaY); void setDiff3Line(int line); void sourceMask(int srcMask, int enabledMask); void slotDirShowBoth(); void slotDirViewToggle(); void slotUpdateAvailabilities(); void slotEditSelectAll(); void slotEditFind(); void slotEditFindNext(); void slotGoCurrent(); void slotGoTop(); void slotGoBottom(); void slotGoPrevUnsolvedConflict(); void slotGoNextUnsolvedConflict(); void slotGoPrevConflict(); void slotGoNextConflict(); void slotGoPrevDelta(); void slotGoNextDelta(); void slotChooseA(); void slotChooseB(); void slotChooseC(); void slotAutoSolve(); void slotUnsolve(); void slotMergeHistory(); void slotRegExpAutoMerge(); void slotConfigure(); void slotConfigureKeys(); void slotRefresh(); void slotSelectionEnd(); void slotSelectionStart(); void slotClipboardChanged(); void slotOutputModified(bool); void slotFinishMainInit(); void slotMergeCurrentFile(); void slotReload(); void slotCheckIfCanContinue(bool& pbContinue); void slotShowWhiteSpaceToggled(); void slotShowLineNumbersToggled(); void slotAutoAdvanceToggled(); void slotWordWrapToggled(); void slotShowWindowAToggled(); void slotShowWindowBToggled(); void slotShowWindowCToggled(); void slotWinFocusNext(); void slotWinFocusPrev(); void slotWinToggleSplitterOrientation(); void slotOverviewNormal(); void slotOverviewAB(); void slotOverviewAC(); void slotOverviewBC(); void slotSplitDiff(); void slotJoinDiffs(); void slotAddManualDiffHelp(); void slotClearManualDiffHelpList(); void slotNoRelevantChangesDetected(); void slotEncodingChanged(QTextCodec*); void slotFinishDrop(); private: /** the configuration object of the application */ //KConfig *config; // QAction pointers to enable/disable actions QAction* fileOpen; QAction* fileSave; QAction* fileSaveAs; QAction* filePrint; QAction* fileQuit; QAction* fileReload; QAction* editCut; QAction* editCopy; QAction* editPaste; QAction* editSelectAll; KToggleAction* viewToolBar = nullptr; KToggleAction* viewStatusBar; //////////////////////////////////////////////////////////////////////// // Special KDiff3 specific stuff starts here QAction* editFind; QAction* editFindNext; QAction* mGoCurrent; QAction* mGoTop; QAction* mGoBottom; QAction* mGoPrevUnsolvedConflict; QAction* mGoNextUnsolvedConflict; QAction* mGoPrevConflict; QAction* mGoNextConflict; QAction* mGoPrevDelta; QAction* mGoNextDelta; KToggleAction* chooseA; KToggleAction* chooseB; KToggleAction* chooseC; KToggleAction* autoAdvance; KToggleAction* wordWrap; QAction* splitDiff; QAction* joinDiffs; QAction* addManualDiffHelp; QAction* clearManualDiffHelpList; KToggleAction* showWhiteSpaceCharacters; KToggleAction* showWhiteSpace; KToggleAction* showLineNumbers; QAction* mAutoSolve; QAction* mUnsolve; QAction* mMergeHistory; QAction* mergeRegExp; KToggleAction* showWindowA; KToggleAction* showWindowB; KToggleAction* showWindowC; QAction* winFocusNext; QAction* winFocusPrev; QAction* winToggleSplitOrientation; KToggleAction* dirShowBoth; QAction* dirViewToggle; KToggleAction* overviewModeNormal; KToggleAction* overviewModeAB; KToggleAction* overviewModeAC; KToggleAction* overviewModeBC; QMenu* m_pMergeEditorPopupMenu; QSplitter* m_pMainSplitter = nullptr; QWidget* m_pMainWidget = nullptr; QWidget* m_pMergeWindowFrame = nullptr; ReversibleScrollBar* m_pHScrollBar = nullptr; QScrollBar* m_pDiffVScrollBar = nullptr; QScrollBar* m_pMergeVScrollBar = nullptr; DiffTextWindow* m_pDiffTextWindow1 = nullptr; DiffTextWindow* m_pDiffTextWindow2 = nullptr; DiffTextWindow* m_pDiffTextWindow3 = nullptr; DiffTextWindowFrame* m_pDiffTextWindowFrame1 = nullptr; DiffTextWindowFrame* m_pDiffTextWindowFrame2 = nullptr; DiffTextWindowFrame* m_pDiffTextWindowFrame3 = nullptr; QSplitter* m_pDiffWindowSplitter = nullptr; MergeResultWindow* m_pMergeResultWindow = nullptr; WindowTitleWidget* m_pMergeResultWindowTitle; bool m_bTripleDiff = false; QSplitter* m_pDirectoryMergeSplitter = nullptr; DirectoryMergeWindow* m_pDirectoryMergeWindow = nullptr; DirectoryMergeInfo* m_pDirectoryMergeInfo; bool m_bDirCompare = false; Overview* m_pOverview = nullptr; QWidget* m_pCornerWidget = nullptr; TotalDiffStatus m_totalDiffStatus; QSharedPointer m_sd1 = QSharedPointer::create(); QSharedPointer m_sd2 = QSharedPointer::create(); QSharedPointer m_sd3 = QSharedPointer::create(); QSharedPointer m_dirinfo; QString m_outputFilename; bool m_bDefaultFilename; DiffList m_diffList12; DiffList m_diffList23; DiffList m_diffList13; QSharedPointer m_diffBufferInfo = QSharedPointer::create(); Diff3LineList m_diff3LineList; Diff3LineVector m_diff3LineVector; //ManualDiffHelpDialog* m_pManualDiffHelpDialog; ManualDiffHelpList m_manualDiffHelpList; int m_neededLines; int m_DTWHeight; bool m_bOutputModified = false; bool m_bFileSaved = false; bool m_bTimerBlock = false; // Synchronization OptionDialog* m_pOptionDialog = nullptr; QSharedPointer m_pOptions = nullptr; FindDialog* m_pFindDialog = nullptr; void mainInit(TotalDiffStatus* pTotalDiffStatus = nullptr, bool bLoadFiles = true, bool bUseCurrentEncoding = false); bool m_bFinishMainInit = false; bool m_bLoadFiles = false; void mainWindowEnable(bool bEnable); virtual void wheelEvent(QWheelEvent* pWheelEvent) override; virtual void keyPressEvent(QKeyEvent* event) override; void resizeEvent(QResizeEvent*) override; bool improveFilenames(bool bCreateNewInstance); bool canContinue(); void choose(e_SrcSelector choice); KActionCollection* actionCollection(); QStatusBar* statusBar(); KToolBar* toolBar(QLatin1String); KDiff3Part* m_pKDiff3Part = nullptr; KParts::MainWindow* m_pKDiff3Shell = nullptr; bool m_bAutoFlag = false; bool m_bAutoMode = false; void recalcWordWrap(int visibleTextWidthForPrinting = -1); bool m_bRecalcWordWrapPosted = false; void setHScrollBarRange(); int m_iCumulativeWheelDelta; int m_firstD3LIdx; // only needed during recalcWordWrap QPointer m_pEventLoopForPrinting; }; #endif // KDIFF3_H diff --git a/src/kdiff3_part.cpp b/src/kdiff3_part.cpp index e85df29..fe3ec6d 100644 --- a/src/kdiff3_part.cpp +++ b/src/kdiff3_part.cpp @@ -1,281 +1,270 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 Joachim Eibl * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "kdiff3_part.h" #include "fileaccess.h" #include "kdiff3.h" #include "Logging.h" #include #include #include #include #include #include #include #include #include KAboutData KDiff3Part::createAboutData() { QString appVersion = QString(KDIFF3_VERSION_STRING); if(sizeof(void*) == 8) appVersion += " (64 bit)"; else if(sizeof(void*) == 4) appVersion += " (32 bit)"; KAboutData aboutData(QLatin1String("kdiff3part"), i18n("KDiff3 Part"), appVersion, i18n("A KPart to display SVG images"), KAboutLicense::GPL_V2, i18n("Copyright 2007, Aurélien Gâteau ")); aboutData.addAuthor(i18n("Joachim Eibl"), QString(), QString("joachim.eibl at gmx.de")); return aboutData; } K_PLUGIN_FACTORY(KDiff3PartFactory, registerPlugin();) //K_EXPORT_PLUGIN( KDiff3PartFactory(createAboutData())) KDiff3Part::KDiff3Part(QWidget* parentWidget, QObject* parent, const QVariantList& args) : KParts::ReadWritePart(parent) { //set AboutData setComponentData(createAboutData()); const QString widgetName = args[0].toString(); // this should be your custom internal widget m_widget = new KDiff3App(parentWidget, widgetName, this); //FIXME: This hack is necessary to avoid a crash when the program terminates. m_bIsShell = qobject_cast(parentWidget) != nullptr; // notify the part that this is our internal widget setWidget(m_widget); // create our actions //KStandardAction::open(this, &KDiff3Part:fileOpen, actionCollection()); //KStandardAction::saveAs(this, &KDiff3Part:fileSaveAs, actionCollection()); //KStandardAction::save(this, &KDiff3Part:save, actionCollection()); setXMLFile("kdiff3_part.rc"); // we are read-write by default setReadWrite(true); // we are not modified since we haven't done anything yet setModified(false); } KDiff3Part::~KDiff3Part() { if(m_widget != nullptr && !m_bIsShell) { m_widget->saveOptions(KSharedConfig::openConfig()); } } void KDiff3Part::setReadWrite(bool /*rw*/) { // ReadWritePart::setReadWrite(rw); } void KDiff3Part::setModified(bool /*modified*/) { /* // get a handle on our Save action and make sure it is valid QAction *save = actionCollection()->action(KStandardAction::stdName(KStandardAction::Save)); if (!save) return; // if so, we either enable or disable it based on the current // state if (modified) save->setEnabled(true); else save->setEnabled(false); // in any event, we want our parent to do it's thing ReadWritePart::setModified(modified); */ } void KDiff3Part::getNameAndVersion(const QString& str, const QString& lineStart, QString& fileName, QString& version) { if(str.left(lineStart.length()) == lineStart && fileName.isEmpty()) { int pos = lineStart.length(); while(pos < str.length() && (str[pos] == ' ' || str[pos] == '\t')) ++pos; int pos2 = str.length() - 1; while(pos2 > pos) { while(pos2 > pos && str[pos2] != ' ' && str[pos2] != '\t') --pos2; fileName = str.mid(pos, pos2 - pos); //fprintf(stderr, "KDiff3: %s\n", fileName.toLatin1().constData()); qCDebug(kdiffMain) << "KDiff3Part::getNameAndVersion: fileName = " << fileName << "\n"; if(FileAccess(fileName).exists()) break; --pos2; } int vpos = str.lastIndexOf("\t", -1); if(vpos > 0 && vpos > pos2) { version = str.mid(vpos + 1); while(!version.right(1)[0].isLetterOrNumber()) version.truncate(version.length() - 1); } } } bool KDiff3Part::openFile() { // m_file is always local so we can use QFile on it //fprintf(stderr, "KDiff3: %s\n", localFilePath().toLatin1().constData()); qCDebug(kdiffMain) << "KDiff3Part::openFile(): localFilePath() == " << localFilePath() << "\n"; QFile file(localFilePath()); if(!file.open(QIODevice::ReadOnly)) return false; //get version and name QTextStream stream(&file); QString str; QString fileName1; QString fileName2; QString version1; QString version2; while(!stream.atEnd() && (fileName1.isEmpty() || fileName2.isEmpty())) { str = stream.readLine() + '\n'; getNameAndVersion(str, "---", fileName1, version1); getNameAndVersion(str, "+++", fileName2, version2); } file.close(); if(fileName1.isEmpty() && fileName2.isEmpty()) { KMessageBox::sorry(m_widget, i18n("Could not find files for comparison.")); return false; } FileAccess f1(fileName1); FileAccess f2(fileName2); if(f1.exists() && f2.exists() && fileName1 != fileName2) { m_widget->slotFileOpen2(fileName1, fileName2, "", "", "", "", "", nullptr); return true; } else if(version1.isEmpty() && f1.exists()) { // Normal patch // patch -f -u --ignore-whitespace -i [inputfile] -o [outfile] [patchfile] QTemporaryFile tmpFile; FileAccess::createTempFile(tmpFile); QString tempFileName = tmpFile.fileName(); QString cmd = "patch -f -u --ignore-whitespace -i \"" + localFilePath() + "\" -o \"" + tempFileName + "\" \"" + fileName1 + "\""; QProcess process; process.start(cmd); process.waitForFinished(-1); m_widget->slotFileOpen2(fileName1, tempFileName, "", "", "", version2.isEmpty() ? fileName2 : "REV:" + version2 + ':' + fileName2, "", nullptr); // alias names // std::cerr << "KDiff3: f1:" << fileName1.toLatin1() <<"<->"<slotFileOpen2(tempFileName, fileName2, "", "", version1.isEmpty() ? fileName1 : "REV:" + version1 + ':' + fileName1, "", "", nullptr); // alias name // std::cerr << "KDiff3: f2:" << fileName2.toLatin1() <<"<->"<" << fileName2 << "\n"; //fprintf(stderr, "KDiff3: f1/2:%s<->%s\n", fileName1.toLatin1().constData(), fileName2.toLatin1().constData()); // FIXME: Why must this be cvs? // Assuming that files are on CVS: Try to get them // cvs update -p -r [REV] [FILE] > [OUTPUTFILE] QTemporaryFile tmpFile1; FileAccess::createTempFile(tmpFile1); QString tempFileName1 = tmpFile1.fileName(); QString cmd1 = "cvs update -p -r " + version1 + " \"" + fileName1 + "\" >\"" + tempFileName1 + "\""; QProcess process1; process1.start(cmd1); process1.waitForFinished(-1); QTemporaryFile tmpFile2; FileAccess::createTempFile(tmpFile2); QString tempFileName2 = tmpFile2.fileName(); QString cmd2 = "cvs update -p -r " + version2 + " \"" + fileName2 + "\" >\"" + tempFileName2 + "\""; QProcess process2; process2.start(cmd2); process2.waitForFinished(-1); m_widget->slotFileOpen2(tempFileName1, tempFileName2, "", "", "REV:" + version1 + ':' + fileName1, "REV:" + version2 + ':' + fileName2, "", nullptr); // std::cerr << "KDiff3: f1/2:" << tempFileName1.toLatin1() <<"<->"<text(); file.close(); return true; */ return false; // Not implemented } #include "kdiff3_part.moc" diff --git a/src/kdiff3_part.h b/src/kdiff3_part.h index 5965d3b..3f13325 100644 --- a/src/kdiff3_part.h +++ b/src/kdiff3_part.h @@ -1,83 +1,71 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef KDIFF3PART_H #define KDIFF3PART_H #include #include #include class QWidget; class KDiff3App; /** * This is a "Part". It that does all the real work in a KPart * application. * * @short Main Part * @author Joachim Eibl */ class KDiff3Part : public KParts::ReadWritePart { Q_OBJECT public: /** * Default constructor */ KDiff3Part(QWidget *parentWidget, QObject *parent, const QVariantList &args ); /** * Destructor */ ~KDiff3Part() override; /** * This is a virtual function inherited from KParts::ReadWritePart. * A shell will use this to inform this Part if it should act * read-only */ void setReadWrite(bool rw) override; /** * Reimplemented to disable and enable Save action */ void setModified(bool modified) override; protected: /** * This must be implemented by each part */ bool openFile() override; /** * This must be implemented by each read-write part */ bool saveFile() override; private: void getNameAndVersion(const QString& str, const QString& lineStart, QString& fileName, QString& version); KAboutData createAboutData(); KDiff3App* m_widget; bool m_bIsShell; }; #endif // _KDIFF3PART_H_ diff --git a/src/kdiff3_shell.cpp b/src/kdiff3_shell.cpp index 198be74..df60da3 100644 --- a/src/kdiff3_shell.cpp +++ b/src/kdiff3_shell.cpp @@ -1,172 +1,160 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "kdiff3_shell.h" #include "kdiff3.h" #include "kdiff3_part.h" #include #include #include #include #include #include #include #include #include #include KDiff3Shell::KDiff3Shell(bool bCompleteInit) { m_bUnderConstruction = true; // set the shell's ui resource file setXMLFile("kdiff3_shell.rc"); // and a status bar statusBar()->show(); /*const QVector plugin_offers = KPluginLoader::findPlugins( "kf5/kdiff3part" ); for( const KPluginMetaData & service: plugin_offers ) { KPluginFactory *factory = KPluginLoader( service.fileName() ).factory(); m_part = factory->create( this, QVariantList() << QVariant( QLatin1String( "KDiff3Part" ) ) ); if( m_part ) break; }*/ m_part = new KDiff3Part(this, this, {QVariant(QLatin1String("KDiff3Part"))}); m_widget = qobject_cast(m_part->widget()); if(m_part) { // and integrate the part's GUI with the shell's createGUI(m_part); //toolBar()->setToolButtonStyle( Qt::ToolButtonIconOnly ); // tell the KParts::MainWindow that this is indeed the main widget setCentralWidget(m_widget); if(bCompleteInit) m_widget->completeInit(QString()); connect(m_widget, &KDiff3App::createNewInstance, this, &KDiff3Shell::slotNewInstance); } else { // if we couldn't find our Part, we exit since the Shell by // itself can't do anything useful KMessageBox::error(this, i18n("Could not initialize the KDiff3 part.\n" "This usually happens due to an installation problem. " "Please read the README-file in the source package for details.")); //kapp->quit(); ::exit(-1); //kapp->quit() doesn't work here yet. // we return here, cause kapp->quit() only means "exit the // next time we enter the event loop... return; } // apply the saved mainwindow settings, if any, and ask the mainwindow // to automatically save settings if changed: window size, toolbar // position, icon size, etc. setAutoSaveSettings(); m_bUnderConstruction = false; } KDiff3Shell::~KDiff3Shell() { } bool KDiff3Shell::queryClose() { if(m_part) return ((KDiff3App*)m_part->widget())->queryClose(); else return true; } bool KDiff3Shell::queryExit() { return true; } void KDiff3Shell::closeEvent(QCloseEvent* e) { if(queryClose()) { e->accept(); bool bFileSaved = ((KDiff3App*)m_part->widget())->isFileSaved(); bool bDirCompare = ((KDiff3App*)m_part->widget())->isDirComparison(); QApplication::exit(bFileSaved || bDirCompare ? 0 : 1); } else e->ignore(); } void KDiff3Shell::optionsShowToolbar() { // this is all very cut and paste code for showing/hiding the // toolbar if(m_toolbarAction->isChecked()) toolBar()->show(); else toolBar()->hide(); } void KDiff3Shell::optionsShowStatusbar() { // this is all very cut and paste code for showing/hiding the // statusbar if(m_statusbarAction->isChecked()) statusBar()->show(); else statusBar()->hide(); } void KDiff3Shell::optionsConfigureKeys() { KShortcutsDialog::configure(actionCollection() /*, "kdiff3_shell.rc" */); } void KDiff3Shell::optionsConfigureToolbars() { KConfigGroup mainWindowGroup(KSharedConfig::openConfig(), "MainWindow"); saveMainWindowSettings(mainWindowGroup); // use the standard toolbar editor KEditToolBar dlg(factory()); connect(&dlg, &KEditToolBar::newToolBarConfig, this, &KDiff3Shell::applyNewToolbarConfig); dlg.exec(); } void KDiff3Shell::applyNewToolbarConfig() { KConfigGroup mainWindowGroup(KSharedConfig::openConfig(), "MainWindow"); applyMainWindowSettings(mainWindowGroup); } void KDiff3Shell::slotNewInstance(const QString& fn1, const QString& fn2, const QString& fn3) { static KDiff3Shell* pKDiff3Shell = new KDiff3Shell(false); ((KDiff3App*)pKDiff3Shell->m_part->widget())->completeInit(fn1, fn2, fn3); } //#include "kdiff3_shell.moc" diff --git a/src/kdiff3_shell.h b/src/kdiff3_shell.h index f58935a..b8d4e14 100644 --- a/src/kdiff3_shell.h +++ b/src/kdiff3_shell.h @@ -1,83 +1,71 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef KDIFF3SHELL_H #define KDIFF3SHELL_H #include #include #include class KToggleAction; namespace KParts { class ReadWritePart; } class KDiff3App; /** * This is the application "Shell". It has a menubar, toolbar, and * statusbar but relies on the "Part" to do all the real work. * * @short Application Shell * @author Joachim Eibl */ class KDiff3Shell : public KParts::MainWindow { Q_OBJECT public: /** * Default Constructor */ explicit KDiff3Shell(bool bCompleteInit=true); /** * Default Destructor */ ~KDiff3Shell() override; bool queryClose() override; bool queryExit(); void closeEvent(QCloseEvent*e) override; static inline QCommandLineParser* getParser(){ static QCommandLineParser *parser = new QCommandLineParser(); return parser; }; private Q_SLOTS: void optionsShowToolbar(); void optionsShowStatusbar(); void optionsConfigureKeys(); void optionsConfigureToolbars(); void applyNewToolbarConfig(); void slotNewInstance( const QString& fn1, const QString& fn2, const QString& fn3 ); private: KParts::ReadWritePart *m_part; KDiff3App *m_widget; KToggleAction *m_toolbarAction; KToggleAction *m_statusbarAction; bool m_bUnderConstruction; }; #endif // _KDIFF3_H_ diff --git a/src/main.cpp b/src/main.cpp index 51b78ee..a82b8af 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,186 +1,183 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "kdiff3_shell.h" #include "version.h" #include "Logging.h" #include // for fileno, stderr #include // for exit #ifndef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include #include #include #include void initialiseCmdLineArgs(QCommandLineParser* cmdLineParser) { QString configFileName = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, "kdiff3rc"); QFile configFile(configFileName); QString ignorableOptionsLine = "-u;-query;-html;-abort"; if(configFile.open(QIODevice::ReadOnly)) { QTextStream ts(&configFile); while(!ts.atEnd()) { QString line = ts.readLine(); if(line.startsWith(QLatin1String("IgnorableCmdLineOptions="))) { int pos = line.indexOf('='); if(pos >= 0) { ignorableOptionsLine = line.mid(pos + 1); } break; } } } //support our own old preferences this is obsolete QStringList sl = ignorableOptionsLine.split(','); if(!sl.isEmpty()) { const QStringList ignorableOptions = sl.front().split(';'); for(QString ignorableOption : ignorableOptions) { ignorableOption.remove('-'); if(!ignorableOption.isEmpty()) { if(ignorableOption.length() == 1) { cmdLineParser->addOption(QCommandLineOption({ignorableOption, QLatin1String("ignore")}, i18n("Ignored. (User defined.)"))); } else { cmdLineParser->addOption(QCommandLineOption(ignorableOption, i18n("Ignored. (User defined.)"))); } } } } } int main(int argc, char* argv[]) { const QLatin1String appName("kdiff3"); QApplication app(argc, argv); // KAboutData and QCommandLineParser depend on this being setup. KLocalizedString::setApplicationDomain(appName.data()); KCrash::initialize(); const QString i18nName = i18n("KDiff3"); QString appVersion(KDIFF3_VERSION_STRING); if(sizeof(void*) == 8) appVersion += i18n(" (64 bit)"); else if(sizeof(void*) == 4) appVersion += i18n(" (32 bit)"); const QString description = i18n("Tool for Comparison and Merge of Files and Folders"); const QString copyright = i18n("(c) 2002-2014 Joachim Eibl, (c) 2017 Michael Reeves KF5/Qt5 port"); const QString homePage = QStringLiteral("https://kde.org/applications/development/kdiff3"); KAboutData aboutData(appName, i18nName, appVersion, description, KAboutLicense::GPL_V2, copyright, QString(), homePage); KAboutData::setApplicationData(aboutData); QCommandLineParser* cmdLineParser = KDiff3Shell::getParser(); cmdLineParser->setApplicationDescription(aboutData.shortDescription()); aboutData.setupCommandLine(cmdLineParser); initialiseCmdLineArgs(cmdLineParser); // ignorable command options cmdLineParser->addOption(QCommandLineOption({QLatin1String("m"), QLatin1String("merge")}, i18n("Merge the input."))); cmdLineParser->addOption(QCommandLineOption({QLatin1String("b"), QLatin1String("base")}, i18n("Explicit base file. For compatibility with certain tools."), QLatin1String("file"))); cmdLineParser->addOption(QCommandLineOption({QLatin1String("o"), QLatin1String("output")}, i18n("Output file. Implies -m. E.g.: -o newfile.txt"), QLatin1String("file"))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("out"), i18n("Output file, again. (For compatibility with certain tools.)"), QLatin1String("file"))); #ifdef ENABLE_AUTO cmdLineParser->addOption(QCommandLineOption(QLatin1String("auto"), i18n("No GUI if all conflicts are auto-solvable. (Needs -o file)"))); #else cmdLineParser->addOption(QCommandLineOption(QLatin1String("auto"), i18n("Ignored."))); #endif cmdLineParser->addOption(QCommandLineOption(QLatin1String("qall"), i18n("Do not solve conflicts automatically."))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("L1"), i18n("Visible name replacement for input file 1 (base)."), QLatin1String("alias1"))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("L2"), i18n("Visible name replacement for input file 2."), QLatin1String("alias2"))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("L3"), i18n("Visible name replacement for input file 3."), QLatin1String("alias3"))); cmdLineParser->addOption(QCommandLineOption({QLatin1String("L"), QLatin1String("fname")}, i18n("Alternative visible name replacement. Supply this once for every input."), QLatin1String("alias"))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("cs"), i18n("Override a config setting. Use once for every setting. E.g.: --cs \"AutoAdvance=1\""), QLatin1String("string"))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("confighelp"), i18n("Show list of config settings and current values."))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("config"), i18n("Use a different config file."), QLatin1String("file"))); // other command options cmdLineParser->addPositionalArgument(QLatin1String("[File1]"), i18n("file1 to open (base, if not specified via --base)")); cmdLineParser->addPositionalArgument(QLatin1String("[File2]"), i18n("file2 to open")); cmdLineParser->addPositionalArgument(QLatin1String("[File3]"), i18n("file3 to open")); bool isAtty = true; #ifndef Q_OS_WIN isAtty = isatty(fileno(stderr)) == 1;//will be true for redirected output as well #endif /* QCommandLineParser::process does what is expected on windows or when running from a commandline. However, it only accounts for a lack of terminal output on windows. */ if(isAtty) { cmdLineParser->process(QCoreApplication::arguments()); } else { /* There is no terminal connected so don't just exit mysteriously on error. */ if(!cmdLineParser->parse(QCoreApplication::arguments())) { QString errorMessage = cmdLineParser->errorText(); KMessageBox::error(nullptr, "

" + errorMessage + "

" + i18n("See kdiff3 --help for supported options.") + "
", aboutData.displayName()); exit(1); } if(cmdLineParser->isSet(QStringLiteral("version"))) { KMessageBox::information(nullptr, aboutData.displayName() + ' ' + aboutData.version(), aboutData.displayName()); exit(0); } if(cmdLineParser->isSet(QStringLiteral("help"))) { KMessageBox::information(nullptr, "
" + cmdLineParser->helpText() + "
", aboutData.displayName()); exit(0); } } aboutData.processCommandLine(cmdLineParser); KDiff3Shell* p = new KDiff3Shell(); p->show(); //p->setWindowState( p->windowState() | Qt::WindowActive ); // Patch for ubuntu: window not active on startup //app.installEventFilter( new CFilter ); int retVal = QApplication::exec(); /* if (QApplication::clipboard()->text().size() == 0) QApplication::clipboard()->clear(); // Patch for Ubuntu: Fix issue with Qt clipboard*/ return retVal; } diff --git a/src/merger.cpp b/src/merger.cpp index 4eea7c7..126d6d0 100644 --- a/src/merger.cpp +++ b/src/merger.cpp @@ -1,76 +1,73 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "merger.h" #include Merger::Merger(const DiffList* pDiffList1, const DiffList* pDiffList2) : md1(pDiffList1, 0), md2(pDiffList2, 1) { } Merger::MergeData::MergeData(const DiffList* p, int i) { idx = i; pDiffList = p; if(p != nullptr) { it = p->begin(); update(); } } bool Merger::MergeData::eq() const { return pDiffList == nullptr || d.numberOfEquals() > 0; } bool Merger::MergeData::isEnd() const { return (pDiffList == nullptr || (it == pDiffList->end() && d.numberOfEquals() == 0 && (idx == 0 ? d.diff1() == 0 : d.diff2() == 0))); } void Merger::MergeData::update() { if(d.numberOfEquals() > 0) d.adjustNumberOfEquals(-1); else if(idx == 0 && d.diff1() > 0) d.adjustDiff1(-1); else if(idx == 1 && d.diff2() > 0) d.adjustDiff2(-1); while(d.numberOfEquals() == 0 && ((idx == 0 && d.diff1() == 0) || (idx == 1 && d.diff2() == 0)) && pDiffList != nullptr && it != pDiffList->end()) { d = *it; ++it; } } void Merger::next() { md1.update(); md2.update(); } ChangeFlags Merger::whatChanged() { ChangeFlags changed = ChangeFlag::NoChange; changed |= md1.eq() ? ChangeFlag::NoChange : ChangeFlag::AChanged; changed |= md2.eq() ? ChangeFlag::NoChange : ChangeFlag::BChanged; return changed; } bool Merger::isEndReached() { return md1.isEnd() && md2.isEnd(); } diff --git a/src/merger.h b/src/merger.h index b8e1b54..c4f67d1 100644 --- a/src/merger.h +++ b/src/merger.h @@ -1,55 +1,52 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef MERGER_H #define MERGER_H #include "diff.h" class Merger { public: Merger(const DiffList* pDiffList1, const DiffList* pDiffList2); /** Go one step. */ void next(); /** Information about what changed. Can be used for coloring. The return value is 0 if nothing changed here, bit 1 is set if a difference from pDiffList1 was detected, bit 2 is set if a difference from pDiffList2 was detected. */ ChangeFlags whatChanged(); /** End of both diff lists reached. */ bool isEndReached(); private: class MergeData { private: DiffList::const_iterator it; const DiffList* pDiffList = nullptr; Diff d; int idx; public: MergeData(const DiffList* p, int i); bool eq() const; void update(); bool isEnd() const; }; MergeData md1; MergeData md2; }; #endif diff --git a/src/mergeresultwindow.cpp b/src/mergeresultwindow.cpp index bb7533f..660d9a3 100644 --- a/src/mergeresultwindow.cpp +++ b/src/mergeresultwindow.cpp @@ -1,3266 +1,3263 @@ -/*************************************************************************** - * Copyright (C) 2002-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "mergeresultwindow.h" #include "kdiff3.h" #include "options.h" #include "RLPainter.h" #include "guiutils.h" #include "Utils.h" // for Utils #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool g_bAutoSolve = true; #undef leftInfoWidth QAction* MergeResultWindow::chooseAEverywhere = nullptr; QAction* MergeResultWindow::chooseBEverywhere = nullptr; QAction* MergeResultWindow::chooseCEverywhere = nullptr; QAction* MergeResultWindow::chooseAForUnsolvedConflicts = nullptr; QAction* MergeResultWindow::chooseBForUnsolvedConflicts = nullptr; QAction* MergeResultWindow::chooseCForUnsolvedConflicts = nullptr; QAction* MergeResultWindow::chooseAForUnsolvedWhiteSpaceConflicts = nullptr; QAction* MergeResultWindow::chooseBForUnsolvedWhiteSpaceConflicts = nullptr; QAction* MergeResultWindow::chooseCForUnsolvedWhiteSpaceConflicts = nullptr; MergeResultWindow::MergeResultWindow( QWidget* pParent, const QSharedPointer &pOptions, QStatusBar* pStatusBar) : QWidget(pParent) { setObjectName("MergeResultWindow"); setFocusPolicy(Qt::ClickFocus); m_firstLine = 0; m_horizScrollOffset = 0; m_nofLines = 0; m_bMyUpdate = false; m_bInsertMode = true; m_scrollDeltaX = 0; m_scrollDeltaY = 0; m_bModified = false; m_eOverviewMode = Overview::eOMNormal; m_pldA = nullptr; m_pldB = nullptr; m_pldC = nullptr; m_sizeA = 0; m_sizeB = 0; m_sizeC = 0; m_pDiff3LineList = nullptr; m_pTotalDiffStatus = nullptr; m_pStatusBar = pStatusBar; if(m_pStatusBar != nullptr) connect(m_pStatusBar, &QStatusBar::messageChanged, this, &MergeResultWindow::slotStatusMessageChanged); m_pOptions = pOptions; setUpdatesEnabled(false); m_delayedDrawTimer = 0; m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_bCursorOn = true; m_bCursorUpdate = false; m_maxTextWidth = -1; connect(&m_cursorTimer, &QTimer::timeout, this, &MergeResultWindow::slotCursorUpdate); m_cursorTimer.setSingleShot(true); m_cursorTimer.start(500 /*ms*/); m_selection.reset(); setMinimumSize(QSize(20, 20)); setFont(m_pOptions->m_font); } void MergeResultWindow::init( const QVector* pLineDataA, LineRef sizeA, const QVector* pLineDataB, LineRef sizeB, const QVector* pLineDataC, LineRef sizeC, const Diff3LineList* pDiff3LineList, TotalDiffStatus* pTotalDiffStatus) { m_firstLine = 0; m_horizScrollOffset = 0; m_nofLines = 0; m_bMyUpdate = false; m_bInsertMode = true; m_scrollDeltaX = 0; m_scrollDeltaY = 0; setModified(false); m_pldA = pLineDataA; m_pldB = pLineDataB; m_pldC = pLineDataC; m_sizeA = sizeA; m_sizeB = sizeB; m_sizeC = sizeC; m_pDiff3LineList = pDiff3LineList; m_pTotalDiffStatus = pTotalDiffStatus; m_selection.reset(); m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_maxTextWidth = -1; merge(g_bAutoSolve, Invalid); g_bAutoSolve = true; update(); updateSourceMask(); showUnsolvedConflictsStatusMessage(); } //This must be called before KXMLGui::SetXMLFile and friends or the actions will not be shown in the menu. //At that point in startup we don't have a MergeResultWindow object so we cannot connect the signals yet. void MergeResultWindow::initActions(KActionCollection* ac) { if(ac == nullptr){ KMessageBox::error(nullptr, "actionCollection==0"); exit(-1);//we cannot recover from this. } chooseAEverywhere = GuiUtils::createAction(i18n("Choose A Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_1), ac, "merge_choose_a_everywhere"); chooseBEverywhere = GuiUtils::createAction(i18n("Choose B Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_2), ac, "merge_choose_b_everywhere"); chooseCEverywhere = GuiUtils::createAction(i18n("Choose C Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3), ac, "merge_choose_c_everywhere"); chooseAForUnsolvedConflicts = GuiUtils::createAction(i18n("Choose A for All Unsolved Conflicts"), ac, "merge_choose_a_for_unsolved_conflicts"); chooseBForUnsolvedConflicts = GuiUtils::createAction(i18n("Choose B for All Unsolved Conflicts"), ac, "merge_choose_b_for_unsolved_conflicts"); chooseCForUnsolvedConflicts = GuiUtils::createAction(i18n("Choose C for All Unsolved Conflicts"), ac, "merge_choose_c_for_unsolved_conflicts"); chooseAForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction(i18n("Choose A for All Unsolved Whitespace Conflicts"), ac, "merge_choose_a_for_unsolved_whitespace_conflicts"); chooseBForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction(i18n("Choose B for All Unsolved Whitespace Conflicts"), ac, "merge_choose_b_for_unsolved_whitespace_conflicts"); chooseCForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction(i18n("Choose C for All Unsolved Whitespace Conflicts"), ac, "merge_choose_c_for_unsolved_whitespace_conflicts"); } void MergeResultWindow::connectActions() const { QObject::connect(chooseAEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseAEverywhere); QObject::connect(chooseBEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseBEverywhere); QObject::connect(chooseCEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseCEverywhere); QObject::connect(chooseAForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseAForUnsolvedConflicts); QObject::connect(chooseBForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseBForUnsolvedConflicts); QObject::connect(chooseCForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseCForUnsolvedConflicts); QObject::connect(chooseAForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseAForUnsolvedWhiteSpaceConflicts); QObject::connect(chooseBForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseBForUnsolvedWhiteSpaceConflicts); QObject::connect(chooseCForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseCForUnsolvedWhiteSpaceConflicts); } void MergeResultWindow::setupConnections(const KDiff3App *app) const { connect(this, &MergeResultWindow::scrollMergeResultWindow, app, &KDiff3App::scrollMergeResultWindow); connect(this, &MergeResultWindow::sourceMask, app, &KDiff3App::sourceMask); connect(this, &MergeResultWindow::resizeSignal, app, &KDiff3App::resizeMergeResultWindow); connect(this, &MergeResultWindow::selectionEnd, app, &KDiff3App::slotSelectionEnd); connect(this, &MergeResultWindow::newSelection, app, &KDiff3App::slotSelectionStart); connect(this, &MergeResultWindow::modifiedChanged, app, &KDiff3App::slotOutputModified); connect(this, &MergeResultWindow::updateAvailabilities, app, &KDiff3App::slotUpdateAvailabilities); connect(this, &MergeResultWindow::showPopupMenu, app, &KDiff3App::showPopupMenu); connect(this, &MergeResultWindow::noRelevantChangesDetected, app, &KDiff3App::slotNoRelevantChangesDetected); connect(this, static_cast(&MergeResultWindow::update), app, &KDiff3App::showWhiteSpaceToggled); connect(app, &KDiff3App::doRefresh, this, &MergeResultWindow::slotRefresh); connect(app, &KDiff3App::autoSolve, this, &MergeResultWindow::slotAutoSolve); connect(app, &KDiff3App::unsolve, this, &MergeResultWindow::slotUnsolve); connect(app, &KDiff3App::mergeHistory, this, &MergeResultWindow::slotMergeHistory); connect(app, &KDiff3App::regExpAutoMerge, this, &MergeResultWindow::slotRegExpAutoMerge); connect(app, &KDiff3App::goCurrent, this, &MergeResultWindow::slotGoCurrent); connect(app, &KDiff3App::goTop, this, &MergeResultWindow::slotGoTop); connect(app, &KDiff3App::goBottom, this, &MergeResultWindow::slotGoBottom); connect(app, &KDiff3App::goPrevUnsolvedConflict, this, &MergeResultWindow::slotGoPrevUnsolvedConflict); connect(app, &KDiff3App::goNextUnsolvedConflict, this, &MergeResultWindow::slotGoNextUnsolvedConflict); connect(app, &KDiff3App::goPrevConflict, this, &MergeResultWindow::slotGoPrevConflict); connect(app, &KDiff3App::goNextConflict, this, &MergeResultWindow::slotGoNextConflict); connect(app, &KDiff3App::goPrevDelta, this, &MergeResultWindow::slotGoPrevDelta); connect(app, &KDiff3App::goNextDelta, this, &MergeResultWindow::slotGoNextDelta); } void MergeResultWindow::showUnsolvedConflictsStatusMessage() { if(m_pStatusBar != nullptr) { int wsc; int nofUnsolved = getNrOfUnsolvedConflicts(&wsc); m_persistentStatusMessage = i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)", nofUnsolved, wsc); m_pStatusBar->showMessage(m_persistentStatusMessage); } } void MergeResultWindow::slotRefresh() { setFont(m_pOptions->m_font); update(); } void MergeResultWindow::slotUpdateAvailabilities(bool bMergeEditorVisible,const bool bTripleDiff) { chooseAEverywhere->setEnabled(bMergeEditorVisible); chooseBEverywhere->setEnabled(bMergeEditorVisible); chooseCEverywhere->setEnabled(bMergeEditorVisible && bTripleDiff); chooseAForUnsolvedConflicts->setEnabled(bMergeEditorVisible); chooseBForUnsolvedConflicts->setEnabled(bMergeEditorVisible); chooseCForUnsolvedConflicts->setEnabled(bMergeEditorVisible && bTripleDiff); chooseAForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible); chooseBForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible); chooseCForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible && bTripleDiff); } void MergeResultWindow::slotStatusMessageChanged(const QString& s) { if(s.isEmpty() && !m_persistentStatusMessage.isEmpty()) { m_pStatusBar->showMessage(m_persistentStatusMessage, 0); } } void MergeResultWindow::reset() { m_pDiff3LineList = nullptr; m_pTotalDiffStatus = nullptr; m_pldA = nullptr; m_pldB = nullptr; m_pldC = nullptr; if(!m_persistentStatusMessage.isEmpty()) { m_persistentStatusMessage = QString(); } } // Calculate the merge information for the given Diff3Line. // Results will be stored in mergeDetails, bConflict, bLineRemoved and src. void Diff3Line::mergeOneLine( e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, e_SrcSelector& src, bool bTwoInputs) const { mergeDetails = eDefault; bConflict = false; bLineRemoved = false; src = None; if(bTwoInputs) // Only two input files { if(getLineA().isValid() && getLineB().isValid()) { if(pFineAB == nullptr) { mergeDetails = eNoChange; src = A; } else { mergeDetails = eBChanged; bConflict = true; } } else { mergeDetails = eBDeleted; bConflict = true; } return; } // A is base. if(getLineA().isValid() && getLineB().isValid() && getLineC().isValid()) { if(pFineAB == nullptr && pFineBC == nullptr && pFineCA == nullptr) { mergeDetails = eNoChange; src = A; } else if(pFineAB == nullptr && pFineBC != nullptr && pFineCA != nullptr) { mergeDetails = eCChanged; src = C; } else if(pFineAB != nullptr && pFineBC != nullptr && pFineCA == nullptr) { mergeDetails = eBChanged; src = B; } else if(pFineAB != nullptr && pFineBC == nullptr && pFineCA != nullptr) { mergeDetails = eBCChangedAndEqual; src = C; } else if(pFineAB != nullptr && pFineBC != nullptr && pFineCA != nullptr) { mergeDetails = eBCChanged; bConflict = true; } else Q_ASSERT(true); } else if(getLineA().isValid() && getLineB().isValid() && !getLineC().isValid()) { if(pFineAB != nullptr) { mergeDetails = eBChanged_CDeleted; bConflict = true; } else { mergeDetails = eCDeleted; bLineRemoved = true; src = C; } } else if(getLineA().isValid() && !getLineB().isValid() && getLineC().isValid()) { if(pFineCA != nullptr) { mergeDetails = eCChanged_BDeleted; bConflict = true; } else { mergeDetails = eBDeleted; bLineRemoved = true; src = B; } } else if(!getLineA().isValid() && getLineB().isValid() && getLineC().isValid()) { if(pFineBC != nullptr) { mergeDetails = eBCAdded; bConflict = true; } else // B==C { mergeDetails = eBCAddedAndEqual; src = C; } } else if(!getLineA().isValid() && !getLineB().isValid() && getLineC().isValid()) { mergeDetails = eCAdded; src = C; } else if(!getLineA().isValid() && getLineB().isValid() && !getLineC().isValid()) { mergeDetails = eBAdded; src = B; } else if(getLineA().isValid() && !getLineB().isValid() && !getLineC().isValid()) { mergeDetails = eBCDeleted; bLineRemoved = true; src = C; } else Q_ASSERT(true); } bool MergeResultWindow::sameKindCheck(const MergeLine& ml1, const MergeLine& ml2) { if(ml1.bConflict && ml2.bConflict) { // Both lines have conflicts: If one is only a white space conflict and // the other one is a real conflict, then this line returns false. return ml1.id3l->isEqualAC() == ml2.id3l->isEqualAC() && ml1.id3l->isEqualAB() == ml2.id3l->isEqualAB(); } else return ( (!ml1.bConflict && !ml2.bConflict && ml1.bDelta && ml2.bDelta && ml1.srcSelect == ml2.srcSelect && (ml1.mergeDetails == ml2.mergeDetails || (ml1.mergeDetails != eBCAddedAndEqual && ml2.mergeDetails != eBCAddedAndEqual))) || (!ml1.bDelta && !ml2.bDelta)); } void MergeResultWindow::merge(bool bAutoSolve, e_SrcSelector defaultSelector, bool bConflictsOnly, bool bWhiteSpaceOnly) { if(!bConflictsOnly) { if(m_bModified) { int result = KMessageBox::warningYesNo(this, i18n("The output has been modified.\n" "If you continue your changes will be lost."), i18n("Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel()); if(result == KMessageBox::No) return; } m_mergeLineList.clear(); int lineIdx = 0; Diff3LineList::const_iterator it; for(it = m_pDiff3LineList->begin(); it != m_pDiff3LineList->end(); ++it, ++lineIdx) { const Diff3Line& d = *it; MergeLine ml; bool bLineRemoved; d.mergeOneLine( ml.mergeDetails, ml.bConflict, bLineRemoved, ml.srcSelect, m_pldC == nullptr); // Automatic solving for only whitespace changes. if(ml.bConflict && ((m_pldC == nullptr && (d.isEqualAB() || (d.isWhiteLine(A) && d.isWhiteLine(B)))) || (m_pldC != nullptr && ((d.isEqualAB() && d.isEqualAC()) || (d.isWhiteLine(A) && d.isWhiteLine(B) && d.isWhiteLine(C)))))) { ml.bWhiteSpaceConflict = true; } ml.d3lLineIdx = lineIdx; ml.bDelta = ml.srcSelect != A; ml.id3l = it; ml.srcRangeLength = 1; MergeLine* back = m_mergeLineList.empty() ? nullptr : &m_mergeLineList.back(); bool bSame = back != nullptr && sameKindCheck(ml, *back); if(bSame) { ++back->srcRangeLength; if(back->bWhiteSpaceConflict && !ml.bWhiteSpaceConflict) back->bWhiteSpaceConflict = false; } else { m_mergeLineList.push_back(ml); } if(!ml.bConflict) { MergeLine& tmpBack = m_mergeLineList.back(); MergeEditLine mel(ml.id3l); mel.setSource(ml.srcSelect, bLineRemoved); tmpBack.mergeEditLineList.push_back(mel); } else if(back == nullptr || !back->bConflict || !bSame) { MergeLine& tmpBack = m_mergeLineList.back(); MergeEditLine mel(ml.id3l); mel.setConflict(); tmpBack.mergeEditLineList.push_back(mel); } } } bool bSolveWhiteSpaceConflicts = false; if(bAutoSolve) // when true, then the other params are not used and we can change them here. (see all invocations of merge()) { if(m_pldC == nullptr && m_pOptions->m_whiteSpace2FileMergeDefault != None) // Only two inputs { Q_ASSERT(m_pOptions->m_whiteSpace2FileMergeDefault <= Max && m_pOptions->m_whiteSpace2FileMergeDefault >= Min); defaultSelector = (e_SrcSelector)m_pOptions->m_whiteSpace2FileMergeDefault; bWhiteSpaceOnly = true; bSolveWhiteSpaceConflicts = true; } else if(m_pldC != nullptr && m_pOptions->m_whiteSpace3FileMergeDefault != None) { Q_ASSERT(m_pOptions->m_whiteSpace3FileMergeDefault <= Max && m_pOptions->m_whiteSpace2FileMergeDefault >= Min); defaultSelector = (e_SrcSelector)m_pOptions->m_whiteSpace3FileMergeDefault; bWhiteSpaceOnly = true; bSolveWhiteSpaceConflicts = true; } } if(!bAutoSolve || bSolveWhiteSpaceConflicts) { // Change all auto selections MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; bool bConflict = ml.mergeEditLineList.empty() || ml.mergeEditLineList.begin()->isConflict(); if(ml.bDelta && (!bConflictsOnly || bConflict) && (!bWhiteSpaceOnly || ml.bWhiteSpaceConflict)) { ml.mergeEditLineList.clear(); if(defaultSelector == Invalid && ml.bDelta) { MergeEditLine mel(ml.id3l); ; mel.setConflict(); ml.bConflict = true; ml.mergeEditLineList.push_back(mel); } else { Diff3LineList::const_iterator d3llit = ml.id3l; int j; for(j = 0; j < ml.srcRangeLength; ++j) { MergeEditLine mel(d3llit); mel.setSource(defaultSelector, false); LineRef srcLine = defaultSelector == A ? d3llit->getLineA() : defaultSelector == B ? d3llit->getLineB() : defaultSelector == C ? d3llit->getLineC() : LineRef(); if(srcLine.isValid()) { ml.mergeEditLineList.push_back(mel); } ++d3llit; } if(ml.mergeEditLineList.empty()) // Make a line nevertheless { MergeEditLine mel(ml.id3l); mel.setRemoved(defaultSelector); ml.mergeEditLineList.push_back(mel); } } } } } MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; // Remove all lines that are empty, because no src lines are there. LineRef oldSrcLine; e_SrcSelector oldSrc = Invalid; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; e_SrcSelector melsrc = mel.src(); LineRef srcLine = mel.isRemoved() ? LineRef() : melsrc == A ? mel.id3l()->getLineA() : melsrc == B ? mel.id3l()->getLineB() : melsrc == C ? mel.id3l()->getLineC() : LineRef(); // At least one line remains because oldSrc != melsrc for first line in list // Other empty lines will be removed if(!srcLine.isValid() && !oldSrcLine.isValid() && oldSrc == melsrc) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; oldSrcLine = srcLine; oldSrc = melsrc; } } if(bAutoSolve && !bConflictsOnly) { if(m_pOptions->m_bRunHistoryAutoMergeOnMergeStart) slotMergeHistory(); if(m_pOptions->m_bRunRegExpAutoMergeOnMergeStart) slotRegExpAutoMerge(); if(m_pldC != nullptr && !doRelevantChangesExist()) Q_EMIT noRelevantChangesDetected(); } int nrOfSolvedConflicts = 0; int nrOfUnsolvedConflicts = 0; int nrOfWhiteSpaceConflicts = 0; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict) ++nrOfUnsolvedConflicts; else if(i->bDelta) ++nrOfSolvedConflicts; if(i->bWhiteSpaceConflict) ++nrOfWhiteSpaceConflicts; } m_pTotalDiffStatus->setUnsolvedConflicts(nrOfUnsolvedConflicts); m_pTotalDiffStatus->setSolvedConflicts(nrOfSolvedConflicts); m_pTotalDiffStatus->setWhitespaceConflicts(nrOfWhiteSpaceConflicts); m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_maxTextWidth = -1; //m_firstLine = 0; // Must not set line/column without scrolling there //m_horizScrollOffset = 0; setModified(false); m_currentMergeLineIt = m_mergeLineList.begin(); slotGoTop(); Q_EMIT updateAvailabilities(); update(); } void MergeResultWindow::setFirstLine(QtNumberType firstLine) { m_firstLine = std::max(0, firstLine); update(); } void MergeResultWindow::setHorizScrollOffset(int horizScrollOffset) { m_horizScrollOffset = std::max(0, horizScrollOffset); update(); } int MergeResultWindow::getMaxTextWidth() { if(m_maxTextWidth < 0) { m_maxTextWidth = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; QString s = mel.getString(m_pldA, m_pldB, m_pldC); QTextLayout textLayout(s, font(), this); textLayout.beginLayout(); textLayout.createLine(); textLayout.endLayout(); if(m_maxTextWidth < textLayout.maximumWidth()) { m_maxTextWidth = qCeil(textLayout.maximumWidth()); } } } m_maxTextWidth += 5; // cursorwidth } return m_maxTextWidth; } int MergeResultWindow::getNofLines() const { return m_nofLines; } int MergeResultWindow::getVisibleTextAreaWidth() { // QFontMetrics fm = fontMetrics(); // FIXME used? return width() - getTextXOffset(); } int MergeResultWindow::getNofVisibleLines() { QFontMetrics fm = fontMetrics(); return (height() - 3) / fm.lineSpacing() - 2; } int MergeResultWindow::getTextXOffset() { QFontMetrics fm = fontMetrics(); return 3 * Utils::getHorizontalAdvance(fm, '0'); } void MergeResultWindow::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); Q_EMIT resizeSignal(); } Overview::e_OverviewMode MergeResultWindow::getOverviewMode() { return m_eOverviewMode; } void MergeResultWindow::setOverviewMode(Overview::e_OverviewMode eOverviewMode) { m_eOverviewMode = eOverviewMode; } // Check whether we should ignore current delta when moving to next/previous delta bool MergeResultWindow::checkOverviewIgnore(MergeLineList::iterator& i) { if(m_eOverviewMode == Overview::eOMNormal) return false; if(m_eOverviewMode == Overview::eOMAvsB) return i->mergeDetails == eCAdded || i->mergeDetails == eCDeleted || i->mergeDetails == eCChanged; if(m_eOverviewMode == Overview::eOMAvsC) return i->mergeDetails == eBAdded || i->mergeDetails == eBDeleted || i->mergeDetails == eBChanged; if(m_eOverviewMode == Overview::eOMBvsC) return i->mergeDetails == eBCAddedAndEqual || i->mergeDetails == eBCDeleted || i->mergeDetails == eBCChangedAndEqual; return false; } // Go to prev/next delta/conflict or first/last delta. void MergeResultWindow::go(e_Direction eDir, e_EndPoint eEndPoint) { Q_ASSERT(eDir == eUp || eDir == eDown); MergeLineList::iterator i = m_currentMergeLineIt; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(eEndPoint == eEnd) { if(eDir == eUp) i = m_mergeLineList.begin(); // first mergeline else i = --m_mergeLineList.end(); // last mergeline while(isItAtEnd(eDir == eUp, i) && !i->bDelta) { if(eDir == eUp) ++i; // search downwards else --i; // search upwards } } else if(eEndPoint == eDelta && isItAtEnd(eDir != eUp, i)) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && (!i->bDelta || checkOverviewIgnore(i) || (bSkipWhiteConflicts && i->bWhiteSpaceConflict))); } else if(eEndPoint == eConflict && isItAtEnd(eDir != eUp, i)) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && (!i->bConflict || (bSkipWhiteConflicts && i->bWhiteSpaceConflict))); } else if(isItAtEnd(eDir != eUp, i) && eEndPoint == eUnsolvedConflict) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && !i->mergeEditLineList.begin()->isConflict()); } if(isVisible()) setFocus(); setFastSelector(i); } bool MergeResultWindow::isDeltaAboveCurrent() { bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; do { --i; if(i->bDelta && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isDeltaBelowCurrent() { bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->bDelta && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } } return false; } bool MergeResultWindow::isConflictAboveCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; do { --i; if(i->bConflict && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isConflictBelowCurrent() { MergeLineList::iterator i = m_currentMergeLineIt; if(m_mergeLineList.empty()) return false; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->bConflict && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } } return false; } bool MergeResultWindow::isUnsolvedConflictAtCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; return i->mergeEditLineList.begin()->isConflict(); } bool MergeResultWindow::isUnsolvedConflictAboveCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; do { --i; if(i->mergeEditLineList.begin()->isConflict()) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isUnsolvedConflictBelowCurrent() { MergeLineList::iterator i = m_currentMergeLineIt; if(m_mergeLineList.empty()) return false; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->mergeEditLineList.begin()->isConflict()) return true; } } return false; } void MergeResultWindow::slotGoTop() { go(eUp, eEnd); } void MergeResultWindow::slotGoCurrent() { setFastSelector(m_currentMergeLineIt); } void MergeResultWindow::slotGoBottom() { go(eDown, eEnd); } void MergeResultWindow::slotGoPrevDelta() { go(eUp, eDelta); } void MergeResultWindow::slotGoNextDelta() { go(eDown, eDelta); } void MergeResultWindow::slotGoPrevConflict() { go(eUp, eConflict); } void MergeResultWindow::slotGoNextConflict() { go(eDown, eConflict); } void MergeResultWindow::slotGoPrevUnsolvedConflict() { go(eUp, eUnsolvedConflict); } void MergeResultWindow::slotGoNextUnsolvedConflict() { go(eDown, eUnsolvedConflict); } /** The line is given as a index in the Diff3LineList. The function calculates the corresponding iterator. */ void MergeResultWindow::slotSetFastSelectorLine(LineIndex line) { MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(line >= i->d3lLineIdx && line < i->d3lLineIdx + i->srcRangeLength) { //if ( i->bDelta ) { setFastSelector(i); } break; } } } int MergeResultWindow::getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts) { int nrOfUnsolvedConflicts = 0; if(pNrOfWhiteSpaceConflicts != nullptr) *pNrOfWhiteSpaceConflicts = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt = ml.mergeEditLineList.begin(); if(melIt->isConflict()) { ++nrOfUnsolvedConflicts; if(ml.bWhiteSpaceConflict && pNrOfWhiteSpaceConflicts != nullptr) ++*pNrOfWhiteSpaceConflicts; } } return nrOfUnsolvedConflicts; } void MergeResultWindow::showNrOfConflicts() { if(!m_pOptions->m_bShowInfoDialogs) return; int nrOfConflicts = 0; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict || i->bDelta) ++nrOfConflicts; } QString totalInfo; if(m_pTotalDiffStatus->isBinaryEqualAB() && m_pTotalDiffStatus->isBinaryEqualAC()) totalInfo += i18n("All input files are binary equal."); else if(m_pTotalDiffStatus->isTextEqualAB() && m_pTotalDiffStatus->isTextEqualAC()) totalInfo += i18n("All input files contain the same text."); else { if(m_pTotalDiffStatus->isBinaryEqualAB()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B")); else if(m_pTotalDiffStatus->isTextEqualAB()) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("B")); if(m_pTotalDiffStatus->isBinaryEqualAC()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C")); else if(m_pTotalDiffStatus->isTextEqualAC()) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("C")); if(m_pTotalDiffStatus->isBinaryEqualBC()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C")); else if(m_pTotalDiffStatus->isTextEqualBC()) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("B"), i18n("C")); } int nrOfUnsolvedConflicts = getNrOfUnsolvedConflicts(); KMessageBox::information(this, i18n("Total number of conflicts: %1\n" "Nr of automatically solved conflicts: %2\n" "Nr of unsolved conflicts: %3\n" "%4", nrOfConflicts, nrOfConflicts - nrOfUnsolvedConflicts, nrOfUnsolvedConflicts, totalInfo), i18n("Conflicts")); } void MergeResultWindow::setFastSelector(MergeLineList::iterator i) { if(i == m_mergeLineList.end()) return; m_currentMergeLineIt = i; Q_EMIT setFastSelectorRange(i->d3lLineIdx, i->srcRangeLength); int line1 = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { if(mlIt == m_currentMergeLineIt) break; line1 += mlIt->mergeEditLineList.size(); } int nofLines = m_currentMergeLineIt->mergeEditLineList.size(); int newFirstLine = getBestFirstLine(line1, nofLines, m_firstLine, getNofVisibleLines()); if(newFirstLine != m_firstLine) { Q_EMIT scrollMergeResultWindow(0, newFirstLine - m_firstLine); } if(m_selection.isEmpty()) { m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = line1; } update(); updateSourceMask(); Q_EMIT updateAvailabilities(); } void MergeResultWindow::choose(e_SrcSelector selector) { if(m_currentMergeLineIt == m_mergeLineList.end()) return; setModified(); // First find range for which this change works. MergeLine& ml = *m_currentMergeLineIt; MergeEditLineList::iterator melIt; // Now check if selector is active for this range already. bool bActive = false; // Remove unneeded lines in the range. for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; if(mel.src() == selector) bActive = true; if(mel.src() == selector || !mel.isEditableText() || mel.isModified()) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; } if(!bActive) // Selected source wasn't active. { // Append the lines from selected source here at rangeEnd. Diff3LineList::const_iterator d3llit = ml.id3l; int j; for(j = 0; j < ml.srcRangeLength; ++j) { MergeEditLine mel(d3llit); mel.setSource(selector, false); ml.mergeEditLineList.push_back(mel); ++d3llit; } } if(!ml.mergeEditLineList.empty()) { // Remove all lines that are empty, because no src lines are there. for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; LineRef srcLine = mel.src() == A ? mel.id3l()->getLineA() : mel.src() == B ? mel.id3l()->getLineB() : mel.src() == C ? mel.id3l()->getLineC() : LineRef(); if(!srcLine.isValid()) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; } } if(ml.mergeEditLineList.empty()) { // Insert a dummy line: MergeEditLine mel(ml.id3l); if(bActive) mel.setConflict(); // All src entries deleted => conflict else mel.setRemoved(selector); // No lines in corresponding src found. ml.mergeEditLineList.push_back(mel); } if(m_cursorYPos >= m_nofLines) { m_cursorYPos = m_nofLines - 1; m_cursorXPos = 0; } m_maxTextWidth = -1; update(); updateSourceMask(); Q_EMIT updateAvailabilities(); showUnsolvedConflictsStatusMessage(); } // bConflictsOnly: automatically choose for conflicts only (true) or for everywhere (false) void MergeResultWindow::chooseGlobal(e_SrcSelector selector, bool bConflictsOnly, bool bWhiteSpaceOnly) { resetSelection(); merge(false, selector, bConflictsOnly, bWhiteSpaceOnly); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } void MergeResultWindow::slotAutoSolve() { resetSelection(); merge(true, Invalid); setModified(true); update(); showUnsolvedConflictsStatusMessage(); showNrOfConflicts(); } void MergeResultWindow::slotUnsolve() { resetSelection(); merge(false, Invalid); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } bool findParenthesesGroups(const QString& s, QStringList& sl) { sl.clear(); int i = 0; std::list startPosStack; int length = s.length(); for(i = 0; i < length; ++i) { if(s[i] == '\\' && i + 1 < length && (s[i + 1] == '\\' || s[i + 1] == '(' || s[i + 1] == ')')) { ++i; continue; } if(s[i] == '(') { startPosStack.push_back(i); } else if(s[i] == ')') { if(startPosStack.empty()) return false; // Parentheses don't match int startPos = startPosStack.back(); startPosStack.pop_back(); sl.push_back(s.mid(startPos + 1, i - startPos - 1)); } } return startPosStack.empty(); // false if parentheses don't match } QString calcHistorySortKey(const QString& keyOrder, QRegExp& matchedRegExpr, const QStringList& parenthesesGroupList) { const QStringList keyOrderList = keyOrder.split(','); QString key; for(const QString& keyIt : keyOrderList) { if(keyIt.isEmpty()) continue; bool bOk = false; int groupIdx = keyIt.toInt(&bOk); if(!bOk || groupIdx < 0 || groupIdx > parenthesesGroupList.size()) continue; QString s = matchedRegExpr.cap(groupIdx); if(groupIdx == 0) { key += s + ' '; continue; } QString groupRegExp = parenthesesGroupList[groupIdx - 1]; if(groupRegExp.indexOf('|') < 0 || groupRegExp.indexOf('(') >= 0) { bOk = false; int i = s.toInt(&bOk); if(bOk && i >= 0 && i < 10000) s.asprintf("%04d", i); // This should help for correct sorting of numbers. key += s + ' '; } else { // Assume that the groupRegExp consists of something like "Jan|Feb|Mar|Apr" // s is the string that managed to match. // Now we want to know at which position it occurred. e.g. Jan=0, Feb=1, Mar=2, etc. QStringList sl = groupRegExp.split('|'); int idx = sl.indexOf(s); if(idx < 0) { // Didn't match } else { QString sIdx; sIdx.asprintf("%02d", idx + 1); // Up to 99 words in the groupRegExp (more than 12 aren't expected) key += sIdx + ' '; } } } return key; } void MergeResultWindow::collectHistoryInformation( e_SrcSelector src, Diff3LineList::const_iterator &iHistoryBegin, Diff3LineList::const_iterator &iHistoryEnd, HistoryMap& historyMap, std::list& hitList // list of iterators ) { std::list::iterator itHitListFront = hitList.begin(); Diff3LineList::const_iterator id3l = iHistoryBegin; QString historyLead; { const LineData* pld = id3l->getLineData(src); historyLead = Utils::calcHistoryLead(pld->getLine()); } QRegExp historyStart(m_pOptions->m_historyStartRegExp); if(id3l == iHistoryEnd) return; ++id3l; // Skip line with "$Log ... $" QRegExp newHistoryEntry(m_pOptions->m_historyEntryStartRegExp); QStringList parenthesesGroups; findParenthesesGroups(m_pOptions->m_historyEntryStartRegExp, parenthesesGroups); QString key; MergeEditLineList melList; bool bPrevLineIsEmpty = true; bool bUseRegExp = !m_pOptions->m_historyEntryStartRegExp.isEmpty(); for(; id3l != iHistoryEnd; ++id3l) { const LineData* pld = id3l->getLineData(src); if(!pld) continue; const QString& oriLine = pld->getLine(); if(historyLead.isEmpty()) historyLead = Utils::calcHistoryLead(oriLine); QString sLine = oriLine.mid(historyLead.length()); if((!bUseRegExp && !sLine.trimmed().isEmpty() && bPrevLineIsEmpty) || (bUseRegExp && newHistoryEntry.exactMatch(sLine))) { if(!key.isEmpty() && !melList.empty()) { // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key. std::pair p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry())); HistoryMapEntry& hme = p.first->second; if(src == A) hme.mellA = melList; if(src == B) hme.mellB = melList; if(src == C) hme.mellC = melList; if(p.second) // Not in list yet? { hitList.insert(itHitListFront, p.first); } } if(!bUseRegExp) key = sLine; else key = calcHistorySortKey(m_pOptions->m_historyEntryStartSortKeyOrder, newHistoryEntry, parenthesesGroups); melList.clear(); melList.push_back(MergeEditLine(id3l, src)); } else if(!historyStart.exactMatch(oriLine)) { melList.push_back(MergeEditLine(id3l, src)); } bPrevLineIsEmpty = sLine.trimmed().isEmpty(); } if(!key.isEmpty()) { // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key. std::pair p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry())); HistoryMapEntry& hme = p.first->second; if(src == A) hme.mellA = melList; if(src == B) hme.mellB = melList; if(src == C) hme.mellC = melList; if(p.second) // Not in list yet? { hitList.insert(itHitListFront, p.first); } } // End of the history } MergeEditLineList& MergeResultWindow::HistoryMapEntry::choice(bool bThreeInputs) { if(!bThreeInputs) return mellA.empty() ? mellB : mellA; else { if(mellA.empty()) return mellC.empty() ? mellB : mellC; // A doesn't exist, return one that exists else if(!mellB.empty() && !mellC.empty()) { // A, B and C exist return mellA; } else return mellB.empty() ? mellB : mellC; // A exists, return the one that doesn't exist } } bool MergeResultWindow::HistoryMapEntry::staysInPlace(bool bThreeInputs, Diff3LineList::const_iterator& iHistoryEnd) { // The entry should stay in place if the decision made by the automerger is correct. Diff3LineList::const_iterator& iHistoryLast = iHistoryEnd; --iHistoryLast; if(!bThreeInputs) { if(!mellA.empty() && !mellB.empty() && mellA.begin()->id3l() == mellB.begin()->id3l() && mellA.back().id3l() == iHistoryLast && mellB.back().id3l() == iHistoryLast) { iHistoryEnd = mellA.begin()->id3l(); return true; } else { return false; } } else { if(!mellA.empty() && !mellB.empty() && !mellC.empty() && mellA.begin()->id3l() == mellB.begin()->id3l() && mellA.begin()->id3l() == mellC.begin()->id3l() && mellA.back().id3l() == iHistoryLast && mellB.back().id3l() == iHistoryLast && mellC.back().id3l() == iHistoryLast) { iHistoryEnd = mellA.begin()->id3l(); return true; } else { return false; } } } void MergeResultWindow::slotMergeHistory() { Diff3LineList::const_iterator iD3LHistoryBegin; Diff3LineList::const_iterator iD3LHistoryEnd; int d3lHistoryBeginLineIdx = -1; int d3lHistoryEndLineIdx = -1; // Search for history start, history end in the diff3LineList m_pDiff3LineList->findHistoryRange(QRegExp(m_pOptions->m_historyStartRegExp), m_pldC != nullptr, iD3LHistoryBegin, iD3LHistoryEnd, d3lHistoryBeginLineIdx, d3lHistoryEndLineIdx); if(iD3LHistoryBegin != m_pDiff3LineList->end()) { // Now collect the historyMap information HistoryMap historyMap; std::list hitList; if(m_pldC == nullptr) { collectHistoryInformation(A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); } else { collectHistoryInformation(A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(C, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); } Diff3LineList::const_iterator iD3LHistoryOrigEnd = iD3LHistoryEnd; bool bHistoryMergeSorting = m_pOptions->m_bHistoryMergeSorting && !m_pOptions->m_historyEntryStartSortKeyOrder.isEmpty() && !m_pOptions->m_historyEntryStartRegExp.isEmpty(); if(m_pOptions->m_maxNofHistoryEntries == -1) { // Remove parts from the historyMap and hitList that stay in place if(bHistoryMergeSorting) { while(!historyMap.empty()) { HistoryMap::iterator hMapIt = historyMap.begin(); if(hMapIt->second.staysInPlace(m_pldC != nullptr, iD3LHistoryEnd)) historyMap.erase(hMapIt); else break; } } else { while(!hitList.empty()) { HistoryMap::iterator hMapIt = hitList.back(); if(hMapIt->second.staysInPlace(m_pldC != nullptr, iD3LHistoryEnd)) hitList.pop_back(); else break; } } while(iD3LHistoryOrigEnd != iD3LHistoryEnd) { --iD3LHistoryOrigEnd; --d3lHistoryEndLineIdx; } } MergeLineList::iterator iMLLStart = splitAtDiff3LineIdx(d3lHistoryBeginLineIdx); MergeLineList::iterator iMLLEnd = splitAtDiff3LineIdx(d3lHistoryEndLineIdx); // Now join all MergeLines in the history MergeLineList::iterator i = iMLLStart; if(i != iMLLEnd) { ++i; while(i != iMLLEnd) { iMLLStart->join(*i); i = m_mergeLineList.erase(i); } } iMLLStart->mergeEditLineList.clear(); // Now insert the complete history into the first MergeLine of the history iMLLStart->mergeEditLineList.push_back(MergeEditLine(iD3LHistoryBegin, m_pldC == nullptr ? B : C)); QString lead = Utils::calcHistoryLead(iD3LHistoryBegin->getString(A)); MergeEditLine mel(m_pDiff3LineList->end()); mel.setString(lead); iMLLStart->mergeEditLineList.push_back(mel); int historyCount = 0; if(bHistoryMergeSorting) { // Create a sorted history HistoryMap::reverse_iterator hmit; for(hmit = historyMap.rbegin(); hmit != historyMap.rend(); ++hmit) { if(historyCount == m_pOptions->m_maxNofHistoryEntries) break; ++historyCount; HistoryMapEntry& hme = hmit->second; MergeEditLineList& mell = hme.choice(m_pldC != nullptr); if(!mell.empty()) iMLLStart->mergeEditLineList.splice(iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end()); } } else { // Create history in order of appearance std::list::iterator hlit; for(hlit = hitList.begin(); hlit != hitList.end(); ++hlit) { if(historyCount == m_pOptions->m_maxNofHistoryEntries) break; ++historyCount; HistoryMapEntry& hme = (*hlit)->second; MergeEditLineList& mell = hme.choice(m_pldC != nullptr); if(!mell.empty()) iMLLStart->mergeEditLineList.splice(iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end()); } // If the end of start is empty and the first line at the end is empty remove the last line of start if(!iMLLStart->mergeEditLineList.empty() && !iMLLEnd->mergeEditLineList.empty()) { QString lastLineOfStart = iMLLStart->mergeEditLineList.back().getString(m_pldA, m_pldB, m_pldC); QString firstLineOfEnd = iMLLEnd->mergeEditLineList.front().getString(m_pldA, m_pldB, m_pldC); if(lastLineOfStart.mid(lead.length()).trimmed().isEmpty() && firstLineOfEnd.mid(lead.length()).trimmed().isEmpty()) iMLLStart->mergeEditLineList.pop_back(); } } setFastSelector(iMLLStart); update(); } } void MergeResultWindow::slotRegExpAutoMerge() { if(m_pOptions->m_autoMergeRegExp.isEmpty()) return; QRegExp vcsKeywords(m_pOptions->m_autoMergeRegExp); MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict) { Diff3LineList::const_iterator id3l = i->id3l; if(vcsKeywords.exactMatch(id3l->getString(A)) && vcsKeywords.exactMatch(id3l->getString(B)) && (m_pldC == nullptr || vcsKeywords.exactMatch(id3l->getString(C)))) { MergeEditLine& mel = *i->mergeEditLineList.begin(); mel.setSource(m_pldC == nullptr ? B : C, false); splitAtDiff3LineIdx(i->d3lLineIdx + 1); } } } update(); } // This doesn't detect user modifications and should only be called after automatic merge // This will only do something for three file merge. // Irrelevant changes are those where all contributions from B are already contained in C. // Also irrelevant are conflicts automatically solved (automerge regexp and history automerge) // Precondition: The VCS-keyword would also be C. bool MergeResultWindow::doRelevantChangesExist() { if(m_pldC == nullptr || m_mergeLineList.size() <= 1) return true; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if((i->bConflict && i->mergeEditLineList.begin()->src() != C) || i->srcSelect == B) { return true; } } return false; } // Returns the iterator to the MergeLine after the split MergeLineList::iterator MergeResultWindow::splitAtDiff3LineIdx(int d3lLineIdx) { MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->d3lLineIdx == d3lLineIdx) { // No split needed, this is the beginning of a MergeLine return i; } else if(i->d3lLineIdx > d3lLineIdx) { // The split must be in the previous MergeLine --i; MergeLine& ml = *i; MergeLine newML; ml.split(newML, d3lLineIdx); ++i; return m_mergeLineList.insert(i, newML); } } // The split must be in the previous MergeLine --i; MergeLine& ml = *i; MergeLine newML; ml.split(newML, d3lLineIdx); ++i; return m_mergeLineList.insert(i, newML); } void MergeResultWindow::slotSplitDiff(int firstD3lLineIdx, int lastD3lLineIdx) { if(lastD3lLineIdx >= 0) splitAtDiff3LineIdx(lastD3lLineIdx + 1); setFastSelector(splitAtDiff3LineIdx(firstD3lLineIdx)); } void MergeResultWindow::slotJoinDiffs(int firstD3lLineIdx, int lastD3lLineIdx) { MergeLineList::iterator i; MergeLineList::iterator iMLLStart = m_mergeLineList.end(); MergeLineList::iterator iMLLEnd = m_mergeLineList.end(); for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { MergeLine& ml = *i; if(firstD3lLineIdx >= ml.d3lLineIdx && firstD3lLineIdx < ml.d3lLineIdx + ml.srcRangeLength) { iMLLStart = i; } if(lastD3lLineIdx >= ml.d3lLineIdx && lastD3lLineIdx < ml.d3lLineIdx + ml.srcRangeLength) { iMLLEnd = i; ++iMLLEnd; break; } } bool bJoined = false; for(i = iMLLStart; i != iMLLEnd && i != m_mergeLineList.end();) { if(i == iMLLStart) { ++i; } else { iMLLStart->join(*i); i = m_mergeLineList.erase(i); bJoined = true; } } if(bJoined) { iMLLStart->mergeEditLineList.clear(); // Insert a conflict line as placeholder iMLLStart->mergeEditLineList.push_back(MergeEditLine(iMLLStart->id3l)); } setFastSelector(iMLLStart); } void MergeResultWindow::myUpdate(int afterMilliSecs) { if(m_delayedDrawTimer) killTimer(m_delayedDrawTimer); m_bMyUpdate = true; m_delayedDrawTimer = startTimer(afterMilliSecs); } void MergeResultWindow::timerEvent(QTimerEvent*) { killTimer(m_delayedDrawTimer); m_delayedDrawTimer = 0; if(m_bMyUpdate) { update(); m_bMyUpdate = false; } if(m_scrollDeltaX != 0 || m_scrollDeltaY != 0) { m_selection.end(m_selection.getLastLine() + m_scrollDeltaY, m_selection.getLastPos() + m_scrollDeltaX); Q_EMIT scrollMergeResultWindow(m_scrollDeltaX, m_scrollDeltaY); killTimer(m_delayedDrawTimer); m_delayedDrawTimer = startTimer(50); } } QVector MergeResultWindow::getTextLayoutForLine(int line, const QString& str, QTextLayout& textLayout) { // tabs QTextOption textOption; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) textOption.setTabStop(QFontMetricsF(font()).width(' ') * m_pOptions->m_tabSize); #else textOption.setTabStopDistance(QFontMetricsF(font()).width(' ') * m_pOptions->m_tabSize); #endif if(m_pOptions->m_bShowWhiteSpaceCharacters) { textOption.setFlags(QTextOption::ShowTabsAndSpaces); } textLayout.setTextOption(textOption); if(m_pOptions->m_bShowWhiteSpaceCharacters) { // This additional format is only necessary for the tab arrow QVector formats; QTextLayout::FormatRange formatRange; formatRange.start = 0; formatRange.length = str.length(); formatRange.format.setFont(font()); formats.append(formatRange); textLayout.setFormats(formats); } QVector selectionFormat; textLayout.beginLayout(); if(m_selection.lineWithin(line)) { int firstPosInText = m_selection.firstPosInLine(line); int lastPosInText = m_selection.lastPosInLine(line); int lengthInText = std::max(0, lastPosInText - firstPosInText); if(lengthInText > 0) m_selection.bSelectionContainsData = true; QTextLayout::FormatRange selection; selection.start = firstPosInText; selection.length = lengthInText; selection.format.setBackground(palette().highlight()); selection.format.setForeground(palette().highlightedText().color()); selectionFormat.push_back(selection); } QTextLine textLine = textLayout.createLine(); textLine.setPosition(QPointF(0, fontMetrics().leading())); textLayout.endLayout(); int cursorWidth = 5; if(m_pOptions->m_bRightToLeftLanguage) textLayout.setPosition(QPointF(width() - textLayout.maximumWidth() - getTextXOffset() + m_horizScrollOffset - cursorWidth, 0)); else textLayout.setPosition(QPointF(getTextXOffset() - m_horizScrollOffset, 0)); return selectionFormat; } void MergeResultWindow::writeLine( RLPainter& p, int line, const QString& str, int srcSelect, e_MergeDetails mergeDetails, int rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int fontAscent = fm.ascent(); int topLineYOffset = 0; int xOffset = getTextXOffset(); int yOffset = (line - m_firstLine) * fontHeight; if(yOffset < 0 || yOffset > height()) return; yOffset += topLineYOffset; QString srcName = QChar(' '); if(bUserModified) srcName = QChar('m'); else if(srcSelect == A && mergeDetails != eNoChange) srcName = i18n("A"); else if(srcSelect == B) srcName = i18n("B"); else if(srcSelect == C) srcName = i18n("C"); if(rangeMark & 4) { p.fillRect(xOffset, yOffset, width(), fontHeight, m_pOptions->m_currentRangeBgColor); } if((srcSelect > 0 || bUserModified) && !bLineRemoved) { if(!m_pOptions->m_bRightToLeftLanguage) p.setClipRect(QRectF(xOffset, 0, width() - xOffset, height())); else p.setClipRect(QRectF(0, 0, width() - xOffset, height())); int outPos = 0; QString s; int size = str.length(); for(int i = 0; i < size; ++i) { int spaces = 1; if(str[i] == '\t') { spaces = tabber(outPos, m_pOptions->m_tabSize); for(int j = 0; j < spaces; ++j) s += ' '; } else { s += str[i]; } outPos += spaces; } p.setPen(m_pOptions->m_fgColor); QTextLayout textLayout(str, font(), this); QVector selectionFormat = getTextLayoutForLine(line, str, textLayout); textLayout.draw(&p, QPointF(0, yOffset), selectionFormat); if(line == m_cursorYPos) { m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(m_cursorXPos)); if(m_pOptions->m_bRightToLeftLanguage) m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); } p.setClipping(false); p.setPen(m_pOptions->m_fgColor); p.drawText(1, yOffset + fontAscent, srcName, true); } else if(bLineRemoved) { p.setPen(m_pOptions->m_colorForConflict); p.drawText(xOffset, yOffset + fontAscent, i18n("")); p.drawText(1, yOffset + fontAscent, srcName); if(m_cursorYPos == line) m_cursorXPos = 0; } else if(srcSelect == 0) { p.setPen(m_pOptions->m_colorForConflict); if(bWhiteSpaceConflict) p.drawText(xOffset, yOffset + fontAscent, i18n("")); else p.drawText(xOffset, yOffset + fontAscent, i18n("")); p.drawText(1, yOffset + fontAscent, "?"); if(m_cursorYPos == line) m_cursorXPos = 0; } else Q_ASSERT(true); xOffset -= Utils::getHorizontalAdvance(fm, '0'); p.setPen(m_pOptions->m_fgColor); if(rangeMark & 1) // begin mark { p.drawLine(xOffset, yOffset + 1, xOffset, yOffset + fontHeight / 2); p.drawLine(xOffset, yOffset + 1, xOffset - 2, yOffset + 1); } else { p.drawLine(xOffset, yOffset, xOffset, yOffset + fontHeight / 2); } if(rangeMark & 2) // end mark { p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight - 1); p.drawLine(xOffset, yOffset + fontHeight - 1, xOffset - 2, yOffset + fontHeight - 1); } else { p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight); } if(rangeMark & 4) { p.fillRect(xOffset + 3, yOffset, 3, fontHeight, m_pOptions->m_fgColor); /* p.setPen( blue ); p.drawLine( xOffset+2, yOffset, xOffset+2, yOffset+fontHeight-1 ); p.drawLine( xOffset+3, yOffset, xOffset+3, yOffset+fontHeight-1 );*/ } } void MergeResultWindow::setPaintingAllowed(bool bPaintingAllowed) { setUpdatesEnabled(bPaintingAllowed); if(!bPaintingAllowed) { m_currentMergeLineIt = m_mergeLineList.end(); reset(); } else update(); } void MergeResultWindow::paintEvent(QPaintEvent*) { if(m_pDiff3LineList == nullptr) return; bool bOldSelectionContainsData = m_selection.selectionContainsData(); const QFontMetrics& fm = fontMetrics(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); if(!m_bCursorUpdate) // Don't redraw everything for blinking cursor? { m_selection.bSelectionContainsData = false; if(size() != m_pixmap.size()) m_pixmap = QPixmap(size()); RLPainter p(&m_pixmap, m_pOptions->m_bRightToLeftLanguage, width(), fontWidth); p.setFont(font()); p.QPainter::fillRect(rect(), m_pOptions->m_bgColor); //int visibleLines = height() / fontHeight; int lastVisibleLine = m_firstLine + getNofVisibleLines() + 5; LineRef line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; if(line > lastVisibleLine || line + ml.mergeEditLineList.size() < m_firstLine) { line += ml.mergeEditLineList.size(); } else { MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { if(line >= m_firstLine && line <= lastVisibleLine) { MergeEditLine& mel = *melIt; MergeEditLineList::iterator melIt1 = melIt; ++melIt1; int rangeMark = 0; if(melIt == ml.mergeEditLineList.begin()) rangeMark |= 1; // Begin range mark if(melIt1 == ml.mergeEditLineList.end()) rangeMark |= 2; // End range mark if(mlIt == m_currentMergeLineIt) rangeMark |= 4; // Mark of the current line QString s; s = mel.getString(m_pldA, m_pldB, m_pldC); writeLine(p, line, s, mel.src(), ml.mergeDetails, rangeMark, mel.isModified(), mel.isRemoved(), ml.bWhiteSpaceConflict); } ++line; } } } if(line != m_nofLines) { m_nofLines = line; Q_EMIT resizeSignal(); } p.end(); } QPainter painter(this); if(!m_bCursorUpdate) painter.drawPixmap(0, 0, m_pixmap); else { painter.drawPixmap(0, 0, m_pixmap); // Draw everything. (Internally cursor rect is clipped anyway.) m_bCursorUpdate = false; } if(m_bCursorOn && hasFocus() && m_cursorYPos >= m_firstLine) { painter.setPen(m_pOptions->m_fgColor); QString str = getString(m_cursorYPos); QTextLayout textLayout(str, font(), this); getTextLayoutForLine(m_cursorYPos, str, textLayout); textLayout.drawCursor(&painter, QPointF(0, (m_cursorYPos - m_firstLine) * fontMetrics().lineSpacing()), m_cursorXPos); } painter.end(); if(!bOldSelectionContainsData && m_selection.selectionContainsData()) Q_EMIT newSelection(); } void MergeResultWindow::updateSourceMask() { int srcMask = 0; int enabledMask = 0; if(!hasFocus() || m_pDiff3LineList == nullptr || !updatesEnabled() || m_currentMergeLineIt == m_mergeLineList.end()) { srcMask = 0; enabledMask = 0; } else { enabledMask = m_pldC == nullptr ? 3 : 7; MergeLine& ml = *m_currentMergeLineIt; srcMask = 0; bool bModified = false; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.src() == A) srcMask |= 1; if(mel.src() == B) srcMask |= 2; if(mel.src() == C) srcMask |= 4; if(mel.isModified() || !mel.isEditableText()) bModified = true; } if(ml.mergeDetails == eNoChange) { srcMask = 0; enabledMask = bModified ? 1 : 0; } } Q_EMIT sourceMask(srcMask, enabledMask); } void MergeResultWindow::focusInEvent(QFocusEvent* e) { updateSourceMask(); QWidget::focusInEvent(e); } LineRef MergeResultWindow::convertToLine(int y) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int topLineYOffset = 0; int yOffset = topLineYOffset - m_firstLine * fontHeight; LineRef line = std::min((y - yOffset) / fontHeight, m_nofLines - 1); return line; } void MergeResultWindow::mousePressEvent(QMouseEvent* e) { m_bCursorOn = true; int xOffset = getTextXOffset(); LineRef line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); QtNumberType pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); bool bLMB = e->button() == Qt::LeftButton; bool bMMB = e->button() == Qt::MidButton; bool bRMB = e->button() == Qt::RightButton; if((bLMB && (e->x() < xOffset)) || bRMB) // Fast range selection { m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = std::max((LineRef::LineType)line, 0); int l = 0; MergeLineList::iterator i = m_mergeLineList.begin(); for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(l == line) break; l += i->mergeEditLineList.size(); if(l > line) break; } m_selection.reset(); // Disable current selection m_bCursorOn = true; setFastSelector(i); if(bRMB) { Q_EMIT showPopupMenu(QCursor::pos()); } } else if(bLMB) // Normal cursor placement { pos = std::max(pos, 0); line = std::max((LineRef::LineType)line, 0); if(e->QInputEvent::modifiers() & Qt::ShiftModifier) { if(!m_selection.isValidFirstLine()) m_selection.start(line, pos); m_selection.end(line, pos); } else { // Selection m_selection.reset(); m_selection.start(line, pos); m_selection.end(line, pos); } m_cursorXPos = pos; m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(pos)); if(m_pOptions->m_bRightToLeftLanguage) m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; update(); //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar ); } else if(bMMB) // Paste clipboard { pos = std::max(pos, 0); line = std::max((LineRef::LineType)line, 0); m_selection.reset(); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; pasteClipboard(true); } } void MergeResultWindow::mouseDoubleClickEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { LineRef line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; if(!s.isEmpty()) { int pos1, pos2; Utils::calcTokenPos(s, pos, pos1, pos2); resetSelection(); m_selection.start(line, pos1); m_selection.end(line, pos2); update(); // Q_EMIT selectionEnd() happens in the mouseReleaseEvent. } } } void MergeResultWindow::mouseReleaseEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { if(m_delayedDrawTimer) { killTimer(m_delayedDrawTimer); m_delayedDrawTimer = 0; } if(m_selection.isValidFirstLine()) { Q_EMIT selectionEnd(); } } } void MergeResultWindow::mouseMoveEvent(QMouseEvent* e) { LineRef line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; if(m_selection.isValidFirstLine()) { m_selection.end(line, pos); myUpdate(0); //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar ); // Scroll because mouse moved out of the window const QFontMetrics& fm = fontMetrics(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); int topLineYOffset = 0; int deltaX = 0; int deltaY = 0; if(!m_pOptions->m_bRightToLeftLanguage) { if(e->x() < getTextXOffset()) deltaX = -1; if(e->x() > width()) deltaX = +1; } else { if(e->x() > width() - 1 - getTextXOffset()) deltaX = -1; if(e->x() < fontWidth) deltaX = +1; } if(e->y() < topLineYOffset) deltaY = -1; if(e->y() > height()) deltaY = +1; m_scrollDeltaX = deltaX; m_scrollDeltaY = deltaY; if(deltaX != 0 || deltaY != 0) { Q_EMIT scrollMergeResultWindow(deltaX, deltaY); } } } void MergeResultWindow::slotCursorUpdate() { m_cursorTimer.stop(); m_bCursorOn = !m_bCursorOn; if(isVisible()) { m_bCursorUpdate = true; const QFontMetrics& fm = fontMetrics(); int topLineYOffset = 0; int yOffset = (m_cursorYPos - m_firstLine) * fm.lineSpacing() + topLineYOffset; repaint(0, yOffset, width(), fm.lineSpacing() + 2); m_bCursorUpdate = false; } m_cursorTimer.start(500); } void MergeResultWindow::wheelEvent(QWheelEvent* e) { int d = -e->delta() * QApplication::wheelScrollLines() / 120; e->accept(); Q_EMIT scrollMergeResultWindow(0, std::min(d, getNofVisibleLines())); } bool MergeResultWindow::event(QEvent* e) { if(e->type() == QEvent::KeyPress) { QKeyEvent* ke = static_cast(e); if(ke->key() == Qt::Key_Tab) { // special tab handling here to avoid moving focus keyPressEvent(ke); return true; } } return QWidget::event(e); } void MergeResultWindow::keyPressEvent(QKeyEvent* e) { int y = m_cursorYPos; MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt; calcIteratorFromLineNr(y, mlIt, melIt); QString str = melIt->getString(m_pldA, m_pldB, m_pldC); int x = m_cursorXPos; QTextLayout textLayoutOrig(str, font(), this); getTextLayoutForLine(y, str, textLayoutOrig); bool bCtrl = (e->QInputEvent::modifiers() & Qt::ControlModifier) != 0; bool bShift = (e->QInputEvent::modifiers() & Qt::ShiftModifier) != 0; #ifdef Q_OS_WIN bool bAlt = (e->QInputEvent::modifiers() & Qt::AltModifier) != 0; if(bCtrl && bAlt) { bCtrl = false; bAlt = false; } // AltGr-Key pressed. #endif bool bYMoveKey = false; // Special keys switch(e->key()) { case Qt::Key_Escape: //case Key_Tab: break; case Qt::Key_Backtab: break; case Qt::Key_Delete: { if(deleteSelection2(str, x, y, mlIt, melIt) || !melIt->isEditableText()) break; if(x >= str.length()) { if(y < m_nofLines - 1) { setModified(); MergeLineList::iterator mlIt1; MergeEditLineList::iterator melIt1; calcIteratorFromLineNr(y + 1, mlIt1, melIt1); if(melIt1->isEditableText()) { QString s2 = melIt1->getString(m_pldA, m_pldB, m_pldC); melIt->setString(str + s2); // Remove the line if(mlIt1->mergeEditLineList.size() > 1) mlIt1->mergeEditLineList.erase(melIt1); else melIt1->setRemoved(); } } } else { QString s = str.left(x); s += str.midRef(x + 1); melIt->setString(s); setModified(); } break; } case Qt::Key_Backspace: { if(deleteSelection2(str, x, y, mlIt, melIt)) break; if(!melIt->isEditableText()) break; if(x == 0) { if(y > 0) { setModified(); MergeLineList::iterator mlIt1; MergeEditLineList::iterator melIt1; calcIteratorFromLineNr(y - 1, mlIt1, melIt1); if(melIt1->isEditableText()) { QString s1 = melIt1->getString(m_pldA, m_pldB, m_pldC); melIt1->setString(s1 + str); // Remove the previous line if(mlIt->mergeEditLineList.size() > 1) mlIt->mergeEditLineList.erase(melIt); else melIt->setRemoved(); --y; x = str.length(); } } } else { QString s = str.left(x - 1); s += str.midRef(x); --x; melIt->setString(s); setModified(); } break; } case Qt::Key_Return: case Qt::Key_Enter: { if(!melIt->isEditableText()) break; deleteSelection2(str, x, y, mlIt, melIt); setModified(); QString indentation; if(m_pOptions->m_bAutoIndentation) { // calc last indentation MergeLineList::iterator mlIt1 = mlIt; MergeEditLineList::iterator melIt1 = melIt; for(;;) { const QString s = melIt1->getString(m_pldA, m_pldB, m_pldC); if(!s.isEmpty()) { int i; for(i = 0; i < s.length(); ++i) { if(s[i] != ' ' && s[i] != '\t') break; } if(i < s.length()) { indentation = s.left(i); break; } } // Go back one line if(melIt1 != mlIt1->mergeEditLineList.begin()) --melIt1; else { if(mlIt1 == m_mergeLineList.begin()) break; --mlIt1; melIt1 = mlIt1->mergeEditLineList.end(); --melIt1; } } } MergeEditLine mel(mlIt->id3l); // Associate every mel with an id3l, even if not really valid. mel.setString(indentation + str.mid(x)); if(x < str.length()) // Cut off the old line. { // Since ps possibly points into melIt->str, first copy it into a temporary. QString temp = str.left(x); melIt->setString(temp); } ++melIt; mlIt->mergeEditLineList.insert(melIt, mel); x = indentation.length(); ++y; break; } case Qt::Key_Insert: m_bInsertMode = !m_bInsertMode; break; case Qt::Key_Pause: case Qt::Key_Print: case Qt::Key_SysReq: break; case Qt::Key_Home: x = 0; if(bCtrl) { y = 0; } break; // cursor movement case Qt::Key_End: x = INT_MAX; if(bCtrl) { y = INT_MAX; } break; case Qt::Key_Left: case Qt::Key_Right: if((e->key() == Qt::Key_Left) != m_pOptions->m_bRightToLeftLanguage) { if(!bCtrl) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x && y > 0) { --y; x = INT_MAX; } else { x = newX; } } else { while(x > 0 && (str[x - 1] == ' ' || str[x - 1] == '\t')) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x) break; x = newX; } while(x > 0 && (str[x - 1] != ' ' && str[x - 1] != '\t')) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x) break; x = newX; } } } else { if(!bCtrl) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x && y < m_nofLines - 1) { ++y; x = 0; } else { x = newX; } } else { while(x < str.length() && (str[x] == ' ' || str[x] == '\t')) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x) break; x = newX; } while(x < str.length() && (str[x] != ' ' && str[x] != '\t')) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x) break; x = newX; } } } break; case Qt::Key_Up: if(!bCtrl) { --y; bYMoveKey = true; } break; case Qt::Key_Down: if(!bCtrl) { ++y; bYMoveKey = true; } break; case Qt::Key_PageUp: if(!bCtrl) { y -= getNofVisibleLines(); bYMoveKey = true; } break; case Qt::Key_PageDown: if(!bCtrl) { y += getNofVisibleLines(); bYMoveKey = true; } break; default: { QString t = e->text(); if(t.isEmpty() || bCtrl) { e->ignore(); return; } else { if(bCtrl) { e->ignore(); return; } else { if(!melIt->isEditableText()) break; deleteSelection2(str, x, y, mlIt, melIt); setModified(); // Characters to insert QString s = str; if(t[0] == '\t' && m_pOptions->m_bReplaceTabs) { int spaces = (m_cursorXPos / m_pOptions->m_tabSize + 1) * m_pOptions->m_tabSize - m_cursorXPos; t.fill(' ', spaces); } if(m_bInsertMode) s.insert(x, t); else s.replace(x, t.length(), t); melIt->setString(s); x += t.length(); bShift = false; } } } } y = qBound(0, y, m_nofLines - 1); calcIteratorFromLineNr(y, mlIt, melIt); str = melIt->getString(m_pldA, m_pldB, m_pldC); x = qBound(0, x, (int)str.length()); int newFirstLine = m_firstLine; int newHorizScrollOffset = m_horizScrollOffset; if(y < m_firstLine) newFirstLine = y; else if(y > m_firstLine + getNofVisibleLines()) newFirstLine = y - getNofVisibleLines(); QTextLayout textLayout(str, font(), this); getTextLayoutForLine(m_cursorYPos, str, textLayout); // try to preserve cursor x pixel position when moving to another line if(bYMoveKey) { if(m_pOptions->m_bRightToLeftLanguage) x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos - (textLayout.position().x() - m_horizScrollOffset)); else x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos); } m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(x)); int hF = 1; // horizontal factor if(m_pOptions->m_bRightToLeftLanguage) { m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); hF = -1; } int cursorWidth = 5; if(m_cursorXPixelPos < hF * m_horizScrollOffset) newHorizScrollOffset = hF * m_cursorXPixelPos; else if(m_cursorXPixelPos > hF * m_horizScrollOffset + getVisibleTextAreaWidth() - cursorWidth) newHorizScrollOffset = hF * (m_cursorXPixelPos - (getVisibleTextAreaWidth() - cursorWidth)); int newCursorX = x; if(bShift) { if(!m_selection.isValidFirstLine()) m_selection.start(m_cursorYPos, m_cursorXPos); m_selection.end(y, newCursorX); } else m_selection.reset(); m_cursorYPos = y; m_cursorXPos = newCursorX; // TODO if width of current line exceeds the current maximum width then force recalculating the scrollbars if(textLayout.maximumWidth() > getMaxTextWidth()) { m_maxTextWidth = qCeil(textLayout.maximumWidth()); Q_EMIT resizeSignal(); } if(!bYMoveKey) m_cursorOldXPixelPos = m_cursorXPixelPos; m_bCursorOn = true; m_cursorTimer.start(500); update(); if(newFirstLine != m_firstLine || newHorizScrollOffset != m_horizScrollOffset) { Q_EMIT scrollMergeResultWindow(newHorizScrollOffset - m_horizScrollOffset, newFirstLine - m_firstLine); return; } } void MergeResultWindow::calcIteratorFromLineNr( int line, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt) { for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; if(line > ml.mergeEditLineList.size()) { line -= ml.mergeEditLineList.size(); } else { for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { --line; if(line < 0) return; } } } } QString MergeResultWindow::getSelection() { QString selectionString; int line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(m_selection.lineWithin(line)) { int outPos = 0; if(mel.isEditableText()) { const QString str = mel.getString(m_pldA, m_pldB, m_pldC); // Consider tabs for(int i = 0; i < str.length(); ++i) { int spaces = 1; if(str[i] == '\t') { spaces = tabber(outPos, m_pOptions->m_tabSize); } if(m_selection.within(line, outPos)) { selectionString += str[i]; } outPos += spaces; } } else if(mel.isConflict()) { selectionString += i18n(""); } if(m_selection.within(line, outPos)) { #ifdef Q_OS_WIN selectionString += '\r'; #endif selectionString += '\n'; } } ++line; } } return selectionString; } bool MergeResultWindow::deleteSelection2(QString& s, int& x, int& y, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt) { if(m_selection.selectionContainsData()) { Q_ASSERT(m_selection.isValidFirstLine()); deleteSelection(); y = m_cursorYPos; calcIteratorFromLineNr(y, mlIt, melIt); s = melIt->getString(m_pldA, m_pldB, m_pldC); x = m_cursorXPos; return true; } return false; } void MergeResultWindow::deleteSelection() { if(!m_selection.selectionContainsData()) { return; } Q_ASSERT(m_selection.isValidFirstLine()); setModified(); LineRef line = 0; MergeLineList::iterator mlItFirst; MergeEditLineList::iterator melItFirst; QString firstLineString; LineRef firstLine; LineRef lastLine; MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.isEditableText() && m_selection.lineWithin(line)) { if(!firstLine.isValid()) firstLine = line; lastLine = line; } ++line; } } if(!firstLine.isValid()) { return; // Nothing to delete. } line = 0; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt, melIt1; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; melIt1 = melIt; ++melIt1; if(mel.isEditableText() && m_selection.lineWithin(line)) { QString lineString = mel.getString(m_pldA, m_pldB, m_pldC); int firstPosInLine = m_selection.firstPosInLine(line); int lastPosInLine = m_selection.lastPosInLine(line); if(line == firstLine) { mlItFirst = mlIt; melItFirst = melIt; int pos = firstPosInLine; firstLineString = lineString.left(pos); } if(line == lastLine) { // This is the last line in the selection int pos = lastPosInLine; firstLineString += lineString.midRef(pos); // rest of line melItFirst->setString(firstLineString); } if(line != firstLine || (m_selection.endPos() - m_selection.beginPos()) == lineString.length()) { // Remove the line if(mlIt->mergeEditLineList.size() > 1) mlIt->mergeEditLineList.erase(melIt); else melIt->setRemoved(); } } ++line; melIt = melIt1; } } m_cursorYPos = m_selection.beginLine(); m_cursorXPos = m_selection.beginPos(); m_cursorOldXPixelPos = m_cursorXPixelPos; m_selection.reset(); } void MergeResultWindow::pasteClipboard(bool bFromSelection) { //checking of m_selection if needed is done by deleteSelection no need for check here. deleteSelection(); setModified(); int y = m_cursorYPos; MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt, melItAfter; calcIteratorFromLineNr(y, mlIt, melIt); melItAfter = melIt; ++melItAfter; QString str = melIt->getString(m_pldA, m_pldB, m_pldC); int x = m_cursorXPos; if(!QApplication::clipboard()->supportsSelection()) bFromSelection = false; QString clipBoard = QApplication::clipboard()->text(bFromSelection ? QClipboard::Selection : QClipboard::Clipboard); QString currentLine = str.left(x); QString endOfLine = str.mid(x); int i; int len = clipBoard.length(); for(i = 0; i < len; ++i) { QChar c = clipBoard[i]; if(c == '\r') continue; if(c == '\n') { melIt->setString(currentLine); MergeEditLine mel(mlIt->id3l); // Associate every mel with an id3l, even if not really valid. melIt = mlIt->mergeEditLineList.insert(melItAfter, mel); currentLine = ""; x = 0; ++y; } else { currentLine += c; ++x; } } currentLine += endOfLine; melIt->setString(currentLine); m_cursorYPos = y; m_cursorXPos = x; m_cursorOldXPixelPos = m_cursorXPixelPos; update(); } void MergeResultWindow::resetSelection() { m_selection.reset(); update(); } void MergeResultWindow::setModified(bool bModified) { if(bModified != m_bModified) { m_bModified = bModified; Q_EMIT modifiedChanged(m_bModified); } } /// Saves and returns true when successful. bool MergeResultWindow::saveDocument(const QString& fileName, QTextCodec* pEncoding, e_LineEndStyle eLineEndStyle) { // Are still conflicts somewhere? if(getNrOfUnsolvedConflicts() > 0) { KMessageBox::error(this, i18n("Not all conflicts are solved yet.\n" "File not saved."), i18n("Conflicts Left")); return false; } if(eLineEndStyle == eLineEndStyleConflict || eLineEndStyle == eLineEndStyleUndefined) { KMessageBox::error(this, i18n("There is a line end style conflict. Please choose the line end style manually.\n" "File not saved."), i18n("Conflicts Left")); return false; } update(); FileAccess file(fileName, true /*bWantToWrite*/); if(m_pOptions->m_bDmCreateBakFiles && file.exists()) { bool bSuccess = file.createBackup(".orig"); if(!bSuccess) { KMessageBox::error(this, file.getStatusText() + i18n("\n\nCreating backup failed. File not saved."), i18n("File Save Error")); return false; } } QByteArray dataArray; QTextStream textOutStream(&dataArray, QIODevice::WriteOnly); if(pEncoding->name() == "UTF-8") textOutStream.setGenerateByteOrderMark(false); // Shouldn't be necessary. Bug in Qt or docs else textOutStream.setGenerateByteOrderMark(true); // Only for UTF-16 textOutStream.setCodec(pEncoding); int line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.isEditableText()) { QString str = mel.getString(m_pldA, m_pldB, m_pldC); if(line > 0) // Prepend line feed, but not for first line { if(eLineEndStyle == eLineEndStyleDos) { str.prepend("\r\n"); } else { str.prepend("\n"); } } textOutStream << str; ++line; } } } textOutStream.flush(); bool bSuccess = file.writeFile(dataArray.data(), dataArray.size()); if(!bSuccess) { KMessageBox::error(this, i18n("Error while writing."), i18n("File Save Error")); return false; } setModified(false); update(); return true; } QString MergeResultWindow::getString(int lineIdx) { MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt; if(m_mergeLineList.empty()) { return QString(); } calcIteratorFromLineNr(lineIdx, mlIt, melIt); QString s = melIt->getString(m_pldA, m_pldB, m_pldC); return s; } bool MergeResultWindow::findString(const QString& s, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive) { int it = d3vLine; int endIt = bDirDown ? getNofLines() : -1; int step = bDirDown ? 1 : -1; int startPos = posInLine; for(; it != endIt; it += step) { QString line = getString(it); if(!line.isEmpty()) { int pos = line.indexOf(s, startPos, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); if(pos != -1) { d3vLine = it; posInLine = pos; return true; } startPos = 0; } } return false; } void MergeResultWindow::setSelection(int firstLine, int startPos, int lastLine, int endPos) { if(lastLine >= getNofLines()) { lastLine = getNofLines() - 1; QString s = getString(lastLine); endPos = s.length(); } m_selection.reset(); m_selection.start(firstLine, startPos); m_selection.end(lastLine, endPos); update(); } WindowTitleWidget::WindowTitleWidget(const QSharedPointer &pOptions) { m_pOptions = pOptions; setAutoFillBackground(true); QHBoxLayout* pHLayout = new QHBoxLayout(this); pHLayout->setMargin(2); pHLayout->setSpacing(2); m_pLabel = new QLabel(i18n("Output:")); pHLayout->addWidget(m_pLabel); m_pFileNameLineEdit = new FileNameLineEdit(); pHLayout->addWidget(m_pFileNameLineEdit, 6); m_pFileNameLineEdit->installEventFilter(this);//for focus tracking m_pFileNameLineEdit->setAcceptDrops(true); m_pFileNameLineEdit->setReadOnly(true); //m_pBrowseButton = new QPushButton("..."); //pHLayout->addWidget( m_pBrowseButton, 0 ); //connect( m_pBrowseButton, &QPushButton::clicked), this, &MergeResultWindow::slotBrowseButtonClicked); m_pModifiedLabel = new QLabel(i18n("[Modified]")); pHLayout->addWidget(m_pModifiedLabel); m_pModifiedLabel->setMinimumSize(m_pModifiedLabel->sizeHint()); m_pModifiedLabel->setText(""); pHLayout->addStretch(1); m_pEncodingLabel = new QLabel(i18n("Encoding for saving:")); pHLayout->addWidget(m_pEncodingLabel); m_pEncodingSelector = new QComboBox(); m_pEncodingSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); pHLayout->addWidget(m_pEncodingSelector, 2); setEncodings(nullptr, nullptr, nullptr); m_pLineEndStyleLabel = new QLabel(i18n("Line end style:")); pHLayout->addWidget(m_pLineEndStyleLabel); m_pLineEndStyleSelector = new QComboBox(); m_pLineEndStyleSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); pHLayout->addWidget(m_pLineEndStyleSelector); setLineEndStyles(eLineEndStyleUndefined, eLineEndStyleUndefined, eLineEndStyleUndefined); } void WindowTitleWidget::setFileName(const QString& fileName) { m_pFileNameLineEdit->setText(QDir::toNativeSeparators(fileName)); } QString WindowTitleWidget::getFileName() { return m_pFileNameLineEdit->text(); } //static QString getLineEndStyleName( e_LineEndStyle eLineEndStyle ) //{ // if ( eLineEndStyle == eLineEndStyleDos ) // return "DOS"; // else if ( eLineEndStyle == eLineEndStyleUnix ) // return "Unix"; // return QString(); //} void WindowTitleWidget::setLineEndStyles(e_LineEndStyle eLineEndStyleA, e_LineEndStyle eLineEndStyleB, e_LineEndStyle eLineEndStyleC) { m_pLineEndStyleSelector->clear(); QString dosUsers; if(eLineEndStyleA == eLineEndStyleDos) dosUsers += i18n("A"); if(eLineEndStyleB == eLineEndStyleDos) dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + i18n("B"); if(eLineEndStyleC == eLineEndStyleDos) dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + i18n("C"); QString unxUsers; if(eLineEndStyleA == eLineEndStyleUnix) unxUsers += i18n("A"); if(eLineEndStyleB == eLineEndStyleUnix) unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + i18n("B"); if(eLineEndStyleC == eLineEndStyleUnix) unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + i18n("C"); m_pLineEndStyleSelector->addItem(i18n("Unix") + (unxUsers.isEmpty() ? QString("") : QLatin1String(" (") + unxUsers + QLatin1String(")"))); m_pLineEndStyleSelector->addItem(i18n("DOS") + (dosUsers.isEmpty() ? QString("") : QLatin1String(" (") + dosUsers + QLatin1String(")"))); e_LineEndStyle autoChoice = (e_LineEndStyle)m_pOptions->m_lineEndStyle; if(m_pOptions->m_lineEndStyle == eLineEndStyleAutoDetect) { if(eLineEndStyleA != eLineEndStyleUndefined && eLineEndStyleB != eLineEndStyleUndefined && eLineEndStyleC != eLineEndStyleUndefined) { if(eLineEndStyleA == eLineEndStyleB) autoChoice = eLineEndStyleC; else if(eLineEndStyleA == eLineEndStyleC) autoChoice = eLineEndStyleB; else autoChoice = eLineEndStyleConflict; //conflict (not likely while only two values exist) } else { e_LineEndStyle c1, c2; if(eLineEndStyleA == eLineEndStyleUndefined) { c1 = eLineEndStyleB; c2 = eLineEndStyleC; } else if(eLineEndStyleB == eLineEndStyleUndefined) { c1 = eLineEndStyleA; c2 = eLineEndStyleC; } else /*if( eLineEndStyleC == eLineEndStyleUndefined )*/ { c1 = eLineEndStyleA; c2 = eLineEndStyleB; } if(c1 == c2 && c1 != eLineEndStyleUndefined) autoChoice = c1; else autoChoice = eLineEndStyleConflict; } } if(autoChoice == eLineEndStyleUnix) m_pLineEndStyleSelector->setCurrentIndex(0); else if(autoChoice == eLineEndStyleDos) m_pLineEndStyleSelector->setCurrentIndex(1); else if(autoChoice == eLineEndStyleConflict) { m_pLineEndStyleSelector->addItem(i18n("Conflict")); m_pLineEndStyleSelector->setCurrentIndex(2); } } e_LineEndStyle WindowTitleWidget::getLineEndStyle() { int current = m_pLineEndStyleSelector->currentIndex(); if(current == 0) return eLineEndStyleUnix; else if(current == 1) return eLineEndStyleDos; else return eLineEndStyleConflict; } void WindowTitleWidget::setEncodings(QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC) { m_pEncodingSelector->clear(); // First sort codec names: std::map names; QList mibs = QTextCodec::availableMibs(); for(int i: mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) names[QLatin1String(c->name())] = c; } if(pCodecForA != nullptr) m_pEncodingSelector->addItem(i18n("Codec from A: %1", QLatin1String(pCodecForA->name())), QVariant::fromValue((void*)pCodecForA)); if(pCodecForB != nullptr) m_pEncodingSelector->addItem(i18n("Codec from B: %1", QLatin1String(pCodecForB->name())), QVariant::fromValue((void*)pCodecForB)); if(pCodecForC != nullptr) m_pEncodingSelector->addItem(i18n("Codec from C: %1", QLatin1String(pCodecForC->name())), QVariant::fromValue((void*)pCodecForC)); std::map::iterator it; for(it = names.begin(); it != names.end(); ++it) { m_pEncodingSelector->addItem(it->first, QVariant::fromValue((void*)it->second)); } m_pEncodingSelector->setMinimumSize(m_pEncodingSelector->sizeHint()); if(pCodecForC != nullptr && pCodecForB != nullptr && pCodecForA != nullptr) { if(pCodecForA == pCodecForC) m_pEncodingSelector->setCurrentIndex(1); // B else m_pEncodingSelector->setCurrentIndex(2); // C } else if(pCodecForA != nullptr && pCodecForB != nullptr) m_pEncodingSelector->setCurrentIndex(1); // B else m_pEncodingSelector->setCurrentIndex(0); } QTextCodec* WindowTitleWidget::getEncoding() { return (QTextCodec*)m_pEncodingSelector->itemData(m_pEncodingSelector->currentIndex()).value(); } void WindowTitleWidget::setEncoding(QTextCodec* pEncoding) { int idx = m_pEncodingSelector->findText(QLatin1String(pEncoding->name())); if(idx >= 0) m_pEncodingSelector->setCurrentIndex(idx); } //void WindowTitleWidget::slotBrowseButtonClicked() //{ // QString current = m_pFileNameLineEdit->text(); // // QUrl newURL = KFileDialog::getSaveUrl( current, 0, this, i18n("Select file (not saving yet)")); // if ( !newURL.isEmpty() ) // { // m_pFileNameLineEdit->setText( newURL.url() ); // } //} void WindowTitleWidget::slotSetModified(bool bModified) { m_pModifiedLabel->setText(bModified ? i18n("[Modified]") : ""); } bool WindowTitleWidget::eventFilter(QObject* o, QEvent* e) { Q_UNUSED(o); if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { QPalette p = m_pLabel->palette(); QColor c1 = m_pOptions->m_fgColor; QColor c2 = Qt::lightGray; if(e->type() == QEvent::FocusOut) c2 = m_pOptions->m_bgColor; p.setColor(QPalette::Window, c2); setPalette(p); p.setColor(QPalette::WindowText, c1); m_pLabel->setPalette(p); m_pEncodingLabel->setPalette(p); m_pEncodingSelector->setPalette(p); } return false; } //#include "mergeresultwindow.moc" diff --git a/src/mergeresultwindow.h b/src/mergeresultwindow.h index 5d447d3..b288054 100644 --- a/src/mergeresultwindow.h +++ b/src/mergeresultwindow.h @@ -1,290 +1,287 @@ -/*************************************************************************** - * Copyright (C) 2002-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef MERGERESULTWINDOW_H #define MERGERESULTWINDOW_H #include "diff.h" #include "FileNameLineEdit.h" #include "MergeEditLine.h" #include "Overview.h" #include "selection.h" #include #include #include #include #include class QPainter; class RLPainter; class KActionCollection; class KToggleAction; class KDiff3App; class MergeResultWindow : public QWidget { Q_OBJECT public: MergeResultWindow(QWidget* pParent, const QSharedPointer &pOptions, QStatusBar* pStatusBar); void init( const QVector* pLineDataA, LineRef sizeA, const QVector* pLineDataB, LineRef sizeB, const QVector* pLineDataC, LineRef sizeC, const Diff3LineList* pDiff3LineList, TotalDiffStatus* pTotalDiffStatus ); void setupConnections(const KDiff3App* app) const; inline void clearMergeList() { m_mergeLineList.clear(); } static void initActions(KActionCollection* ac); void connectActions() const; void reset(); bool saveDocument(const QString& fileName, QTextCodec* pEncoding, e_LineEndStyle eLineEndStyle); int getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts = nullptr); void choose(e_SrcSelector selector); void chooseGlobal(e_SrcSelector selector, bool bConflictsOnly, bool bWhiteSpaceOnly); int getMaxTextWidth(); // width of longest text line int getNofLines() const; int getVisibleTextAreaWidth(); // text area width without the border int getNofVisibleLines(); QString getSelection(); void resetSelection(); void showNrOfConflicts(); bool isDeltaAboveCurrent(); bool isDeltaBelowCurrent(); bool isConflictAboveCurrent(); bool isConflictBelowCurrent(); bool isUnsolvedConflictAtCurrent(); bool isUnsolvedConflictAboveCurrent(); bool isUnsolvedConflictBelowCurrent(); bool findString(const QString& s, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive); void setSelection(int firstLine, int startPos, int lastLine, int endPos); void setOverviewMode(Overview::e_OverviewMode eOverviewMode); Overview::e_OverviewMode getOverviewMode(); void slotUpdateAvailabilities(const bool bMergeEditorVisible, const bool m_bTripleDiff); public Q_SLOTS: void setFirstLine(QtNumberType firstLine); void setHorizScrollOffset(int horizScrollOffset); void slotGoCurrent(); void slotGoTop(); void slotGoBottom(); void slotGoPrevDelta(); void slotGoNextDelta(); void slotGoPrevUnsolvedConflict(); void slotGoNextUnsolvedConflict(); void slotGoPrevConflict(); void slotGoNextConflict(); void slotAutoSolve(); void slotUnsolve(); void slotMergeHistory(); void slotRegExpAutoMerge(); void slotSplitDiff(LineIndex firstD3lLineIdx, LineIndex lastD3lLineIdx); void slotJoinDiffs(LineIndex firstD3lLineIdx, LineIndex lastD3lLineIdx); void slotSetFastSelectorLine(LineIndex); void setPaintingAllowed(bool); void updateSourceMask(); void slotStatusMessageChanged(const QString&); void slotChooseAEverywhere() { chooseGlobal(A, false, false); } void slotChooseBEverywhere() { chooseGlobal(B, false, false); } void slotChooseCEverywhere() { chooseGlobal(C, false, false); } void slotChooseAForUnsolvedConflicts() { chooseGlobal(A, true, false); } void slotChooseBForUnsolvedConflicts() { chooseGlobal(B, true, false); } void slotChooseCForUnsolvedConflicts() { chooseGlobal(C, true, false); } void slotChooseAForUnsolvedWhiteSpaceConflicts() { chooseGlobal(A, true, true); } void slotChooseBForUnsolvedWhiteSpaceConflicts() { chooseGlobal(B, true, true); } void slotChooseCForUnsolvedWhiteSpaceConflicts() { chooseGlobal(C, true, true); } void slotRefresh(); Q_SIGNALS: void scrollMergeResultWindow(int deltaX, int deltaY); void modifiedChanged(bool bModified); void setFastSelectorRange(LineRef line1, LineCount nofLines); void sourceMask(int srcMask, int enabledMask); void resizeSignal(); void selectionEnd(); void newSelection(); void updateAvailabilities(); void showPopupMenu(const QPoint& point); void noRelevantChangesDetected(); private: void merge(bool bAutoSolve, e_SrcSelector defaultSelector, bool bConflictsOnly = false, bool bWhiteSpaceOnly = false); QString getString(int lineIdx); static QAction* chooseAEverywhere; static QAction* chooseBEverywhere; static QAction* chooseCEverywhere; static QAction* chooseAForUnsolvedConflicts; static QAction* chooseBForUnsolvedConflicts; static QAction* chooseCForUnsolvedConflicts; static QAction* chooseAForUnsolvedWhiteSpaceConflicts; static QAction* chooseBForUnsolvedWhiteSpaceConflicts; static QAction* chooseCForUnsolvedWhiteSpaceConflicts; QSharedPointer m_pOptions = nullptr; const QVector* m_pldA; const QVector* m_pldB; const QVector* m_pldC; LineRef m_sizeA; LineRef m_sizeB; LineRef m_sizeC; const Diff3LineList* m_pDiff3LineList; TotalDiffStatus* m_pTotalDiffStatus; int m_delayedDrawTimer; Overview::e_OverviewMode m_eOverviewMode; QString m_persistentStatusMessage; void showUnsolvedConflictsStatusMessage(); private: static bool sameKindCheck(const MergeLine& ml1, const MergeLine& ml2); struct HistoryMapEntry { MergeEditLineList mellA; MergeEditLineList mellB; MergeEditLineList mellC; MergeEditLineList& choice(bool bThreeInputs); bool staysInPlace(bool bThreeInputs, Diff3LineList::const_iterator& iHistoryEnd); }; typedef std::map HistoryMap; void collectHistoryInformation(e_SrcSelector src, Diff3LineList::const_iterator& iHistoryBegin, Diff3LineList::const_iterator& iHistoryEnd, HistoryMap& historyMap, std::list& hitList); MergeLineList m_mergeLineList; MergeLineList::iterator m_currentMergeLineIt; bool isItAtEnd(bool bIncrement, MergeLineList::iterator i) { if(bIncrement) return i != m_mergeLineList.end(); else return i != m_mergeLineList.begin(); } int m_currentPos; bool checkOverviewIgnore(MergeLineList::iterator& i); enum e_Direction { eUp, eDown }; enum e_EndPoint { eDelta, eConflict, eUnsolvedConflict, eLine, eEnd }; void go(e_Direction eDir, e_EndPoint eEndPoint); void calcIteratorFromLineNr( int line, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt ); MergeLineList::iterator splitAtDiff3LineIdx(int d3lLineIdx); void paintEvent(QPaintEvent* e) override; int getTextXOffset(); QVector getTextLayoutForLine(int line, const QString& s, QTextLayout& textLayout); void myUpdate(int afterMilliSecs); void timerEvent(QTimerEvent*) override; void writeLine( RLPainter& p, int line, const QString& str, int srcSelect, e_MergeDetails mergeDetails, int rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict ); void setFastSelector(MergeLineList::iterator i); LineRef convertToLine(QtNumberType y); bool event(QEvent*) override; void mousePressEvent(QMouseEvent* e) override; void mouseDoubleClickEvent(QMouseEvent* e) override; void mouseReleaseEvent(QMouseEvent*) override; void mouseMoveEvent(QMouseEvent*) override; void resizeEvent(QResizeEvent* e) override; void keyPressEvent(QKeyEvent* e) override; void wheelEvent(QWheelEvent* e) override; void focusInEvent(QFocusEvent* e) override; QPixmap m_pixmap; LineRef m_firstLine; int m_horizScrollOffset; LineCount m_nofLines; int m_maxTextWidth; bool m_bMyUpdate; bool m_bInsertMode; bool m_bModified; void setModified(bool bModified = true); int m_scrollDeltaX; int m_scrollDeltaY; int m_cursorXPos; int m_cursorXPixelPos; int m_cursorYPos; int m_cursorOldXPixelPos; bool m_bCursorOn; // blinking on and off each second QTimer m_cursorTimer; bool m_bCursorUpdate; QStatusBar* m_pStatusBar; Selection m_selection; bool deleteSelection2(QString& str, int& x, int& y, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt); bool doRelevantChangesExist(); public Q_SLOTS: void deleteSelection(); void pasteClipboard(bool bFromSelection); private Q_SLOTS: void slotCursorUpdate(); }; class QLineEdit; class QTextCodec; class QComboBox; class QLabel; class WindowTitleWidget : public QWidget { Q_OBJECT private: QLabel* m_pLabel; FileNameLineEdit* m_pFileNameLineEdit; //QPushButton* m_pBrowseButton; QLabel* m_pModifiedLabel; QLabel* m_pLineEndStyleLabel; QComboBox* m_pLineEndStyleSelector; QLabel* m_pEncodingLabel; QComboBox* m_pEncodingSelector; QSharedPointer m_pOptions; public: explicit WindowTitleWidget(const QSharedPointer &pOptions); QTextCodec* getEncoding(); void setFileName(const QString& fileName); QString getFileName(); void setEncodings(QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC); void setEncoding(QTextCodec* pEncoding); void setLineEndStyles(e_LineEndStyle eLineEndStyleA, e_LineEndStyle eLineEndStyleB, e_LineEndStyle eLineEndStyleC); e_LineEndStyle getLineEndStyle(); bool eventFilter(QObject* o, QEvent* e) override; public Q_SLOTS: void slotSetModified(bool bModified); //private Q_SLOTS: // void slotBrowseButtonClicked(); }; #endif diff --git a/src/optiondialog.cpp b/src/optiondialog.cpp index 0b0a631..0355c7b 100644 --- a/src/optiondialog.cpp +++ b/src/optiondialog.cpp @@ -1,1608 +1,1595 @@ /* - * kdiff3 - Text Diff And Merge Tool - * Copyright (C) 2002-2009 Joachim Eibl, joachim.eibl at gmx.de - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com + * KDiff3 - Text Diff And Merge Tool * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "optiondialog.h" #include "OptionItems.h" #include "ui_scroller.h" #include "common.h" #include "smalldialogs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include const QString OptionDialog::s_historyEntryStartRegExpToolTip = i18n("A version control history entry consists of several lines.\n" "Specify the regular expression to detect the first line (without the leading comment).\n" "Use parentheses to group the keys you want to use for sorting.\n" "If left empty, then KDiff3 assumes that empty lines separate history entries.\n" "See the documentation for details."); const QString OptionDialog::s_historyEntryStartSortKeyOrderToolTip = i18n("Each pair of parentheses used in the regular expression for the history start entry\n" "groups a key that can be used for sorting.\n" "Specify the list of keys (that are numbered in order of occurrence\n" "starting with 1) using ',' as separator (e.g. \"4,5,6,1,2,3,7\").\n" "If left empty, then no sorting will be done.\n" "See the documentation for details."); const QString OptionDialog::s_autoMergeRegExpToolTip = i18n("Regular expression for lines where KDiff3 should automatically choose one source.\n" "When a line with a conflict matches the regular expression then\n" "- if available - C, otherwise B will be chosen."); const QString OptionDialog::s_historyStartRegExpToolTip = i18n("Regular expression for the start of the version control history entry.\n" "Usually this line contains the \"$Log$\" keyword.\n" "Default value: \".*\\$Log.*\\$.*\""); class OptionCheckBox : public QCheckBox, public OptionBool { public: OptionCheckBox(const QString& text, bool bDefaultVal, const QString& saveName, bool* pbVar, QWidget* pParent) : QCheckBox(text, pParent), OptionBool(pbVar, bDefaultVal, saveName) {} void setToDefault() override { setChecked(getDefault()); } void setToCurrent() override { setChecked(getCurrent()); } using OptionBool::apply; void apply() override { apply(isChecked()); } private: Q_DISABLE_COPY(OptionCheckBox) }; class OptionRadioButton : public QRadioButton, public OptionBool { public: OptionRadioButton(const QString& text, bool bDefaultVal, const QString& saveName, bool* pbVar, QWidget* pParent) : QRadioButton(text, pParent), OptionBool(pbVar, bDefaultVal, saveName) {} void setToDefault() override { setChecked(getDefault()); } void setToCurrent() override { setChecked(getCurrent()); } using OptionBool::apply; void apply() override { apply(isChecked()); } private: Q_DISABLE_COPY(OptionRadioButton) }; FontChooser::FontChooser(QWidget* pParent) : QGroupBox(pParent) { QVBoxLayout* pLayout = new QVBoxLayout(this); m_pLabel = new QLabel(QString()); pLayout->addWidget(m_pLabel); QChar visualTab(0x2192); QChar visualSpace((ushort)0xb7); m_pExampleTextEdit = new QPlainTextEdit(QString("The quick brown fox jumps over the river\n" "but the little red hen escapes with a shiver.\n" ":-)") + visualTab + visualSpace, this); m_pExampleTextEdit->setFont(m_font); m_pExampleTextEdit->setReadOnly(true); pLayout->addWidget(m_pExampleTextEdit); m_pSelectFont = new QPushButton(i18n("Change Font")); m_pSelectFont->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(m_pSelectFont, &QPushButton::clicked, this, &FontChooser::slotSelectFont); pLayout->addWidget(m_pSelectFont); pLayout->setAlignment(m_pSelectFont, Qt::AlignRight); } QFont FontChooser::font() { return m_font; //QFont("courier",10); } void FontChooser::setFont(const QFont& font, bool) { m_font = font; m_pExampleTextEdit->setFont(m_font); m_pLabel->setText(i18n("Font: %1, %2, %3\n\nExample:", m_font.family(), m_font.styleName(), m_font.pointSize())); //update(); } void FontChooser::slotSelectFont() { bool bOk; m_font = QFontDialog::getFont(&bOk, m_font); m_pExampleTextEdit->setFont(m_font); m_pLabel->setText(i18n("Font: %1, %2, %3\n\nExample:", m_font.family(), m_font.styleName(), m_font.pointSize())); } class OptionFontChooser : public FontChooser, public OptionFont { public: OptionFontChooser(const QFont& defaultVal, const QString& saveName, QFont* pVar, QWidget* pParent) : FontChooser(pParent), OptionFont(pVar, defaultVal, saveName) {} void setToDefault() override { setFont(getDefault(), false); } void setToCurrent() override { setFont(getCurrent(), false); } using OptionFont::apply; void apply() override { apply(font()); } private: Q_DISABLE_COPY(OptionFontChooser) }; class OptionColorButton : public KColorButton, public OptionColor { public: OptionColorButton(const QColor &defaultVal, const QString& saveName, QColor* pVar, QWidget* pParent) : KColorButton(pParent), OptionColor(pVar, defaultVal, saveName) {} void setToDefault() override { setColor(getDefault()); } void setToCurrent() override { setColor(getCurrent()); } using OptionColor::apply; void apply() override { apply(color()); } private: Q_DISABLE_COPY(OptionColorButton) }; class OptionLineEdit : public QComboBox, public OptionString { public: OptionLineEdit(const QString& defaultVal, const QString& saveName, QString* pVar, QWidget* pParent) : QComboBox(pParent), OptionString(pVar, defaultVal, saveName) { setMinimumWidth(50); setEditable(true); m_list.push_back(defaultVal); insertText(); } void setToDefault() override { setEditText(getDefault()); } void setToCurrent() override { setEditText(getCurrent()); } using OptionString::apply; void apply() override { apply(currentText()); insertText(); } void write(ValueMap* config) const override { config->writeEntry(m_saveName, m_list); } void read(ValueMap* config) override { m_list = config->readEntry(m_saveName, QStringList(m_defaultVal)); if(!m_list.empty()) setCurrent(m_list.front()); clear(); insertItems(0, m_list); } private: void insertText() { // Check if the text exists. If yes remove it and push it in as first element QString current = currentText(); m_list.removeAll(current); m_list.push_front(current); clear(); if(m_list.size() > 10) m_list.erase(m_list.begin() + 10, m_list.end()); insertItems(0, m_list); } Q_DISABLE_COPY(OptionLineEdit) QStringList m_list; }; class OptionIntEdit : public QLineEdit, public OptionNum { public: OptionIntEdit(int defaultVal, const QString& saveName, int* pVar, int rangeMin, int rangeMax, QWidget* pParent) : QLineEdit(pParent), OptionNum(pVar, defaultVal, saveName) { QIntValidator* v = new QIntValidator(this); v->setRange(rangeMin, rangeMax); setValidator(v); } void setToDefault() override { //QString::setNum does not account for locale settings setText(OptionNum::toString(getDefault())); } void setToCurrent() override { setText(getString()); } using OptionNum::apply; void apply() override { const QIntValidator* v = static_cast(validator()); setCurrent( qBound(v->bottom(), text().toInt(), v->top()) ); setText(getString()); } private: Q_DISABLE_COPY(OptionIntEdit) }; class OptionComboBox : public QComboBox, public OptionItemBase { public: OptionComboBox(int defaultVal, const QString& saveName, int* pVarNum, QWidget* pParent) : QComboBox(pParent), OptionItemBase(saveName) { setMinimumWidth(50); m_pVarNum = pVarNum; m_pVarStr = nullptr; m_defaultVal = defaultVal; setEditable(false); } OptionComboBox(int defaultVal, const QString& saveName, QString* pVarStr, QWidget* pParent) : QComboBox(pParent), OptionItemBase(saveName) { m_pVarNum = nullptr; m_pVarStr = pVarStr; m_defaultVal = defaultVal; setEditable(false); } void setToDefault() override { setCurrentIndex(m_defaultVal); if(m_pVarStr != nullptr) { *m_pVarStr = currentText(); } } void setToCurrent() override { if(m_pVarNum != nullptr) setCurrentIndex(*m_pVarNum); else setText(*m_pVarStr); } using OptionItemBase::apply; void apply() override { if(m_pVarNum != nullptr) { *m_pVarNum = currentIndex(); } else { *m_pVarStr = currentText(); } } void write(ValueMap* config) const override { if(m_pVarStr != nullptr) config->writeEntry(m_saveName, *m_pVarStr); else config->writeEntry(m_saveName, *m_pVarNum); } void read(ValueMap* config) override { if(m_pVarStr != nullptr) setText(config->readEntry(m_saveName, currentText())); else *m_pVarNum = config->readEntry(m_saveName, *m_pVarNum); } void preserve() override { if(m_pVarStr != nullptr) { m_preservedStrVal = *m_pVarStr; } else { m_preservedNumVal = *m_pVarNum; } } void unpreserve() override { if(m_pVarStr != nullptr) { *m_pVarStr = m_preservedStrVal; } else { *m_pVarNum = m_preservedNumVal; } } private: Q_DISABLE_COPY(OptionComboBox) int* m_pVarNum; int m_preservedNumVal = 0; QString* m_pVarStr; QString m_preservedStrVal; int m_defaultVal; void setText(const QString& s) { // Find the string in the combobox-list, don't change the value if nothing fits. for(int i = 0; i < count(); ++i) { if(itemText(i) == s) { if(m_pVarNum != nullptr) *m_pVarNum = i; if(m_pVarStr != nullptr) *m_pVarStr = s; setCurrentIndex(i); return; } } } }; class OptionEncodingComboBox : public QComboBox, public OptionCodec { Q_OBJECT QVector m_codecVec; QTextCodec** m_ppVarCodec; public: OptionEncodingComboBox(const QString& saveName, QTextCodec** ppVarCodec, QWidget* pParent) : QComboBox(pParent), OptionCodec(saveName) { m_ppVarCodec = ppVarCodec; insertCodec(i18n("Unicode, 8 bit"), QTextCodec::codecForName("UTF-8")); insertCodec(i18n("Unicode"), QTextCodec::codecForName("iso-10646-UCS-2")); insertCodec(i18n("Latin1"), QTextCodec::codecForName("iso 8859-1")); // First sort codec names: std::map names; QList mibs = QTextCodec::availableMibs(); for(int i: mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) names[QString(QLatin1String(c->name())).toUpper()] = c; } std::map::iterator it; for(it = names.begin(); it != names.end(); ++it) { insertCodec("", it->second); } this->setToolTip(i18n( "Change this if non-ASCII characters are not displayed correctly.")); } void insertCodec(const QString& visibleCodecName, QTextCodec* c) { if(c != nullptr) { QByteArray nameArray = c->name(); QLatin1String codecName = QLatin1String(nameArray); for(int i = 0; i < m_codecVec.size(); ++i) { if(c == m_codecVec[i]) return; // don't insert any codec twice } // The m_codecVec.size will at this point return the value we need for the index. if(codecName == defaultName()) saveDefaultIndex(m_codecVec.size()); QString itemText = visibleCodecName.isEmpty() ? codecName : visibleCodecName + QLatin1String(" (") + codecName + QLatin1String(")"); addItem(itemText, m_codecVec.size()); m_codecVec.push_back(c); } } void setToDefault() override { int index = getDefaultIndex(); setCurrentIndex(index); if(m_ppVarCodec != nullptr) { *m_ppVarCodec = m_codecVec[index]; } } void setToCurrent() override { if(m_ppVarCodec != nullptr) { for(int i = 0; i < m_codecVec.size(); ++i) { if(*m_ppVarCodec == m_codecVec[i]) { setCurrentIndex(i); break; } } } } using OptionCodec::apply; void apply() override { if(m_ppVarCodec != nullptr) { *m_ppVarCodec = m_codecVec[currentIndex()]; } } void write(ValueMap* config) const override { if(m_ppVarCodec != nullptr) config->writeEntry(m_saveName, (const char*)(*m_ppVarCodec)->name()); } void read(ValueMap* config) override { QString codecName = config->readEntry(m_saveName, (const char*)m_codecVec[currentIndex()]->name()); for(int i = 0; i < m_codecVec.size(); ++i) { if(codecName == QLatin1String(m_codecVec[i]->name())) { setCurrentIndex(i); if(m_ppVarCodec != nullptr) *m_ppVarCodec = m_codecVec[i]; break; } } } protected: void preserve() override { m_preservedVal = currentIndex(); } void unpreserve() override { setCurrentIndex(m_preservedVal); } int m_preservedVal; }; void OptionDialog::addOptionItem(OptionItemBase* p) { m_options->addOptionItem(p); } OptionDialog::OptionDialog(bool bShowDirMergeSettings, QWidget* parent) : KPageDialog(parent) { setFaceType(List); setWindowTitle(i18n("Configure")); setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Apply | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); setModal(true); setMinimumSize(600, 500); //showButtonSeparator( true ); //setHelp( "kdiff3/index.html", QString::null ); m_options->init(); setupFontPage(); setupColorPage(); setupEditPage(); setupDiffPage(); setupMergePage(); setupOtherOptions(); if(bShowDirMergeSettings) setupDirectoryMergePage(); setupRegionalPage(); setupIntegrationPage(); // Initialize all values in the dialog resetToDefaults(); slotApply(); connect(button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &OptionDialog::slotApply); connect(button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &OptionDialog::slotOk); connect(button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &OptionDialog::slotDefault); connect(button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &QDialog::reject); connect(button(QDialogButtonBox::Help), &QPushButton::clicked, this, &OptionDialog::helpRequested); //connect(this, &OptionDialog::applyClicked, this, &OptionDialog::slotApply); //helpClicked() is connected in KDiff3App::KDiff3App -- Really where? //connect(this, &OptionDialog::defaultClicked, this, &OptionDialog::slotDefault); } void OptionDialog::helpRequested() { KHelpClient::invokeHelp(QStringLiteral("kdiff3/index.html")); } OptionDialog::~OptionDialog() { } void OptionDialog::setupOtherOptions() { //TODO move to Options class addOptionItem(new OptionToggleAction(false, "AutoAdvance", &m_options->m_bAutoAdvance)); addOptionItem(new OptionToggleAction(true, "ShowWhiteSpaceCharacters", &m_options->m_bShowWhiteSpaceCharacters)); addOptionItem(new OptionToggleAction(true, "ShowWhiteSpace", &m_options->m_bShowWhiteSpace)); addOptionItem(new OptionToggleAction(false, "ShowLineNumbers", &m_options->m_bShowLineNumbers)); addOptionItem(new OptionToggleAction(true, "HorizDiffWindowSplitting", &m_options->m_bHorizDiffWindowSplitting)); addOptionItem(new OptionToggleAction(false, "WordWrap", &m_options->m_bWordWrap)); addOptionItem(new OptionToggleAction(true, "ShowIdenticalFiles", &m_options->m_bDmShowIdenticalFiles)); addOptionItem(new OptionStringList(&m_options->m_recentAFiles, "RecentAFiles")); addOptionItem(new OptionStringList(&m_options->m_recentBFiles, "RecentBFiles")); addOptionItem(new OptionStringList(&m_options->m_recentCFiles, "RecentCFiles")); addOptionItem(new OptionStringList(&m_options->m_recentOutputFiles, "RecentOutputFiles")); addOptionItem(new OptionStringList(&m_options->m_recentEncodings, "RecentEncodings")); } void OptionDialog::setupFontPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Font")); pageItem->setHeader(i18n("Editor & Diff Output Font")); //not all themes have this icon if(QIcon::hasThemeIcon(QStringLiteral("font-select-symbolic"))) pageItem->setIcon(QIcon::fromTheme(QStringLiteral("font-select-symbolic"))); else pageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); //requires QT 5.2 or later. static const QFont defaultFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); static QFont defaultAppFont = QApplication::font(); OptionFontChooser* pAppFontChooser = new OptionFontChooser(defaultAppFont, "ApplicationFont", &m_options->m_appFont, page); addOptionItem(pAppFontChooser); topLayout->addWidget(pAppFontChooser); pAppFontChooser->setTitle(i18n("Application font")); OptionFontChooser* pFontChooser = new OptionFontChooser(defaultFont, "Font", &m_options->m_font, page); addOptionItem(pFontChooser); topLayout->addWidget(pFontChooser); pFontChooser->setTitle(i18n("File view font")); QGridLayout* gbox = new QGridLayout(); topLayout->addLayout(gbox); //int line=0; // This currently does not work (see rendering in class DiffTextWindow) //OptionCheckBox* pItalicDeltas = new OptionCheckBox( i18n("Italic font for deltas"), false, "ItalicForDeltas", &m_options->m_bItalicForDeltas, page, this ); //addOptionItem(pItalicDeltas); //gbox->addWidget( pItalicDeltas, line, 0, 1, 2 ); //pItalicDeltas->setToolTip( i18n( // "Selects the italic version of the font for differences.\n" // "If the font doesn't support italic characters, then this does nothing.") // ); } void OptionDialog::setupColorPage() { QScrollArea* pageFrame = new QScrollArea(); KPageWidgetItem* pageItem = new KPageWidgetItem(pageFrame, i18nc("Title for color settings page","Color")); pageItem->setHeader(i18n("Colors Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("colormanagement"))); addPage(pageItem); QVBoxLayout* scrollLayout = new QVBoxLayout(); scrollLayout->setMargin(2); scrollLayout->addWidget(pageFrame); QScopedPointer scrollArea(new Ui::ScrollArea()); scrollArea->setupUi(pageFrame); QWidget* page = pageFrame->findChild("contents"); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); QLabel* label; int line = 0; int depth = QPixmap::defaultDepth(); bool bLowColor = depth <= 8; label = new QLabel(i18n("Editor and Diff Views:"), page); gbox->addWidget(label, line, 0); QFont f(label->font()); f.setBold(true); label->setFont(f); ++line; OptionColorButton* pFgColor = new OptionColorButton(Qt::black, "FgColor", &m_options->m_fgColor, page); label = new QLabel(i18n("Foreground color:"), page); label->setBuddy(pFgColor); addOptionItem(pFgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pFgColor, line, 1); ++line; OptionColorButton* pBgColor = new OptionColorButton(Qt::white, "BgColor", &m_options->m_bgColor, page); label = new QLabel(i18n("Background color:"), page); label->setBuddy(pBgColor); addOptionItem(pBgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pBgColor, line, 1); ++line; OptionColorButton* pDiffBgColor = new OptionColorButton( bLowColor ? QColor(Qt::lightGray) : qRgb(224, 224, 224), "DiffBgColor", &m_options->m_diffBgColor, page); label = new QLabel(i18n("Diff background color:"), page); label->setBuddy(pDiffBgColor); addOptionItem(pDiffBgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pDiffBgColor, line, 1); ++line; OptionColorButton* pColorA = new OptionColorButton( bLowColor ? qRgb(0, 0, 255) : qRgb(0, 0, 200) /*blue*/, "ColorA", &m_options->m_colorA, page); label = new QLabel(i18n("Color A:"), page); label->setBuddy(pColorA); addOptionItem(pColorA); gbox->addWidget(label, line, 0); gbox->addWidget(pColorA, line, 1); ++line; OptionColorButton* pColorB = new OptionColorButton( bLowColor ? qRgb(0, 128, 0) : qRgb(0, 150, 0) /*green*/, "ColorB", &m_options->m_colorB, page); label = new QLabel(i18n("Color B:"), page); label->setBuddy(pColorB); addOptionItem(pColorB); gbox->addWidget(label, line, 0); gbox->addWidget(pColorB, line, 1); ++line; OptionColorButton* pColorC = new OptionColorButton( bLowColor ? qRgb(128, 0, 128) : qRgb(150, 0, 150) /*magenta*/, "ColorC", &m_options->m_colorC, page); label = new QLabel(i18n("Color C:"), page); label->setBuddy(pColorC); addOptionItem(pColorC); gbox->addWidget(label, line, 0); gbox->addWidget(pColorC, line, 1); ++line; OptionColorButton* pColorForConflict = new OptionColorButton(Qt::red, "ColorForConflict", &m_options->m_colorForConflict, page); label = new QLabel(i18n("Conflict color:"), page); label->setBuddy(pColorForConflict); addOptionItem(pColorForConflict); gbox->addWidget(label, line, 0); gbox->addWidget(pColorForConflict, line, 1); ++line; OptionColorButton* pColor = new OptionColorButton( bLowColor ? qRgb(192, 192, 192) : qRgb(220, 220, 100), "CurrentRangeBgColor", &m_options->m_currentRangeBgColor, page); label = new QLabel(i18n("Current range background color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; pColor = new OptionColorButton( bLowColor ? qRgb(255, 255, 0) : qRgb(255, 255, 150), "CurrentRangeDiffBgColor", &m_options->m_currentRangeDiffBgColor, page); label = new QLabel(i18n("Current range diff background color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; pColor = new OptionColorButton(qRgb(0xff, 0xd0, 0x80), "ManualAlignmentRangeColor", &m_options->m_manualHelpRangeColor, page); label = new QLabel(i18n("Color for manually aligned difference ranges:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; label = new QLabel(i18n("Folder Comparison View:"), page); gbox->addWidget(label, line, 0); label->setFont(f); ++line; pColor = new OptionColorButton(qRgb(0, 0xd0, 0), "NewestFileColor", &m_options->m_newestFileColor, page); label = new QLabel(i18n("Newest file color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); QString dirColorTip = i18n("Changing this color will only be effective when starting the next folder comparison."); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0xf0, 0, 0), "OldestFileColor", &m_options->m_oldestFileColor, page); label = new QLabel(i18n("Oldest file color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0xc0, 0xc0, 0), "MidAgeFileColor", &m_options->m_midAgeFileColor, page); label = new QLabel(i18n("Middle age file color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0, 0, 0), "MissingFileColor", &m_options->m_missingFileColor, page); label = new QLabel(i18n("Color for missing files:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; topLayout->addStretch(10); } void OptionDialog::setupEditPage() { QScrollArea* pageFrame = new QScrollArea(); KPageWidgetItem* pageItem = new KPageWidgetItem(pageFrame, i18n("Editor")); pageItem->setHeader(i18n("Editor Behavior")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor"))); addPage(pageItem); QVBoxLayout* scrollLayout = new QVBoxLayout(); scrollLayout->setMargin(2); scrollLayout->addWidget(pageFrame); QScopedPointer scrollArea(new Ui::ScrollArea()); scrollArea->setupUi(pageFrame); QWidget* page = pageFrame->findChild("contents"); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); QLabel* label; int line = 0; OptionCheckBox* pReplaceTabs = new OptionCheckBox(i18n("Tab inserts spaces"), false, "ReplaceTabs", &m_options->m_bReplaceTabs, page); addOptionItem(pReplaceTabs); gbox->addWidget(pReplaceTabs, line, 0, 1, 2); pReplaceTabs->setToolTip(i18n( "On: Pressing tab generates the appropriate number of spaces.\n" "Off: A tab character will be inserted.")); ++line; OptionIntEdit* pTabSize = new OptionIntEdit(8, "TabSize", &m_options->m_tabSize, 1, 100, page); label = new QLabel(i18n("Tab size:"), page); label->setBuddy(pTabSize); addOptionItem(pTabSize); gbox->addWidget(label, line, 0); gbox->addWidget(pTabSize, line, 1); ++line; OptionCheckBox* pAutoIndentation = new OptionCheckBox(i18n("Auto indentation"), true, "AutoIndentation", &m_options->m_bAutoIndentation, page); gbox->addWidget(pAutoIndentation, line, 0, 1, 2); addOptionItem(pAutoIndentation); pAutoIndentation->setToolTip(i18n( "On: The indentation of the previous line is used for a new line.\n")); ++line; OptionCheckBox* pAutoCopySelection = new OptionCheckBox(i18n("Auto copy selection"), false, "AutoCopySelection", &m_options->m_bAutoCopySelection, page); gbox->addWidget(pAutoCopySelection, line, 0, 1, 2); addOptionItem(pAutoCopySelection); pAutoCopySelection->setToolTip(i18n( "On: Any selection is immediately written to the clipboard.\n" "Off: You must explicitly copy e.g. via Ctrl-C.")); ++line; label = new QLabel(i18n("Line end style:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pLineEndStyle = new OptionComboBox(eLineEndStyleAutoDetect, "LineEndStyle", (int*)&m_options->m_lineEndStyle, page); gbox->addWidget(pLineEndStyle, line, 1); addOptionItem(pLineEndStyle); pLineEndStyle->insertItem(eLineEndStyleUnix, "Unix"); pLineEndStyle->insertItem(eLineEndStyleDos, "Dos/Windows"); pLineEndStyle->insertItem(eLineEndStyleAutoDetect, "Autodetect"); label->setToolTip(i18n( "Sets the line endings for when an edited file is saved.\n" "DOS/Windows: CR+LF; UNIX: LF; with CR=0D, LF=0A")); ++line; topLayout->addStretch(10); } void OptionDialog::setupDiffPage() { QScrollArea* pageFrame = new QScrollArea(); KPageWidgetItem* pageItem = new KPageWidgetItem(pageFrame, i18n("Diff")); pageItem->setHeader(i18n("Diff Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("text-x-patch"))); addPage(pageItem); QVBoxLayout* scrollLayout = new QVBoxLayout(); scrollLayout->setMargin(2); scrollLayout->addWidget(pageFrame); QScopedPointer scrollArea(new Ui::ScrollArea()); scrollArea->setupUi(pageFrame); QWidget* page = pageFrame->findChild("contents"); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label = nullptr; m_options->m_bPreserveCarriageReturn = false; /* OptionCheckBox* pPreserveCarriageReturn = new OptionCheckBox( i18n("Preserve carriage return"), false, "PreserveCarriageReturn", &m_options->m_bPreserveCarriageReturn, page, this ); addOptionItem(pPreserveCarriageReturn); gbox->addWidget( pPreserveCarriageReturn, line, 0, 1, 2 ); pPreserveCarriageReturn->setToolTip( i18n( "Show carriage return characters '\\r' if they exist.\n" "Helps to compare files that were modified under different operating systems.") ); ++line; */ OptionCheckBox* pIgnoreNumbers = new OptionCheckBox(i18n("Ignore numbers (treat as white space)"), false, "IgnoreNumbers", &m_options->m_bIgnoreNumbers, page); gbox->addWidget(pIgnoreNumbers, line, 0, 1, 2); addOptionItem(pIgnoreNumbers); pIgnoreNumbers->setToolTip(i18n( "Ignore number characters during line matching phase. (Similar to Ignore white space.)\n" "Might help to compare files with numeric data.")); ++line; OptionCheckBox* pIgnoreComments = new OptionCheckBox(i18n("Ignore C/C++ comments (treat as white space)"), false, "IgnoreComments", &m_options->m_bIgnoreComments, page); gbox->addWidget(pIgnoreComments, line, 0, 1, 2); addOptionItem(pIgnoreComments); pIgnoreComments->setToolTip(i18n("Treat C/C++ comments like white space.")); ++line; OptionCheckBox* pIgnoreCase = new OptionCheckBox(i18n("Ignore case (treat as white space)"), false, "IgnoreCase", &m_options->m_bIgnoreCase, page); gbox->addWidget(pIgnoreCase, line, 0, 1, 2); addOptionItem(pIgnoreCase); pIgnoreCase->setToolTip(i18n( "Treat case differences like white space changes. ('a'<=>'A')")); ++line; label = new QLabel(i18n("Preprocessor command:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pLE = new OptionLineEdit("", "PreProcessorCmd", &m_options->m_PreProcessorCmd, page); gbox->addWidget(pLE, line, 1); addOptionItem(pLE); label->setToolTip(i18n("User defined pre-processing. (See the docs for details.)")); ++line; label = new QLabel(i18n("Line-matching preprocessor command:"), page); gbox->addWidget(label, line, 0); pLE = new OptionLineEdit("", "LineMatchingPreProcessorCmd", &m_options->m_LineMatchingPreProcessorCmd, page); gbox->addWidget(pLE, line, 1); addOptionItem(pLE); label->setToolTip(i18n("This pre-processor is only used during line matching.\n(See the docs for details.)")); ++line; OptionCheckBox* pTryHard = new OptionCheckBox(i18n("Try hard (slower)"), true, "TryHard", &m_options->m_bTryHard, page); gbox->addWidget(pTryHard, line, 0, 1, 2); addOptionItem(pTryHard); pTryHard->setToolTip(i18n( "Enables the --minimal option for the external diff.\n" "The analysis of big files will be much slower.")); ++line; OptionCheckBox* pDiff3AlignBC = new OptionCheckBox(i18n("Align B and C for 3 input files"), false, "Diff3AlignBC", &m_options->m_bDiff3AlignBC, page); gbox->addWidget(pDiff3AlignBC, line, 0, 1, 2); addOptionItem(pDiff3AlignBC); pDiff3AlignBC->setToolTip(i18n( "Try to align B and C when comparing or merging three input files.\n" "Not recommended for merging because merge might get more complicated.\n" "(Default is off.)")); ++line; topLayout->addStretch(10); } void OptionDialog::setupMergePage() { QScrollArea* pageFrame = new QScrollArea(); KPageWidgetItem* pageItem = new KPageWidgetItem(pageFrame, i18nc("Settings page", "Merge")); pageItem->setHeader(i18n("Merge Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("merge"))); addPage(pageItem); QVBoxLayout* scrollLayout = new QVBoxLayout(); scrollLayout->setMargin(2); scrollLayout->addWidget(pageFrame); QScopedPointer scrollArea(new Ui::ScrollArea()); scrollArea->setupUi(pageFrame); QWidget* page = pageFrame->findChild("contents"); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label = nullptr; label = new QLabel(i18n("Auto advance delay (ms):"), page); gbox->addWidget(label, line, 0); OptionIntEdit* pAutoAdvanceDelay = new OptionIntEdit(500, "AutoAdvanceDelay", &m_options->m_autoAdvanceDelay, 0, 2000, page); gbox->addWidget(pAutoAdvanceDelay, line, 1); addOptionItem(pAutoAdvanceDelay); label->setToolTip(i18n( "When in Auto-Advance mode the result of the current selection is shown \n" "for the specified time, before jumping to the next conflict. Range: 0-2000 ms")); ++line; OptionCheckBox* pShowInfoDialogs = new OptionCheckBox(i18n("Show info dialogs"), true, "ShowInfoDialogs", &m_options->m_bShowInfoDialogs, page); gbox->addWidget(pShowInfoDialogs, line, 0, 1, 2); addOptionItem(pShowInfoDialogs); pShowInfoDialogs->setToolTip(i18n("Show a dialog with information about the number of conflicts.")); ++line; label = new QLabel(i18n("White space 2-file merge default:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pWhiteSpace2FileMergeDefault = new OptionComboBox(0, "WhiteSpace2FileMergeDefault", &m_options->m_whiteSpace2FileMergeDefault, page); gbox->addWidget(pWhiteSpace2FileMergeDefault, line, 1); addOptionItem(pWhiteSpace2FileMergeDefault); pWhiteSpace2FileMergeDefault->insertItem(0, i18n("Manual Choice")); pWhiteSpace2FileMergeDefault->insertItem(1, i18n("A")); pWhiteSpace2FileMergeDefault->insertItem(2, i18n("B")); label->setToolTip(i18n( "Allow the merge algorithm to automatically select an input for " "white-space-only changes.")); ++line; label = new QLabel(i18n("White space 3-file merge default:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pWhiteSpace3FileMergeDefault = new OptionComboBox(0, "WhiteSpace3FileMergeDefault", &m_options->m_whiteSpace3FileMergeDefault, page); gbox->addWidget(pWhiteSpace3FileMergeDefault, line, 1); addOptionItem(pWhiteSpace3FileMergeDefault); pWhiteSpace3FileMergeDefault->insertItem(0, i18n("Manual Choice")); pWhiteSpace3FileMergeDefault->insertItem(1, i18n("A")); pWhiteSpace3FileMergeDefault->insertItem(2, i18n("B")); pWhiteSpace3FileMergeDefault->insertItem(3, i18n("C")); label->setToolTip(i18n( "Allow the merge algorithm to automatically select an input for " "white-space-only changes.")); ++line; QGroupBox* pGroupBox = new QGroupBox(i18n("Automatic Merge Regular Expression")); gbox->addWidget(pGroupBox, line, 0, 1, 2); ++line; { gbox = new QGridLayout(pGroupBox); gbox->setColumnStretch(1, 10); line = 0; label = new QLabel(i18n("Auto merge regular expression:"), page); gbox->addWidget(label, line, 0); m_pAutoMergeRegExpLineEdit = new OptionLineEdit(".*\\$(Version|Header|Date|Author).*\\$.*", "AutoMergeRegExp", &m_options->m_autoMergeRegExp, page); gbox->addWidget(m_pAutoMergeRegExpLineEdit, line, 1); addOptionItem(m_pAutoMergeRegExpLineEdit); label->setToolTip(s_autoMergeRegExpToolTip); ++line; OptionCheckBox* pAutoMergeRegExp = new OptionCheckBox(i18n("Run regular expression auto merge on merge start"), false, "RunRegExpAutoMergeOnMergeStart", &m_options->m_bRunRegExpAutoMergeOnMergeStart, page); addOptionItem(pAutoMergeRegExp); gbox->addWidget(pAutoMergeRegExp, line, 0, 1, 2); pAutoMergeRegExp->setToolTip(i18n("Run the merge for auto merge regular expressions\n" "immediately when a merge starts.\n")); ++line; } pGroupBox = new QGroupBox(i18n("Version Control History Merging")); gbox->addWidget(pGroupBox, line, 0, 1, 2); ++line; { gbox = new QGridLayout(pGroupBox); gbox->setColumnStretch(1, 10); line = 0; label = new QLabel(i18n("History start regular expression:"), page); gbox->addWidget(label, line, 0); m_pHistoryStartRegExpLineEdit = new OptionLineEdit(".*\\$Log.*\\$.*", "HistoryStartRegExp", &m_options->m_historyStartRegExp, page); gbox->addWidget(m_pHistoryStartRegExpLineEdit, line, 1); addOptionItem(m_pHistoryStartRegExpLineEdit); label->setToolTip(s_historyStartRegExpToolTip); ++line; label = new QLabel(i18n("History entry start regular expression:"), page); gbox->addWidget(label, line, 0); // Example line: "** \main\rolle_fsp_dev_008\1 17 Aug 2001 10:45:44 rolle" QString historyEntryStartDefault = "\\s*\\\\main\\\\(\\S+)\\s+" // Start with "\main\" "([0-9]+) " // day "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) " //month "([0-9][0-9][0-9][0-9]) " // year "([0-9][0-9]:[0-9][0-9]:[0-9][0-9])\\s+(.*)"; // time, name m_pHistoryEntryStartRegExpLineEdit = new OptionLineEdit(historyEntryStartDefault, "HistoryEntryStartRegExp", &m_options->m_historyEntryStartRegExp, page); gbox->addWidget(m_pHistoryEntryStartRegExpLineEdit, line, 1); addOptionItem(m_pHistoryEntryStartRegExpLineEdit); label->setToolTip(s_historyEntryStartRegExpToolTip); ++line; m_pHistoryMergeSorting = new OptionCheckBox(i18n("History merge sorting"), false, "HistoryMergeSorting", &m_options->m_bHistoryMergeSorting, page); gbox->addWidget(m_pHistoryMergeSorting, line, 0, 1, 2); addOptionItem(m_pHistoryMergeSorting); m_pHistoryMergeSorting->setToolTip(i18n("Sort version control history by a key.")); ++line; //QString branch = newHistoryEntry.cap(1); //int day = newHistoryEntry.cap(2).toInt(); //int month = QString("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec").find(newHistoryEntry.cap(3))/4 + 1; //int year = newHistoryEntry.cap(4).toInt(); //QString time = newHistoryEntry.cap(5); //QString name = newHistoryEntry.cap(6); QString defaultSortKeyOrder = "4,3,2,5,1,6"; //QDate(year,month,day).toString(Qt::ISODate) +" "+ time + " " + branch + " " + name; label = new QLabel(i18n("History entry start sort key order:"), page); gbox->addWidget(label, line, 0); m_pHistorySortKeyOrderLineEdit = new OptionLineEdit(defaultSortKeyOrder, "HistoryEntryStartSortKeyOrder", &m_options->m_historyEntryStartSortKeyOrder, page); gbox->addWidget(m_pHistorySortKeyOrderLineEdit, line, 1); addOptionItem(m_pHistorySortKeyOrderLineEdit); label->setToolTip(s_historyEntryStartSortKeyOrderToolTip); m_pHistorySortKeyOrderLineEdit->setEnabled(false); connect(m_pHistoryMergeSorting, &OptionCheckBox::toggled, m_pHistorySortKeyOrderLineEdit, &OptionLineEdit::setEnabled); ++line; m_pHistoryAutoMerge = new OptionCheckBox(i18n("Merge version control history on merge start"), false, "RunHistoryAutoMergeOnMergeStart", &m_options->m_bRunHistoryAutoMergeOnMergeStart, page); addOptionItem(m_pHistoryAutoMerge); gbox->addWidget(m_pHistoryAutoMerge, line, 0, 1, 2); m_pHistoryAutoMerge->setToolTip(i18n("Run version control history automerge on merge start.")); ++line; OptionIntEdit* pMaxNofHistoryEntries = new OptionIntEdit(-1, "MaxNofHistoryEntries", &m_options->m_maxNofHistoryEntries, -1, 1000, page); label = new QLabel(i18n("Max number of history entries:"), page); gbox->addWidget(label, line, 0); gbox->addWidget(pMaxNofHistoryEntries, line, 1); addOptionItem(pMaxNofHistoryEntries); pMaxNofHistoryEntries->setToolTip(i18n("Cut off after specified number. Use -1 for infinite number of entries.")); ++line; } QPushButton* pButton = new QPushButton(i18n("Test your regular expressions"), page); gbox->addWidget(pButton, line, 0); connect(pButton, &QPushButton::clicked, this, &OptionDialog::slotHistoryMergeRegExpTester); ++line; label = new QLabel(i18n("Irrelevant merge command:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pLE = new OptionLineEdit("", "IrrelevantMergeCmd", &m_options->m_IrrelevantMergeCmd, page); gbox->addWidget(pLE, line, 1); addOptionItem(pLE); label->setToolTip(i18n("If specified this script is run after automerge\n" "when no other relevant changes were detected.\n" "Called with the parameters: filename1 filename2 filename3")); ++line; OptionCheckBox* pAutoSaveAndQuit = new OptionCheckBox(i18n("Auto save and quit on merge without conflicts"), false, "AutoSaveAndQuitOnMergeWithoutConflicts", &m_options->m_bAutoSaveAndQuitOnMergeWithoutConflicts, page); gbox->addWidget(pAutoSaveAndQuit, line, 0, 1, 2); addOptionItem(pAutoSaveAndQuit); pAutoSaveAndQuit->setToolTip(i18n("If KDiff3 was started for a file-merge from the command line and all\n" "conflicts are solvable without user interaction then automatically save and quit.\n" "(Similar to command line option \"--auto\".)")); ++line; topLayout->addStretch(10); } void OptionDialog::setupDirectoryMergePage() { QScrollArea* pageFrame = new QScrollArea(); KPageWidgetItem* pageItem = new KPageWidgetItem(pageFrame, i18n("Folder")); pageItem->setHeader(i18n("Folder")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("inode-directory"))); addPage(pageItem); QVBoxLayout* scrollLayout = new QVBoxLayout(); scrollLayout->setMargin(2); scrollLayout->addWidget(pageFrame); QScopedPointer scrollArea(new Ui::ScrollArea()); scrollArea->setupUi(pageFrame); QWidget* page = pageFrame->findChild("contents"); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; OptionCheckBox* pRecursiveDirs = new OptionCheckBox(i18n("Recursive folders"), true, "RecursiveDirs", &m_options->m_bDmRecursiveDirs, page); gbox->addWidget(pRecursiveDirs, line, 0, 1, 2); addOptionItem(pRecursiveDirs); pRecursiveDirs->setToolTip(i18n("Whether to analyze subfolders or not.")); ++line; QLabel* label = new QLabel(i18n("File pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pFilePattern = new OptionLineEdit("*", "FilePattern", &m_options->m_DmFilePattern, page); gbox->addWidget(pFilePattern, line, 1); addOptionItem(pFilePattern); label->setToolTip(i18n( "Pattern(s) of files to be analyzed. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; label = new QLabel(i18n("File-anti-pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pFileAntiPattern = new OptionLineEdit("*.orig;*.o;*.obj;*.rej;*.bak", "FileAntiPattern", &m_options->m_DmFileAntiPattern, page); gbox->addWidget(pFileAntiPattern, line, 1); addOptionItem(pFileAntiPattern); label->setToolTip(i18n( "Pattern(s) of files to be excluded from analysis. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; label = new QLabel(i18n("Folder-anti-pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pDirAntiPattern = new OptionLineEdit("CVS;.deps;.svn;.hg;.git", "DirAntiPattern", &m_options->m_DmDirAntiPattern, page); gbox->addWidget(pDirAntiPattern, line, 1); addOptionItem(pDirAntiPattern); label->setToolTip(i18n( "Pattern(s) of folders to be excluded from analysis. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; OptionCheckBox* pUseCvsIgnore = new OptionCheckBox(i18n("Use .cvsignore"), false, "UseCvsIgnore", &m_options->m_bDmUseCvsIgnore, page); gbox->addWidget(pUseCvsIgnore, line, 0, 1, 2); addOptionItem(pUseCvsIgnore); pUseCvsIgnore->setToolTip(i18n( "Extends the antipattern to anything that would be ignored by CVS.\n" "Via local \".cvsignore\" files this can be folder-specific.")); ++line; OptionCheckBox* pFindHidden = new OptionCheckBox(i18n("Find hidden files and folders"), true, "FindHidden", &m_options->m_bDmFindHidden, page); gbox->addWidget(pFindHidden, line, 0, 1, 2); addOptionItem(pFindHidden); pFindHidden->setToolTip(i18n("Finds hidden files and folders.")); ++line; OptionCheckBox* pFollowFileLinks = new OptionCheckBox(i18n("Follow file links"), false, "FollowFileLinks", &m_options->m_bDmFollowFileLinks, page); gbox->addWidget(pFollowFileLinks, line, 0, 1, 2); addOptionItem(pFollowFileLinks); pFollowFileLinks->setToolTip(i18n( "On: Compare the file the link points to.\n" "Off: Compare the links.")); ++line; OptionCheckBox* pFollowDirLinks = new OptionCheckBox(i18n("Follow folder links"), false, "FollowDirLinks", &m_options->m_bDmFollowDirLinks, page); gbox->addWidget(pFollowDirLinks, line, 0, 1, 2); addOptionItem(pFollowDirLinks); pFollowDirLinks->setToolTip(i18n( "On: Compare the folder the link points to.\n" "Off: Compare the links.")); ++line; #if defined(Q_OS_WIN) bool bCaseSensitiveFilenameComparison = false; #else bool bCaseSensitiveFilenameComparison = true; #endif OptionCheckBox* pCaseSensitiveFileNames = new OptionCheckBox(i18n("Case sensitive filename comparison"), bCaseSensitiveFilenameComparison, "CaseSensitiveFilenameComparison", &m_options->m_bDmCaseSensitiveFilenameComparison, page); gbox->addWidget(pCaseSensitiveFileNames, line, 0, 1, 2); addOptionItem(pCaseSensitiveFileNames); pCaseSensitiveFileNames->setToolTip(i18n( "The folder comparison will compare files or folders when their names match.\n" "Set this option if the case of the names must match. (Default for Windows is off, otherwise on.)")); ++line; OptionCheckBox* pUnfoldSubdirs = new OptionCheckBox(i18n("Unfold all subfolders on load"), false, "UnfoldSubdirs", &m_options->m_bDmUnfoldSubdirs, page); gbox->addWidget(pUnfoldSubdirs, line, 0, 1, 2); addOptionItem(pUnfoldSubdirs); pUnfoldSubdirs->setToolTip(i18n( "On: Unfold all subfolders when starting a folder diff.\n" "Off: Leave subfolders folded.")); ++line; OptionCheckBox* pSkipDirStatus = new OptionCheckBox(i18n("Skip folder status report"), false, "SkipDirStatus", &m_options->m_bDmSkipDirStatus, page); gbox->addWidget(pSkipDirStatus, line, 0, 1, 2); addOptionItem(pSkipDirStatus); pSkipDirStatus->setToolTip(i18n( "On: Do not show the Folder Comparison Status.\n" "Off: Show the status dialog on start.")); ++line; QGroupBox* pBG = new QGroupBox(i18n("File Comparison Mode")); gbox->addWidget(pBG, line, 0, 1, 2); QVBoxLayout* pBGLayout = new QVBoxLayout(pBG); OptionRadioButton* pBinaryComparison = new OptionRadioButton(i18n("Binary comparison"), true, "BinaryComparison", &m_options->m_bDmBinaryComparison, pBG); addOptionItem(pBinaryComparison); pBinaryComparison->setToolTip(i18n("Binary comparison of each file. (Default)")); pBGLayout->addWidget(pBinaryComparison); OptionRadioButton* pFullAnalysis = new OptionRadioButton(i18n("Full analysis"), false, "FullAnalysis", &m_options->m_bDmFullAnalysis, pBG); addOptionItem(pFullAnalysis); pFullAnalysis->setToolTip(i18n("Do a full analysis and show statistics information in extra columns.\n" "(Slower than a binary comparison, much slower for binary files.)")); pBGLayout->addWidget(pFullAnalysis); OptionRadioButton* pTrustDate = new OptionRadioButton(i18n("Trust the size and modification date (unsafe)"), false, "TrustDate", &m_options->m_bDmTrustDate, pBG); addOptionItem(pTrustDate); pTrustDate->setToolTip(i18n("Assume that files are equal if the modification date and file length are equal.\n" "Files with equal contents but different modification dates will appear as different.\n" "Useful for big folders or slow networks.")); pBGLayout->addWidget(pTrustDate); OptionRadioButton* pTrustDateFallbackToBinary = new OptionRadioButton(i18n("Trust the size and date, but use binary comparison if date does not match (unsafe)"), false, "TrustDateFallbackToBinary", &m_options->m_bDmTrustDateFallbackToBinary, pBG); addOptionItem(pTrustDateFallbackToBinary); pTrustDateFallbackToBinary->setToolTip(i18n("Assume that files are equal if the modification date and file length are equal.\n" "If the dates are not equal but the sizes are, use binary comparison.\n" "Useful for big folders or slow networks.")); pBGLayout->addWidget(pTrustDateFallbackToBinary); OptionRadioButton* pTrustSize = new OptionRadioButton(i18n("Trust the size (unsafe)"), false, "TrustSize", &m_options->m_bDmTrustSize, pBG); addOptionItem(pTrustSize); pTrustSize->setToolTip(i18n("Assume that files are equal if their file lengths are equal.\n" "Useful for big folders or slow networks when the date is modified during download.")); pBGLayout->addWidget(pTrustSize); ++line; // Some two Dir-options: Affects only the default actions. OptionCheckBox* pSyncMode = new OptionCheckBox(i18n("Synchronize folders"), false, "SyncMode", &m_options->m_bDmSyncMode, page); addOptionItem(pSyncMode); gbox->addWidget(pSyncMode, line, 0, 1, 2); pSyncMode->setToolTip(i18n( "Offers to store files in both folders so that\n" "both folders are the same afterwards.\n" "Works only when comparing two folders without specifying a destination.")); ++line; // Allow white-space only differences to be considered equal OptionCheckBox* pWhiteSpaceDiffsEqual = new OptionCheckBox(i18n("White space differences considered equal"), true, "WhiteSpaceEqual", &m_options->m_bDmWhiteSpaceEqual, page); addOptionItem(pWhiteSpaceDiffsEqual); gbox->addWidget(pWhiteSpaceDiffsEqual, line, 0, 1, 2); pWhiteSpaceDiffsEqual->setToolTip(i18n( "If files differ only by white space consider them equal.\n" "This is only active when full analysis is chosen.")); connect(pFullAnalysis, &OptionRadioButton::toggled, pWhiteSpaceDiffsEqual, &OptionCheckBox::setEnabled); pWhiteSpaceDiffsEqual->setEnabled(false); ++line; OptionCheckBox* pCopyNewer = new OptionCheckBox(i18n("Copy newer instead of merging (unsafe)"), false, "CopyNewer", &m_options->m_bDmCopyNewer, page); addOptionItem(pCopyNewer); gbox->addWidget(pCopyNewer, line, 0, 1, 2); pCopyNewer->setToolTip(i18n( "Do not look inside, just take the newer file.\n" "(Use this only if you know what you are doing!)\n" "Only effective when comparing two folders.")); ++line; OptionCheckBox* pCreateBakFiles = new OptionCheckBox(i18n("Backup files (.orig)"), true, "CreateBakFiles", &m_options->m_bDmCreateBakFiles, page); gbox->addWidget(pCreateBakFiles, line, 0, 1, 2); addOptionItem(pCreateBakFiles); pCreateBakFiles->setToolTip(i18n( "If a file would be saved over an old file, then the old file\n" "will be renamed with a '.orig' extension instead of being deleted.")); ++line; topLayout->addStretch(10); } void OptionDialog::setupRegionalPage() { QScrollArea* pageFrame = new QScrollArea(); KPageWidgetItem* pageItem = new KPageWidgetItem(pageFrame, i18n("Regional Settings")); pageItem->setHeader(i18n("Regional Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-locale"))); addPage(pageItem); QVBoxLayout* scrollLayout = new QVBoxLayout(); scrollLayout->setMargin(2); scrollLayout->addWidget(pageFrame); QScopedPointer scrollArea(new Ui::ScrollArea()); scrollArea->setupUi(pageFrame); QWidget* page = pageFrame->findChild("contents"); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label; m_pSameEncoding = new OptionCheckBox(i18n("Use the same encoding for everything:"), true, "SameEncoding", &m_options->m_bSameEncoding, page); addOptionItem(m_pSameEncoding); gbox->addWidget(m_pSameEncoding, line, 0, 1, 2); m_pSameEncoding->setToolTip(i18n( "Enable this allows to change all encodings by changing the first only.\n" "Disable this if different individual settings are needed.")); ++line; label = new QLabel(i18n("Note: Local Encoding is \"%1\"", QLatin1String(QTextCodec::codecForLocale()->name())), page); gbox->addWidget(label, line, 0); ++line; label = new QLabel(i18n("File Encoding for A:"), page); gbox->addWidget(label, line, 0); m_pEncodingAComboBox = new OptionEncodingComboBox("EncodingForA", &m_options->m_pEncodingA, page); addOptionItem(m_pEncodingAComboBox); gbox->addWidget(m_pEncodingAComboBox, line, 1); QString autoDetectToolTip = i18n( "If enabled then Unicode (UTF-16 or UTF-8) encoding will be detected.\n" "If the file is not Unicode then the selected encoding will be used as fallback.\n" "(Unicode detection depends on the first bytes of a file.)"); m_pAutoDetectUnicodeA = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeA", &m_options->m_bAutoDetectUnicodeA, page); gbox->addWidget(m_pAutoDetectUnicodeA, line, 2); addOptionItem(m_pAutoDetectUnicodeA); m_pAutoDetectUnicodeA->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for B:"), page); gbox->addWidget(label, line, 0); m_pEncodingBComboBox = new OptionEncodingComboBox("EncodingForB", &m_options->m_pEncodingB, page); addOptionItem(m_pEncodingBComboBox); gbox->addWidget(m_pEncodingBComboBox, line, 1); m_pAutoDetectUnicodeB = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeB", &m_options->m_bAutoDetectUnicodeB, page); addOptionItem(m_pAutoDetectUnicodeB); gbox->addWidget(m_pAutoDetectUnicodeB, line, 2); m_pAutoDetectUnicodeB->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for C:"), page); gbox->addWidget(label, line, 0); m_pEncodingCComboBox = new OptionEncodingComboBox("EncodingForC", &m_options->m_pEncodingC, page); addOptionItem(m_pEncodingCComboBox); gbox->addWidget(m_pEncodingCComboBox, line, 1); m_pAutoDetectUnicodeC = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeC", &m_options->m_bAutoDetectUnicodeC, page); addOptionItem(m_pAutoDetectUnicodeC); gbox->addWidget(m_pAutoDetectUnicodeC, line, 2); m_pAutoDetectUnicodeC->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for Merge Output and Saving:"), page); gbox->addWidget(label, line, 0); m_pEncodingOutComboBox = new OptionEncodingComboBox("EncodingForOutput", &m_options->m_pEncodingOut, page); addOptionItem(m_pEncodingOutComboBox); gbox->addWidget(m_pEncodingOutComboBox, line, 1); m_pAutoSelectOutEncoding = new OptionCheckBox(i18n("Auto Select"), true, "AutoSelectOutEncoding", &m_options->m_bAutoSelectOutEncoding, page); addOptionItem(m_pAutoSelectOutEncoding); gbox->addWidget(m_pAutoSelectOutEncoding, line, 2); m_pAutoSelectOutEncoding->setToolTip(i18n( "If enabled then the encoding from the input files is used.\n" "In ambiguous cases a dialog will ask the user to choose the encoding for saving.")); ++line; label = new QLabel(i18n("File Encoding for Preprocessor Files:"), page); gbox->addWidget(label, line, 0); m_pEncodingPPComboBox = new OptionEncodingComboBox("EncodingForPP", &m_options->m_pEncodingPP, page); addOptionItem(m_pEncodingPPComboBox); gbox->addWidget(m_pEncodingPPComboBox, line, 1); ++line; connect(m_pSameEncoding, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); connect(m_pEncodingAComboBox, static_cast(&OptionEncodingComboBox::activated), this, &OptionDialog::slotEncodingChanged); connect(m_pAutoDetectUnicodeA, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); connect(m_pAutoSelectOutEncoding, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); OptionCheckBox* pRightToLeftLanguage = new OptionCheckBox(i18n("Right To Left Language"), false, "RightToLeftLanguage", &m_options->m_bRightToLeftLanguage, page); addOptionItem(pRightToLeftLanguage); gbox->addWidget(pRightToLeftLanguage, line, 0, 1, 2); pRightToLeftLanguage->setToolTip(i18n( "Some languages are read from right to left.\n" "This setting will change the viewer and editor accordingly.")); ++line; topLayout->addStretch(10); } void OptionDialog::setupIntegrationPage() { QScrollArea* pageFrame = new QScrollArea(); KPageWidgetItem* pageItem = new KPageWidgetItem(pageFrame, i18n("Integration")); pageItem->setHeader(i18n("Integration Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); addPage(pageItem); QVBoxLayout* scrollLayout = new QVBoxLayout(); scrollLayout->setMargin(2); scrollLayout->addWidget(pageFrame); QScopedPointer scrollArea(new Ui::ScrollArea()); scrollArea->setupUi(pageFrame); QWidget* page = pageFrame->findChild("contents"); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(2, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label; label = new QLabel(i18n("Command line options to ignore:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pIgnorableCmdLineOptions = new OptionLineEdit("-u;-query;-html;-abort", "IgnorableCmdLineOptions", &m_options->m_ignorableCmdLineOptions, page); gbox->addWidget(pIgnorableCmdLineOptions, line, 1, 1, 2); addOptionItem(pIgnorableCmdLineOptions); label->setToolTip(i18n( "List of command line options that should be ignored when KDiff3 is used by other tools.\n" "Several values can be specified if separated via ';'\n" "This will suppress the \"Unknown option\" error.")); ++line; OptionCheckBox* pEscapeKeyQuits = new OptionCheckBox(i18n("Quit also via Escape key"), false, "EscapeKeyQuits", &m_options->m_bEscapeKeyQuits, page); gbox->addWidget(pEscapeKeyQuits, line, 0, 1, 2); addOptionItem(pEscapeKeyQuits); pEscapeKeyQuits->setToolTip(i18n( "Fast method to exit.\n" "For those who are used to using the Escape key.")); ++line; topLayout->addStretch(10); } void OptionDialog::slotEncodingChanged() { if(m_pSameEncoding->isChecked()) { m_pEncodingBComboBox->setEnabled(false); m_pEncodingBComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingCComboBox->setEnabled(false); m_pEncodingCComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingOutComboBox->setEnabled(false); m_pEncodingOutComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingPPComboBox->setEnabled(false); m_pEncodingPPComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pAutoDetectUnicodeB->setEnabled(false); m_pAutoDetectUnicodeB->setCheckState(m_pAutoDetectUnicodeA->checkState()); m_pAutoDetectUnicodeC->setEnabled(false); m_pAutoDetectUnicodeC->setCheckState(m_pAutoDetectUnicodeA->checkState()); m_pAutoSelectOutEncoding->setEnabled(false); m_pAutoSelectOutEncoding->setCheckState(m_pAutoDetectUnicodeA->checkState()); } else { m_pEncodingBComboBox->setEnabled(true); m_pEncodingCComboBox->setEnabled(true); m_pEncodingOutComboBox->setEnabled(true); m_pEncodingPPComboBox->setEnabled(true); m_pAutoDetectUnicodeB->setEnabled(true); m_pAutoDetectUnicodeC->setEnabled(true); m_pAutoSelectOutEncoding->setEnabled(true); m_pEncodingOutComboBox->setEnabled(m_pAutoSelectOutEncoding->checkState() == Qt::Unchecked); } } void OptionDialog::slotOk() { slotApply(); accept(); } /** Copy the values from the widgets to the public variables.*/ void OptionDialog::slotApply() { m_options->apply(); Q_EMIT applyDone(); } /** Set the default values in the widgets only, while the public variables remain unchanged. */ void OptionDialog::slotDefault() { int result = KMessageBox::warningContinueCancel(this, i18n("This resets all options. Not only those of the current topic.")); if(result == KMessageBox::Cancel) return; else resetToDefaults(); } void OptionDialog::resetToDefaults() { m_options->resetToDefaults(); slotEncodingChanged(); } /** Initialise the widgets using the values in the public varibles. */ void OptionDialog::setState() { m_options->setToCurrent(); slotEncodingChanged(); } void OptionDialog::saveOptions(KSharedConfigPtr config) { // No i18n()-Translations here! m_options->saveOptions(config); } void OptionDialog::readOptions(KSharedConfigPtr config) { // No i18n()-Translations here! m_options->readOptions(config); setState(); } const QString OptionDialog::parseOptions(const QStringList& optionList) { return m_options->parseOptions(optionList); } QString OptionDialog::calcOptionHelp() { return m_options->calcOptionHelp(); } void OptionDialog::slotHistoryMergeRegExpTester() { QPointer dlg=QPointer(new RegExpTester(this, s_autoMergeRegExpToolTip, s_historyStartRegExpToolTip, s_historyEntryStartRegExpToolTip, s_historyEntryStartSortKeyOrderToolTip)); dlg->init(m_pAutoMergeRegExpLineEdit->currentText(), m_pHistoryStartRegExpLineEdit->currentText(), m_pHistoryEntryStartRegExpLineEdit->currentText(), m_pHistorySortKeyOrderLineEdit->currentText()); if(dlg->exec()) { m_pAutoMergeRegExpLineEdit->setEditText(dlg->autoMergeRegExp()); m_pHistoryStartRegExpLineEdit->setEditText(dlg->historyStartRegExp()); m_pHistoryEntryStartRegExpLineEdit->setEditText(dlg->historyEntryStartRegExp()); m_pHistorySortKeyOrderLineEdit->setEditText(dlg->historySortKeyOrder()); } } #include "optiondialog.moc" diff --git a/src/optiondialog.h b/src/optiondialog.h index 7002933..8183015 100644 --- a/src/optiondialog.h +++ b/src/optiondialog.h @@ -1,134 +1,122 @@ /* - * kdiff3 - Text Diff And Merge Tool - * Copyright (C) 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * KDiff3 - Text Diff And Merge Tool * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef OPTION_DIALOG_H #define OPTION_DIALOG_H #include "options.h" #include // for QSharedPointer #include #include #include #include class QLabel; class QPlainTextEdit; class OptionItemBase; class OptionCheckBox; class OptionEncodingComboBox; class OptionLineEdit; class OptionDialog : public KPageDialog { Q_OBJECT public: explicit OptionDialog( bool bShowDirMergeSettings, QWidget *parent = nullptr ); ~OptionDialog() override; const QString parseOptions( const QStringList& optionList ); QString calcOptionHelp(); void saveOptions(KSharedConfigPtr config); void readOptions(KSharedConfigPtr config); void setState(); // Must be called before calling exec(); void addOptionItem(OptionItemBase*); QSharedPointer getOptions() { return m_options; } static const QString s_historyEntryStartRegExpToolTip; static const QString s_historyEntryStartSortKeyOrderToolTip; static const QString s_autoMergeRegExpToolTip; static const QString s_historyStartRegExpToolTip; protected Q_SLOTS: virtual void slotDefault(); virtual void slotOk(); virtual void slotApply(); //virtual void buttonClicked( QAbstractButton* ); virtual void helpRequested(); void slotEncodingChanged(); void slotHistoryMergeRegExpTester(); Q_SIGNALS: void applyDone(); private: void setupFontPage(); void setupColorPage(); void setupEditPage(); void setupDiffPage(); void setupMergePage(); void setupDirectoryMergePage(); void setupRegionalPage(); void setupIntegrationPage(); void setupOtherOptions(); void resetToDefaults(); QSharedPointer m_options=QSharedPointer::create(); //QDialogButtonBox *mButtonBox; OptionCheckBox* m_pSameEncoding; OptionEncodingComboBox* m_pEncodingAComboBox; OptionCheckBox* m_pAutoDetectUnicodeA; OptionEncodingComboBox* m_pEncodingBComboBox; OptionCheckBox* m_pAutoDetectUnicodeB; OptionEncodingComboBox* m_pEncodingCComboBox; OptionCheckBox* m_pAutoDetectUnicodeC; OptionEncodingComboBox* m_pEncodingOutComboBox; OptionCheckBox* m_pAutoSelectOutEncoding; OptionEncodingComboBox* m_pEncodingPPComboBox; OptionCheckBox* m_pHistoryAutoMerge; OptionLineEdit* m_pAutoMergeRegExpLineEdit; OptionLineEdit* m_pHistoryStartRegExpLineEdit; OptionLineEdit* m_pHistoryEntryStartRegExpLineEdit; OptionCheckBox* m_pHistoryMergeSorting; OptionLineEdit* m_pHistorySortKeyOrderLineEdit; }; class FontChooser : public QGroupBox { Q_OBJECT QFont m_font; QPushButton* m_pSelectFont; QPlainTextEdit* m_pExampleTextEdit; QLabel* m_pLabel; public: explicit FontChooser( QWidget* pParent ); QFont font(); void setFont( const QFont&, bool ); private Q_SLOTS: void slotSelectFont(); }; #endif diff --git a/src/options.h b/src/options.h index 34c8db1..aed9422 100644 --- a/src/options.h +++ b/src/options.h @@ -1,201 +1,189 @@ /* - * kdiff3 - Text Diff And Merge Tool - * Copyright (C) 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * KDiff3 - Text Diff And Merge Tool * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef OPTIONS_H #define OPTIONS_H #include #include #include #include #include #include #include class OptionItemBase; enum e_LineEndStyle { eLineEndStyleUnix=0, eLineEndStyleDos, eLineEndStyleAutoDetect, eLineEndStyleUndefined, // only one line exists eLineEndStyleConflict // User must resolve manually }; class Options { public: void init(); void apply(); void resetToDefaults(); void setToCurrent(); void saveOptions(const KSharedConfigPtr config); void readOptions(const KSharedConfigPtr config); const QString parseOptions(const QStringList& optionList); QString calcOptionHelp(); void addOptionItem(OptionItemBase* inItem); const QSize& getGeometry() const { return m_geometry; } void setGeometry(const QSize& size) { m_geometry = size; } const QPoint& getPosition() const { return m_position; } void setPosition(const QPoint& pos) { m_position = pos; } bool isMaximised() const { return m_bMaximised; }; void setMaximised(const bool maximised) { m_bMaximised = maximised;}; Qt::ToolBarArea getToolbarPos() const { return m_toolBarPos; } bool isToolBarVisable() const { return m_bShowToolBar; } void setToolbarState(bool inShown) { m_bShowToolBar = inShown; } bool isStatusBarVisable() const { return m_bShowStatusBar; } void setStatusBarState(bool inShown) { m_bShowStatusBar = inShown; } private: std::list mOptionItemList; // Some settings that are not available in the option dialog: QSize m_geometry = QSize(600, 400); QPoint m_position = QPoint(0, 22); bool m_bMaximised = false; bool m_bShowToolBar = true; bool m_bShowStatusBar = true; Qt::ToolBarArea m_toolBarPos = Qt::TopToolBarArea; public: // These are the results of the option dialog. QFont m_font; //bool m_bItalicForDeltas; QFont m_appFont; QColor m_fgColor = Qt::black; QColor m_bgColor = Qt::white; QColor m_diffBgColor; QColor m_colorA; QColor m_colorB; QColor m_colorC; QColor m_colorForConflict = Qt::red; QColor m_currentRangeBgColor; QColor m_currentRangeDiffBgColor; QColor m_oldestFileColor = qRgb(0xf0, 0, 0); QColor m_midAgeFileColor = qRgb(0xc0, 0xc0, 0); QColor m_newestFileColor = qRgb(0, 0xd0, 0); QColor m_missingFileColor = qRgb(0, 0, 0); QColor m_manualHelpRangeColor = qRgb(0xff, 0xd0, 0x80); bool m_bWordWrap = false; bool m_bReplaceTabs = false; bool m_bAutoIndentation = true; int m_tabSize = 8; bool m_bAutoCopySelection = false; bool m_bSameEncoding = true; QTextCodec* m_pEncodingA = nullptr; bool m_bAutoDetectUnicodeA = true; QTextCodec* m_pEncodingB = nullptr; bool m_bAutoDetectUnicodeB = true; QTextCodec* m_pEncodingC = nullptr; bool m_bAutoDetectUnicodeC = true; QTextCodec* m_pEncodingOut = nullptr; bool m_bAutoSelectOutEncoding = true; ; QTextCodec* m_pEncodingPP = nullptr; e_LineEndStyle m_lineEndStyle = eLineEndStyleAutoDetect; bool m_bPreserveCarriageReturn = false; bool m_bTryHard = true; bool m_bShowWhiteSpaceCharacters = true; bool m_bShowWhiteSpace = true; bool m_bShowLineNumbers = false; bool m_bHorizDiffWindowSplitting = true; bool m_bShowInfoDialogs = true; bool m_bDiff3AlignBC = false; int m_whiteSpace2FileMergeDefault = 0; int m_whiteSpace3FileMergeDefault = 0; bool m_bIgnoreCase = false; bool m_bIgnoreNumbers = false; bool m_bIgnoreComments = false; QString m_PreProcessorCmd; QString m_LineMatchingPreProcessorCmd; bool m_bRunRegExpAutoMergeOnMergeStart = false; QString m_autoMergeRegExp = ".*\\$(Version|Header|Date|Author).*\\$.*"; bool m_bRunHistoryAutoMergeOnMergeStart = false; QString m_historyStartRegExp = ".*\\$Log.*\\$.*"; QString m_historyEntryStartRegExp; bool m_bHistoryMergeSorting = false; QString m_historyEntryStartSortKeyOrder = "4,3,2,5,1,6"; int m_maxNofHistoryEntries = -1; QString m_IrrelevantMergeCmd; bool m_bAutoSaveAndQuitOnMergeWithoutConflicts = false; bool m_bAutoAdvance = false; int m_autoAdvanceDelay = 500; QStringList m_recentAFiles; QStringList m_recentBFiles; QStringList m_recentCFiles; QStringList m_recentEncodings; QStringList m_recentOutputFiles; // Directory Merge options bool m_bDmSyncMode = false; bool m_bDmRecursiveDirs = true; bool m_bDmFollowFileLinks = false; bool m_bDmFollowDirLinks = false; bool m_bDmFindHidden = true; bool m_bDmCreateBakFiles; bool m_bDmBinaryComparison = true; bool m_bDmFullAnalysis = false; bool m_bDmTrustDate = false; bool m_bDmTrustDateFallbackToBinary = false; bool m_bDmTrustSize = false; bool m_bDmCopyNewer = false; //bool m_bDmShowOnlyDeltas; bool m_bDmShowIdenticalFiles = true; bool m_bDmUseCvsIgnore = false; bool m_bDmWhiteSpaceEqual = true; bool m_bDmCaseSensitiveFilenameComparison; bool m_bDmUnfoldSubdirs = false; bool m_bDmSkipDirStatus = false; QString m_DmFilePattern = "*"; QString m_DmFileAntiPattern = "*.orig;*.o;*.obj;*.rej;*.bak"; QString m_DmDirAntiPattern = "CVS;.deps;.svn;.hg;.git"; bool m_bRightToLeftLanguage = false; QString m_ignorableCmdLineOptions = QString("-u;-query;-html;-abort"); bool m_bEscapeKeyQuits = false; }; #endif diff --git a/src/pdiff.cpp b/src/pdiff.cpp index fd25bd7..11b8eec 100644 --- a/src/pdiff.cpp +++ b/src/pdiff.cpp @@ -1,2291 +1,2288 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* + * This file is part of KDiff3. + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "difftextwindow.h" #include "directorymergewindow.h" #include "fileaccess.h" #include "Logging.h" #include "kdiff3.h" #include "optiondialog.h" #include "progress.h" #include "Utils.h" #include "DirectoryInfo.h" #include "mergeresultwindow.h" #include "smalldialogs.h" #include #include #include #include #include #include #include #include #include // QKeyEvent, QDropEvent, QInputEvent #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Function uses setMinSize( sizeHint ) before adding the widget. // void addWidget(QBoxLayout* layout, QWidget* widget); template void addWidget(L* layout, W* widget) { QSize s = widget->sizeHint(); widget->setMinimumSize(QSize(std::max(s.width(), 0), std::max(s.height(), 0))); layout->addWidget(widget); } void KDiff3App::mainInit(TotalDiffStatus* pTotalDiffStatus, bool bLoadFiles, bool bUseCurrentEncoding) { ProgressProxy pp; QStringList errors; // When doing a full analysis in the directory-comparison, then the statistics-results // will be stored in the given TotalDiffStatus. Otherwise it will be 0. bool bGUI = pTotalDiffStatus == nullptr; if(pTotalDiffStatus == nullptr) pTotalDiffStatus = &m_totalDiffStatus; //bool bPreserveCarriageReturn = m_pOptions->m_bPreserveCarriageReturn; bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty(); if(bVisibleMergeResultWindow && bGUI) { //bPreserveCarriageReturn = false; QString msg; if(!m_pOptions->m_PreProcessorCmd.isEmpty()) { msg += "- " + i18n("PreprocessorCmd: ") + m_pOptions->m_PreProcessorCmd + '\n'; } if(!msg.isEmpty()) { int result = KMessageBox::warningYesNo(this, i18n("The following option(s) you selected might change data:\n") + msg + i18n("\nMost likely this is not wanted during a merge.\n" "Do you want to disable these settings or continue with these settings active?"), i18n("Option Unsafe for Merging"), KGuiItem(i18n("Use These Options During Merge")), KGuiItem(i18n("Disable Unsafe Options"))); if(result == KMessageBox::No) { m_pOptions->m_PreProcessorCmd = ""; } } } // Because of the progressdialog paintevents can occur, but data is invalid, // so painting must be suppressed if(bGUI) setLockPainting(true); //insure merge result window never has stale iterators. if(m_pMergeResultWindow) m_pMergeResultWindow->clearMergeList(); m_diff3LineList.clear(); m_diff3LineVector.clear(); if(bLoadFiles) { m_manualDiffHelpList.clear(); if(m_sd3->isEmpty()) pp.setMaxNofSteps(4); // Read 2 files, 1 comparison, 1 finediff else pp.setMaxNofSteps(9); // Read 3 files, 3 comparisons, 3 finediffs // First get all input data. pp.setInformation(i18n("Loading A")); qCInfo(kdiffMain) << i18n("Loading A: %1", m_sd1->getFilename()); if(bUseCurrentEncoding) errors = m_sd1->readAndPreprocess(m_sd1->getEncoding(), false); else errors = m_sd1->readAndPreprocess(m_pOptions->m_pEncodingA, m_pOptions->m_bAutoDetectUnicodeA); if(!errors.isEmpty()) { KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file A."), errors); } pp.step(); pp.setInformation(i18n("Loading B")); qCInfo(kdiffMain) << i18n("Loading B: %1", m_sd2->getFilename()); if(bUseCurrentEncoding) errors = m_sd2->readAndPreprocess(m_sd2->getEncoding(), false); else errors = m_sd2->readAndPreprocess(m_pOptions->m_pEncodingB, m_pOptions->m_bAutoDetectUnicodeB); if(!errors.isEmpty()) KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file B."), errors); pp.step(); } else { if(m_sd3->isEmpty()) pp.setMaxNofSteps(2); // 1 comparison, 1 finediff else pp.setMaxNofSteps(6); // 3 comparisons, 3 finediffs } if(pTotalDiffStatus) pTotalDiffStatus->reset(); if(errors.isEmpty()) { // Run the diff. if(m_sd3->isEmpty()) { pTotalDiffStatus->setBinaryEqualAB(m_sd1->isBinaryEqualWith(m_sd2)); if(m_sd1->isText() && m_sd2->isText()) { pp.setInformation(i18n("Diff: A <-> B")); qCInfo(kdiffMain) << i18n("Diff: A <-> B") ; m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_diffList12, A, B, m_pOptionDialog->getOptions()); pp.step(); pp.setInformation(i18n("Linediff: A <-> B")); qCInfo(kdiffMain) << i18n("Linediff: A <-> B"); m_diff3LineList.calcDiff3LineListUsingAB(&m_diffList12); pTotalDiffStatus->setTextEqualAB(m_diff3LineList.fineDiff(A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay())); if(m_sd1->getSizeBytes() == 0) pTotalDiffStatus->setTextEqualAB(false); pp.step(); } else { pp.step(); pp.step(); } } else { if(bLoadFiles) { pp.setInformation(i18n("Loading C")); qCInfo(kdiffMain) << i18n("Loading C: %1", m_sd2->getFilename()); if(bUseCurrentEncoding) errors = m_sd3->readAndPreprocess(m_sd3->getEncoding(), false); else errors = m_sd3->readAndPreprocess(m_pOptions->m_pEncodingC, m_pOptions->m_bAutoDetectUnicodeC); if(!errors.isEmpty()) KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file C."), errors); pp.step(); } pTotalDiffStatus->setBinaryEqualAB(m_sd1->isBinaryEqualWith(m_sd2)); pTotalDiffStatus->setBinaryEqualAC(m_sd1->isBinaryEqualWith(m_sd3)); pTotalDiffStatus->setBinaryEqualBC(m_sd3->isBinaryEqualWith(m_sd2)); pp.setInformation(i18n("Diff: A <-> B")); qCInfo(kdiffMain) << i18n("Diff: A <-> B"); if(m_sd1->isText() && m_sd2->isText()) { m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_diffList12, A, B, m_pOptionDialog->getOptions()); m_diff3LineList.calcDiff3LineListUsingAB(&m_diffList12); } pp.step(); pp.setInformation(i18n("Diff: A <-> C")); qCInfo(kdiffMain) << i18n("Diff: A <-> C"); if(m_sd1->isText() && m_sd3->isText()) { m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines(), m_diffList13, A, C, m_pOptionDialog->getOptions()); m_diff3LineList.calcDiff3LineListUsingAC(&m_diffList13); m_diff3LineList.correctManualDiffAlignment(&m_manualDiffHelpList); m_diff3LineList.calcDiff3LineListTrim(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff(), &m_manualDiffHelpList); } pp.step(); pp.setInformation(i18n("Diff: B <-> C")); qCInfo(kdiffMain) << i18n("Diff: B <-> C"); if(m_sd2->isText() && m_sd3->isText()) { m_manualDiffHelpList.runDiff(m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines(), m_diffList23, B, C, m_pOptionDialog->getOptions()); if(m_pOptions->m_bDiff3AlignBC) { m_diff3LineList.calcDiff3LineListUsingBC(&m_diffList23); m_diff3LineList.correctManualDiffAlignment(&m_manualDiffHelpList); m_diff3LineList.calcDiff3LineListTrim(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff(), &m_manualDiffHelpList); } } pp.step(); m_diff3LineList.debugLineCheck(m_sd1->getSizeLines(), A); m_diff3LineList.debugLineCheck(m_sd2->getSizeLines(), B); m_diff3LineList.debugLineCheck(m_sd3->getSizeLines(), C); pp.setInformation(i18n("Linediff: A <-> B")); qCInfo(kdiffMain) << i18n("Linediff: A <-> B"); if(m_sd1->hasData() && m_sd2->hasData() && m_sd1->isText() && m_sd2->isText()) pTotalDiffStatus->setTextEqualAB(m_diff3LineList.fineDiff(A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay())); pp.step(); pp.setInformation(i18n("Linediff: B <-> C")); qCInfo(kdiffMain) << i18n("Linediff: B <-> C"); if(m_sd2->hasData() && m_sd3->hasData() && m_sd2->isText() && m_sd3->isText()) pTotalDiffStatus->setTextEqualBC(m_diff3LineList.fineDiff(B, m_sd2->getLineDataForDisplay(), m_sd3->getLineDataForDisplay())); pp.step(); pp.setInformation(i18n("Linediff: A <-> C")); qCInfo(kdiffMain) << i18n("Linediff: A <-> C"); if(m_sd1->hasData() && m_sd3->hasData() && m_sd1->isText() && m_sd3->isText()) pTotalDiffStatus->setTextEqualAC(m_diff3LineList.fineDiff(C, m_sd3->getLineDataForDisplay(), m_sd1->getLineDataForDisplay())); m_diff3LineList.debugLineCheck(m_sd2->getSizeLines(), B); m_diff3LineList.debugLineCheck(m_sd3->getSizeLines(), C); pp.setInformation(i18n("Linediff: A <-> B")); if(m_sd1->hasData() && m_sd2->hasData() && m_sd1->isText() && m_sd2->isText()) pTotalDiffStatus->setTextEqualAB(m_diff3LineList.fineDiff(A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay())); pp.step(); pp.setInformation(i18n("Linediff: B <-> C")); if(m_sd3->hasData() && m_sd2->hasData() && m_sd3->isText() && m_sd2->isText()) pTotalDiffStatus->setTextEqualBC(m_diff3LineList.fineDiff(B, m_sd2->getLineDataForDisplay(), m_sd3->getLineDataForDisplay())); pp.step(); pp.setInformation(i18n("Linediff: A <-> C")); if(m_sd1->hasData() && m_sd3->hasData() && m_sd1->isText() && m_sd3->isText()) pTotalDiffStatus->setTextEqualAC(m_diff3LineList.fineDiff(C, m_sd3->getLineDataForDisplay(), m_sd1->getLineDataForDisplay())); pp.step(); if(m_sd1->getSizeBytes() == 0) { pTotalDiffStatus->setTextEqualAB(false); pTotalDiffStatus->setTextEqualAC(false); } if(m_sd2->getSizeBytes() == 0) { pTotalDiffStatus->setTextEqualAB(false); pTotalDiffStatus->setTextEqualBC(false); } } } else { pp.clear(); } if(errors.isEmpty() && m_sd1->isText() && m_sd2->isText()) { m_diffBufferInfo->init(&m_diff3LineList, &m_diff3LineVector, m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines()); Diff3Line::m_pDiffBufferInfo = m_diffBufferInfo; m_diff3LineList.calcWhiteDiff3Lines(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff()); m_diff3LineList.calcDiff3LineVector(m_diff3LineVector); } // Calc needed lines for display m_neededLines = m_diff3LineList.size(); QList oldHeights; if(m_pDirectoryMergeSplitter->isVisible()) oldHeights = m_pMainSplitter->sizes(); initView(); m_pMergeResultWindow->connectActions(); if(m_pDirectoryMergeSplitter->isVisible()) { if(oldHeights.count() < 2) oldHeights.append(0); if(oldHeights[1] == 0) // Distribute the available space evenly between the two widgets. { oldHeights[1] = oldHeights[0] / 2; oldHeights[0] -= oldHeights[1]; } if(oldHeights[0] == 0 && oldHeights[1] == 0) { oldHeights[1] = 100; oldHeights[0] = 100; } m_pMainSplitter->setSizes(oldHeights); } m_pMainWidget->setVisible(bGUI); m_bTripleDiff = !m_sd3->isEmpty(); m_pMergeResultWindowTitle->setEncodings(m_sd1->getEncoding(), m_sd2->getEncoding(), m_sd3->getEncoding()); if(!m_pOptions->m_bAutoSelectOutEncoding) m_pMergeResultWindowTitle->setEncoding(m_pOptions->m_pEncodingOut); m_pMergeResultWindowTitle->setLineEndStyles(m_sd1->getLineEndStyle(), m_sd2->getLineEndStyle(), m_sd3->getLineEndStyle()); if(bGUI) { const ManualDiffHelpList* pMDHL = &m_manualDiffHelpList; m_pDiffTextWindow1->init(m_sd1->getAliasName(), m_sd1->getEncoding(), m_sd1->getLineEndStyle(), m_sd1->getLineDataForDisplay(), m_sd1->getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindowFrame1->init(); m_pDiffTextWindow2->init(m_sd2->getAliasName(), m_sd2->getEncoding(), m_sd2->getLineEndStyle(), m_sd2->getLineDataForDisplay(), m_sd2->getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindowFrame2->init(); m_pDiffTextWindow3->init(m_sd3->getAliasName(), m_sd3->getEncoding(), m_sd3->getLineEndStyle(), m_sd3->getLineDataForDisplay(), m_sd3->getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindowFrame3->init(); m_pDiffTextWindowFrame3->setVisible(m_bTripleDiff); } m_bOutputModified = bVisibleMergeResultWindow; m_pMergeResultWindow->init( m_sd1->getLineDataForDisplay(), m_sd1->getSizeLines(), m_sd2->getLineDataForDisplay(), m_sd2->getSizeLines(), m_bTripleDiff ? m_sd3->getLineDataForDisplay() : nullptr, m_sd3->getSizeLines(), &m_diff3LineList, pTotalDiffStatus); m_pMergeResultWindowTitle->setFileName(m_outputFilename.isEmpty() ? QString("unnamed.txt") : m_outputFilename); if(!bGUI) { // We now have all needed information. The rest below is only for GUI-activation. m_sd1->reset(); m_sd2->reset(); m_sd3->reset(); } else { m_pOverview->init(&m_diff3LineList, m_bTripleDiff); m_pDiffVScrollBar->setValue(0); m_pHScrollBar->setValue(0); m_pMergeVScrollBar->setValue(0); setLockPainting(false); if(!bVisibleMergeResultWindow) m_pMergeWindowFrame->hide(); else m_pMergeWindowFrame->show(); // Try to create a meaningful but not too long caption if(!isPart() && errors.isEmpty()) { createCaption(); } //initialize wheel tracking to zero m_iCumulativeWheelDelta = 0; m_bFinishMainInit = true; // call slotFinishMainInit after finishing the word wrap m_bLoadFiles = bLoadFiles; postRecalcWordWrap(); } } void KDiff3App::setLockPainting(bool bLock) { if(m_pDiffTextWindow1) m_pDiffTextWindow1->setPaintingAllowed(!bLock); if(m_pDiffTextWindow2) m_pDiffTextWindow2->setPaintingAllowed(!bLock); if(m_pDiffTextWindow3) m_pDiffTextWindow3->setPaintingAllowed(!bLock); if(m_pOverview) m_pOverview->setPaintingAllowed(!bLock); if(m_pMergeResultWindow) m_pMergeResultWindow->setPaintingAllowed(!bLock); } void KDiff3App::createCaption() { // Try to create a meaningful but not too long caption // 1. If the filenames are equal then show only one filename QString caption; QString f1 = m_sd1->getAliasName(); QString f2 = m_sd2->getAliasName(); QString f3 = m_sd3->getAliasName(); int p; if((p = f1.lastIndexOf('/')) >= 0 || (p = f1.lastIndexOf('\\')) >= 0) f1 = f1.mid(p + 1); if((p = f2.lastIndexOf('/')) >= 0 || (p = f2.lastIndexOf('\\')) >= 0) f2 = f2.mid(p + 1); if((p = f3.lastIndexOf('/')) >= 0 || (p = f3.lastIndexOf('\\')) >= 0) f3 = f3.mid(p + 1); if(!f1.isEmpty()) { if((f2.isEmpty() && f3.isEmpty()) || (f2.isEmpty() && f1 == f3) || (f3.isEmpty() && f1 == f2) || (f1 == f2 && f1 == f3)) caption = f1; } else if(!f2.isEmpty()) { if(f3.isEmpty() || f2 == f3) caption = f2; } else if(!f3.isEmpty()) caption = f3; // 2. If the files don't have the same name then show all names if(caption.isEmpty() && (!f1.isEmpty() || !f2.isEmpty() || !f3.isEmpty())) { caption = (f1.isEmpty() ? QString("") : f1); caption += QLatin1String(caption.isEmpty() || f2.isEmpty() ? "" : " <-> ") + (f2.isEmpty() ? QString("") : f2); caption += QLatin1String(caption.isEmpty() || f3.isEmpty() ? "" : " <-> ") + (f3.isEmpty() ? QString("") : f3); } m_pKDiff3Shell->setWindowTitle(caption.isEmpty() ? QString("KDiff3") : caption + QString(" - KDiff3")); } void KDiff3App::setHScrollBarRange() { int w1 = m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->isVisible() ? m_pDiffTextWindow1->getMaxTextWidth() : 0; int w2 = m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->isVisible() ? m_pDiffTextWindow2->getMaxTextWidth() : 0; int w3 = m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->isVisible() ? m_pDiffTextWindow3->getMaxTextWidth() : 0; int wm = m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() ? m_pMergeResultWindow->getMaxTextWidth() : 0; int v1 = m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->isVisible() ? m_pDiffTextWindow1->getVisibleTextAreaWidth() : 0; int v2 = m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->isVisible() ? m_pDiffTextWindow2->getVisibleTextAreaWidth() : 0; int v3 = m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->isVisible() ? m_pDiffTextWindow3->getVisibleTextAreaWidth() : 0; int vm = m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() ? m_pMergeResultWindow->getVisibleTextAreaWidth() : 0; // Find the minimum, but don't consider 0. int pageStep = 0; if((pageStep == 0 || pageStep > v1) && v1 > 0) pageStep = v1; if((pageStep == 0 || pageStep > v2) && v2 > 0) pageStep = v2; if((pageStep == 0 || pageStep > v3) && v3 > 0) pageStep = v3; if((pageStep == 0 || pageStep > vm) && vm > 0) pageStep = vm; int rangeMax = 0; if(w1 > v1 && w1 - v1 > rangeMax && v1 > 0) rangeMax = w1 - v1; if(w2 > v2 && w2 - v2 > rangeMax && v2 > 0) rangeMax = w2 - v2; if(w3 > v3 && w3 - v3 > rangeMax && v3 > 0) rangeMax = w3 - v3; if(wm > vm && wm - vm > rangeMax && vm > 0) rangeMax = wm - vm; m_pHScrollBar->setRange(0, rangeMax); m_pHScrollBar->setPageStep(pageStep); } void KDiff3App::resizeDiffTextWindowHeight(int newHeight) { m_DTWHeight = newHeight; m_pDiffVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - newHeight)); m_pDiffVScrollBar->setPageStep(newHeight); m_pOverview->setRange(m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep()); setHScrollBarRange(); } void KDiff3App::resizeMergeResultWindow() { MergeResultWindow* p = m_pMergeResultWindow; m_pMergeVScrollBar->setRange(0, std::max(0, p->getNofLines() - p->getNofVisibleLines())); m_pMergeVScrollBar->setPageStep(p->getNofVisibleLines()); setHScrollBarRange(); } void KDiff3App::scrollDiffTextWindow(int deltaX, int deltaY) { if(deltaY != 0 && m_pDiffVScrollBar != nullptr) { m_pDiffVScrollBar->setValue(m_pDiffVScrollBar->value() + deltaY); m_pOverview->setRange(m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep()); } if(deltaX != 0 && m_pHScrollBar != nullptr) m_pHScrollBar->QScrollBar::setValue(m_pHScrollBar->value() + deltaX); } void KDiff3App::scrollMergeResultWindow(int deltaX, int deltaY) { if(deltaY != 0) m_pMergeVScrollBar->setValue(m_pMergeVScrollBar->value() + deltaY); if(deltaX != 0) m_pHScrollBar->setValue(m_pHScrollBar->value() + deltaX); } void KDiff3App::setDiff3Line(int line) { m_pDiffVScrollBar->setValue(line); } void KDiff3App::sourceMask(int srcMask, int enabledMask) { chooseA->blockSignals(true); chooseB->blockSignals(true); chooseC->blockSignals(true); chooseA->setChecked((srcMask & 1) != 0); chooseB->setChecked((srcMask & 2) != 0); chooseC->setChecked((srcMask & 4) != 0); chooseA->blockSignals(false); chooseB->blockSignals(false); chooseC->blockSignals(false); chooseA->setEnabled((enabledMask & 1) != 0); chooseB->setEnabled((enabledMask & 2) != 0); chooseC->setEnabled((enabledMask & 4) != 0); } void KDiff3App::initView() { // set the main widget here if(m_pMainWidget != nullptr) { return; //delete m_pMainWidget; } m_pMainWidget = new QWidget(); // Contains vertical splitter and horiz scrollbar m_pMainSplitter->addWidget(m_pMainWidget); m_pMainWidget->setObjectName("MainWidget"); QVBoxLayout* pVLayout = new QVBoxLayout(m_pMainWidget); pVLayout->setMargin(0); pVLayout->setSpacing(0); QSplitter* pVSplitter = new QSplitter(); pVSplitter->setObjectName("VSplitter"); pVSplitter->setOpaqueResize(false); pVSplitter->setOrientation(Qt::Vertical); pVLayout->addWidget(pVSplitter); QWidget* pDiffWindowFrame = new QWidget(); // Contains diff windows, overview and vert scrollbar pDiffWindowFrame->setObjectName("DiffWindowFrame"); QHBoxLayout* pDiffHLayout = new QHBoxLayout(pDiffWindowFrame); pDiffHLayout->setMargin(0); pDiffHLayout->setSpacing(0); pVSplitter->addWidget(pDiffWindowFrame); m_pDiffWindowSplitter = new QSplitter(); m_pDiffWindowSplitter->setObjectName("DiffWindowSplitter"); m_pDiffWindowSplitter->setOpaqueResize(false); m_pDiffWindowSplitter->setOrientation(m_pOptions->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical); pDiffHLayout->addWidget(m_pDiffWindowSplitter); m_pOverview = new Overview(m_pOptionDialog->getOptions()); m_pOverview->setObjectName("Overview"); pDiffHLayout->addWidget(m_pOverview); connect(m_pOverview, &Overview::setLine, this, &KDiff3App::setDiff3Line); m_pDiffVScrollBar = new QScrollBar(Qt::Vertical, pDiffWindowFrame); pDiffHLayout->addWidget(m_pDiffVScrollBar); m_pDiffTextWindowFrame1 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), m_pOptionDialog->getOptions(), A, m_sd1); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame1); m_pDiffTextWindowFrame2 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), m_pOptionDialog->getOptions(), B, m_sd2); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame2); m_pDiffTextWindowFrame3 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), m_pOptionDialog->getOptions(), C, m_sd3); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame3); m_pDiffTextWindow1 = m_pDiffTextWindowFrame1->getDiffTextWindow(); m_pDiffTextWindow2 = m_pDiffTextWindowFrame2->getDiffTextWindow(); m_pDiffTextWindow3 = m_pDiffTextWindowFrame3->getDiffTextWindow(); m_pDiffTextWindowFrame1->setupConnections(this); m_pDiffTextWindowFrame2->setupConnections(this); m_pDiffTextWindowFrame3->setupConnections(this); // Merge window m_pMergeWindowFrame = new QWidget(pVSplitter); m_pMergeWindowFrame->setObjectName("MergeWindowFrame"); pVSplitter->addWidget(m_pMergeWindowFrame); QHBoxLayout* pMergeHLayout = new QHBoxLayout(m_pMergeWindowFrame); pMergeHLayout->setMargin(0); pMergeHLayout->setSpacing(0); QVBoxLayout* pMergeVLayout = new QVBoxLayout(); pMergeHLayout->addLayout(pMergeVLayout, 1); m_pMergeResultWindowTitle = new WindowTitleWidget(m_pOptionDialog->getOptions()); pMergeVLayout->addWidget(m_pMergeResultWindowTitle); m_pMergeResultWindow = new MergeResultWindow(m_pMergeWindowFrame, m_pOptionDialog->getOptions(), statusBar()); pMergeVLayout->addWidget(m_pMergeResultWindow, 1); m_pMergeVScrollBar = new QScrollBar(Qt::Vertical, m_pMergeWindowFrame); pMergeHLayout->addWidget(m_pMergeVScrollBar); m_pMainSplitter->addWidget(m_pMainWidget); autoAdvance->setEnabled(true); QList sizes = pVSplitter->sizes(); int total = sizes[0] + sizes[1]; if(total < 10) total = 100; sizes[0] = total / 2; sizes[1] = total / 2; pVSplitter->setSizes(sizes); QList hSizes; hSizes << 1 << 1 << 1; m_pDiffWindowSplitter->setSizes(hSizes); m_pMergeResultWindow->installEventFilter(m_pMergeResultWindowTitle); // for focus tracking QHBoxLayout* pHScrollBarLayout = new QHBoxLayout(); pVLayout->addLayout(pHScrollBarLayout); m_pHScrollBar = new ReversibleScrollBar(Qt::Horizontal, &m_pOptions->m_bRightToLeftLanguage); pHScrollBarLayout->addWidget(m_pHScrollBar); m_pCornerWidget = new QWidget(m_pMainWidget); pHScrollBarLayout->addWidget(m_pCornerWidget); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pOverview, &Overview::setFirstLine); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow1, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow1, &DiffTextWindow::setHorizScrollOffset); m_pDiffTextWindow1->setupConnections(this); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow2, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow2, &DiffTextWindow::setHorizScrollOffset); m_pDiffTextWindow2->setupConnections(this); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow3, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow3, &DiffTextWindow::setHorizScrollOffset); m_pDiffTextWindow3->setupConnections(this); MergeResultWindow* p = m_pMergeResultWindow; connect(m_pMergeVScrollBar, &QScrollBar::valueChanged, p, &MergeResultWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, p, &MergeResultWindow::setHorizScrollOffset); connect(p, &MergeResultWindow::modifiedChanged, m_pMergeResultWindowTitle, &WindowTitleWidget::slotSetModified); p->setupConnections(this); sourceMask(0, 0); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow1, &DiffTextWindow::setFastSelectorRange); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow2, &DiffTextWindow::setFastSelectorRange); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow3, &DiffTextWindow::setFastSelectorRange); connect(m_pDiffTextWindow1, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow2, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow3, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow1, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow2, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow3, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDirectoryMergeInfo, &DirectoryMergeInfo::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow1, &DiffTextWindow::resizeHeightChangedSignal, this, &KDiff3App::resizeDiffTextWindowHeight); // The following two connects cause the wordwrap to be recalced thrice, just to make sure. Better than forgetting one. connect(m_pDiffTextWindow1, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); connect(m_pDiffTextWindow2, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); connect(m_pDiffTextWindow3, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); m_pDiffTextWindow1->setFocus(); m_pMainWidget->setMinimumSize(50, 50); m_pCornerWidget->setFixedSize(m_pDiffVScrollBar->width(), m_pHScrollBar->height()); showWindowA->setChecked(true); showWindowB->setChecked(true); showWindowC->setChecked(true); } // called after word wrap is complete void KDiff3App::slotFinishMainInit() { Q_ASSERT(m_pDiffTextWindow1 != nullptr && m_pDiffVScrollBar != nullptr); setHScrollBarRange(); int newHeight = m_pDiffTextWindow1->getNofVisibleLines(); /*int newWidth = m_pDiffTextWindow1->getNofVisibleColumns();*/ m_DTWHeight = newHeight; m_pDiffVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - newHeight)); m_pDiffVScrollBar->setPageStep(newHeight); m_pOverview->setRange(m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep()); int d3l = -1; if(!m_manualDiffHelpList.empty()) d3l = m_manualDiffHelpList.front().calcManualDiffFirstDiff3LineIdx(m_diff3LineVector); if(d3l >= 0 && m_pDiffTextWindow1) { int line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(d3l); m_pDiffVScrollBar->setValue(std::max(0, line - 1)); } else { m_pMergeResultWindow->slotGoTop(); if(!m_outputFilename.isEmpty() && !m_pMergeResultWindow->isUnsolvedConflictAtCurrent()) m_pMergeResultWindow->slotGoNextUnsolvedConflict(); } if(m_pCornerWidget) m_pCornerWidget->setFixedSize(m_pDiffVScrollBar->width(), m_pHScrollBar->height()); slotUpdateAvailabilities(); setUpdatesEnabled(true); bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty(); TotalDiffStatus* pTotalDiffStatus = &m_totalDiffStatus; if(m_bLoadFiles) { if(bVisibleMergeResultWindow) m_pMergeResultWindow->showNrOfConflicts(); else if( // Avoid showing this message during startup without parameters. !(m_sd1->getAliasName().isEmpty() && m_sd2->getAliasName().isEmpty() && m_sd3->getAliasName().isEmpty()) && (m_sd1->isValid() && m_sd2->isValid() && m_sd3->isValid())) { QString totalInfo; if(pTotalDiffStatus->isBinaryEqualAB() && pTotalDiffStatus->isBinaryEqualAC()) totalInfo += i18n("All input files are binary equal."); else if(pTotalDiffStatus->isTextEqualAB() && pTotalDiffStatus->isTextEqualAC()) totalInfo += i18n("All input files contain the same text, but are not binary equal."); else { if(pTotalDiffStatus->isBinaryEqualAB()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B")); else if(pTotalDiffStatus->isTextEqualAB()) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("A"), i18n("B")); if(pTotalDiffStatus->isBinaryEqualAC()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C")); else if(pTotalDiffStatus->isTextEqualAC()) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("A"), i18n("C")); if(pTotalDiffStatus->isBinaryEqualBC()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C")); else if(pTotalDiffStatus->isTextEqualBC()) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("B"), i18n("C")); } if(!totalInfo.isEmpty()) KMessageBox::information(this, totalInfo); } if(bVisibleMergeResultWindow && (!m_sd1->isText() || !m_sd2->isText() || !m_sd3->isText())) { KMessageBox::information(this, i18n( "Some input files do not seem to be pure text files.\n" "Note that the KDiff3 merge was not meant for binary data.\n" "Continue at your own risk.")); } if(m_sd1->isIncompleteConversion() || m_sd2->isIncompleteConversion() || m_sd3->isIncompleteConversion()) { QString files; if(m_sd1->isIncompleteConversion()) files += i18n("A"); if(m_sd2->isIncompleteConversion()) files += files.isEmpty() ? i18n("B") : i18n(", B"); if(m_sd3->isIncompleteConversion()) files += files.isEmpty() ? i18n("C") : i18n(", C"); KMessageBox::information(this, i18n("Some input characters could not be converted to valid unicode.\n" "You might be using the wrong codec. (e.g. UTF-8 for non UTF-8 files).\n" "Do not save the result if unsure. Continue at your own risk.\n" "Affected input files are in %1.", files)); } } if(bVisibleMergeResultWindow && m_pMergeResultWindow) { m_pMergeResultWindow->setFocus(); } else if(m_pDiffTextWindow1) { m_pDiffTextWindow1->setFocus(); } } void KDiff3App::resizeEvent(QResizeEvent* e) { QSplitter::resizeEvent(e); if(m_pCornerWidget) m_pCornerWidget->setFixedSize(m_pDiffVScrollBar->width(), m_pHScrollBar->height()); } void KDiff3App::wheelEvent(QWheelEvent* pWheelEvent) { pWheelEvent->accept(); int deltaX = 0; int d = pWheelEvent->delta(); //As per QT documentation, some mice/OS combos send delta values //less than 120 units(15 degrees) d = d + m_iCumulativeWheelDelta; if(d > -120 && d < 120) { //not enough for a full step in either direction, add it up //to use on a successive call m_iCumulativeWheelDelta = d; } else { //reset cumulative tracking of the wheel since we have enough //for a 15 degree movement m_iCumulativeWheelDelta = 0; } int deltaY = -d / 120 * QApplication::wheelScrollLines(); scrollDiffTextWindow(deltaX, deltaY); } void KDiff3App::keyPressEvent(QKeyEvent* keyEvent) { if(keyEvent->key() == Qt::Key_Escape && m_pKDiff3Shell && m_pOptions->m_bEscapeKeyQuits) { m_pKDiff3Shell->close(); return; } //FIXME: Move use QAction int deltaX = 0; int deltaY = 0; int pageSize = m_DTWHeight; bool bCtrl = (keyEvent->QInputEvent::modifiers() & Qt::ControlModifier) != 0; switch(keyEvent->key()) { case Qt::Key_Down: if(!bCtrl) ++deltaY; break; case Qt::Key_Up: if(!bCtrl) --deltaY; break; case Qt::Key_PageDown: if(!bCtrl) deltaY += pageSize; break; case Qt::Key_PageUp: if(!bCtrl) deltaY -= pageSize; break; case Qt::Key_Left: if(!bCtrl) --deltaX; break; case Qt::Key_Right: if(!bCtrl) ++deltaX; break; case Qt::Key_Home: if(bCtrl) { if(m_pDiffVScrollBar != nullptr) m_pDiffVScrollBar->setValue(0); } else { if(m_pHScrollBar != nullptr) m_pHScrollBar->setValue(0); } break; case Qt::Key_End: if(bCtrl) { if(m_pDiffVScrollBar != nullptr) m_pDiffVScrollBar->setValue(m_pDiffVScrollBar->maximum()); } else { if(m_pHScrollBar != nullptr) m_pHScrollBar->setValue(m_pHScrollBar->maximum()); } break; default: break; } scrollDiffTextWindow(deltaX, deltaY); } void KDiff3App::slotFinishDrop() { raise(); mainInit(); } void KDiff3App::slotFileOpen() { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; //create dummy DirectoryInfo record for first run so we don't crash. if(m_dirinfo == nullptr) m_dirinfo = QSharedPointer::create(); if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a folder merge. Are you sure, you want to abort?"), i18n("Warning"), KGuiItem(i18n("Abort")), KGuiItem(i18n("Continue Merging"))); if(result != KMessageBox::Yes) return; } slotStatusMsg(i18n("Opening files...")); for(;;) { QPointer d = QPointer(new OpenDialog(this, QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirA().prettyAbsPath() : m_sd1->isFromBuffer() ? QString("") : m_sd1->getAliasName()), QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirB().prettyAbsPath() : m_sd2->isFromBuffer() ? QString("") : m_sd2->getAliasName()), QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirC().prettyAbsPath() : m_sd3->isFromBuffer() ? QString("") : m_sd3->getAliasName()), m_bDirCompare ? !m_dirinfo->destDir().prettyAbsPath().isEmpty() : !m_outputFilename.isEmpty(), QDir::toNativeSeparators(m_bDefaultFilename ? QString("") : m_outputFilename), m_pOptionDialog->getOptions())); int status = d->exec(); if(status == QDialog::Accepted) { m_sd1->setFilename(d->m_pLineA->currentText()); m_sd2->setFilename(d->m_pLineB->currentText()); m_sd3->setFilename(d->m_pLineC->currentText()); if(d->m_pMerge->isChecked()) { if(d->m_pLineOut->currentText().isEmpty()) { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } else { m_outputFilename = d->m_pLineOut->currentText(); m_bDefaultFilename = false; } } else m_outputFilename = ""; m_bDirCompare = FileAccess(m_sd1->getFilename()).isDir(); bool bSuccess = improveFilenames(false); if(!bSuccess) continue; if(m_bDirCompare) { m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) { m_pMainWidget->hide(); } break; } else { m_pDirectoryMergeSplitter->hide(); mainInit(); if((!m_sd1->isEmpty() && !m_sd1->hasData()) || (!m_sd2->isEmpty() && !m_sd2->hasData()) || (!m_sd3->isEmpty() && !m_sd3->hasData())) { QString text(i18n("Opening of these files failed:")); text += "\n\n"; if(!m_sd1->isEmpty() && !m_sd1->hasData()) text += " - " + m_sd1->getAliasName() + '\n'; if(!m_sd2->isEmpty() && !m_sd2->hasData()) text += " - " + m_sd2->getAliasName() + '\n'; if(!m_sd3->isEmpty() && !m_sd3->hasData()) text += " - " + m_sd3->getAliasName() + '\n'; KMessageBox::sorry(this, text, i18n("File open error")); continue; } } } break; } slotUpdateAvailabilities(); slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFileOpen2(const QString& fn1, const QString& fn2, const QString& fn3, const QString& ofn, const QString& an1, const QString& an2, const QString& an3, TotalDiffStatus* pTotalDiffStatus) { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; if(fn1.isEmpty() && fn2.isEmpty() && fn3.isEmpty() && ofn.isEmpty() && m_pMainWidget != nullptr) { m_pMainWidget->hide(); return; } slotStatusMsg(i18n("Opening files...")); m_sd1->setFilename(fn1); m_sd2->setFilename(fn2); m_sd3->setFilename(fn3); m_sd1->setAliasName(an1); m_sd2->setAliasName(an2); m_sd3->setAliasName(an3); if(!ofn.isEmpty()) { m_outputFilename = ofn; m_bDefaultFilename = false; } else { m_outputFilename = ""; m_bDefaultFilename = true; } improveFilenames(true); // Create new window for KDiff3 for directory comparison. if(!FileAccess(m_sd1->getFilename()).isDir()) { mainInit(pTotalDiffStatus); if(pTotalDiffStatus != nullptr) return; if(!((!m_sd1->isEmpty() && !m_sd1->hasData()) || (!m_sd2->isEmpty() && !m_sd2->hasData()) || (!m_sd3->isEmpty() && !m_sd3->hasData()))) { if(m_pDirectoryMergeWindow != nullptr && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFileNameChanged(const QString& fileName, e_SrcSelector winIdx) { QString fn1 = m_sd1->getFilename(); QString an1 = m_sd1->getAliasName(); QString fn2 = m_sd2->getFilename(); QString an2 = m_sd2->getAliasName(); QString fn3 = m_sd3->getFilename(); QString an3 = m_sd3->getAliasName(); if(winIdx == A) { fn1 = fileName; an1 = ""; } if(winIdx == B) { fn2 = fileName; an2 = ""; } if(winIdx == C) { fn3 = fileName; an3 = ""; } slotFileOpen2(fn1, fn2, fn3, m_outputFilename, an1, an2, an3, nullptr); } void KDiff3App::slotEditCut() { slotStatusMsg(i18n("Cutting selection...")); QString s; if(m_pMergeResultWindow != nullptr) { s = m_pMergeResultWindow->getSelection(); m_pMergeResultWindow->deleteSelection(); m_pMergeResultWindow->update(); } if(!s.isEmpty()) { QApplication::clipboard()->setText(s, QClipboard::Clipboard); } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditCopy() { slotStatusMsg(i18n("Copying selection to clipboard...")); QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty()) { QApplication::clipboard()->setText(s, QClipboard::Clipboard); } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditPaste() { slotStatusMsg(i18n("Inserting clipboard contents...")); if(m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible()) { m_pMergeResultWindow->pasteClipboard(false); } else { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(bShouldConintue) { QString error; bool do_init = false; if(m_pDiffTextWindow1->hasFocus()) { error = m_sd1->setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } else if(m_pDiffTextWindow2->hasFocus()) { error = m_sd2->setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } else if(m_pDiffTextWindow3->hasFocus()) { error = m_sd3->setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } if(!error.isEmpty()) { KMessageBox::error(m_pOptionDialog, error); } if(do_init) { mainInit(); } } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditSelectAll() { LineRef l = 0; int p = 0; // needed as dummy return values if(m_pMergeResultWindow && m_pMergeResultWindow->hasFocus()) { m_pMergeResultWindow->setSelection(0, 0, m_pMergeResultWindow->getNofLines(), 0); } else if(m_pDiffTextWindow1 && m_pDiffTextWindow1->hasFocus()) { m_pDiffTextWindow1->setSelection(0, 0, m_pDiffTextWindow1->getNofLines(), 0, l, p); } else if(m_pDiffTextWindow2 && m_pDiffTextWindow2->hasFocus()) { m_pDiffTextWindow2->setSelection(0, 0, m_pDiffTextWindow2->getNofLines(), 0, l, p); } else if(m_pDiffTextWindow3 && m_pDiffTextWindow3->hasFocus()) { m_pDiffTextWindow3->setSelection(0, 0, m_pDiffTextWindow3->getNofLines(), 0, l, p); } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotGoCurrent() { Q_EMIT goCurrent(); } void KDiff3App::slotGoTop() { Q_EMIT goTop(); } void KDiff3App::slotGoBottom() { Q_EMIT goBottom(); } void KDiff3App::slotGoPrevUnsolvedConflict() { Q_EMIT goPrevUnsolvedConflict(); } void KDiff3App::slotGoNextUnsolvedConflict() { m_bTimerBlock = false; Q_EMIT goNextUnsolvedConflict(); } void KDiff3App::slotGoPrevConflict() { Q_EMIT goPrevConflict(); } void KDiff3App::slotGoNextConflict() { m_bTimerBlock = false; Q_EMIT goNextConflict(); } void KDiff3App::slotGoPrevDelta() { Q_EMIT goPrevDelta(); } void KDiff3App::slotGoNextDelta() { Q_EMIT goNextDelta(); } void KDiff3App::choose(e_SrcSelector choice) { if(!m_bTimerBlock) { if(m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->hasFocus()) { if(choice == A) m_pDirectoryMergeWindow->slotCurrentChooseA(); if(choice == B) m_pDirectoryMergeWindow->slotCurrentChooseB(); if(choice == C) m_pDirectoryMergeWindow->slotCurrentChooseC(); chooseA->setChecked(false); chooseB->setChecked(false); chooseC->setChecked(false); } else if(m_pMergeResultWindow) { m_pMergeResultWindow->choose(choice); if(autoAdvance->isChecked()) { m_bTimerBlock = true; QTimer::singleShot(m_pOptions->m_autoAdvanceDelay, this, &KDiff3App::slotGoNextUnsolvedConflict); } } } } void KDiff3App::slotChooseA() { choose(A); } void KDiff3App::slotChooseB() { choose(B); } void KDiff3App::slotChooseC() { choose(C); } void KDiff3App::slotAutoSolve() { Q_EMIT autoSolve(); slotUpdateAvailabilities(); } void KDiff3App::slotUnsolve() { Q_EMIT unsolve(); } void KDiff3App::slotMergeHistory() { Q_EMIT mergeHistory(); } void KDiff3App::slotRegExpAutoMerge() { Q_EMIT regExpAutoMerge(); } void KDiff3App::slotSplitDiff() { LineRef firstLine; LineRef lastLine; DiffTextWindow* pDTW = nullptr; if(m_pDiffTextWindow1) { pDTW = m_pDiffTextWindow1; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow2) { pDTW = m_pDiffTextWindow2; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow3) { pDTW = m_pDiffTextWindow3; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(pDTW && firstLine.isValid() && m_pMergeResultWindow) { pDTW->resetSelection(); m_pMergeResultWindow->slotSplitDiff(firstLine, lastLine); } } void KDiff3App::slotJoinDiffs() { LineRef firstLine; LineRef lastLine; DiffTextWindow* pDTW = nullptr; if(m_pDiffTextWindow1) { pDTW = m_pDiffTextWindow1; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow2) { pDTW = m_pDiffTextWindow2; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow3) { pDTW = m_pDiffTextWindow3; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(pDTW && firstLine.isValid() && m_pMergeResultWindow) { pDTW->resetSelection(); m_pMergeResultWindow->slotJoinDiffs(firstLine, lastLine); } } void KDiff3App::slotConfigure() { m_pOptionDialog->setState(); m_pOptionDialog->setMinimumHeight(m_pOptionDialog->minimumHeight() + 40); m_pOptionDialog->exec(); slotRefresh(); } void KDiff3App::slotConfigureKeys() { KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); } void KDiff3App::slotRefresh() { QApplication::setFont(m_pOptions->m_appFont); Q_EMIT doRefresh(); if(m_pHScrollBar != nullptr) { m_pHScrollBar->setAgain(); } if(m_pDiffWindowSplitter != nullptr) { m_pDiffWindowSplitter->setOrientation(m_pOptions->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical); } if(m_pDirectoryMergeWindow) { m_pDirectoryMergeWindow->updateFileVisibilities(); } } void KDiff3App::slotSelectionStart() { //editCopy->setEnabled( false ); //editCut->setEnabled( false ); const QObject* s = sender(); if(m_pDiffTextWindow1 && s != m_pDiffTextWindow1) m_pDiffTextWindow1->resetSelection(); if(m_pDiffTextWindow2 && s != m_pDiffTextWindow2) m_pDiffTextWindow2->resetSelection(); if(m_pDiffTextWindow3 && s != m_pDiffTextWindow3) m_pDiffTextWindow3->resetSelection(); if(m_pMergeResultWindow && s != m_pMergeResultWindow) m_pMergeResultWindow->resetSelection(); } void KDiff3App::slotSelectionEnd() { //const QObject* s = sender(); //editCopy->setEnabled(true); //editCut->setEnabled( s==m_pMergeResultWindow ); if(m_pOptions->m_bAutoCopySelection) { slotEditCopy(); } else { QClipboard* clipBoard = QApplication::clipboard(); if(clipBoard->supportsSelection()) { QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty()) { clipBoard->setText(s, QClipboard::Selection); } } } } void KDiff3App::slotClipboardChanged() { const QClipboard* clipboard = QApplication::clipboard(); const QMimeData* mimeData = clipboard->mimeData(); if(mimeData->hasText()) { QString s = clipboard->text(); editPaste->setEnabled(!s.isEmpty()); } else { editPaste->setEnabled(false); } } void KDiff3App::slotOutputModified(bool bModified) { if(bModified && !m_bOutputModified) { m_bOutputModified = true; slotUpdateAvailabilities(); } } void KDiff3App::slotAutoAdvanceToggled() { m_pOptions->m_bAutoAdvance = autoAdvance->isChecked(); } void KDiff3App::slotWordWrapToggled() { m_pOptions->m_bWordWrap = wordWrap->isChecked(); postRecalcWordWrap(); } // Enable or disable all widgets except the status bar widget. void KDiff3App::mainWindowEnable(bool bEnable) { if(QMainWindow* pWindow = dynamic_cast(window())) { QWidget* pStatusBarWidget = pWindow->statusBar(); pWindow->setEnabled(bEnable); pStatusBarWidget->setEnabled(true); } } void KDiff3App::postRecalcWordWrap() { if(!m_bRecalcWordWrapPosted) { m_bRecalcWordWrapPosted = true; m_firstD3LIdx = -1; Q_EMIT sigRecalcWordWrap(); } else { g_pProgressDialog->cancel(ProgressDialog::eResize); } } void KDiff3App::slotRecalcWordWrap() { recalcWordWrap(); } // visibleTextWidthForPrinting is >=0 only for printing, otherwise the really visible width is used void KDiff3App::recalcWordWrap(int visibleTextWidthForPrinting) { m_bRecalcWordWrapPosted = true; mainWindowEnable(false); if(m_firstD3LIdx < 0) { m_firstD3LIdx = 0; if(m_pDiffTextWindow1) m_firstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(m_pDiffTextWindow1->getFirstLine()); } // Convert selection to D3L-coords (converting back happens in DiffTextWindow::recalcWordWrap() if(m_pDiffTextWindow1) m_pDiffTextWindow1->convertSelectionToD3LCoords(); if(m_pDiffTextWindow2) m_pDiffTextWindow2->convertSelectionToD3LCoords(); if(m_pDiffTextWindow3) m_pDiffTextWindow3->convertSelectionToD3LCoords(); g_pProgressDialog->clearCancelState(); // clear cancelled state if previously set if(!m_diff3LineList.empty()) { if(m_pOptions->m_bWordWrap) { m_diff3LineList.recalcWordWrap(true); // Let every window calc how many lines will be needed. if(m_pDiffTextWindow1) { m_pDiffTextWindow1->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } if(m_pDiffTextWindow2) { m_pDiffTextWindow2->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } if(m_pDiffTextWindow3) { m_pDiffTextWindow3->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } } else { m_neededLines = m_diff3LineVector.size(); if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(false, 0, 0); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(false, 0, 0); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(false, 0, 0); } bool bRunnablesStarted = DiffTextWindow::startRunnables(); if(!bRunnablesStarted) slotFinishRecalcWordWrap(visibleTextWidthForPrinting); else { g_pProgressDialog->setInformation(m_pOptions->m_bWordWrap ? i18n("Word wrap (Cancel disables word wrap)") : i18n("Calculating max width for horizontal scrollbar"), false); } } else { //don't leave proccessing incomplete if m_diff3LineList isEmpty as when an error occures during reading. slotFinishRecalcWordWrap(visibleTextWidthForPrinting); } } void KDiff3App::slotFinishRecalcWordWrap(int visibleTextWidthForPrinting) { g_pProgressDialog->pop(); if(m_pOptions->m_bWordWrap && g_pProgressDialog->wasCancelled()) { if(g_pProgressDialog->cancelReason() == ProgressDialog::eUserAbort) { wordWrap->setChecked(false); m_pOptions->m_bWordWrap = wordWrap->isChecked(); } Q_EMIT sigRecalcWordWrap(); return; } else { m_bRecalcWordWrapPosted = false; } g_pProgressDialog->setStayHidden(false); bool bPrinting = visibleTextWidthForPrinting >= 0; if(!m_diff3LineList.empty()) { if(m_pOptions->m_bWordWrap) { LineCount sumOfLines = m_diff3LineList.recalcWordWrap(false); // Finish the word wrap if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); m_neededLines = sumOfLines; } else { if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(false, 1, 0); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(false, 1, 0); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(false, 1, 0); } slotStatusMsg(QString()); } if(!bPrinting) { if(m_pOverview) m_pOverview->slotRedraw(); if(m_pDiffVScrollBar) m_pDiffVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - m_DTWHeight)); if(m_pDiffTextWindow1) { if(m_pDiffVScrollBar) m_pDiffVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(m_firstD3LIdx)); setHScrollBarRange(); m_pHScrollBar->setValue(0); } } mainWindowEnable(true); if(m_bFinishMainInit) { m_bFinishMainInit = false; slotFinishMainInit(); } if(m_pEventLoopForPrinting) m_pEventLoopForPrinting->quit(); } void KDiff3App::slotShowWhiteSpaceToggled() { m_pOptions->m_bShowWhiteSpaceCharacters = showWhiteSpaceCharacters->isChecked(); m_pOptions->m_bShowWhiteSpace = showWhiteSpace->isChecked(); Q_EMIT showWhiteSpaceToggled(); if(m_pOverview != nullptr) m_pOverview->slotRedraw(); } void KDiff3App::slotShowLineNumbersToggled() { m_pOptions->m_bShowLineNumbers = showLineNumbers->isChecked(); if(wordWrap->isChecked()) recalcWordWrap(); Q_EMIT showLineNumbersToggled(); } /// Return true for success, else false bool KDiff3App::improveFilenames(bool bCreateNewInstance) { FileAccess f1(m_sd1->getFilename()); FileAccess f2(m_sd2->getFilename()); FileAccess f3(m_sd3->getFilename()); FileAccess f4(m_outputFilename); if(f1.isFile() && f1.exists()) { if(f2.isDir()) { f2.addPath(f1.fileName()); if(f2.isFile() && f2.exists()) m_sd2->setFileAccess(f2); } if(f3.isDir()) { f3.addPath(f1.fileName()); if(f3.isFile() && f3.exists()) m_sd3->setFileAccess(f3); } if(f4.isDir()) { f4.addPath(f1.fileName()); if(f4.isFile() && f4.exists()) m_outputFilename = f4.absoluteFilePath(); } } else if(f1.isDir()) { if(bCreateNewInstance) { Q_EMIT createNewInstance(f1.absoluteFilePath(), f2.absoluteFilePath(), f3.absoluteFilePath()); } else { bool bDirCompare = m_bDirCompare; FileAccess destDir; if(!m_bDefaultFilename) destDir = f4; m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) m_pMainWidget->hide(); setUpdatesEnabled(true); m_dirinfo = QSharedPointer::create(f1, f2, f3, destDir); bool bSuccess = m_pDirectoryMergeWindow->init( m_dirinfo, !m_outputFilename.isEmpty()); //This is a bug if it still happens. Q_ASSERT(m_bDirCompare == bDirCompare); if(bSuccess) { m_sd1->reset(); if(m_pDiffTextWindow1 != nullptr) { m_pDiffTextWindow1->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_pDiffTextWindowFrame1->init(); } m_sd2->reset(); if(m_pDiffTextWindow2 != nullptr) { m_pDiffTextWindow2->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_pDiffTextWindowFrame2->init(); } m_sd3->reset(); if(m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindow3->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_pDiffTextWindowFrame3->init(); } } slotUpdateAvailabilities(); return bSuccess; } } return true; } void KDiff3App::slotReload() { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; mainInit(); } bool KDiff3App::canContinue() { // First test if anything must be saved. if(m_bOutputModified) { int result = KMessageBox::warningYesNoCancel(this, i18n("The merge result has not been saved."), i18n("Warning"), KGuiItem(i18n("Save && Continue")), KGuiItem(i18n("Continue Without Saving"))); if(result == KMessageBox::Cancel) return false; else if(result == KMessageBox::Yes) { slotFileSave(); if(m_bOutputModified) { KMessageBox::sorry(this, i18n("Saving the merge result failed."), i18n("Warning")); return false; } } } m_bOutputModified = false; return true; } /* Translate canContinue return value to out param because Qt does not support return values on slots. */ void KDiff3App::slotCheckIfCanContinue(bool& pbContinue) { pbContinue = canContinue(); } void KDiff3App::slotDirShowBoth() { if(dirShowBoth->isChecked()) { if(m_pDirectoryMergeSplitter) m_pDirectoryMergeSplitter->setVisible(m_bDirCompare); if(m_pMainWidget != nullptr) m_pMainWidget->show(); } else { bool bTextDataAvailable = (m_sd1->hasData() || m_sd2->hasData() || m_sd3->hasData()); if(m_pMainWidget != nullptr && bTextDataAvailable) { m_pMainWidget->show(); m_pDirectoryMergeSplitter->hide(); } else if(m_bDirCompare) { m_pDirectoryMergeSplitter->show(); } } slotUpdateAvailabilities(); } void KDiff3App::slotDirViewToggle() { if(m_bDirCompare) { if(!m_pDirectoryMergeSplitter->isVisible()) { m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) m_pMainWidget->hide(); } else { if(m_pMainWidget != nullptr) { m_pDirectoryMergeSplitter->hide(); m_pMainWidget->show(); } } } slotUpdateAvailabilities(); } void KDiff3App::slotShowWindowAToggled() { if(m_pDiffTextWindow1 != nullptr) { m_pDiffTextWindowFrame1->setVisible(showWindowA->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotShowWindowBToggled() { if(m_pDiffTextWindow2 != nullptr) { m_pDiffTextWindowFrame2->setVisible(showWindowB->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotShowWindowCToggled() { if(m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindowFrame3->setVisible(showWindowC->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotEditFind() { m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; m_pFindDialog->currentWindow = 1; // Use currently selected text: QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty() && !s.contains('\n')) { m_pFindDialog->m_pSearchString->setText(s); } if(QDialog::Accepted == m_pFindDialog->exec()) { slotEditFindNext(); } } void KDiff3App::slotEditFindNext() { QString s = m_pFindDialog->m_pSearchString->text(); if(s.isEmpty()) { slotEditFind(); return; } bool bDirDown = true; bool bCaseSensitive = m_pFindDialog->m_pCaseSensitive->isChecked(); LineRef d3vLine = m_pFindDialog->currentLine; int posInLine = m_pFindDialog->currentPos; LineRef l = 0; int p = 0; if(m_pFindDialog->currentWindow == 1) { if(m_pFindDialog->m_pSearchInA->isChecked() && m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow1->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); m_pDiffVScrollBar->setValue(l - m_pDiffVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 2; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 2) { if(m_pFindDialog->m_pSearchInB->isChecked() && m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow2->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); m_pDiffVScrollBar->setValue(l - m_pDiffVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 3; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 3) { if(m_pFindDialog->m_pSearchInC->isChecked() && m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow3->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); m_pDiffVScrollBar->setValue(l - m_pDiffVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 4; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 4) { if(m_pFindDialog->m_pSearchInOutput->isChecked() && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() && m_pMergeResultWindow->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pMergeResultWindow->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length()); m_pMergeVScrollBar->setValue(d3vLine - m_pMergeVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, posInLine + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 5; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } KMessageBox::information(this, i18n("Search complete."), i18n("Search Complete")); m_pFindDialog->currentWindow = 1; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } void KDiff3App::slotMergeCurrentFile() { if(m_bDirCompare && m_pDirectoryMergeWindow->isVisible() && m_pDirectoryMergeWindow->isFileSelected()) { m_pDirectoryMergeWindow->mergeCurrentFile(); } else if(m_pMainWidget != nullptr && m_pMainWidget->isVisible()) { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; if(m_outputFilename.isEmpty()) { if(!m_sd3->isEmpty() && !m_sd3->isFromBuffer()) { m_outputFilename = m_sd3->getFilename(); } else if(!m_sd2->isEmpty() && !m_sd2->isFromBuffer()) { m_outputFilename = m_sd2->getFilename(); } else if(!m_sd1->isEmpty() && !m_sd1->isFromBuffer()) { m_outputFilename = m_sd1->getFilename(); } else { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } } mainInit(); } } void KDiff3App::slotWinFocusNext() { QWidget* focus = qApp->focusWidget(); if(focus == m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } std::list visibleWidgetList; if(m_pDiffTextWindow1 && m_pDiffTextWindow1->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow1); if(m_pDiffTextWindow2 && m_pDiffTextWindow2->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow2); if(m_pDiffTextWindow3 && m_pDiffTextWindow3->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow3); if(m_pMergeResultWindow && m_pMergeResultWindow->isVisible()) visibleWidgetList.push_back(m_pMergeResultWindow); if(m_bDirCompare /*m_pDirectoryMergeWindow->isVisible()*/) visibleWidgetList.push_back(m_pDirectoryMergeWindow); //if ( m_pDirectoryMergeInfo->isVisible() ) visibleWidgetList.push_back(m_pDirectoryMergeInfo->getInfoList()); std::list::iterator i = std::find(visibleWidgetList.begin(), visibleWidgetList.end(), focus); ++i; if(i == visibleWidgetList.end()) i = visibleWidgetList.begin(); if(i != visibleWidgetList.end()) { if(*i == m_pDirectoryMergeWindow && !dirShowBoth->isChecked()) { slotDirViewToggle(); } (*i)->setFocus(); } } void KDiff3App::slotWinFocusPrev() { QWidget* focus = qApp->focusWidget(); if(focus == m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } std::list visibleWidgetList; if(m_pDiffTextWindow1 && m_pDiffTextWindow1->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow1); if(m_pDiffTextWindow2 && m_pDiffTextWindow2->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow2); if(m_pDiffTextWindow3 && m_pDiffTextWindow3->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow3); if(m_pMergeResultWindow && m_pMergeResultWindow->isVisible()) visibleWidgetList.push_back(m_pMergeResultWindow); if(m_bDirCompare /* m_pDirectoryMergeWindow->isVisible() */) visibleWidgetList.push_back(m_pDirectoryMergeWindow); //if ( m_pDirectoryMergeInfo->isVisible() ) visibleWidgetList.push_back(m_pDirectoryMergeInfo->getInfoList()); std::list::iterator i = std::find(visibleWidgetList.begin(), visibleWidgetList.end(), focus); if(i == visibleWidgetList.begin()) i = visibleWidgetList.end(); --i; if(i != visibleWidgetList.end()) { if(*i == m_pDirectoryMergeWindow && !dirShowBoth->isChecked()) { slotDirViewToggle(); } (*i)->setFocus(); } } void KDiff3App::slotWinToggleSplitterOrientation() { if(m_pDiffWindowSplitter != nullptr) { m_pDiffWindowSplitter->setOrientation( m_pDiffWindowSplitter->orientation() == Qt::Vertical ? Qt::Horizontal : Qt::Vertical); m_pOptions->m_bHorizDiffWindowSplitting = m_pDiffWindowSplitter->orientation() == Qt::Horizontal; } } void KDiff3App::slotOverviewNormal() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMNormal); if(m_pMergeResultWindow != nullptr) m_pMergeResultWindow->setOverviewMode(Overview::eOMNormal); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewAB() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMAvsB); m_pMergeResultWindow->setOverviewMode(Overview::eOMAvsB); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewAC() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMAvsC); if(m_pMergeResultWindow != nullptr) m_pMergeResultWindow->setOverviewMode(Overview::eOMAvsC); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewBC() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMBvsC); if(m_pMergeResultWindow != nullptr) m_pMergeResultWindow->setOverviewMode(Overview::eOMBvsC); slotUpdateAvailabilities(); } void KDiff3App::slotNoRelevantChangesDetected() { if(m_bTripleDiff && !m_outputFilename.isEmpty()) { //KMessageBox::information( this, "No relevant changes detected", "KDiff3" ); if(!m_pOptions->m_IrrelevantMergeCmd.isEmpty()) { /* QProcess doesn't check for single quotes and uses non-standard escaping syntax for double quotes. The distinction between single and double quotes is purely a command shell issue. So we split the command string ourselves. */ QStringList args; QString program; Utils::getArguments(m_pOptions->m_IrrelevantMergeCmd, program, args); QProcess process; process.start(program, args); process.waitForFinished(-1); } } } void KDiff3App::slotAddManualDiffHelp() { LineRef firstLine; LineRef lastLine; e_SrcSelector winIdx = Invalid; if(m_pDiffTextWindow1) { m_pDiffTextWindow1->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = A; } if(firstLine < 0 && m_pDiffTextWindow2) { m_pDiffTextWindow2->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = B; } if(firstLine < 0 && m_pDiffTextWindow3) { m_pDiffTextWindow3->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = C; } if(firstLine < 0 || lastLine < 0 || lastLine < firstLine) KMessageBox::information(this, i18n("Nothing is selected in either diff input window."), i18n("Error while adding manual diff range")); else { m_manualDiffHelpList.insertEntry(winIdx, firstLine, lastLine); mainInit(nullptr, false); // Init without reload slotRefresh(); } } void KDiff3App::slotClearManualDiffHelpList() { m_manualDiffHelpList.clear(); mainInit(nullptr, false); // Init without reload slotRefresh(); } void KDiff3App::slotEncodingChanged(QTextCodec* c) { Q_UNUSED(c); mainInit(nullptr, true, true); // Init with reload slotRefresh(); } void KDiff3App::slotUpdateAvailabilities() { if(m_pMainSplitter == nullptr || m_pDiffTextWindow2 == nullptr || m_pDiffTextWindow1 == nullptr || m_pDiffTextWindow3 == nullptr) return; bool bTextDataAvailable = (m_sd1->hasData() || m_sd2->hasData() || m_sd3->hasData()); if(dirShowBoth->isChecked()) { if(m_pDirectoryMergeSplitter != nullptr) m_pDirectoryMergeSplitter->setVisible(m_bDirCompare); if(m_pMainWidget != nullptr && !m_pMainWidget->isVisible() && bTextDataAvailable && !m_pDirectoryMergeWindow->isScanning()) m_pMainWidget->show(); } bool bDiffWindowVisible = m_pMainWidget != nullptr && m_pMainWidget->isVisible(); bool bMergeEditorVisible = m_pMergeWindowFrame != nullptr && m_pMergeWindowFrame->isVisible() && m_pMergeResultWindow != nullptr; m_pDirectoryMergeWindow->updateAvailabilities(m_bDirCompare, bDiffWindowVisible, chooseA, chooseB, chooseC); dirShowBoth->setEnabled(m_bDirCompare); dirViewToggle->setEnabled( m_bDirCompare && ((m_pDirectoryMergeSplitter != nullptr && m_pMainWidget != nullptr) && ((!m_pDirectoryMergeSplitter->isVisible() && m_pMainWidget->isVisible()) || (m_pDirectoryMergeSplitter->isVisible() && !m_pMainWidget->isVisible() && bTextDataAvailable)))); bool bDirWindowHasFocus = m_pDirectoryMergeSplitter != nullptr && m_pDirectoryMergeSplitter->isVisible() && m_pDirectoryMergeWindow->hasFocus(); showWhiteSpaceCharacters->setEnabled(bDiffWindowVisible); autoAdvance->setEnabled(bMergeEditorVisible); mAutoSolve->setEnabled(bMergeEditorVisible && m_bTripleDiff); mUnsolve->setEnabled(bMergeEditorVisible); if(!bDirWindowHasFocus) { chooseA->setEnabled(bMergeEditorVisible); chooseB->setEnabled(bMergeEditorVisible); chooseC->setEnabled(bMergeEditorVisible && m_bTripleDiff); } if(m_pMergeResultWindow != nullptr) { m_pMergeResultWindow->slotUpdateAvailabilities(bMergeEditorVisible, m_bTripleDiff); } mMergeHistory->setEnabled(bMergeEditorVisible); mergeRegExp->setEnabled(bMergeEditorVisible); showWindowA->setEnabled(bDiffWindowVisible && (m_pDiffTextWindow2->isVisible() || m_pDiffTextWindow3->isVisible())); showWindowB->setEnabled(bDiffWindowVisible && (m_pDiffTextWindow1->isVisible() || m_pDiffTextWindow3->isVisible())); showWindowC->setEnabled(bDiffWindowVisible && m_bTripleDiff && (m_pDiffTextWindow1->isVisible() || m_pDiffTextWindow2->isVisible())); editFind->setEnabled(bDiffWindowVisible); editFindNext->setEnabled(bDiffWindowVisible); m_pFindDialog->m_pSearchInC->setEnabled(m_bTripleDiff); m_pFindDialog->m_pSearchInOutput->setEnabled(bMergeEditorVisible); bool bSavable = bMergeEditorVisible && m_pMergeResultWindow->getNrOfUnsolvedConflicts() == 0; fileSave->setEnabled(m_bOutputModified && bSavable); fileSaveAs->setEnabled(bSavable); mGoTop->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaAboveCurrent()); mGoBottom->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaBelowCurrent()); mGoCurrent->setEnabled(bDiffWindowVisible); mGoPrevUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictAboveCurrent()); mGoNextUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictBelowCurrent()); mGoPrevConflict->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isConflictAboveCurrent()); mGoNextConflict->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isConflictBelowCurrent()); mGoPrevDelta->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaAboveCurrent()); mGoNextDelta->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaBelowCurrent()); overviewModeNormal->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeAB->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeAC->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeBC->setEnabled(m_bTripleDiff && bDiffWindowVisible); Overview::e_OverviewMode overviewMode = m_pOverview == nullptr ? Overview::eOMNormal : m_pOverview->getOverviewMode(); overviewModeNormal->setChecked(overviewMode == Overview::eOMNormal); overviewModeAB->setChecked(overviewMode == Overview::eOMAvsB); overviewModeAC->setChecked(overviewMode == Overview::eOMAvsC); overviewModeBC->setChecked(overviewMode == Overview::eOMBvsC); winToggleSplitOrientation->setEnabled(bDiffWindowVisible && m_pDiffWindowSplitter != nullptr); } diff --git a/src/progress.cpp b/src/progress.cpp index cd8ac57..3f21ef7 100644 --- a/src/progress.cpp +++ b/src/progress.cpp @@ -1,559 +1,556 @@ -/*************************************************************************** - * Copyright (C) 2003-2011 by Joachim Eibl * - * joachim.eibl at gmx.de * - * Copyright (C) 2019 Michael Reeves * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "progress.h" #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include ProgressDialog* g_pProgressDialog = nullptr; ProgressDialog::ProgressDialog(QWidget* pParent, QStatusBar* pStatusBar) : QDialog(pParent), m_pStatusBar(pStatusBar) { m_pGuiThread = QThread::currentThread(); setObjectName("ProgressDialog"); m_bStayHidden = false; setModal(true); QVBoxLayout* layout = new QVBoxLayout(this); m_pInformation = new QLabel(" ", this); layout->addWidget(m_pInformation); m_pProgressBar = new QProgressBar(); m_pProgressBar->setRange(0, 1000); layout->addWidget(m_pProgressBar); m_pSubInformation = new QLabel(" ", this); layout->addWidget(m_pSubInformation); m_pSubProgressBar = new QProgressBar(); m_pSubProgressBar->setRange(0, 1000); layout->addWidget(m_pSubProgressBar); m_pSlowJobInfo = new QLabel(" ", this); layout->addWidget(m_pSlowJobInfo); QHBoxLayout* hlayout = new QHBoxLayout(); layout->addLayout(hlayout); hlayout->addStretch(1); m_pAbortButton = new QPushButton(i18n("&Cancel"), this); hlayout->addWidget(m_pAbortButton); connect(m_pAbortButton, &QPushButton::clicked, this, &ProgressDialog::slotAbort); if(m_pStatusBar != nullptr) { m_pStatusBarWidget = new QWidget; QHBoxLayout* pStatusBarLayout = new QHBoxLayout(m_pStatusBarWidget); pStatusBarLayout->setMargin(0); pStatusBarLayout->setSpacing(3); m_pStatusProgressBar = new QProgressBar; m_pStatusProgressBar->setRange(0, 1000); m_pStatusProgressBar->setTextVisible(false); m_pStatusAbortButton = new QPushButton(i18n("&Cancel")); connect(m_pStatusAbortButton, &QPushButton::clicked, this, &ProgressDialog::slotAbort); pStatusBarLayout->addWidget(m_pStatusProgressBar); pStatusBarLayout->addWidget(m_pStatusAbortButton); m_pStatusBar->addPermanentWidget(m_pStatusBarWidget, 0); m_pStatusBarWidget->setFixedHeight(m_pStatusBar->height()); m_pStatusBarWidget->hide(); } else { m_pStatusProgressBar = nullptr; m_pStatusAbortButton = nullptr; } m_progressDelayTimer = 0; m_delayedHideTimer = 0; m_delayedHideStatusBarWidgetTimer = 0; resize(400, 100); m_t1.start(); m_t2.start(); m_bWasCancelled = false; m_eCancelReason = eUserAbort; m_pJob = nullptr; } void ProgressDialog::setStayHidden(bool bStayHidden) { if(m_bStayHidden != bStayHidden) { m_bStayHidden = bStayHidden; if(m_pStatusBarWidget != nullptr) { if(m_bStayHidden) { if(m_delayedHideStatusBarWidgetTimer) { killTimer(m_delayedHideStatusBarWidgetTimer); m_delayedHideStatusBarWidgetTimer = 0; } m_pStatusBarWidget->show(); } else hideStatusBarWidget(); // delayed } if(isVisible() && m_bStayHidden) hide(); // delayed hide } } void ProgressDialog::push() { ProgressLevelData pld; if(!m_progressStack.empty()) { pld.m_dRangeMax = m_progressStack.back().m_dSubRangeMax; pld.m_dRangeMin = m_progressStack.back().m_dSubRangeMin; } else { m_bWasCancelled = false; m_t1.restart(); m_t2.restart(); if(!m_bStayHidden) show(); } m_progressStack.push_back(pld); } void ProgressDialog::pop(bool bRedrawUpdate) { if(!m_progressStack.empty()) { m_progressStack.pop_back(); if(m_progressStack.empty()) { hide(); } else recalc(bRedrawUpdate); } } void ProgressDialog::setInformation(const QString& info, int current, bool bRedrawUpdate) { if(m_progressStack.empty()) return; ProgressLevelData& pld = m_progressStack.back(); pld.m_current = current; int level = m_progressStack.size(); if(level == 1) { m_pInformation->setText(info); m_pSubInformation->setText(""); if(m_pStatusBar != nullptr && m_bStayHidden) m_pStatusBar->showMessage(info); } else if(level == 2) { m_pSubInformation->setText(info); } recalc(bRedrawUpdate); } void ProgressDialog::setInformation(const QString& info, bool bRedrawUpdate) { if(m_progressStack.empty()) return; //ProgressLevelData& pld = m_progressStack.back(); int level = m_progressStack.size(); if(level == 1) { m_pInformation->setText(info); m_pSubInformation->setText(""); if(m_pStatusBar && m_bStayHidden) m_pStatusBar->showMessage(info); } else if(level == 2) { m_pSubInformation->setText(info); } recalc(bRedrawUpdate); } void ProgressDialog::setMaxNofSteps(const qint64 maxNofSteps) { if(m_progressStack.empty() || maxNofSteps == 0) return; ProgressLevelData& pld = m_progressStack.back(); pld.m_maxNofSteps = maxNofSteps; pld.m_current = 0; } void ProgressDialog::addNofSteps(const qint64 nofSteps) { if(m_progressStack.empty()) return; ProgressLevelData& pld = m_progressStack.back(); pld.m_maxNofSteps.fetchAndAddRelaxed(nofSteps); } void ProgressDialog::step(bool bRedrawUpdate) { if(m_progressStack.empty()) return; ProgressLevelData& pld = m_progressStack.back(); pld.m_current.fetchAndAddRelaxed(1); recalc(bRedrawUpdate); } void ProgressDialog::setCurrent(qint64 subCurrent, bool bRedrawUpdate) { if(m_progressStack.empty()) return; ProgressLevelData& pld = m_progressStack.back(); pld.m_current = subCurrent; recalc(bRedrawUpdate); } void ProgressDialog::clear() { if(m_progressStack.isEmpty()) return; ProgressLevelData& pld = m_progressStack.back(); setCurrent(pld.m_maxNofSteps); } // The progressbar goes from 0 to 1 usually. // By supplying a subrange transformation the subCurrent-values // 0 to 1 will be transformed to dMin to dMax instead. // Requirement: 0 < dMin < dMax < 1 void ProgressDialog::setRangeTransformation(double dMin, double dMax) { if(m_progressStack.empty()) return; ProgressLevelData& pld = m_progressStack.back(); pld.m_dRangeMin = dMin; pld.m_dRangeMax = dMax; pld.m_current = 0; } void ProgressDialog::setSubRangeTransformation(double dMin, double dMax) { if(m_progressStack.empty()) return; ProgressLevelData& pld = m_progressStack.back(); pld.m_dSubRangeMin = dMin; pld.m_dSubRangeMax = dMax; } void ProgressDialog::enterEventLoop(KJob* pJob, const QString& jobInfo) { m_pJob = pJob; m_currentJobInfo = jobInfo; m_pSlowJobInfo->setText(m_currentJobInfo); if(m_progressDelayTimer) killTimer(m_progressDelayTimer); m_progressDelayTimer = startTimer(3000); /* 3 s delay */ // immediately show the progress dialog for KIO jobs, because some KIO jobs require password authentication, // but if the progress dialog pops up at a later moment, this might cover the login dialog and hide it from the user. if(m_pJob && !m_bStayHidden) show(); // instead of using exec() the eventloop is entered and exited often without hiding/showing the window. if(m_eventLoop == nullptr) { m_eventLoop = QPointer(new QEventLoop(this)); m_eventLoop->exec(); // this function only returns after ProgressDialog::exitEventLoop() is called. m_eventLoop.clear(); } else { m_eventLoop->processEvents(QEventLoop::WaitForMoreEvents); } } void ProgressDialog::exitEventLoop() { if(m_progressDelayTimer) killTimer(m_progressDelayTimer); m_progressDelayTimer = 0; m_pJob = nullptr; if( m_eventLoop != nullptr) m_eventLoop->exit(); } void ProgressDialog::recalc(bool bUpdate) { if(!m_bWasCancelled) { if(QThread::currentThread() == m_pGuiThread) { if(m_progressDelayTimer) killTimer(m_progressDelayTimer); m_progressDelayTimer = 0; if(!m_bStayHidden) m_progressDelayTimer = startTimer(3000); /* 3 s delay */ int level = m_progressStack.size(); if((bUpdate && level == 1) || m_t1.elapsed() > 200) { if(m_progressStack.empty()) { m_pProgressBar->setValue(0); m_pSubProgressBar->setValue(0); } else { QList::iterator i = m_progressStack.begin(); int value = int(1000.0 * (getAtomic(i->m_current) * (i->m_dRangeMax - i->m_dRangeMin) / getAtomic(i->m_maxNofSteps) + i->m_dRangeMin)); m_pProgressBar->setValue(value); if(m_bStayHidden && m_pStatusProgressBar) m_pStatusProgressBar->setValue(value); ++i; if(i != m_progressStack.end()) m_pSubProgressBar->setValue(int(1000.0 * (getAtomic(i->m_current) * (i->m_dRangeMax - i->m_dRangeMin) / getAtomic(i->m_maxNofSteps) + i->m_dRangeMin))); else m_pSubProgressBar->setValue(int(1000.0 * m_progressStack.front().m_dSubRangeMin)); } if(!m_bStayHidden && !isVisible()) show(); qApp->processEvents(); m_t1.restart(); } } else { QMetaObject::invokeMethod(this, "recalc", Qt::QueuedConnection, Q_ARG(bool, bUpdate)); } } } void ProgressDialog::show() { if(m_progressDelayTimer) killTimer(m_progressDelayTimer); if(m_delayedHideTimer) killTimer(m_delayedHideTimer); m_progressDelayTimer = 0; m_delayedHideTimer = 0; if(!isVisible() && (parentWidget() == nullptr || parentWidget()->isVisible())) { QDialog::show(); } } void ProgressDialog::hide() { if(m_progressDelayTimer) killTimer(m_progressDelayTimer); m_progressDelayTimer = 0; // Calling QDialog::hide() directly doesn't always work. (?) if(m_delayedHideTimer) killTimer(m_delayedHideTimer); m_delayedHideTimer = startTimer(100); } void ProgressDialog::delayedHide() { if(m_pJob != nullptr) { m_pJob->kill(KJob::Quietly); m_pJob = nullptr; } QDialog::hide(); m_pInformation->setText(""); //m_progressStack.clear(); m_pProgressBar->setValue(0); m_pSubProgressBar->setValue(0); m_pSubInformation->setText(""); m_pSlowJobInfo->setText(""); } void ProgressDialog::hideStatusBarWidget() { if(m_delayedHideStatusBarWidgetTimer) killTimer(m_delayedHideStatusBarWidgetTimer); m_delayedHideStatusBarWidgetTimer = startTimer(100); } void ProgressDialog::delayedHideStatusBarWidget() { if(m_progressDelayTimer) killTimer(m_progressDelayTimer); m_progressDelayTimer = 0; if(m_pStatusBarWidget != nullptr) { m_pStatusBarWidget->hide(); m_pStatusProgressBar->setValue(0); m_pStatusBar->clearMessage(); } } void ProgressDialog::reject() { cancel(eUserAbort); QDialog::reject(); } void ProgressDialog::slotAbort() { reject(); } bool ProgressDialog::wasCancelled() { if(QThread::currentThread() == m_pGuiThread) { if(m_t2.elapsed() > 100) { qApp->processEvents(); m_t2.restart(); } } return m_bWasCancelled; } void ProgressDialog::clearCancelState() { m_bWasCancelled = false; } void ProgressDialog::cancel(e_CancelReason eCancelReason) { if(!m_bWasCancelled) { m_bWasCancelled = true; m_eCancelReason = eCancelReason; if(m_eventLoop != nullptr) m_eventLoop->exit(1); } } ProgressDialog::e_CancelReason ProgressDialog::cancelReason() { return m_eCancelReason; } void ProgressDialog::timerEvent(QTimerEvent* te) { if(te->timerId() == m_progressDelayTimer) { if(!isVisible() && !m_bStayHidden) { show(); } m_pSlowJobInfo->setText(m_currentJobInfo); } else if(te->timerId() == m_delayedHideTimer) { killTimer(m_delayedHideTimer); m_delayedHideTimer = 0; delayedHide(); } else if(te->timerId() == m_delayedHideStatusBarWidgetTimer) { killTimer(m_delayedHideStatusBarWidgetTimer); m_delayedHideStatusBarWidgetTimer = 0; delayedHideStatusBarWidget(); } } ProgressProxy::ProgressProxy() { g_pProgressDialog->push(); } ProgressProxy::~ProgressProxy() { g_pProgressDialog->pop(false); } void ProgressProxy::enterEventLoop(KJob* pJob, const QString& jobInfo) { g_pProgressDialog->enterEventLoop(pJob, jobInfo); } void ProgressProxy::exitEventLoop() { g_pProgressDialog->exitEventLoop(); } QDialog* ProgressProxy::getDialog() { return g_pProgressDialog; } void ProgressProxy::setInformation(const QString& info, bool bRedrawUpdate) { g_pProgressDialog->setInformation(info, bRedrawUpdate); } void ProgressProxy::setInformation(const QString& info, int current, bool bRedrawUpdate) { g_pProgressDialog->setInformation(info, current, bRedrawUpdate); } void ProgressProxy::setCurrent(qint64 current, bool bRedrawUpdate) { g_pProgressDialog->setCurrent(current, bRedrawUpdate); } void ProgressProxy::step(bool bRedrawUpdate) { g_pProgressDialog->step(bRedrawUpdate); } void ProgressProxy::clear() { g_pProgressDialog->clear(); } void ProgressProxy::setMaxNofSteps(const qint64 maxNofSteps) { g_pProgressDialog->setMaxNofSteps(maxNofSteps); } void ProgressProxy::addNofSteps(const qint64 nofSteps) { g_pProgressDialog->addNofSteps(nofSteps); } bool ProgressProxy::wasCancelled() { return g_pProgressDialog->wasCancelled(); } void ProgressProxy::setRangeTransformation(double dMin, double dMax) { g_pProgressDialog->setRangeTransformation(dMin, dMax); } void ProgressProxy::setSubRangeTransformation(double dMin, double dMax) { g_pProgressDialog->setSubRangeTransformation(dMin, dMax); } void ProgressProxy::recalc() { g_pProgressDialog->recalc(true); } diff --git a/src/progress.h b/src/progress.h index 13b4394..88ff5b7 100644 --- a/src/progress.h +++ b/src/progress.h @@ -1,144 +1,141 @@ -/*************************************************************************** - * Copyright (C) 2003-2007 by Joachim Eibl * - * joachim.eibl at gmx.de * - * Copyright (C) 2019 Michael Reeves * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef PROGRESS_H #define PROGRESS_H #include #include #include #include class KJob; class QEventLoop; class QLabel; class QProgressBar; class QStatusBar; class ProgressDialog : public QDialog { Q_OBJECT public: ProgressDialog( QWidget* pParent,QStatusBar* ); void setStayHidden( bool bStayHidden ); void setInformation( const QString& info, bool bRedrawUpdate=true ); void setInformation( const QString& info, int current, bool bRedrawUpdate=true ); void setCurrent( qint64 current, bool bRedrawUpdate=true ); void step( bool bRedrawUpdate=true ); void clear(); void setMaxNofSteps(const qint64 dMaxNofSteps); void addNofSteps(const qint64 nofSteps ); void push(); void pop(bool bRedrawUpdate=true); // The progressbar goes from 0 to 1 usually. // By supplying a subrange transformation the subCurrent-values // 0 to 1 will be transformed to dMin to dMax instead. // Requirement: 0 < dMin < dMax < 1 void setRangeTransformation( double dMin, double dMax ); void setSubRangeTransformation( double dMin, double dMax ); void exitEventLoop(); void enterEventLoop( KJob* pJob, const QString& jobInfo ); bool wasCancelled(); enum e_CancelReason{eUserAbort,eResize}; void cancel(e_CancelReason); e_CancelReason cancelReason(); void clearCancelState(); void show(); void hide(); void hideStatusBarWidget(); void delayedHideStatusBarWidget(); void timerEvent(QTimerEvent* event) override; public Q_SLOTS: void recalc(bool bUpdate); private: struct ProgressLevelData { ProgressLevelData() { m_current=0; m_maxNofSteps=1; m_dRangeMin=0; m_dRangeMax=1; m_dSubRangeMin = 0; m_dSubRangeMax = 1; } QAtomicInteger m_current; QAtomicInteger m_maxNofSteps; // when step() is used. double m_dRangeMax; double m_dRangeMin; double m_dSubRangeMax; double m_dSubRangeMin; }; QList m_progressStack; int m_progressDelayTimer; int m_delayedHideTimer; int m_delayedHideStatusBarWidgetTimer; QPointer m_eventLoop; QProgressBar* m_pProgressBar; QProgressBar* m_pSubProgressBar; QLabel* m_pInformation; QLabel* m_pSubInformation; QLabel* m_pSlowJobInfo; QPushButton* m_pAbortButton; QElapsedTimer m_t1; QElapsedTimer m_t2; bool m_bWasCancelled; e_CancelReason m_eCancelReason; KJob* m_pJob = nullptr; QString m_currentJobInfo; // Needed if the job doesn't stop after a reasonable time. bool m_bStayHidden; QThread* m_pGuiThread; QStatusBar* m_pStatusBar; // status bar of main window (if exists) QWidget* m_pStatusBarWidget; QProgressBar* m_pStatusProgressBar; QPushButton* m_pStatusAbortButton; protected: void reject() override; private Q_SLOTS: void delayedHide(); void slotAbort(); }; // When using the ProgressProxy you need not take care of the push and pop, except when explicit. class ProgressProxy: public QObject { Q_OBJECT public: ProgressProxy(); ~ProgressProxy() override; void setInformation( const QString& info, bool bRedrawUpdate=true ); void setInformation( const QString& info, int current, bool bRedrawUpdate=true ); void setCurrent( qint64 current, bool bRedrawUpdate=true ); void step( bool bRedrawUpdate=true ); void clear(); void setMaxNofSteps( const qint64 maxNofSteps ); void addNofSteps( const qint64 nofSteps ); bool wasCancelled(); void setRangeTransformation( double dMin, double dMax ); void setSubRangeTransformation( double dMin, double dMax ); static void exitEventLoop(); static void enterEventLoop( KJob* pJob, const QString& jobInfo ); static QDialog *getDialog(); static void recalc(); private: }; extern ProgressDialog* g_pProgressDialog; #endif diff --git a/src/selection.cpp b/src/selection.cpp index febaf81..e01974f 100644 --- a/src/selection.cpp +++ b/src/selection.cpp @@ -1,109 +1,107 @@ -/*************************************************************************** - * Copyright (C) 2003-2011 by Joachim Eibl * - * joachim.eibl at gmx.de * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #include "selection.h" #include // for INT_MAX #include // for swap #include int Selection::firstPosInLine(LineRef l) { Q_ASSERT(firstLine != invalidRef); LineRef l1 = firstLine; LineRef l2 = lastLine; int p1 = firstPos; int p2 = lastPos; if(l1 > l2) { std::swap(l1, l2); std::swap(p1, p2); } if(l1 == l2 && p1 > p2) { std::swap(p1, p2); } if(l == l1) return p1; return 0; } int Selection::lastPosInLine(LineRef l) { Q_ASSERT(firstLine != invalidRef); LineRef l1 = firstLine; LineRef l2 = lastLine; int p1 = firstPos; int p2 = lastPos; if(l1 > l2) { std::swap(l1, l2); std::swap(p1, p2); } if(l1 == l2 && p1 > p2) { std::swap(p1, p2); } if(l == l2) return p2; return INT_MAX; } bool Selection::within(LineRef l, LineRef p) { if(firstLine == invalidRef) return false; LineRef l1 = firstLine; LineRef l2 = lastLine; int p1 = firstPos; int p2 = lastPos; if(l1 > l2) { std::swap(l1, l2); std::swap(p1, p2); } if(l1 == l2 && p1 > p2) { std::swap(p1, p2); } if(l1 <= l && l <= l2) { if(l1 == l2) return p >= p1 && p < p2; if(l == l1) return p >= p1; if(l == l2) return p < p2; return true; } return false; } bool Selection::lineWithin(LineRef l) { if(firstLine == invalidRef) return false; LineRef l1 = firstLine; LineRef l2 = lastLine; if(l1 > l2) { std::swap(l1, l2); } return (l1 <= l && l <= l2); } diff --git a/src/selection.h b/src/selection.h index 8606000..bcf6405 100644 --- a/src/selection.h +++ b/src/selection.h @@ -1,83 +1,81 @@ -/*************************************************************************** - * Copyright (C) 2003-2011 by Joachim Eibl * - * joachim.eibl at gmx.de * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef SELECTION_H #define SELECTION_H #include "LineRef.h" #include // for max, min class Selection { public: Selection(){} private: const LineRef invalidRef = -1; LineRef firstLine = invalidRef; LineRef lastLine = invalidRef; int firstPos = -1; int lastPos = -1; LineRef oldFirstLine = invalidRef; LineRef oldLastLine = invalidRef; public: //private: bool bSelectionContainsData = false; public: inline LineRef getFirstLine() { return firstLine; }; inline LineRef getLastLine() { return lastLine; }; inline int getFirstPos() const { return firstPos; }; inline int getLastPos() const { return lastPos; }; inline bool isValidFirstLine() { return firstLine != invalidRef; } inline void clearOldSelection() { oldLastLine = invalidRef, oldFirstLine = invalidRef; }; inline LineRef getOldLastLine() { return oldLastLine; }; inline LineRef getOldFirstLine() { return oldFirstLine; }; inline bool selectionContainsData() const { return bSelectionContainsData; }; bool isEmpty() { return firstLine == invalidRef || (firstLine == lastLine && firstPos == lastPos) || !bSelectionContainsData; } void reset() { oldLastLine = lastLine; oldFirstLine = firstLine; firstLine = invalidRef; lastLine = invalidRef; bSelectionContainsData = false; } void start( LineRef l, int p ) { firstLine = l; firstPos = p; } void end( LineRef l, int p ) { if ( oldLastLine == invalidRef ) oldLastLine = lastLine; lastLine = l; lastPos = p; //bSelectionContainsData = (firstLine == lastLine && firstPos == lastPos); } bool within( LineRef l, LineRef p ); bool lineWithin( LineRef l ); int firstPosInLine(LineRef l); int lastPosInLine(LineRef l); LineRef beginLine(){ if (firstLine<0 && lastLine<0) return invalidRef; return std::max((LineRef)0,std::min(firstLine,lastLine)); } LineRef endLine(){ if (firstLine<0 && lastLine<0) return invalidRef; return std::max(firstLine,lastLine); } int beginPos() { return firstLine==lastLine ? std::min(firstPos,lastPos) : firstLine #include #include #include #include #include #include #include #include #include #include #include #include #include #include // OpenDialog ************************************************************** OpenDialog::OpenDialog( KDiff3App* pParent, const QString& n1, const QString& n2, const QString& n3, bool bMerge, const QString& outputName, const QSharedPointer &pOptions) : QDialog(pParent) { setObjectName("OpenDialog"); setModal(true); m_pOptions = pOptions; QVBoxLayout* v = new QVBoxLayout(this); v->setMargin(5); QGridLayout* h = new QGridLayout(); v->addLayout(h); h->setSpacing(5); h->setColumnStretch(1, 10); QLabel* label = new QLabel(i18n("A (Base):"), this); m_pLineA = new QComboBox(); m_pLineA->setEditable(true); m_pLineA->insertItems(0, m_pOptions->m_recentAFiles); m_pLineA->setEditText(QUrl(n1).toDisplayString()); m_pLineA->setMinimumWidth(200); QPushButton* button = new QPushButton(QIcon::fromTheme("document-new"), i18n("File..."), this); connect(button, &QPushButton::clicked, this, &OpenDialog::selectFileA); QPushButton* button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Folder..."), this); connect(button2, &QPushButton::clicked, this, &OpenDialog::selectDirA); connect(m_pLineA, &QComboBox::editTextChanged, this, &OpenDialog::inputFilenameChanged); h->addWidget(label, 0, 0); h->addWidget(m_pLineA, 0, 1); h->addWidget(button, 0, 2); h->addWidget(button2, 0, 3); label = new QLabel("B:", this); m_pLineB = new QComboBox(); m_pLineB->setEditable(true); m_pLineB->insertItems(0, m_pOptions->m_recentBFiles); m_pLineB->setEditText(QUrl(n2).toDisplayString()); m_pLineB->setMinimumWidth(200); button = new QPushButton(QIcon::fromTheme("document-new"), i18n("File..."), this); connect(button, &QPushButton::clicked, this, &OpenDialog::selectFileB); button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Folder..."), this); connect(button2, &QPushButton::clicked, this, &OpenDialog::selectDirB); connect(m_pLineB, &QComboBox::editTextChanged, this, &OpenDialog::inputFilenameChanged); h->addWidget(label, 1, 0); h->addWidget(m_pLineB, 1, 1); h->addWidget(button, 1, 2); h->addWidget(button2, 1, 3); label = new QLabel(i18n("C (Optional):"), this); m_pLineC = new QComboBox(); m_pLineC->setEditable(true); m_pLineC->insertItems(0, m_pOptions->m_recentCFiles); m_pLineC->setEditText(QUrl(n3).toDisplayString()); m_pLineC->setMinimumWidth(200); button = new QPushButton(QIcon::fromTheme("document-new"), i18n("File..."), this); connect(button, &QPushButton::clicked, this, &OpenDialog::selectFileC); button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Folder..."), this); connect(button2, &QPushButton::clicked, this, &OpenDialog::selectDirC); connect(m_pLineC, &QComboBox::editTextChanged, this, &OpenDialog::inputFilenameChanged); h->addWidget(label, 2, 0); h->addWidget(m_pLineC, 2, 1); h->addWidget(button, 2, 2); h->addWidget(button2, 2, 3); m_pMerge = new QCheckBox(i18n("Merge"), this); h->addWidget(m_pMerge, 3, 0); QHBoxLayout* hl = new QHBoxLayout(); h->addLayout(hl, 3, 1); hl->addStretch(2); button = new QPushButton(i18n("Swap/Copy Names..."), this); //button->setToggleButton(false); hl->addWidget(button); QMenu* m = new QMenu(this); m->addAction(i18n("Swap %1<->%2", i18n("A"), i18n("B"))); m->addAction(i18n("Swap %1<->%2", i18n("B"), i18n("C"))); m->addAction(i18n("Swap %1<->%2", i18n("C"), i18n("A"))); m->addAction(i18n("Copy %1->Output", i18n("A"))); m->addAction(i18n("Copy %1->Output", i18n("B"))); m->addAction(i18n("Copy %1->Output", i18n("C"))); m->addAction(i18n("Swap %1<->Output", i18n("A"))); m->addAction(i18n("Swap %1<->Output", i18n("B"))); m->addAction(i18n("Swap %1<->Output", i18n("C"))); connect(m, &QMenu::triggered, this, &OpenDialog::slotSwapCopyNames); button->setMenu(m); hl->addStretch(2); label = new QLabel(i18n("Output (optional):"), this); m_pLineOut = new QComboBox(); m_pLineOut->setEditable(true); m_pLineOut->insertItems(0, m_pOptions->m_recentOutputFiles); m_pLineOut->setEditText(QUrl(outputName).toDisplayString()); m_pLineOut->setMinimumWidth(200); button = new QPushButton(QIcon::fromTheme("document-new"), i18n("File..."), this); connect(button, &QPushButton::clicked, this, &OpenDialog::selectOutputName); button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Folder..."), this); connect(button2, &QPushButton::clicked, this, &OpenDialog::selectOutputDir); connect(m_pMerge, &QCheckBox::stateChanged, this, &OpenDialog::internalSlot); connect(this, &OpenDialog::internalSignal, m_pLineOut, &QComboBox::setEnabled); connect(this, &OpenDialog::internalSignal, button, &QPushButton::setEnabled); connect(this, &OpenDialog::internalSignal, button2, &QPushButton::setEnabled); m_pMerge->setChecked(!bMerge); m_pMerge->setChecked(bMerge); // m_pLineOutput->setEnabled( bMerge ); // button->setEnabled( bMerge ); h->addWidget(label, 4, 0); h->addWidget(m_pLineOut, 4, 1); h->addWidget(button, 4, 2); h->addWidget(button2, 4, 3); h->addItem(new QSpacerItem(200, 0), 0, 1); QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); v->addWidget(box); button = box->addButton(i18n("Configure..."), QDialogButtonBox::ActionRole); button->setIcon(QIcon::fromTheme("configure")); connect(button, &QPushButton::clicked, pParent, &KDiff3App::slotConfigure); connect(box, &QDialogButtonBox::accepted, this, &OpenDialog::accept); connect(box, &QDialogButtonBox::rejected, this, &OpenDialog::reject); QSize sh = sizeHint(); setFixedHeight(sh.height()); m_bInputFileNameChanged = false; #ifdef Q_OS_WIN m_pLineA->lineEdit()->installEventFilter(this); m_pLineB->lineEdit()->installEventFilter(this); m_pLineC->lineEdit()->installEventFilter(this); m_pLineOut->lineEdit()->installEventFilter(this); #endif } // Eventfilter: Only needed under Windows. // Without this, files dropped in the line edit have URL-encoding. // This eventfilter decodes the filenames as needed by KDiff3. bool OpenDialog::eventFilter(QObject* o, QEvent* e) { if(e->type() == QEvent::DragEnter) { QDragEnterEvent* d = static_cast(e); d->setAccepted(d->mimeData()->hasUrls()); return true; } if(e->type() == QEvent::Drop) { QDropEvent* d = static_cast(e); if(!d->mimeData()->hasUrls()) return false; QList lst = d->mimeData()->urls(); if(lst.count() > 0) { static_cast(o)->setText(QDir::toNativeSeparators(lst[0].toLocalFile())); static_cast(o)->setFocus(); } return true; } return false; } void OpenDialog::selectURL(QComboBox* pLine, bool bDir, int i, bool bSave) { QString current = pLine->currentText(); QUrl currentUrl; if(current.isEmpty() && i > 3) { current = m_pLineC->currentText(); } if(current.isEmpty()) { current = m_pLineB->currentText(); } if(current.isEmpty()) { current = m_pLineA->currentText(); } currentUrl = QUrl::fromUserInput(current, QString(), QUrl::AssumeLocalFile); QUrl newURL = bDir ? QFileDialog::getExistingDirectoryUrl(this, i18n("Open Folder"), currentUrl) : bSave ? QFileDialog::getSaveFileUrl(this, i18n("Select Output File"), currentUrl, i18n("All files (*)")) : QFileDialog::getOpenFileUrl(this, i18n("Open File"), currentUrl, i18n("All files (*)")); if(!newURL.isEmpty()) { /* Since we are selecting a directory open in the parent directory not the one selected. */ //QFileDialog::setStartDir( KIO::upUrl( newURL ) ); pLine->setEditText(newURL.url()); } // newURL won't be modified if nothing was selected. } void OpenDialog::selectFileA() { selectURL(m_pLineA, false, 1, false); } void OpenDialog::selectFileB() { selectURL(m_pLineB, false, 2, false); } void OpenDialog::selectFileC() { selectURL(m_pLineC, false, 3, false); } void OpenDialog::selectOutputName() { selectURL(m_pLineOut, false, 4, true); } void OpenDialog::selectDirA() { selectURL(m_pLineA, true, 1, false); } void OpenDialog::selectDirB() { selectURL(m_pLineB, true, 2, false); } void OpenDialog::selectDirC() { selectURL(m_pLineC, true, 3, false); } void OpenDialog::selectOutputDir() { selectURL(m_pLineOut, true, 4, true); } void OpenDialog::internalSlot(int i) { Q_EMIT internalSignal(i != 0); } // Clear the output-filename when any input-filename changed, // because users forgot to change the output and accidentally overwrote it with // wrong data during a merge. void OpenDialog::inputFilenameChanged() { if(!m_bInputFileNameChanged) { m_bInputFileNameChanged = true; m_pLineOut->clearEditText(); } } void OpenDialog::fixCurrentText(QComboBox* pCB) { QString s = pCB->currentText(); int pos = s.indexOf('\n'); if(pos >= 0) s = s.left(pos); pos = s.indexOf('\r'); if(pos >= 0) s = s.left(pos); pCB->setEditText(s); } void OpenDialog::accept() { int maxNofRecentFiles = 10; fixCurrentText(m_pLineA); QString s = m_pLineA->currentText(); s = QUrl::fromUserInput(s, QString(), QUrl::AssumeLocalFile).toLocalFile(); QStringList* sl = &m_pOptions->m_recentAFiles; // If an item exist, remove it from the list and reinsert it at the beginning. sl->removeAll(s); if(!s.isEmpty()) sl->prepend(s); if(sl->count() > maxNofRecentFiles) sl->erase(sl->begin() + maxNofRecentFiles, sl->end()); fixCurrentText(m_pLineB); s = m_pLineB->currentText(); s = QUrl::fromUserInput(s, QString(), QUrl::AssumeLocalFile).toLocalFile(); sl = &m_pOptions->m_recentBFiles; sl->removeAll(s); if(!s.isEmpty()) sl->prepend(s); if(sl->count() > maxNofRecentFiles) sl->erase(sl->begin() + maxNofRecentFiles, sl->end()); fixCurrentText(m_pLineC); s = m_pLineC->currentText(); s = QUrl::fromUserInput(s, QString(), QUrl::AssumeLocalFile).toLocalFile(); sl = &m_pOptions->m_recentCFiles; sl->removeAll(s); if(!s.isEmpty()) sl->prepend(s); if(sl->count() > maxNofRecentFiles) sl->erase(sl->begin() + maxNofRecentFiles, sl->end()); fixCurrentText(m_pLineOut); s = m_pLineOut->currentText(); s = QUrl::fromUserInput(s, QString(), QUrl::AssumeLocalFile).toLocalFile(); sl = &m_pOptions->m_recentOutputFiles; sl->removeAll(s); if(!s.isEmpty()) sl->prepend(s); if(sl->count() > maxNofRecentFiles) sl->erase(sl->begin() + maxNofRecentFiles, sl->end()); QDialog::accept(); } void OpenDialog::slotSwapCopyNames(QAction* pAction) const // id selected in the popup menu { int id = pAction->parentWidget()->actions().indexOf(pAction); QComboBox* cb1 = nullptr; QComboBox* cb2 = nullptr; switch(id) { case 0: cb1 = m_pLineA; cb2 = m_pLineB; break; case 1: cb1 = m_pLineB; cb2 = m_pLineC; break; case 2: cb1 = m_pLineC; cb2 = m_pLineA; break; case 3: cb1 = m_pLineA; cb2 = m_pLineOut; break; case 4: cb1 = m_pLineB; cb2 = m_pLineOut; break; case 5: cb1 = m_pLineC; cb2 = m_pLineOut; break; case 6: cb1 = m_pLineA; cb2 = m_pLineOut; break; case 7: cb1 = m_pLineB; cb2 = m_pLineOut; break; case 8: cb1 = m_pLineC; cb2 = m_pLineOut; break; } if(cb1 && cb2) { QString t1 = cb1->currentText(); QString t2 = cb2->currentText(); cb2->setEditText(t1); if(id <= 2 || id >= 6) { cb1->setEditText(t2); } } } // FindDialog ********************************************* FindDialog::FindDialog(QWidget* pParent) : QDialog(pParent) { QGridLayout* layout = new QGridLayout(this); layout->setMargin(5); layout->setSpacing(5); int line = 0; layout->addWidget(new QLabel(i18n("Search text:"), this), line, 0, 1, 2); ++line; m_pSearchString = new QLineEdit(this); layout->addWidget(m_pSearchString, line, 0, 1, 2); ++line; m_pCaseSensitive = new QCheckBox(i18n("Case sensitive"), this); layout->addWidget(m_pCaseSensitive, line, 1); m_pSearchInA = new QCheckBox(i18n("Search A"), this); layout->addWidget(m_pSearchInA, line, 0); m_pSearchInA->setChecked(true); ++line; m_pSearchInB = new QCheckBox(i18n("Search B"), this); layout->addWidget(m_pSearchInB, line, 0); m_pSearchInB->setChecked(true); ++line; m_pSearchInC = new QCheckBox(i18n("Search C"), this); layout->addWidget(m_pSearchInC, line, 0); m_pSearchInC->setChecked(true); ++line; m_pSearchInOutput = new QCheckBox(i18n("Search output"), this); layout->addWidget(m_pSearchInOutput, line, 0); m_pSearchInOutput->setChecked(true); ++line; QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Cancel, this); layout->addWidget(box, line, 0, 1, 2); box->addButton(i18n("&Search"), QDialogButtonBox::AcceptRole); connect(box, &QDialogButtonBox::accepted, this, &FindDialog::accept); connect(box, &QDialogButtonBox::rejected, this, &FindDialog::reject); hide(); } void FindDialog::setVisible(bool bVisible) { QDialog::setVisible(bVisible); m_pSearchString->selectAll(); m_pSearchString->setFocus(); } RegExpTester::RegExpTester(QWidget* pParent, const QString& autoMergeRegExpToolTip, const QString& historyStartRegExpToolTip, const QString& historyEntryStartRegExpToolTip, const QString& historySortKeyOrderToolTip) : QDialog(pParent) { int line = 0; setWindowTitle(i18n("Regular Expression Tester")); QGridLayout* pGrid = new QGridLayout(this); pGrid->setSpacing(5); pGrid->setMargin(5); QLabel* l = new QLabel(i18n("Auto merge regular expression:"), this); pGrid->addWidget(l, line, 0); l->setToolTip(autoMergeRegExpToolTip); m_pAutoMergeRegExpEdit = new QLineEdit(this); pGrid->addWidget(m_pAutoMergeRegExpEdit, line, 1); connect(m_pAutoMergeRegExpEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Example auto merge line:"), this); pGrid->addWidget(l, line, 0); l->setToolTip(i18n("To test auto merge, copy a line as used in your files.")); m_pAutoMergeExampleEdit = new QLineEdit(this); pGrid->addWidget(m_pAutoMergeExampleEdit, line, 1); connect(m_pAutoMergeExampleEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Match result:"), this); pGrid->addWidget(l, line, 0); m_pAutoMergeMatchResult = new QLineEdit(this); m_pAutoMergeMatchResult->setReadOnly(true); pGrid->addWidget(m_pAutoMergeMatchResult, line, 1); ++line; pGrid->addItem(new QSpacerItem(100, 20), line, 0); pGrid->setRowStretch(line, 5); ++line; l = new QLabel(i18n("History start regular expression:"), this); pGrid->addWidget(l, line, 0); l->setToolTip(historyStartRegExpToolTip); m_pHistoryStartRegExpEdit = new QLineEdit(this); pGrid->addWidget(m_pHistoryStartRegExpEdit, line, 1); connect(m_pHistoryStartRegExpEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Example history start line (with leading comment):"), this); pGrid->addWidget(l, line, 0); l->setToolTip(i18n("Copy a history start line as used in your files,\n" "including the leading comment.")); m_pHistoryStartExampleEdit = new QLineEdit(this); pGrid->addWidget(m_pHistoryStartExampleEdit, line, 1); connect(m_pHistoryStartExampleEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Match result:"), this); pGrid->addWidget(l, line, 0); m_pHistoryStartMatchResult = new QLineEdit(this); m_pHistoryStartMatchResult->setReadOnly(true); pGrid->addWidget(m_pHistoryStartMatchResult, line, 1); ++line; pGrid->addItem(new QSpacerItem(100, 20), line, 0); pGrid->setRowStretch(line, 5); ++line; l = new QLabel(i18n("History entry start regular expression:"), this); pGrid->addWidget(l, line, 0); l->setToolTip(historyEntryStartRegExpToolTip); m_pHistoryEntryStartRegExpEdit = new QLineEdit(this); pGrid->addWidget(m_pHistoryEntryStartRegExpEdit, line, 1); connect(m_pHistoryEntryStartRegExpEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("History sort key order:"), this); pGrid->addWidget(l, line, 0); l->setToolTip(historySortKeyOrderToolTip); m_pHistorySortKeyOrderEdit = new QLineEdit(this); pGrid->addWidget(m_pHistorySortKeyOrderEdit, line, 1); connect(m_pHistorySortKeyOrderEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Example history entry start line (without leading comment):"), this); pGrid->addWidget(l, line, 0); l->setToolTip(i18n("Copy a history entry start line as used in your files,\n" "but omit the leading comment.")); m_pHistoryEntryStartExampleEdit = new QLineEdit(this); pGrid->addWidget(m_pHistoryEntryStartExampleEdit, line, 1); connect(m_pHistoryEntryStartExampleEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Match result:"), this); pGrid->addWidget(l, line, 0); m_pHistoryEntryStartMatchResult = new QLineEdit(this); m_pHistoryEntryStartMatchResult->setReadOnly(true); pGrid->addWidget(m_pHistoryEntryStartMatchResult, line, 1); ++line; l = new QLabel(i18n("Sort key result:"), this); pGrid->addWidget(l, line, 0); m_pHistorySortKeyResult = new QLineEdit(this); m_pHistorySortKeyResult->setReadOnly(true); pGrid->addWidget(m_pHistorySortKeyResult, line, 1); ++line; QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); pGrid->addWidget(box, line, 0, 1, 2); connect(box, &QDialogButtonBox::accepted, this, &RegExpTester::accept); connect(box, &QDialogButtonBox::rejected, this, &RegExpTester::reject); resize(800, sizeHint().height()); } void RegExpTester::init(const QString& autoMergeRegExp, const QString& historyStartRegExp, const QString& historyEntryStartRegExp, const QString& historySortKeyOrder) { m_pAutoMergeRegExpEdit->setText(autoMergeRegExp); m_pHistoryStartRegExpEdit->setText(historyStartRegExp); m_pHistoryEntryStartRegExpEdit->setText(historyEntryStartRegExp); m_pHistorySortKeyOrderEdit->setText(historySortKeyOrder); } QString RegExpTester::autoMergeRegExp() { return m_pAutoMergeRegExpEdit->text(); } QString RegExpTester::historyStartRegExp() { return m_pHistoryStartRegExpEdit->text(); } QString RegExpTester::historyEntryStartRegExp() { return m_pHistoryEntryStartRegExpEdit->text(); } QString RegExpTester::historySortKeyOrder() { return m_pHistorySortKeyOrderEdit->text(); } void RegExpTester::slotRecalc() { QRegExp autoMergeRegExp(m_pAutoMergeRegExpEdit->text()); if(autoMergeRegExp.exactMatch(m_pAutoMergeExampleEdit->text())) { m_pAutoMergeMatchResult->setText(i18n("Match success.")); } else { m_pAutoMergeMatchResult->setText(i18n("Match failed.")); } QRegExp historyStartRegExp(m_pHistoryStartRegExpEdit->text()); if(historyStartRegExp.exactMatch(m_pHistoryStartExampleEdit->text())) { m_pHistoryStartMatchResult->setText(i18n("Match success.")); } else { m_pHistoryStartMatchResult->setText(i18n("Match failed.")); } QStringList parenthesesGroups; bool bSuccess = findParenthesesGroups(m_pHistoryEntryStartRegExpEdit->text(), parenthesesGroups); if(!bSuccess) { m_pHistoryEntryStartMatchResult->setText(i18n("Opening and closing parentheses do not match in regular expression.")); m_pHistorySortKeyResult->setText(""); return; } QRegExp historyEntryStartRegExp(m_pHistoryEntryStartRegExpEdit->text()); QString s = m_pHistoryEntryStartExampleEdit->text(); if(historyEntryStartRegExp.exactMatch(s)) { m_pHistoryEntryStartMatchResult->setText(i18n("Match success.")); QString key = calcHistorySortKey(m_pHistorySortKeyOrderEdit->text(), historyEntryStartRegExp, parenthesesGroups); m_pHistorySortKeyResult->setText(key); } else { m_pHistoryEntryStartMatchResult->setText(i18n("Match failed.")); m_pHistorySortKeyResult->setText(""); } } //#include "smalldialogs.moc" diff --git a/src/smalldialogs.h b/src/smalldialogs.h index 80ce125..016d423 100644 --- a/src/smalldialogs.h +++ b/src/smalldialogs.h @@ -1,121 +1,108 @@ -/*************************************************************************** - * Copyright (C) 2005 by Joachim Eibl * - * joachim.eibl at gmx.de * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - ***************************************************************************/ +/* + * KDiff3 - Text Diff And Merge Tool + * + * SPDX-FileCopyrightText: 2002-2007 Joachim Eibl, joachim.eibl at gmx.de + * SPDX-License-Identifier: GPL-2.0-or-later +*/ #ifndef SMALLDIALOGS_H #define SMALLDIALOGS_H #include class Options; class QComboBox; class QCheckBox; class QLineEdit; class KDiff3App; class OpenDialog : public QDialog { Q_OBJECT public: OpenDialog(// krazy:exclude=explicit KDiff3App* pParent, const QString& n1, const QString& n2, const QString& n3, bool bMerge, const QString& outputName, const QSharedPointer &pOptions ); QComboBox* m_pLineA; QComboBox* m_pLineB; QComboBox* m_pLineC; QComboBox* m_pLineOut; QCheckBox* m_pMerge; void accept() override; bool eventFilter(QObject* o, QEvent* e) override; private: void selectURL( QComboBox* pLine, bool bDir, int i, bool bSave ); void fixCurrentText(QComboBox* pCB); QSharedPointer m_pOptions; bool m_bInputFileNameChanged; private Q_SLOTS: void selectFileA(); void selectFileB(); void selectFileC(); void selectDirA(); void selectDirB(); void selectDirC(); void selectOutputName(); void selectOutputDir(); void internalSlot(int); void inputFilenameChanged(); void slotSwapCopyNames(QAction*) const; Q_SIGNALS: void internalSignal(bool); }; class FindDialog : public QDialog { Q_OBJECT public: explicit FindDialog(QWidget* pParent); void setVisible(bool) override; Q_SIGNALS: void findNext(); public: QLineEdit* m_pSearchString; QCheckBox* m_pSearchInA; QCheckBox* m_pSearchInB; QCheckBox* m_pSearchInC; QCheckBox* m_pSearchInOutput; QCheckBox* m_pCaseSensitive; int currentLine = 0; int currentPos = 0; int currentWindow = 0; }; class RegExpTester : public QDialog { Q_OBJECT private: QLineEdit* m_pAutoMergeRegExpEdit; QLineEdit* m_pAutoMergeMatchResult; QLineEdit* m_pAutoMergeExampleEdit; QLineEdit* m_pHistoryStartRegExpEdit; QLineEdit* m_pHistoryStartMatchResult; QLineEdit* m_pHistoryStartExampleEdit; QLineEdit* m_pHistoryEntryStartRegExpEdit; QLineEdit* m_pHistorySortKeyOrderEdit; QLineEdit* m_pHistoryEntryStartExampleEdit; QLineEdit* m_pHistoryEntryStartMatchResult; QLineEdit* m_pHistorySortKeyResult; public: RegExpTester( QWidget* pParent, const QString& autoMergeRegExpToolTip, const QString& historyStartRegExpToolTip, const QString& historyEntryStartRegExpToolTip, const QString& historySortKeyOrderToolTip ); void init( const QString& autoMergeRegExp, const QString& historyStartRegExp, const QString& historyEntryStartRegExp, const QString& sortKeyOrder ); QString autoMergeRegExp(); QString historyStartRegExp(); QString historyEntryStartRegExp(); QString historySortKeyOrder(); public Q_SLOTS: void slotRecalc(); }; #endif